aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--.devcontainer/devcontainer.json2
-rw-r--r--.gitattributes2
-rw-r--r--.github/CODEOWNERS45
-rw-r--r--.github/ISSUE_TEMPLATE/bug.yml1
-rw-r--r--.github/ISSUE_TEMPLATE/crash.yml1
-rw-r--r--.github/workflows/build.yml16
-rw-r--r--.github/workflows/jit.yml4
-rw-r--r--.github/workflows/mypy.yml8
-rwxr-xr-x.github/workflows/posix-deps-apt.sh8
-rw-r--r--.github/workflows/reusable-context.yml3
-rw-r--r--.github/workflows/reusable-ubsan.yml74
-rw-r--r--.github/workflows/tail-call.yml1
-rw-r--r--.gitignore6
-rw-r--r--.pre-commit-config.yaml15
-rw-r--r--.readthedocs.yml1
-rw-r--r--Android/README.md4
-rw-r--r--Android/android-env.sh6
-rwxr-xr-xAndroid/android.py231
-rw-r--r--Android/testbed/app/build.gradle.kts20
-rw-r--r--Android/testbed/app/src/androidTest/java/org/python/testbed/PythonSuite.kt8
-rw-r--r--Android/testbed/app/src/main/java/org/python/testbed/MainActivity.kt27
-rw-r--r--Android/testbed/app/src/main/python/android_testbed_main.py (renamed from Android/testbed/app/src/main/python/main.py)20
-rw-r--r--Android/testbed/build.gradle.kts2
-rw-r--r--Android/testbed/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--Doc/c-api/allocation.rst132
-rw-r--r--Doc/c-api/arg.rst17
-rw-r--r--Doc/c-api/capsule.rst10
-rw-r--r--Doc/c-api/code.rst2
-rw-r--r--Doc/c-api/exceptions.rst22
-rw-r--r--Doc/c-api/extension-modules.rst247
-rw-r--r--Doc/c-api/function.rst22
-rw-r--r--Doc/c-api/gcsupport.rst61
-rw-r--r--Doc/c-api/import.rst13
-rw-r--r--Doc/c-api/index.rst1
-rw-r--r--Doc/c-api/init.rst271
-rw-r--r--Doc/c-api/init_config.rst14
-rw-r--r--Doc/c-api/intro.rst90
-rw-r--r--Doc/c-api/lifecycle.dot156
-rw-r--r--Doc/c-api/lifecycle.dot.css21
-rw-r--r--Doc/c-api/lifecycle.dot.pdfbin0 -> 19328 bytes
-rw-r--r--Doc/c-api/lifecycle.dot.svg374
-rw-r--r--Doc/c-api/lifecycle.rst271
-rw-r--r--Doc/c-api/long.rst2
-rw-r--r--Doc/c-api/memory.rst18
-rw-r--r--Doc/c-api/module.rst253
-rw-r--r--Doc/c-api/object.rst33
-rw-r--r--Doc/c-api/objimpl.rst1
-rw-r--r--Doc/c-api/refcounting.rst11
-rw-r--r--Doc/c-api/stable.rst1
-rw-r--r--Doc/c-api/sys.rst51
-rw-r--r--Doc/c-api/type.rst29
-rw-r--r--Doc/c-api/typeobj.rst483
-rw-r--r--Doc/c-api/unicode.rst52
-rw-r--r--Doc/conf.py7
-rw-r--r--Doc/data/refcounts.dat47
-rw-r--r--Doc/data/stable_abi.dat15
-rw-r--r--Doc/deprecations/c-api-pending-removal-in-3.15.rst15
-rw-r--r--Doc/deprecations/c-api-pending-removal-in-3.16.rst4
-rw-r--r--Doc/deprecations/index.rst2
-rw-r--r--Doc/deprecations/pending-removal-in-3.14.rst2
-rw-r--r--Doc/deprecations/pending-removal-in-3.15.rst16
-rw-r--r--Doc/deprecations/pending-removal-in-3.19.rst24
-rw-r--r--Doc/deprecations/pending-removal-in-future.rst2
-rw-r--r--Doc/extending/building.rst49
-rw-r--r--Doc/extending/embedding.rst12
-rw-r--r--Doc/extending/extending.rst170
-rw-r--r--Doc/extending/index.rst20
-rw-r--r--Doc/extending/newtypes_tutorial.rst39
-rw-r--r--Doc/extending/windows.rst2
-rw-r--r--Doc/faq/design.rst10
-rw-r--r--Doc/faq/extending.rst21
-rw-r--r--Doc/glossary.rst6
-rw-r--r--Doc/howto/annotations.rst7
-rw-r--r--Doc/howto/cporting.rst6
-rw-r--r--Doc/howto/curses.rst2
-rw-r--r--Doc/howto/free-threading-extensions.rst14
-rw-r--r--Doc/howto/free-threading-python.rst19
-rw-r--r--Doc/howto/functional.rst6
-rw-r--r--Doc/howto/isolating-extensions.rst24
-rw-r--r--Doc/howto/logging-cookbook.rst2
-rw-r--r--Doc/howto/perf_profiling.rst4
-rw-r--r--Doc/howto/regex.rst8
-rw-r--r--Doc/howto/urllib2.rst86
-rw-r--r--Doc/includes/newtypes/custom.c41
-rw-r--r--Doc/includes/newtypes/custom2.c42
-rw-r--r--Doc/includes/newtypes/custom3.c40
-rw-r--r--Doc/includes/newtypes/custom4.c40
-rw-r--r--Doc/includes/newtypes/sublist.c46
-rw-r--r--Doc/installing/index.rst2
-rw-r--r--Doc/library/annotationlib.rst248
-rw-r--r--Doc/library/archiving.rst4
-rw-r--r--Doc/library/argparse.rst19
-rw-r--r--Doc/library/ast.rst337
-rw-r--r--Doc/library/asyncio-dev.rst4
-rw-r--r--Doc/library/asyncio-eventloop.rst31
-rw-r--r--Doc/library/asyncio-stream.rst9
-rw-r--r--Doc/library/asyncio-task.rst21
-rw-r--r--Doc/library/audit_events.rst2
-rw-r--r--Doc/library/base64.rst48
-rw-r--r--Doc/library/calendar.rst2
-rw-r--r--Doc/library/cmdline.rst2
-rw-r--r--Doc/library/code.rst6
-rw-r--r--Doc/library/codecs.rst10
-rw-r--r--Doc/library/compileall.rst13
-rw-r--r--Doc/library/compression.rst18
-rw-r--r--Doc/library/compression.zstd.rst897
-rw-r--r--Doc/library/concurrency.rst1
-rw-r--r--Doc/library/concurrent.futures.rst24
-rw-r--r--Doc/library/concurrent.interpreters.rst387
-rw-r--r--Doc/library/concurrent.rst3
-rw-r--r--Doc/library/copy.rst2
-rw-r--r--Doc/library/csv.rst54
-rw-r--r--Doc/library/ctypes.rst85
-rw-r--r--Doc/library/curses.rst37
-rw-r--r--Doc/library/dataclasses.rst11
-rw-r--r--Doc/library/datetime.rst30
-rw-r--r--Doc/library/dbm.rst43
-rw-r--r--Doc/library/decimal.rst74
-rw-r--r--Doc/library/dialog.rst2
-rw-r--r--Doc/library/dis.rst8
-rw-r--r--Doc/library/doctest.rst133
-rw-r--r--Doc/library/email.header.rst34
-rw-r--r--Doc/library/exceptions.rst13
-rw-r--r--Doc/library/faulthandler.rst43
-rw-r--r--Doc/library/fcntl.rst24
-rw-r--r--Doc/library/fractions.rst2
-rw-r--r--Doc/library/functions.rst86
-rw-r--r--Doc/library/functools.rst3
-rw-r--r--Doc/library/gc.rst5
-rw-r--r--Doc/library/getpass.rst11
-rw-r--r--Doc/library/hashlib.rst11
-rw-r--r--Doc/library/heapq-binary-tree.svg211
-rw-r--r--Doc/library/heapq.rst125
-rw-r--r--Doc/library/html.parser.rst51
-rw-r--r--Doc/library/http.server.rst70
-rw-r--r--Doc/library/importlib.resources.abc.rst48
-rw-r--r--Doc/library/io.rst92
-rw-r--r--Doc/library/json.rst15
-rw-r--r--Doc/library/logging.config.rst4
-rw-r--r--Doc/library/logging.handlers.rst23
-rw-r--r--Doc/library/logging.rst21
-rw-r--r--Doc/library/math.rst39
-rw-r--r--Doc/library/mmap.rst2
-rw-r--r--Doc/library/multiprocessing.rst24
-rw-r--r--Doc/library/netrc.rst4
-rw-r--r--Doc/library/os.path.rst32
-rw-r--r--Doc/library/os.rst1
-rw-r--r--Doc/library/pathlib.rst19
-rw-r--r--Doc/library/pdb.rst28
-rw-r--r--Doc/library/pkgutil.rst4
-rw-r--r--Doc/library/platform.rst18
-rw-r--r--Doc/library/python.rst5
-rw-r--r--Doc/library/re.rst18
-rw-r--r--Doc/library/readline.rst8
-rw-r--r--Doc/library/shelve.rst16
-rw-r--r--Doc/library/shutil.rst26
-rw-r--r--Doc/library/signal.rst4
-rw-r--r--Doc/library/socket.rst30
-rw-r--r--Doc/library/socketserver.rst4
-rw-r--r--Doc/library/sqlite3.rst83
-rw-r--r--Doc/library/ssl.rst7
-rw-r--r--Doc/library/stdtypes.rst125
-rw-r--r--Doc/library/string.rst4
-rw-r--r--Doc/library/subprocess.rst18
-rw-r--r--Doc/library/sys.monitoring.rst61
-rw-r--r--Doc/library/sys.rst86
-rw-r--r--Doc/library/tarfile.rst53
-rw-r--r--Doc/library/threading.rst245
-rw-r--r--Doc/library/time.rst7
-rw-r--r--Doc/library/token.rst8
-rw-r--r--Doc/library/typing.rst87
-rw-r--r--Doc/library/unittest.mock.rst4
-rw-r--r--Doc/library/unittest.rst11
-rw-r--r--Doc/library/urllib.request.rst28
-rw-r--r--Doc/library/uuid.rst33
-rw-r--r--Doc/library/venv.rst70
-rw-r--r--Doc/library/wave.rst20
-rw-r--r--Doc/library/webbrowser.rst2
-rw-r--r--Doc/library/zipfile.rst44
-rw-r--r--Doc/library/zlib.rst28
-rw-r--r--Doc/library/zoneinfo.rst4
-rw-r--r--Doc/license.rst37
-rw-r--r--Doc/reference/compound_stmts.rst22
-rw-r--r--Doc/reference/datamodel.rst31
-rw-r--r--Doc/reference/expressions.rst18
-rw-r--r--Doc/reference/grammar.rst18
-rw-r--r--Doc/reference/introduction.rst160
-rw-r--r--Doc/reference/lexical_analysis.rst413
-rw-r--r--Doc/tools/.nitignore1
-rw-r--r--Doc/tools/extensions/audit_events.py16
-rw-r--r--Doc/tools/templates/customsourcelink.html6
-rw-r--r--Doc/tools/templates/download.html2
-rw-r--r--Doc/tools/templates/indexcontent.html2
-rw-r--r--Doc/tools/templates/indexsidebar.html10
-rw-r--r--Doc/tools/templates/layout.html8
-rw-r--r--Doc/tutorial/controlflow.rst5
-rw-r--r--Doc/tutorial/index.rst7
-rw-r--r--Doc/tutorial/interpreter.rst6
-rw-r--r--Doc/tutorial/introduction.rst9
-rw-r--r--Doc/tutorial/modules.rst6
-rw-r--r--Doc/tutorial/stdlib.rst2
-rw-r--r--Doc/tutorial/stdlib2.rst2
-rw-r--r--Doc/using/android.rst9
-rw-r--r--Doc/using/cmdline.rst70
-rw-r--r--Doc/using/configure.rst34
-rw-r--r--Doc/using/ios.rst11
-rw-r--r--Doc/using/mac.rst113
-rw-r--r--Doc/using/windows.rst110
-rw-r--r--Doc/whatsnew/2.6.rst4
-rw-r--r--Doc/whatsnew/3.0.rst2
-rw-r--r--Doc/whatsnew/3.10.rst13
-rw-r--r--Doc/whatsnew/3.12.rst2
-rw-r--r--Doc/whatsnew/3.13.rst43
-rw-r--r--Doc/whatsnew/3.14.rst815
-rw-r--r--Doc/whatsnew/3.15.rst429
-rw-r--r--Doc/whatsnew/3.3.rst2
-rw-r--r--Doc/whatsnew/3.8.rst4
-rw-r--r--Doc/whatsnew/index.rst1
-rw-r--r--Grammar/Tokens5
-rw-r--r--Grammar/python.gram71
-rw-r--r--Include/abstract.h14
-rw-r--r--Include/audit.h10
-rw-r--r--Include/boolobject.h13
-rw-r--r--Include/ceval.h7
-rw-r--r--Include/cpython/audit.h2
-rw-r--r--Include/cpython/lock.h11
-rw-r--r--Include/cpython/object.h2
-rw-r--r--Include/cpython/pystate.h10
-rw-r--r--Include/cpython/unicodeobject.h135
-rw-r--r--Include/import.h3
-rw-r--r--Include/internal/mimalloc/mimalloc/internal.h8
-rw-r--r--Include/internal/mimalloc/mimalloc/types.h33
-rw-r--r--Include/internal/pycore_backoff.h6
-rw-r--r--Include/internal/pycore_bytesobject.h5
-rw-r--r--Include/internal/pycore_ceval.h20
-rw-r--r--Include/internal/pycore_code.h99
-rw-r--r--Include/internal/pycore_compile.h7
-rw-r--r--Include/internal/pycore_critical_section.h24
-rw-r--r--Include/internal/pycore_crossinterp.h165
-rw-r--r--Include/internal/pycore_crossinterp_data_registry.h4
-rw-r--r--Include/internal/pycore_debug_offsets.h25
-rw-r--r--Include/internal/pycore_dict.h2
-rw-r--r--Include/internal/pycore_freelist_state.h2
-rw-r--r--Include/internal/pycore_function.h12
-rw-r--r--Include/internal/pycore_global_objects_fini_generated.h16
-rw-r--r--Include/internal/pycore_global_strings.h16
-rw-r--r--Include/internal/pycore_importdl.h2
-rw-r--r--Include/internal/pycore_interp_structs.h24
-rw-r--r--Include/internal/pycore_interpframe.h4
-rw-r--r--Include/internal/pycore_lock.h15
-rw-r--r--Include/internal/pycore_long.h18
-rw-r--r--Include/internal/pycore_magic_number.h10
-rw-r--r--Include/internal/pycore_modsupport.h3
-rw-r--r--Include/internal/pycore_object.h42
-rw-r--r--Include/internal/pycore_opcode_metadata.h140
-rw-r--r--Include/internal/pycore_opcode_utils.h2
-rw-r--r--Include/internal/pycore_optimizer.h129
-rw-r--r--Include/internal/pycore_pyerrors.h4
-rw-r--r--Include/internal/pycore_pymem.h12
-rw-r--r--Include/internal/pycore_pystate.h3
-rw-r--r--Include/internal/pycore_pythonrun.h23
-rw-r--r--Include/internal/pycore_qsbr.h31
-rw-r--r--Include/internal/pycore_runtime_init.h7
-rw-r--r--Include/internal/pycore_runtime_init_generated.h16
-rw-r--r--Include/internal/pycore_runtime_structs.h3
-rw-r--r--Include/internal/pycore_stackref.h198
-rw-r--r--Include/internal/pycore_sysmodule.h5
-rw-r--r--Include/internal/pycore_typeobject.h1
-rw-r--r--Include/internal/pycore_unicodeobject.h12
-rw-r--r--Include/internal/pycore_unicodeobject_generated.h64
-rw-r--r--Include/internal/pycore_uop_ids.h465
-rw-r--r--Include/internal/pycore_uop_metadata.h189
-rw-r--r--Include/internal/pycore_weakref.h14
-rw-r--r--Include/object.h30
-rw-r--r--Include/opcode_ids.h42
-rw-r--r--Include/patchlevel.h6
-rw-r--r--Include/py_curses.h10
-rw-r--r--Include/pylifecycle.h7
-rw-r--r--Include/pymacro.h87
-rw-r--r--Include/pyport.h24
-rw-r--r--Include/pythonrun.h28
-rw-r--r--Include/refcount.h25
-rw-r--r--Include/sysmodule.h6
-rw-r--r--Include/unicodeobject.h57
-rw-r--r--InternalDocs/README.md7
-rw-r--r--InternalDocs/asyncio.md328
-rw-r--r--InternalDocs/compiler.md4
-rw-r--r--InternalDocs/exception_handling.md10
-rw-r--r--InternalDocs/garbage_collector.md2
-rw-r--r--InternalDocs/generators.md4
-rw-r--r--InternalDocs/qsbr.md129
-rw-r--r--Lib/_ast_unparse.py12
-rw-r--r--Lib/_colorize.py255
-rw-r--r--Lib/_compat_pickle.py3
-rw-r--r--Lib/_opcode_metadata.py46
-rw-r--r--Lib/_pydatetime.py53
-rw-r--r--Lib/_pydecimal.py4
-rw-r--r--Lib/_pyio.py23
-rw-r--r--Lib/_pyrepl/_module_completer.py24
-rw-r--r--Lib/_pyrepl/base_eventqueue.py18
-rw-r--r--Lib/_pyrepl/commands.py10
-rw-r--r--Lib/_pyrepl/main.py11
-rw-r--r--Lib/_pyrepl/reader.py9
-rw-r--r--Lib/_pyrepl/readline.py6
-rw-r--r--Lib/_pyrepl/simple_interact.py16
-rw-r--r--Lib/_pyrepl/unix_console.py21
-rw-r--r--Lib/_pyrepl/utils.py49
-rw-r--r--Lib/_pyrepl/windows_console.py67
-rw-r--r--Lib/_strptime.py247
-rw-r--r--Lib/_threading_local.py122
-rw-r--r--Lib/annotationlib.py522
-rw-r--r--Lib/argparse.py128
-rw-r--r--Lib/ast.py61
-rw-r--r--Lib/asyncio/__main__.py40
-rw-r--r--Lib/asyncio/base_events.py72
-rw-r--r--Lib/asyncio/base_subprocess.py7
-rw-r--r--Lib/asyncio/futures.py17
-rw-r--r--Lib/asyncio/graph.py6
-rw-r--r--Lib/asyncio/selector_events.py2
-rw-r--r--Lib/asyncio/taskgroups.py7
-rw-r--r--Lib/asyncio/tasks.py50
-rw-r--r--Lib/asyncio/tools.py260
-rw-r--r--Lib/calendar.py8
-rw-r--r--Lib/cmd.py2
-rw-r--r--Lib/code.py4
-rw-r--r--Lib/compileall.py4
-rw-r--r--Lib/compression/bz2.py (renamed from Lib/compression/bz2/__init__.py)0
-rw-r--r--Lib/compression/gzip.py (renamed from Lib/compression/gzip/__init__.py)0
-rw-r--r--Lib/compression/lzma.py (renamed from Lib/compression/lzma/__init__.py)0
-rw-r--r--Lib/compression/zlib.py (renamed from Lib/compression/zlib/__init__.py)0
-rw-r--r--Lib/compression/zstd/__init__.py242
-rw-r--r--Lib/compression/zstd/_zstdfile.py345
-rw-r--r--Lib/concurrent/futures/_base.py27
-rw-r--r--Lib/concurrent/futures/interpreter.py212
-rw-r--r--Lib/concurrent/futures/process.py5
-rw-r--r--Lib/concurrent/interpreters/__init__.py (renamed from Lib/test/support/interpreters/__init__.py)50
-rw-r--r--Lib/concurrent/interpreters/_crossinterp.py (renamed from Lib/test/support/interpreters/_crossinterp.py)2
-rw-r--r--Lib/concurrent/interpreters/_queues.py (renamed from Lib/test/support/interpreters/queues.py)145
-rw-r--r--Lib/configparser.py11
-rw-r--r--Lib/ctypes/__init__.py6
-rw-r--r--Lib/ctypes/_layout.py21
-rw-r--r--Lib/curses/__init__.py17
-rw-r--r--Lib/dataclasses.py53
-rw-r--r--Lib/dbm/dumb.py32
-rw-r--r--Lib/dbm/sqlite3.py4
-rw-r--r--Lib/difflib.py54
-rw-r--r--Lib/dis.py2
-rw-r--r--Lib/doctest.py115
-rw-r--r--Lib/email/_header_value_parser.py6
-rw-r--r--Lib/email/header.py17
-rw-r--r--Lib/email/message.py2
-rw-r--r--Lib/email/utils.py10
-rw-r--r--Lib/encodings/aliases.py2
-rw-r--r--Lib/encodings/idna.py2
-rw-r--r--Lib/encodings/palmos.py2
-rw-r--r--Lib/ensurepip/__init__.py2
-rw-r--r--Lib/fractions.py17
-rw-r--r--Lib/functools.py3
-rw-r--r--Lib/genericpath.py11
-rw-r--r--Lib/getpass.py69
-rw-r--r--Lib/glob.py30
-rw-r--r--Lib/gzip.py4
-rw-r--r--Lib/hashlib.py12
-rw-r--r--Lib/heapq.py51
-rw-r--r--Lib/html/parser.py210
-rw-r--r--Lib/http/client.py9
-rw-r--r--Lib/http/server.py394
-rw-r--r--Lib/idlelib/NEWS2x.txt2
-rw-r--r--Lib/idlelib/News3.txt7
-rw-r--r--Lib/idlelib/configdialog.py2
-rw-r--r--Lib/idlelib/debugger.py2
-rw-r--r--Lib/idlelib/editor.py2
-rw-r--r--Lib/idlelib/idle_test/htest.py2
-rw-r--r--Lib/inspect.py6
-rw-r--r--Lib/ipaddress.py17
-rw-r--r--Lib/json/encoder.py5
-rw-r--r--Lib/json/tool.py43
-rw-r--r--Lib/linecache.py67
-rw-r--r--Lib/locale.py5
-rw-r--r--Lib/logging/__init__.py33
-rw-r--r--Lib/logging/config.py2
-rw-r--r--Lib/mimetypes.py4
-rw-r--r--Lib/multiprocessing/connection.py2
-rw-r--r--Lib/multiprocessing/context.py2
-rw-r--r--Lib/multiprocessing/forkserver.py4
-rw-r--r--Lib/multiprocessing/sharedctypes.py7
-rw-r--r--Lib/multiprocessing/util.py79
-rw-r--r--Lib/netrc.py33
-rw-r--r--Lib/ntpath.py41
-rw-r--r--Lib/os.py3
-rw-r--r--Lib/pathlib/__init__.py7
-rw-r--r--Lib/pathlib/_os.py28
-rw-r--r--Lib/pathlib/types.py51
-rw-r--r--Lib/pdb.py394
-rw-r--r--Lib/pickle.py4
-rw-r--r--Lib/pickletools.py4
-rw-r--r--Lib/platform.py93
-rw-r--r--Lib/posixpath.py57
-rw-r--r--Lib/pprint.py79
-rw-r--r--Lib/py_compile.py2
-rw-r--r--Lib/pydoc.py6
-rw-r--r--Lib/pydoc_data/topics.py109
-rw-r--r--Lib/random.py2
-rw-r--r--Lib/reprlib.py19
-rw-r--r--Lib/shelve.py5
-rw-r--r--Lib/shutil.py19
-rw-r--r--Lib/site.py8
-rw-r--r--Lib/socketserver.py2
-rw-r--r--Lib/sqlite3/__main__.py73
-rw-r--r--Lib/sqlite3/_completer.py42
-rw-r--r--Lib/sre_compile.py7
-rw-r--r--Lib/sre_constants.py7
-rw-r--r--Lib/sre_parse.py7
-rw-r--r--Lib/ssl.py2
-rw-r--r--Lib/string/__init__.py47
-rw-r--r--Lib/subprocess.py14
-rw-r--r--Lib/sysconfig/__init__.py15
-rw-r--r--Lib/tarfile.py230
-rw-r--r--Lib/tempfile.py5
-rw-r--r--Lib/test/.ruff.toml10
-rw-r--r--Lib/test/_code_definitions.py160
-rw-r--r--Lib/test/_test_embed_structseq.py2
-rw-r--r--Lib/test/_test_gc_fast_cycles.py48
-rw-r--r--Lib/test/_test_multiprocessing.py84
-rw-r--r--Lib/test/audit-tests.py28
-rw-r--r--Lib/test/datetimetester.py35
-rw-r--r--Lib/test/libregrtest/main.py23
-rw-r--r--Lib/test/libregrtest/setup.py11
-rw-r--r--Lib/test/libregrtest/single.py2
-rw-r--r--Lib/test/libregrtest/tsan.py2
-rw-r--r--Lib/test/libregrtest/utils.py42
-rw-r--r--Lib/test/lock_tests.py43
-rw-r--r--Lib/test/mapping_tests.py4
-rw-r--r--Lib/test/mp_preload_flush.py15
-rw-r--r--Lib/test/pickletester.py38
-rw-r--r--Lib/test/pythoninfo.py24
-rw-r--r--Lib/test/subprocessdata/fd_status.py4
-rw-r--r--Lib/test/support/__init__.py121
-rw-r--r--Lib/test/support/channels.py (renamed from Lib/test/support/interpreters/channels.py)93
-rw-r--r--Lib/test/support/hashlib_helper.py221
-rw-r--r--Lib/test/support/import_helper.py2
-rw-r--r--Lib/test/support/strace_helper.py7
-rw-r--r--Lib/test/support/warnings_helper.py3
-rw-r--r--Lib/test/test__interpchannels.py8
-rw-r--r--Lib/test/test__interpreters.py37
-rw-r--r--Lib/test/test__osx_support.py4
-rw-r--r--Lib/test/test_abstract_numbers.py30
-rw-r--r--Lib/test/test_annotationlib.py381
-rw-r--r--Lib/test/test_argparse.py167
-rw-r--r--Lib/test/test_asdl_parser.py8
-rw-r--r--Lib/test/test_ast/test_ast.py393
-rw-r--r--Lib/test/test_asyncgen.py9
-rw-r--r--Lib/test/test_asyncio/test_base_events.py59
-rw-r--r--Lib/test/test_asyncio/test_eager_task_factory.py37
-rw-r--r--Lib/test/test_asyncio/test_futures.py58
-rw-r--r--Lib/test/test_asyncio/test_selector_events.py16
-rw-r--r--Lib/test/test_asyncio/test_ssl.py10
-rw-r--r--Lib/test/test_asyncio/test_tasks.py73
-rw-r--r--Lib/test/test_asyncio/test_tools.py1706
-rw-r--r--Lib/test/test_audit.py10
-rw-r--r--Lib/test/test_base64.py10
-rw-r--r--Lib/test/test_baseexception.py8
-rw-r--r--Lib/test/test_binascii.py6
-rw-r--r--Lib/test/test_binop.py2
-rw-r--r--Lib/test/test_buffer.py4
-rw-r--r--Lib/test/test_bufio.py2
-rw-r--r--Lib/test/test_build_details.py16
-rw-r--r--Lib/test/test_builtin.py8
-rw-r--r--Lib/test/test_bytes.py10
-rw-r--r--Lib/test/test_bz2.py4
-rw-r--r--Lib/test/test_calendar.py5
-rw-r--r--Lib/test/test_call.py4
-rw-r--r--Lib/test/test_capi/test_abstract.py25
-rw-r--r--Lib/test/test_capi/test_bytearray.py6
-rw-r--r--Lib/test/test_capi/test_bytes.py8
-rw-r--r--Lib/test/test_capi/test_config.py3
-rw-r--r--Lib/test/test_capi/test_import.py2
-rw-r--r--Lib/test/test_capi/test_misc.py6
-rw-r--r--Lib/test/test_capi/test_object.py11
-rw-r--r--Lib/test/test_capi/test_opt.py562
-rw-r--r--Lib/test/test_capi/test_sys.py64
-rw-r--r--Lib/test/test_capi/test_type.py10
-rw-r--r--Lib/test/test_capi/test_unicode.py21
-rw-r--r--Lib/test/test_class.py1
-rw-r--r--Lib/test/test_clinic.py51
-rw-r--r--Lib/test/test_cmd.py32
-rw-r--r--Lib/test/test_cmd_line.py52
-rw-r--r--Lib/test/test_cmd_line_script.py11
-rw-r--r--Lib/test/test_code.py389
-rw-r--r--Lib/test/test_code_module.py2
-rw-r--r--Lib/test/test_codeccallbacks.py38
-rw-r--r--Lib/test/test_codecs.py63
-rw-r--r--Lib/test/test_codeop.py2
-rw-r--r--Lib/test/test_collections.py2
-rw-r--r--Lib/test/test_compileall.py2
-rw-r--r--Lib/test/test_compiler_assemble.py2
-rw-r--r--Lib/test/test_concurrent_futures/test_future.py57
-rw-r--r--Lib/test/test_concurrent_futures/test_init.py4
-rw-r--r--Lib/test/test_concurrent_futures/test_interpreter_pool.py265
-rw-r--r--Lib/test/test_concurrent_futures/test_shutdown.py58
-rw-r--r--Lib/test/test_configparser.py12
-rw-r--r--Lib/test/test_contextlib.py8
-rw-r--r--Lib/test/test_contextlib_async.py8
-rw-r--r--Lib/test/test_copy.py5
-rw-r--r--Lib/test/test_coroutines.py2
-rw-r--r--Lib/test/test_cprofile.py19
-rw-r--r--Lib/test/test_crossinterp.py711
-rw-r--r--Lib/test/test_csv.py55
-rw-r--r--Lib/test/test_ctypes/_support.py1
-rw-r--r--Lib/test/test_ctypes/test_aligned_structures.py1
-rw-r--r--Lib/test/test_ctypes/test_bitfields.py5
-rw-r--r--Lib/test/test_ctypes/test_byteswap.py3
-rw-r--r--Lib/test/test_ctypes/test_generated_structs.py13
-rw-r--r--Lib/test/test_ctypes/test_incomplete.py10
-rw-r--r--Lib/test/test_ctypes/test_parameters.py4
-rw-r--r--Lib/test/test_ctypes/test_pep3118.py3
-rw-r--r--Lib/test/test_ctypes/test_structunion.py18
-rw-r--r--Lib/test/test_ctypes/test_structures.py31
-rw-r--r--Lib/test/test_ctypes/test_unaligned_structures.py2
-rw-r--r--Lib/test/test_curses.py42
-rw-r--r--Lib/test/test_dataclasses/__init__.py78
-rw-r--r--Lib/test/test_dbm.py63
-rw-r--r--Lib/test/test_dbm_gnu.py27
-rw-r--r--Lib/test/test_dbm_sqlite3.py4
-rw-r--r--Lib/test/test_decimal.py3
-rw-r--r--Lib/test/test_deque.py2
-rw-r--r--Lib/test/test_descr.py49
-rw-r--r--Lib/test/test_dict.py61
-rw-r--r--Lib/test/test_difflib.py6
-rw-r--r--Lib/test/test_difflib_expect.html48
-rw-r--r--Lib/test/test_dis.py217
-rw-r--r--Lib/test/test_doctest/sample_doctest_errors.py46
-rw-r--r--Lib/test/test_doctest/test_doctest.py447
-rw-r--r--Lib/test/test_doctest/test_doctest_errors.txt14
-rw-r--r--Lib/test/test_doctest/test_doctest_skip.txt2
-rw-r--r--Lib/test/test_doctest/test_doctest_skip2.txt6
-rw-r--r--Lib/test/test_dynamicclassattribute.py4
-rw-r--r--Lib/test/test_email/test__header_value_parser.py78
-rw-r--r--Lib/test/test_email/test_email.py30
-rw-r--r--Lib/test/test_email/test_utils.py10
-rw-r--r--Lib/test/test_embed.py15
-rw-r--r--Lib/test/test_enum.py27
-rw-r--r--Lib/test/test_errno.py6
-rw-r--r--Lib/test/test_exception_group.py14
-rw-r--r--Lib/test/test_exceptions.py23
-rw-r--r--Lib/test/test_external_inspection.py664
-rw-r--r--Lib/test/test_faulthandler.py23
-rw-r--r--Lib/test/test_fcntl.py59
-rw-r--r--Lib/test/test_fileinput.py2
-rw-r--r--Lib/test/test_fileio.py12
-rw-r--r--Lib/test/test_float.py2
-rw-r--r--Lib/test/test_format.py10
-rw-r--r--Lib/test/test_fractions.py223
-rw-r--r--Lib/test/test_free_threading/test_dict.py16
-rw-r--r--Lib/test/test_free_threading/test_functools.py75
-rw-r--r--Lib/test/test_free_threading/test_generators.py51
-rw-r--r--Lib/test/test_free_threading/test_heapq.py267
-rw-r--r--Lib/test/test_free_threading/test_io.py109
-rw-r--r--Lib/test/test_free_threading/test_itertools.py95
-rw-r--r--Lib/test/test_free_threading/test_itertools_batched.py38
-rw-r--r--Lib/test/test_free_threading/test_itertools_combinatoric.py51
-rw-r--r--Lib/test/test_fstring.py10
-rw-r--r--Lib/test/test_functools.py28
-rw-r--r--Lib/test/test_future_stmt/test_future.py5
-rw-r--r--Lib/test/test_gc.py64
-rw-r--r--Lib/test/test_generated_cases.py480
-rw-r--r--Lib/test/test_genericalias.py18
-rw-r--r--Lib/test/test_genericpath.py6
-rw-r--r--Lib/test/test_getpass.py39
-rw-r--r--Lib/test/test_gettext.py7
-rw-r--r--Lib/test/test_grammar.py14
-rw-r--r--Lib/test/test_gzip.py9
-rw-r--r--Lib/test/test_hashlib.py253
-rw-r--r--Lib/test/test_heapq.py197
-rw-r--r--Lib/test/test_hmac.py153
-rw-r--r--Lib/test/test_htmlparser.py346
-rw-r--r--Lib/test/test_http_cookiejar.py187
-rw-r--r--Lib/test/test_httpservers.py738
-rw-r--r--Lib/test/test_idle.py2
-rw-r--r--Lib/test/test_importlib/import_/test_relative_imports.py15
-rw-r--r--Lib/test/test_importlib/test_locks.py1
-rw-r--r--Lib/test/test_importlib/test_threaded_import.py15
-rw-r--r--Lib/test/test_inspect/test_inspect.py49
-rw-r--r--Lib/test/test_int.py2
-rw-r--r--Lib/test/test_interpreters/test_api.py884
-rw-r--r--Lib/test/test_interpreters/test_channels.py56
-rw-r--r--Lib/test/test_interpreters/test_lifecycle.py4
-rw-r--r--Lib/test/test_interpreters/test_queues.py271
-rw-r--r--Lib/test/test_interpreters/test_stress.py32
-rw-r--r--Lib/test/test_interpreters/utils.py3
-rw-r--r--Lib/test/test_io.py103
-rw-r--r--Lib/test/test_ioctl.py16
-rw-r--r--Lib/test/test_ipaddress.py54
-rw-r--r--Lib/test/test_isinstance.py2
-rw-r--r--Lib/test/test_iter.py2
-rw-r--r--Lib/test/test_json/test_dump.py8
-rw-r--r--Lib/test/test_json/test_fail.py2
-rw-r--r--Lib/test/test_json/test_recursion.py3
-rw-r--r--Lib/test/test_json/test_tool.py87
-rw-r--r--Lib/test/test_launcher.py8
-rw-r--r--Lib/test/test_linecache.py37
-rw-r--r--Lib/test/test_list.py15
-rw-r--r--Lib/test/test_listcomps.py2
-rw-r--r--Lib/test/test_locale.py13
-rw-r--r--Lib/test/test_logging.py25
-rw-r--r--Lib/test/test_lzma.py4
-rw-r--r--Lib/test/test_math.py43
-rw-r--r--Lib/test/test_memoryio.py4
-rw-r--r--Lib/test/test_memoryview.py20
-rw-r--r--Lib/test/test_mimetypes.py8
-rw-r--r--Lib/test/test_minidom.py182
-rw-r--r--Lib/test/test_monitoring.py15
-rw-r--r--Lib/test/test_netrc.py13
-rw-r--r--Lib/test/test_ntpath.py360
-rw-r--r--Lib/test/test_opcache.py14
-rw-r--r--Lib/test/test_optparse.py11
-rw-r--r--Lib/test/test_ordered_dict.py8
-rw-r--r--Lib/test/test_os.py28
-rw-r--r--Lib/test/test_pathlib/support/lexical_path.py11
-rw-r--r--Lib/test/test_pathlib/support/local_path.py10
-rw-r--r--Lib/test/test_pathlib/support/zip_path.py45
-rw-r--r--Lib/test/test_pathlib/test_join_windows.py17
-rw-r--r--Lib/test/test_pathlib/test_pathlib.py36
-rw-r--r--Lib/test/test_pdb.py90
-rw-r--r--Lib/test/test_peepholer.py127
-rw-r--r--Lib/test/test_peg_generator/test_c_parser.py4
-rw-r--r--Lib/test/test_peg_generator/test_pegen.py6
-rw-r--r--Lib/test/test_perf_profiler.py9
-rw-r--r--Lib/test/test_pickle.py14
-rw-r--r--Lib/test/test_platform.py28
-rw-r--r--Lib/test/test_positional_only_arg.py12
-rw-r--r--Lib/test/test_posix.py57
-rw-r--r--Lib/test/test_posixpath.py388
-rw-r--r--Lib/test/test_pprint.py355
-rw-r--r--Lib/test/test_property.py4
-rw-r--r--Lib/test/test_pstats.py7
-rw-r--r--Lib/test/test_pty.py1
-rw-r--r--Lib/test/test_pulldom.py4
-rw-r--r--Lib/test/test_pyclbr.py6
-rw-r--r--Lib/test/test_pydoc/test_pydoc.py4
-rw-r--r--Lib/test/test_pyexpat.py20
-rw-r--r--Lib/test/test_pyrepl/support.py3
-rw-r--r--Lib/test/test_pyrepl/test_eventqueue.py78
-rw-r--r--Lib/test/test_pyrepl/test_interact.py2
-rw-r--r--Lib/test/test_pyrepl/test_pyrepl.py212
-rw-r--r--Lib/test/test_pyrepl/test_reader.py90
-rw-r--r--Lib/test/test_pyrepl/test_unix_console.py13
-rw-r--r--Lib/test/test_pyrepl/test_windows_console.py227
-rw-r--r--Lib/test/test_queue.py20
-rw-r--r--Lib/test/test_random.py340
-rw-r--r--Lib/test/test_re.py34
-rw-r--r--Lib/test/test_readline.py8
-rw-r--r--Lib/test/test_regrtest.py65
-rw-r--r--Lib/test/test_remote_pdb.py457
-rw-r--r--Lib/test/test_repl.py6
-rw-r--r--Lib/test/test_reprlib.py97
-rw-r--r--Lib/test/test_rlcompleter.py2
-rw-r--r--Lib/test/test_runpy.py2
-rw-r--r--Lib/test/test_scope.py2
-rw-r--r--Lib/test/test_script_helper.py3
-rw-r--r--Lib/test/test_set.py6
-rw-r--r--Lib/test/test_shlex.py2
-rw-r--r--Lib/test/test_shutil.py12
-rw-r--r--Lib/test/test_site.py22
-rw-r--r--Lib/test/test_socket.py31
-rw-r--r--Lib/test/test_source_encoding.py3
-rw-r--r--Lib/test/test_sqlite3/test_cli.py160
-rw-r--r--Lib/test/test_sqlite3/test_dbapi.py14
-rw-r--r--Lib/test/test_sqlite3/test_factory.py15
-rw-r--r--Lib/test/test_sqlite3/test_hooks.py22
-rw-r--r--Lib/test/test_sqlite3/test_userfunctions.py55
-rw-r--r--Lib/test/test_ssl.py54
-rw-r--r--Lib/test/test_stable_abi_ctypes.py4
-rw-r--r--Lib/test/test_stat.py6
-rw-r--r--Lib/test/test_statistics.py25
-rw-r--r--Lib/test/test_str.py8
-rw-r--r--Lib/test/test_strftime.py16
-rw-r--r--Lib/test/test_string/_support.py1
-rw-r--r--Lib/test/test_string/test_string.py8
-rw-r--r--Lib/test/test_string/test_templatelib.py7
-rw-r--r--Lib/test/test_strptime.py37
-rw-r--r--Lib/test/test_structseq.py4
-rw-r--r--Lib/test/test_subprocess.py22
-rw-r--r--Lib/test/test_super.py10
-rw-r--r--Lib/test/test_support.py5
-rw-r--r--Lib/test/test_syntax.py109
-rw-r--r--Lib/test/test_sys.py192
-rw-r--r--Lib/test/test_sysconfig.py20
-rw-r--r--Lib/test/test_tarfile.py405
-rw-r--r--Lib/test/test_tempfile.py16
-rw-r--r--Lib/test/test_termios.py4
-rw-r--r--Lib/test/test_threadedtempfile.py4
-rw-r--r--Lib/test/test_threading.py112
-rw-r--r--Lib/test/test_time.py12
-rw-r--r--Lib/test/test_timeit.py4
-rw-r--r--Lib/test/test_tkinter/support.py2
-rw-r--r--Lib/test/test_tkinter/test_misc.py24
-rw-r--r--Lib/test/test_tokenize.py78
-rw-r--r--Lib/test/test_tools/i18n_data/docstrings.py2
-rw-r--r--Lib/test/test_traceback.py143
-rw-r--r--Lib/test/test_tstring.py1
-rw-r--r--Lib/test/test_type_annotations.py22
-rw-r--r--Lib/test/test_type_comments.py2
-rw-r--r--Lib/test/test_types.py80
-rw-r--r--Lib/test/test_typing.py279
-rw-r--r--Lib/test/test_unittest/test_case.py24
-rw-r--r--Lib/test/test_unittest/test_result.py37
-rw-r--r--Lib/test/test_unittest/test_runner.py52
-rw-r--r--Lib/test/test_unittest/testmock/testhelpers.py21
-rw-r--r--Lib/test/test_unparse.py9
-rw-r--r--Lib/test/test_urllib.py19
-rw-r--r--Lib/test/test_urlparse.py398
-rw-r--r--Lib/test/test_userdict.py2
-rwxr-xr-xLib/test/test_uuid.py28
-rw-r--r--Lib/test/test_venv.py6
-rw-r--r--Lib/test/test_warnings/__init__.py10
-rw-r--r--Lib/test/test_wave.py26
-rw-r--r--Lib/test/test_weakref.py6
-rw-r--r--Lib/test/test_weakset.py4
-rw-r--r--Lib/test/test_webbrowser.py1
-rw-r--r--Lib/test/test_winconsoleio.py6
-rw-r--r--Lib/test/test_with.py2
-rw-r--r--Lib/test/test_wmi.py4
-rw-r--r--Lib/test/test_wsgiref.py14
-rw-r--r--Lib/test/test_xml_etree.py76
-rw-r--r--Lib/test/test_xxlimited.py2
-rw-r--r--Lib/test/test_zipapp.py6
-rw-r--r--Lib/test/test_zipfile/__main__.py2
-rw-r--r--Lib/test/test_zipfile/_path/_test_params.py2
-rw-r--r--Lib/test/test_zipfile/_path/test_complexity.py2
-rw-r--r--Lib/test/test_zipfile/_path/test_path.py22
-rw-r--r--Lib/test/test_zipfile/_path/write-alpharep.py1
-rw-r--r--Lib/test/test_zipfile/test_core.py64
-rw-r--r--Lib/test/test_zipimport.py4
-rw-r--r--Lib/test/test_zlib.py108
-rw-r--r--Lib/test/test_zoneinfo/test_zoneinfo.py47
-rw-r--r--Lib/test/test_zstd.py2794
-rw-r--r--Lib/threading.py19
-rw-r--r--Lib/tokenize.py18
-rw-r--r--Lib/trace.py2
-rw-r--r--Lib/traceback.py118
-rw-r--r--Lib/types.py2
-rw-r--r--Lib/typing.py144
-rw-r--r--Lib/unittest/_log.py5
-rw-r--r--Lib/unittest/case.py10
-rw-r--r--Lib/unittest/main.py4
-rw-r--r--Lib/unittest/mock.py15
-rw-r--r--Lib/unittest/runner.py89
-rw-r--r--Lib/unittest/suite.py20
-rw-r--r--Lib/urllib/request.py19
-rw-r--r--Lib/uuid.py24
-rw-r--r--Lib/venv/__init__.py11
-rw-r--r--Lib/wave.py29
-rw-r--r--Lib/webbrowser.py4
-rw-r--r--Lib/wsgiref/handlers.py3
-rw-r--r--Lib/xml/etree/ElementTree.py10
-rw-r--r--Lib/xmlrpc/server.py2
-rw-r--r--Lib/zipapp.py2
-rw-r--r--Lib/zipfile/__init__.py80
-rw-r--r--Lib/zipfile/_path/__init__.py38
-rw-r--r--Lib/zipfile/_path/_functools.py20
-rw-r--r--Lib/zipfile/_path/glob.py1
-rw-r--r--Lib/zoneinfo/_common.py8
-rw-r--r--Lib/zoneinfo/_zoneinfo.py6
-rw-r--r--Makefile.pre.in21
-rw-r--r--Misc/ACKS10
-rw-r--r--Misc/NEWS.d/3.10.0a3.rst6
-rw-r--r--Misc/NEWS.d/3.11.0a4.rst2
-rw-r--r--Misc/NEWS.d/3.13.0a1.rst16
-rw-r--r--Misc/NEWS.d/3.14.0a1.rst2
-rw-r--r--Misc/NEWS.d/3.14.0a6.rst2
-rw-r--r--Misc/NEWS.d/3.14.0b1.rst2112
-rw-r--r--Misc/NEWS.d/3.9.0a1.rst4
-rw-r--r--Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-02-21-08-36.gh-issue-132026.ptnR7T.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-08-09-11-32.gh-issue-132257.oZWBV-.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-16-09-38-48.gh-issue-117088.EFt_5c.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-17-19-10-15.gh-issue-132649.DZqGoq.rst2
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-20-20-07-44.gh-issue-132758.N2a3wp.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-29-15-29-11.gh-issue-133171.YbwbwP.rst2
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-30-10-23-18.gh-issue-133167.E0jrYJ.rst2
-rw-r--r--Misc/NEWS.d/next/Build/2025-04-30-11-07-53.gh-issue-133183.zCKUeQ.rst2
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-01-17-27-06.gh-issue-113464.vjE5X4.rst3
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-14-09-43-48.gh-issue-131769.H0oy5x.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-16-07-46-06.gh-issue-115119.ALBgS_.rst4
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-19-18-09-20.gh-issue-134273.ZAliyy.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-21-19-46-28.gh-issue-134455.vdwlrq.rst2
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-21-22-13-30.gh-issue-134486.yvdL6f.rst3
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-24-16-59-20.gh-issue-134632.i0W2hc.rst3
-rw-r--r--Misc/NEWS.d/next/Build/2025-05-30-11-02-30.gh-issue-134923.gBkRg4.rst3
-rw-r--r--Misc/NEWS.d/next/Build/2025-06-14-10-32-11.gh-issue-135497.ajlV4F.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-06-16-07-20-28.gh-issue-119132.fcI8s7.rst1
-rw-r--r--Misc/NEWS.d/next/Build/2025-06-25-13-27-14.gh-issue-135927.iCNPQc.rst1
-rw-r--r--Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2024-12-31-15-28-14.gh-issue-50333.KxQUXa.rst5
-rw-r--r--Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst3
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-13-20-52-39.gh-issue-132470.UqBQjN.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-14-07-41-28.gh-issue-131185.ZCjMHD.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-22-13-59-30.gh-issue-132798.asfafhs.rst3
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-25-11-39-24.gh-issue-132909.JC3n_l.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-28-13-27-48.gh-issue-133079.DJL2sK.rst3
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-28-15-36-01.gh-issue-128972.8bZMIm.rst3
-rw-r--r--Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst5
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-01-01-02-11.gh-issue-133166.Ly9Ae2.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-07-21-18-00.gh-issue-133610.asdfjs.rst3
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-08-12-25-47.gh-issue-133644.Yb86Rm.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-08-13-14-45.gh-issue-133644.J8_KZ2.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-13-16-06-46.gh-issue-133968.6alWst.rst4
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst1
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst1
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-29-16-56-23.gh-issue-134891.7eKO8U.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-05-30-11-33-17.gh-issue-134745.GN-zk2.rst3
-rw-r--r--Misc/NEWS.d/next/C_API/2025-06-02-13-19-22.gh-issue-134989.sDDyBN.rst2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-06-05-11-06-07.gh-issue-134989.74p4ud.rst3
-rw-r--r--Misc/NEWS.d/next/C_API/2025-06-19-12-47-18.gh-issue-133157.1WA85f.rst1
-rw-r--r--Misc/NEWS.d/next/C_API/2025-07-01-16-22-39.gh-issue-135075.angu3J.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2022-12-29-19-10-36.gh-issue-89562.g8m8RC.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2023-04-29-23-15-38.gh-issue-103997.BS3uVt.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2024-03-06-22-33-33.gh-issue-116436.y8Thkt.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2024-09-03-15-15-51.gh-issue-123539.RKQS0S.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst16
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-01-26-23-46-43.gh-issue-69605._2Qc1w.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-02-12-01-36-13.gh-issue-129858.M-f7Gb.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-00-14-24.gh-issue-129958.Uj7lyY.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-05-09-31.gh-issue-130070.C8c9gK.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-03-14-13-08-20.gh-issue-127266._tyfBp.rst6
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-19-03-42.gh-issue-131507.q9fvyM.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-03-26-04-55-25.gh-issue-114809.8rNyT7.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-16-41-00.gh-issue-133379.asdjhjdf.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-06-13-17-10.gh-issue-131798.uMrfha.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-07-13-46-57.gh-issue-128398.gJ2zIF.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-09-20-18.gh-issue-131798.Xp1mvN.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-17-48-11.gh-issue-124715.xxzQoD.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-21-20-12.gh-issue-131798.Ft9tIF.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-12-37-31.gh-issue-132286.1ZdsOa.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-13-47-33.gh-issue-126703.kXiQHj.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-14-05-54.gh-issue-130415.llQtUq.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-20-49-04.gh-issue-132284.TxTNka.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-21-51-37.gh-issue-132261.gL8thm.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-10-29-45.gh-issue-127682.X0HoGz.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-18-46-37.gh-issue-132386.pMBFTe.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-22-01-07.gh-issue-131798.TTu_xH.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-12-19-41-16.gh-issue-131798.JkSocg.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-10-34-27.gh-issue-131927.otp80n.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-17-18-01.gh-issue-124476.fvGfQ7.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-15-10-09-49.gh-issue-132508.zVe3iI.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-11-40-13.gh-issue-100239.9RxIxY.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-16-20-03.gh-issue-132639.zRVYU3.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-17-16-46.gh-issue-132542.7T_TY_.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-18-07-34.gh-issue-132737.9mW1il.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-22-59-24.gh-issue-132449.xjdw4p.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-20-10-37-39.gh-issue-132744.ArrCp8.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-07-39-59.gh-issue-132747.L-cnej.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-09-22-15.gh-issue-132479.CCe2sE.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-15-37-05.gh-issue-132661.XE_A42.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-16-38-43.gh-issue-132713.mBWTSZ.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-19-00-03.gh-issue-131591.CdEqBr.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-11-34-39.gh-issue-132825._yv0uL.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-42-55.gh-issue-131798.wVQ1Gt.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-54-17.gh-issue-131798.XYlp09.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-25-14-56-45.gh-issue-131798.NpcKub.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-13-57-13.gh-issue-131798.Gt8CGE.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-01.gh-issue-131798.XiOgw5.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-47.gh-issue-132942.aEEZvZ.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-18-43-31.gh-issue-131798.FsIypo.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-13-09-20.gh-issue-133194.25_G5c.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-14-13-01.gh-issue-132554.GqQaUp.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-01-11-06-29.gh-issue-133197.BHjfh4.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-13-36-01.gh-issue-131798.U4_QEJ.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-22-31-53.gh-issue-131798.fQ0ato.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-06-15-01-41.gh-issue-133516.RqWVf2.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-23-26-53.gh-issue-133541.bHIC55.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-08-13-48-02.gh-issue-132762.tKbygC.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-09-18-11-21.gh-issue-133778.pWEV3t.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-13-40-42.gh-issue-133886.ryBAyo.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-15-11-38-16.gh-issue-133999.uBZ8uS.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-09-06-38.gh-issue-134036.st2e-B.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-17-25-52.gh-issue-134100.5-FbLK.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-20-59-12.gh-issue-134119.w8expI.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-17-20-44-51.gh-issue-134158.ewLNLp.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-14-33-23.gh-issue-69605.ZMO49F.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-15-15-58.gh-issue-131798.PCP71j.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-13-58-18.gh-issue-131798.hG8xBw.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-14-41-50.gh-issue-128066.qzzGfv.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-23-32-11.gh-issue-131798.G9ZQZw.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-13-57-26.gh-issue-131798.QwS5Bb.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-15-14-32.gh-issue-130397.aG6EON.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-18-02-56.gh-issue-127960.W3J_2X.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-14-48-19.gh-issue-134381.2BXhth.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-17-49-39.gh-issue-131798.U6ZmFm.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-23-14-54-07.gh-issue-134584.y-WDjf.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-25-19-32-15.gh-issue-131798.f5h8aI.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-15-55-50.gh-issue-133912.-xAguL.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-18-59-54.gh-issue-134679.FWPBu6.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-21-34.gh-issue-131798.b32zkl.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-29-00.gh-issue-132617.EmUfQQ.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-23-58-50.gh-issue-117852.BO9g7z.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-15-56-19.gh-issue-134908.3a7PxM.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-18-09-54.gh-issue-134889.Ic9UM-.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-19-24-54.gh-issue-134280.NDVbzY.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-13-57-40.gh-issue-116738.ycJsL8.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-20-13-37.gh-issue-131798.JQRFvR.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-03-21-06-22.gh-issue-133136.Usnvri.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-05-21-58-30.gh-issue-131798.nt5Ab7.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-01-09-44.gh-issue-131798.1SuxO9.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-19-17-22.gh-issue-131798.XoV8Eb.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-08-14-24-29.gh-issue-131798.qfw91T.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-09-23-57-37.gh-issue-130077.MHknDB.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-11-15-08-10.gh-issue-127319.OVGFSZ.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-11-19-52.gh-issue-135422.F6yQi6.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-18-12-42.gh-issue-135371.R_YUtR.rst4
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-13-16-05-24.gh-issue-135474.67nOl3.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-14-01-01-14.gh-issue-135496.ER0Me3.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-02-31-42.gh-issue-135543.6b0HOF.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-03-56-15.gh-issue-135551.hRTQO-.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-12-50-48.gh-issue-135608.PnHckD.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-22-34-58.gh-issue-135607.ucsLVu.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-12-19-13.gh-issue-135379.TCvGpj.rst3
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-16-45-36.gh-issue-135106.cpl6Aq.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-20-14-50-44.gh-issue-134584.3CJdAI.rst1
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-18-08-32.gh-issue-135871.50C528.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-06-41-47.gh-issue-129958.EaJuS0.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-16-46-34.gh-issue-135904.78xfon.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-06-26-15-25-51.gh-issue-78465.MbDN8X.rst2
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst2
-rw-r--r--Misc/NEWS.d/next/Documentation/2021-09-15-13-07-25.bpo-45210.RtGk7i.rst2
-rw-r--r--Misc/NEWS.d/next/Documentation/2024-10-08-10-44-14.gh-issue-125142.HVlHrs.rst2
-rw-r--r--Misc/NEWS.d/next/Documentation/2025-06-10-17-02-06.gh-issue-135171.quHvts.rst2
-rw-r--r--Misc/NEWS.d/next/IDLE/2024-11-08-18-07-13.gh-issue-112936.1Q2RcP.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2017-12-30-18-21-00.bpo-28494.Dt_Wks.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2022-07-24-20-56-32.gh-issue-69426.unccw7.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2023-02-13-21-41-34.gh-issue-86155.ppIGSC.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2023-02-13-21-56-38.gh-issue-62824.CBZzX3.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2023-12-29-09-44-41.gh-issue-113539.YDkv9O.rst6
-rw-r--r--Misc/NEWS.d/next/Library/2024-06-06-17-49-07.gh-issue-120170.DUxhmT.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2024-06-07-15-03-54.gh-issue-120220.NNxrr_.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2024-10-22-16-21-55.gh-issue-125843.2ttzYo.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2024-11-14-21-17-48.gh-issue-126838.Yr5vKF.rst5
-rw-r--r--Misc/NEWS.d/next/Library/2024-11-25-10-22-08.gh-issue-126883.MAEF7g.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2024-11-29-13-06-52.gh-issue-127385.PErcyB.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-01-21-11-48-19.gh-issue-129027.w0vxzZ.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-06-11-23-51.gh-issue-129719.Of6rvb.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-11-10-22-11.gh-issue-128384.jyWEkA.rst7
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-12-16-37-34.gh-issue-101410.0GInct.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-16-06-25-01.gh-issue-130167.kUg7Rc.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-21-15-46-43.gh-issue-130402.Rwu_KK.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-22-13-07-06.gh-issue-130317.tnxd0I.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-24-12-22-51.gh-issue-130482.p2DrrL.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-02-27-14-25-01.gh-issue-130631.dmZcZM.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-01-12-37-08.gh-issue-129098.eJ2-6L.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-07-17-47-32.gh-issue-130941.7_GvhW.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-09-03-13-41.gh-issue-130999.tBRBVB.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-09-10-37-00.gh-issue-89157.qg3r138.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-11-05-24-14.gh-issue-130664.g0yNMm.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-11-21-08-46.gh-issue-131127.whcVdY.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-13-20-48-58.gh-issue-123471.cM4w4f.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-14-14-18-49.gh-issue-123471.sduBKk.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-16-17-40-00.gh-issue-85702.qudq12.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-17-23-07-57.gh-issue-100926.B8gcbz.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-21-17-34-27.gh-issue-131524.Vj1pO_.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-23-11-33-09.gh-issue-131423.bQlcEb.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-01-18-24-58.gh-issue-85302.7knfUf.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-03-00-56-48.gh-issue-118761.Vb0S1B.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-03-17-19-42.gh-issue-119605.c7QXAA.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-03-20-28-54.gh-issue-132054.c1nlOx.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-05-02-22-49.gh-issue-132106.XMjhQJ.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-05-16-05-34.gh-issue-131952.HX6gCX.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-06-14-34-29.gh-issue-130664.JF2r-U.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-06-21-17-14.gh-issue-132064.ktPwDM.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-07-06-41-54.gh-issue-131884.ym9BJN.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-08-01-55-11.gh-issue-132250.APBFCw.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-08-10-45-22.gh-issue-129463.b1qEP3.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-08-14-50-39.gh-issue-127495.Q0V0bS.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-09-19-07-22.gh-issue-130645.cVfE1X.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-10-21-43-04.gh-issue-125866.EZ9X8D.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-11-12-41-47.gh-issue-132385.86HoA7.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-11-21-48-49.gh-issue-132417.uILGdS.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-12-12-59-51.gh-issue-132429.OEIdlW.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-13-21-22-37.gh-issue-132491.jJfT4e.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-13-21-35-50.gh-issue-132493.5SAQJn.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-14-20-38-43.gh-issue-132099.0l0LlK.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-14-23-00-00.gh-issue-132527.kTi8T7.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-15-03-20-00.gh-issue-132536.i5Pvof.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-16-01-41-34.gh-issue-121468.rxgE1z.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-16-11-44-56.gh-issue-132561.ekkDPE.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-18-10-00-09.gh-issue-132578.ruNvF-.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-18-14-34-43.gh-issue-132673.0sliCv.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-19-19-58-27.gh-issue-132734.S6F9Cs.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-21-01-03-15.gh-issue-127081.WXRliX.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-22-19-45-46.gh-issue-132451.eIzMvE.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-22-21-00-23.gh-issue-123471.asOLA2.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-23-14-50-45.gh-issue-132742.PB6B7F.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-23-18-35-09.gh-issue-129965.nj7Fx2.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-24-01-03-40.gh-issue-93696.kM-MBp.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-24-09-10-04.gh-issue-132882.6zoyp5.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-24-21-22-46.gh-issue-132893.KFuxZ2.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-25-11-48-00.gh-issue-122781.ajsdns.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-25-16-20-49.gh-issue-121249.uue2nK.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-25-21-41-45.gh-issue-132933.yO3ySJ.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-26-10-54-38.gh-issue-132995.JuDF9p.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-26-10-57-15.gh-issue-132991.ekkqdt.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-26-12-25-42.gh-issue-115032.jnM2Co.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-26-14-44-21.gh-issue-133005.y4SRfk.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-26-15-43-23.gh-issue-124703.jc5auS.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-26-15-50-12.gh-issue-133009.etBuz5.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-26-17-41-20.gh-issue-132987.xxBCqg.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-27-15-21-05.gh-issue-133036.HCNYA7.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-29-23-20-52.gh-issue-133153.M-w9yC.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-30-19-32-18.gh-issue-132969.EagQ3G.rst7
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-01-10-56-44.gh-issue-132813.rKurvp.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-01-16-03-11.gh-issue-133017.k7RLQp.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-02-13-16-44.gh-issue-133290.R5WrLM.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-02-17-23-41.gh-issue-133300.oAh1P2.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-02-21-35-03.gh-issue-133306.-vBye5.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-05-03-14-08.gh-issue-133390.AuTggn.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-05-10-41-41.gh-issue-133253.J5-xDD.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-05-22-11-24.gh-issue-133439.LpmyFz.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-06-14-44-55.gh-issue-133517.Ca6NgW.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-06-22-54-37.gh-issue-133551.rfy1tJ.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-07-13-31-06.gh-issue-92897.ubeqGE.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-07-19-16-41.gh-issue-133581.kERUCJ.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-07-22-15-15.gh-issue-133595.c3U88r.rst7
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-08-13-43-19.gh-issue-133489.9eGS1Z.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-09-15-50-00.gh-issue-77057.fV8SU-.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-09-18-29-25.gh-issue-133684.Y1DFSt.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-09-19-05-24.gh-issue-133783.1voCnR.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-09-20-59-24.gh-issue-132641.3qTw44.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-10-11-04-47.gh-issue-133810.03WhnK.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-10-12-06-55.gh-issue-133653.Gb2aG4.rst7
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-11-10-01-48.gh-issue-133866.g3dHP_.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-11-10-28-11.gh-issue-133873.H03nov.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-11-12-56-52.gh-issue-133604.kFxhc8.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-12-06-52-10.gh-issue-133925.elInBY.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-12-20-38-57.gh-issue-133960.Aee79f.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-13-18-21-59.gh-issue-71253.-3Sf_K.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-13-18-54-56.gh-issue-133970.6G-Oi6.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-15-00-27-09.gh-issue-134004.e8k4-R.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-15-14-27-01.gh-issue-134062.fRbJet.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst6
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-16-20-10-25.gh-issue-134098.YyTkKr.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-17-12-40-12.gh-issue-133889.Eh-zO4.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-17-13-46-20.gh-issue-134097.fgkjE1.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-17-18-08-35.gh-issue-133890.onn9_X.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-17-20-23-57.gh-issue-133982.smS7au.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-18-07-25-15.gh-issue-134173.53oOoF.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-18-12-23-07.gh-issue-134087.HilZWl.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-18-12-48-39.gh-issue-62184.y11l10.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-18-13-23-29.gh-issue-134168.hgx3Xg.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-18-23-46-21.gh-issue-134152.30HwbX.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-19-15-30-00.gh-issue-132983.asdsfs.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-19-17-27-21.gh-issue-80184.LOkbaw.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-19-18-12-42.gh-issue-88994.7avvVu.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-19-20-59-06.gh-issue-134209.anhTcF.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-20-11-35-08.gh-issue-72902.jzEI-E.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-20-15-13-43.gh-issue-86802.trF7TM.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-20-21-45-58.gh-issue-90871.Gkvtp6.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-22-13-10-32.gh-issue-114177.3TYUJ3.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-22-14-12-53.gh-issue-134451.M1rD-j.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-22-18-14-13.gh-issue-134546.fjLVzK.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-23-10-15-36.gh-issue-134565.zmb66C.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-23-20-01-52.gh-issue-134580.xnaJ70.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-23-23-43-39.gh-issue-134582.9POq3l.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-24-03-10-36.gh-issue-80334.z21cMa.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-24-13-10-35.gh-issue-134210.0IuMY2.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-25-13-46-37.gh-issue-134635.ZlPrlX.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-26-11-01-54.gh-issue-134531.my1Fzt.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-26-12-31-08.gh-issue-132710.ApU3TZ.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-26-14-04-39.gh-issue-134696.P04xUa.rst5
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-26-17-06-40.gh-issue-134637.9-3zRL.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-26-22-18-32.gh-issue-134771.RKXpLT.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst8
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst7
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-28-15-53-27.gh-issue-128840.Nur2pB.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-28-20-49-29.gh-issue-134857.dVYXVO.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-29-06-53-40.gh-issue-134885.-_L22o.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-29-17-39-13.gh-issue-108885.MegCRA.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-30-09-46-21.gh-issue-134939.Pu3nnm.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-30-13-07-29.gh-issue-134718.9Qvhxn.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-30-18-13-48.gh-issue-134718.5FEspx.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-31-12-08-12.gh-issue-134970.lgSaxq.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-31-15-49-46.gh-issue-134978.mXXuvW.rst7
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-01-14-18-48.gh-issue-135004.cq3-fp.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-01-15-13-07.gh-issue-66234.Jw7OdC.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-02-14-28-30.gh-issue-130662.EIgIR8.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-02-14-36-28.gh-issue-130662.Gpr2GB.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-03-12-59-17.gh-issue-135069.xop30V.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-06-17-34-18.gh-issue-133934.yT1r68.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-08-10-22-22.gh-issue-135244.Y2SOTJ.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst6
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-10-00-42-30.gh-issue-135321.UHh9jT.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-10-16-11-00.gh-issue-133967.P0c24q.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-12-10-45-02.gh-issue-135368.OjWVHL.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-12-18-15-31.gh-issue-135429.mch75_.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-14-14-19-13.gh-issue-135497.1pzwdA.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-15-03-03-22.gh-issue-65697.COdwZd.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-16-15-03-03.gh-issue-135561.mJCN8D.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-17-22-44-19.gh-issue-119180.Ogv8Nj.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-17-23-13-56.gh-issue-135557.Bfcy4v.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-18-11-43-17.gh-issue-135646.r7ekEn.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-18-13-58-13.gh-issue-135645.109nff.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-20-16-28-47.gh-issue-135759.jne0Zi.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-20-17-06-59.gh-issue-90117.GYWVrn.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst4
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-22-16-23-44.gh-issue-135815.0DandH.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-22-22-03-06.gh-issue-135823.iDBg97.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-23-10-19-11.gh-issue-135855.-J0AGF.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-23-11-04-25.gh-issue-135836.-C-c4v.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-24-10-23-37.gh-issue-135853.6xDNOG.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-24-10-52-35.gh-issue-135836.s37351.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-24-14-43-24.gh-issue-135878.Db4roX.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-26-11-52-40.gh-issue-53203.TMigBr.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-26-17-19-36.gh-issue-105456.eR9oHB.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-26-17-28-49.gh-issue-135995.pPrDCt.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-27-09-26-04.gh-issue-87135.33z0UW.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-27-13-34-28.gh-issue-136028.RY727g.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-06-30-11-12-24.gh-issue-85702.0Lrbwu.rst3
-rw-r--r--Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst2
-rw-r--r--Misc/NEWS.d/next/Security/2024-02-18-02-53-25.gh-issue-115322.Um2Sjx.rst5
-rw-r--r--Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst2
-rw-r--r--Misc/NEWS.d/next/Security/2025-05-07-22-49-27.gh-issue-133623.fgWkBm.rst1
-rw-r--r--Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst2
-rw-r--r--Misc/NEWS.d/next/Security/2025-06-02-11-32-23.gh-issue-135034.RLGjbp.rst6
-rw-r--r--Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst4
-rw-r--r--Misc/NEWS.d/next/Security/2025-06-25-14-13-39.gh-issue-135661.idjQ0B.rst25
-rw-r--r--Misc/NEWS.d/next/Security/2025-06-27-21-23-19.gh-issue-136053.QZxcee.rst1
-rw-r--r--Misc/NEWS.d/next/Tests/2025-03-17-19-47-27.gh-issue-131290.NyCIXR.rst1
-rw-r--r--Misc/NEWS.d/next/Tests/2025-04-18-14-00-38.gh-issue-132678.j_ZKf2.rst3
-rw-r--r--Misc/NEWS.d/next/Tests/2025-04-23-02-23-37.gh-issue-109981.IX3k8p.rst3
-rw-r--r--Misc/NEWS.d/next/Tests/2025-04-23-12-40-27.gh-issue-91048.WJQCdV.rst2
-rw-r--r--Misc/NEWS.d/next/Tests/2025-04-29-14-56-37.gh-issue-133131.1pchjl.rst2
-rw-r--r--Misc/NEWS.d/next/Tests/2025-05-08-15-06-01.gh-issue-133639.50-kbV.rst2
-rw-r--r--Misc/NEWS.d/next/Tests/2025-05-09-04-11-06.gh-issue-133682.-_lwo3.rst1
-rw-r--r--Misc/NEWS.d/next/Tests/2025-05-09-14-54-48.gh-issue-133744.LCquu0.rst3
-rw-r--r--Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst2
-rw-r--r--Misc/NEWS.d/next/Tests/2025-06-04-13-07-44.gh-issue-135120.NapnZT.rst1
-rw-r--r--Misc/NEWS.d/next/Tests/2025-06-14-13-20-17.gh-issue-135489.Uh0yVO.rst (renamed from Misc/NEWS.d/next/Library/2025-04-13-19-17-14.gh-issue-70145.nJ2MKg.rst)3
-rw-r--r--Misc/NEWS.d/next/Tests/2025-06-17-08-48-08.gh-issue-132815.CY1Esu.rst1
-rw-r--r--Misc/NEWS.d/next/Tests/2025-06-19-15-29-38.gh-issue-135494.FVl9a0.rst2
-rw-r--r--Misc/NEWS.d/next/Tests/2025-06-26-15-15-35.gh-issue-135966.EBpF8Y.rst1
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2025-02-16-19-00-00.gh-issue-130195.19274.rst1
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2025-03-10-08-19-22.gh-issue-130453.9B0x8k.rst2
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst1
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2025-06-11-12-14-06.gh-issue-135379.25ttXq.rst4
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2025-06-26-15-58-13.gh-issue-135968.C4v_-W.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-03-27-16-22-58.gh-issue-127405.aASs2Z.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-03-31-15-37-57.gh-issue-131942.jip_aL.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-04-25-13-34-27.gh-issue-132930.6MJumW.rst2
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-07-08-19-15.gh-issue-133537.yzf963.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-07-09-02-19.gh-issue-133562.lqqNW1.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-07-11-25-29.gh-issue-133568.oYV0d8.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-07-11-45-30.gh-issue-133572.Xc2zxH.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-07-13-04-22.gh-issue-133580.jBMujJ.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-08-19-07-26.gh-issue-133626.yFTKYK.rst2
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-13-13-25-27.gh-issue-133779.-YcTBz.rst6
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-19-03-02-04.gh-issue-76023.vHOf6M.rst1
-rw-r--r--Misc/NEWS.d/next/Windows/2025-05-20-21-43-20.gh-issue-130727.-69t4D.rst2
-rw-r--r--Misc/NEWS.d/next/Windows/2025-06-03-18-26-54.gh-issue-135099.Q9usKm.rst2
-rw-r--r--Misc/externals.spdx.json21
-rw-r--r--Misc/python.man15
-rw-r--r--Misc/sbom.spdx.json56
-rw-r--r--Misc/stable_abi.toml19
-rw-r--r--Modules/Setup3
-rw-r--r--Modules/Setup.stdlib.in5
-rw-r--r--Modules/_collectionsmodule.c5
-rw-r--r--Modules/_complex.h54
-rw-r--r--Modules/_csv.c73
-rw-r--r--Modules/_ctypes/_ctypes.c179
-rw-r--r--Modules/_ctypes/_ctypes_test.c6
-rw-r--r--Modules/_ctypes/callbacks.c9
-rw-r--r--Modules/_ctypes/callproc.c23
-rw-r--r--Modules/_ctypes/cfield.c47
-rw-r--r--Modules/_ctypes/clinic/_ctypes.c.h44
-rw-r--r--Modules/_ctypes/ctypes.h25
-rw-r--r--Modules/_curses_panel.c369
-rw-r--r--Modules/_cursesmodule.c1165
-rw-r--r--Modules/_datetimemodule.c18
-rw-r--r--Modules/_dbmmodule.c76
-rw-r--r--Modules/_elementtree.c45
-rw-r--r--Modules/_functoolsmodule.c38
-rw-r--r--Modules/_gdbmmodule.c93
-rw-r--r--Modules/_hacl/Hacl_Hash_Blake2b.c52
-rw-r--r--Modules/_hacl/Hacl_Hash_Blake2b_Simd256.c52
-rw-r--r--Modules/_hacl/Hacl_Hash_Blake2s.c52
-rw-r--r--Modules/_hacl/Hacl_Hash_Blake2s_Simd128.c52
-rw-r--r--Modules/_hacl/Hacl_Hash_MD5.c523
-rw-r--r--Modules/_hacl/Hacl_Hash_SHA1.c12
-rw-r--r--Modules/_hacl/Hacl_Hash_SHA2.c42
-rw-r--r--Modules/_hacl/Hacl_Hash_SHA3.c22
-rw-r--r--Modules/_hacl/Hacl_Streaming_HMAC.c127
-rw-r--r--Modules/_hacl/Lib_Memzero0.c20
-rw-r--r--Modules/_hacl/include/krml/FStar_UInt128_Verified.h45
-rw-r--r--Modules/_hacl/python_hacl_namespaces.h237
-rwxr-xr-xModules/_hacl/refresh.sh2
-rw-r--r--Modules/_hashopenssl.c1023
-rw-r--r--Modules/_heapqmodule.c183
-rw-r--r--Modules/_interpchannelsmodule.c222
-rw-r--r--Modules/_interpqueuesmodule.c209
-rw-r--r--Modules/_interpreters_common.h58
-rw-r--r--Modules/_interpretersmodule.c751
-rw-r--r--Modules/_io/bufferedio.c16
-rw-r--r--Modules/_io/bytesio.c284
-rw-r--r--Modules/_io/clinic/bytesio.c.h66
-rw-r--r--Modules/_io/clinic/stringio.c.h12
-rw-r--r--Modules/_io/fileio.c8
-rw-r--r--Modules/_io/iobase.c4
-rw-r--r--Modules/_io/stringio.c29
-rw-r--r--Modules/_io/textio.c22
-rw-r--r--Modules/_io/winconsoleio.c4
-rw-r--r--Modules/_json.c43
-rw-r--r--Modules/_localemodule.c12
-rw-r--r--Modules/_lsprof.c30
-rw-r--r--Modules/_lzmamodule.c25
-rw-r--r--Modules/_opcode.c2
-rw-r--r--Modules/_pickle.c15
-rw-r--r--Modules/_queuemodule.c5
-rw-r--r--Modules/_randommodule.c20
-rw-r--r--Modules/_remote_debugging_module.c3139
-rw-r--r--Modules/_sqlite/blob.c5
-rw-r--r--Modules/_sqlite/clinic/_sqlite3.connect.c.h14
-rw-r--r--Modules/_sqlite/clinic/connection.c.h279
-rw-r--r--Modules/_sqlite/connection.c27
-rw-r--r--Modules/_sqlite/cursor.c5
-rw-r--r--Modules/_sqlite/module.c61
-rw-r--r--Modules/_sre/sre.c6
-rw-r--r--Modules/_ssl.c10
-rw-r--r--Modules/_ssl/debughelpers.c2
-rw-r--r--Modules/_stat.c4
-rw-r--r--Modules/_struct.c9
-rw-r--r--Modules/_testcapi/abstract.c38
-rw-r--r--Modules/_testcapi/long.c2
-rw-r--r--Modules/_testcapi/object.c8
-rw-r--r--Modules/_testcapi/unicode.c29
-rw-r--r--Modules/_testcapimodule.c51
-rw-r--r--Modules/_testclinic.c118
-rw-r--r--Modules/_testexternalinspection.c1551
-rw-r--r--Modules/_testinternalcapi.c278
-rw-r--r--Modules/_testinternalcapi/test_lock.c5
-rw-r--r--Modules/_testlimitedcapi/import.c9
-rw-r--r--Modules/_testlimitedcapi/sys.c73
-rw-r--r--Modules/_threadmodule.c131
-rw-r--r--Modules/_tkinter.c5
-rw-r--r--Modules/_uuidmodule.c40
-rw-r--r--Modules/_winapi.c28
-rw-r--r--Modules/_zoneinfo.c5
-rw-r--r--Modules/_zstd/_zstdmodule.c767
-rw-r--r--Modules/_zstd/_zstdmodule.h61
-rw-r--r--Modules/_zstd/buffer.h108
-rw-r--r--Modules/_zstd/clinic/_zstdmodule.c.h429
-rw-r--r--Modules/_zstd/clinic/compressor.c.h294
-rw-r--r--Modules/_zstd/clinic/decompressor.c.h223
-rw-r--r--Modules/_zstd/clinic/zstddict.c.h225
-rw-r--r--Modules/_zstd/compressor.c797
-rw-r--r--Modules/_zstd/decompressor.c717
-rw-r--r--Modules/_zstd/zstddict.c273
-rw-r--r--Modules/_zstd/zstddict.h29
-rw-r--r--Modules/arraymodule.c5
-rw-r--r--Modules/blake2module.c874
-rw-r--r--Modules/clinic/_curses_panel.c.h159
-rw-r--r--Modules/clinic/_cursesmodule.c.h88
-rw-r--r--Modules/clinic/_dbmmodule.c.h37
-rw-r--r--Modules/clinic/_gdbmmodule.c.h70
-rw-r--r--Modules/clinic/_hashopenssl.c.h549
-rw-r--r--Modules/clinic/_heapqmodule.c.h145
-rw-r--r--Modules/clinic/_lsprof.c.h35
-rw-r--r--Modules/clinic/_randommodule.c.h10
-rw-r--r--Modules/clinic/_remote_debugging_module.c.h276
-rw-r--r--Modules/clinic/_testclinic_depr.c.h304
-rw-r--r--Modules/clinic/_threadmodule.c.h50
-rw-r--r--Modules/clinic/_winapi.c.h28
-rw-r--r--Modules/clinic/blake2module.c.h110
-rw-r--r--Modules/clinic/mathmodule.c.h108
-rw-r--r--Modules/clinic/md5module.c.h34
-rw-r--r--Modules/clinic/posixmodule.c.h18
-rw-r--r--Modules/clinic/sha1module.c.h34
-rw-r--r--Modules/clinic/sha2module.c.h130
-rw-r--r--Modules/clinic/sha3module.c.h168
-rw-r--r--Modules/clinic/socketmodule.c.h173
-rw-r--r--Modules/clinic/zlibmodule.c.h120
-rw-r--r--Modules/faulthandler.c34
-rw-r--r--Modules/fcntlmodule.c126
-rw-r--r--Modules/hashlib.h137
-rw-r--r--Modules/hmacmodule.c347
-rw-r--r--Modules/itertoolsmodule.c65
-rw-r--r--Modules/main.c35
-rw-r--r--Modules/mathmodule.c83
-rw-r--r--Modules/md5module.c110
-rw-r--r--Modules/mmapmodule.c30
-rw-r--r--Modules/posixmodule.c162
-rw-r--r--Modules/pwdmodule.c25
-rw-r--r--Modules/pyexpat.c85
-rw-r--r--Modules/sha1module.c90
-rw-r--r--Modules/sha2module.c215
-rw-r--r--Modules/sha3module.c219
-rw-r--r--Modules/socketmodule.c196
-rw-r--r--Modules/socketmodule.h2
-rw-r--r--Modules/syslogmodule.c3
-rw-r--r--Modules/timemodule.c2
-rw-r--r--Modules/xxlimited.c11
-rw-r--r--Modules/zlibmodule.c88
-rw-r--r--Objects/bytesobject.c41
-rw-r--r--Objects/call.c35
-rw-r--r--Objects/classobject.c6
-rw-r--r--Objects/codeobject.c475
-rw-r--r--Objects/complexobject.c28
-rw-r--r--Objects/descrobject.c5
-rw-r--r--Objects/dictobject.c30
-rw-r--r--Objects/frameobject.c4
-rw-r--r--Objects/funcobject.c79
-rw-r--r--Objects/genericaliasobject.c11
-rw-r--r--Objects/genobject.c71
-rw-r--r--Objects/interpolationobject.c2
-rw-r--r--Objects/listobject.c51
-rw-r--r--Objects/listsort.txt175
-rw-r--r--Objects/longobject.c71
-rw-r--r--Objects/methodobject.c5
-rw-r--r--Objects/moduleobject.c7
-rw-r--r--Objects/namespaceobject.c15
-rw-r--r--Objects/object.c252
-rw-r--r--Objects/obmalloc.c133
-rw-r--r--Objects/odictobject.c4
-rw-r--r--Objects/picklebufobject.c4
-rw-r--r--Objects/setobject.c4
-rw-r--r--Objects/templateobject.c5
-rw-r--r--Objects/typeobject.c730
-rw-r--r--Objects/typevarobject.c4
-rw-r--r--Objects/unicodeobject.c106
-rw-r--r--Objects/unionobject.c13
-rw-r--r--PC/_wmimodule.cpp22
-rw-r--r--PC/launcher.c3
-rw-r--r--PC/layout/main.py28
-rw-r--r--PC/layout/support/arch.py34
-rw-r--r--PC/layout/support/constants.py45
-rw-r--r--PC/pyconfig.h (renamed from PC/pyconfig.h.in)43
-rwxr-xr-xPC/python3dll.c4
-rw-r--r--PC/python_uwp.cpp4
-rw-r--r--PC/python_ver_rc.h2
-rw-r--r--PC/winreg.c43
-rw-r--r--PCbuild/_freeze_module.vcxproj29
-rw-r--r--PCbuild/_freeze_module.vcxproj.filters2
-rw-r--r--PCbuild/_remote_debugging.vcxproj (renamed from PCbuild/_testexternalinspection.vcxproj)4
-rw-r--r--PCbuild/_remote_debugging.vcxproj.filters (renamed from PCbuild/_testexternalinspection.vcxproj.filters)2
-rw-r--r--PCbuild/_testclinic_limited.vcxproj1
-rw-r--r--PCbuild/_zstd.vcxproj177
-rw-r--r--PCbuild/_zstd.vcxproj.filters209
-rw-r--r--PCbuild/build.bat2
-rwxr-xr-xPCbuild/get_external.py26
-rw-r--r--PCbuild/get_externals.bat1
-rw-r--r--PCbuild/pcbuild.proj6
-rw-r--r--PCbuild/pcbuild.sln39
-rw-r--r--PCbuild/pyproject.props19
-rw-r--r--PCbuild/python.props1
-rw-r--r--PCbuild/pythoncore.vcxproj41
-rw-r--r--PCbuild/pythoncore.vcxproj.filters2
-rw-r--r--PCbuild/readme.txt35
-rw-r--r--PCbuild/regen.targets18
-rw-r--r--PCbuild/rt.bat2
-rw-r--r--Parser/Python.asdl2
-rw-r--r--Parser/action_helpers.c2
-rw-r--r--Parser/asdl.py60
-rwxr-xr-xParser/asdl_c.py32
-rw-r--r--Parser/lexer/lexer.c3
-rw-r--r--Parser/parser.c4188
-rw-r--r--Parser/pegen.c84
-rw-r--r--Parser/pegen.h7
-rw-r--r--Parser/string_parser.c26
-rw-r--r--Programs/_testembed.c110
-rw-r--r--Programs/test_frozenmain.h2
-rw-r--r--Python/Python-ast.c36
-rw-r--r--Python/_warnings.c3
-rw-r--r--Python/asm_trampoline.S22
-rw-r--r--Python/ast_preprocess.c (renamed from Python/ast_opt.c)78
-rw-r--r--Python/ast_unparse.c39
-rw-r--r--Python/bltinmodule.c176
-rw-r--r--Python/bytecodes.c734
-rw-r--r--Python/ceval.c250
-rw-r--r--Python/ceval_gil.c29
-rw-r--r--Python/ceval_macros.h9
-rw-r--r--Python/clinic/sysmodule.c.h86
-rw-r--r--Python/codegen.c42
-rw-r--r--Python/compile.c8
-rw-r--r--Python/context.c10
-rw-r--r--Python/crossinterp.c2062
-rw-r--r--Python/crossinterp_data_lookup.h187
-rw-r--r--Python/crossinterp_exceptions.h13
-rw-r--r--Python/dynload_win.c6
-rw-r--r--Python/emscripten_trampoline.c69
-rw-r--r--Python/errors.c5
-rw-r--r--Python/executor_cases.c.h1053
-rw-r--r--Python/fileutils.c37
-rw-r--r--Python/flowgraph.c46
-rw-r--r--Python/gc.c3
-rw-r--r--Python/gc_free_threading.c225
-rw-r--r--Python/generated_cases.c.h875
-rw-r--r--Python/getopt.c9
-rw-r--r--Python/getversion.c2
-rw-r--r--Python/hamt.c6
-rw-r--r--Python/import.c75
-rw-r--r--Python/index_pool.c4
-rw-r--r--Python/initconfig.c60
-rw-r--r--Python/intrinsics.c3
-rw-r--r--Python/lock.c25
-rw-r--r--Python/marshal.c5
-rw-r--r--Python/modsupport.c2
-rw-r--r--Python/opcode_targets.h10
-rw-r--r--Python/optimizer.c70
-rw-r--r--Python/optimizer_analysis.c154
-rw-r--r--Python/optimizer_bytecodes.c612
-rw-r--r--Python/optimizer_cases.c.h1431
-rw-r--r--Python/optimizer_symbols.c621
-rw-r--r--Python/parking_lot.c22
-rw-r--r--Python/pathconfig.c20
-rw-r--r--Python/perf_jit_trampoline.c1418
-rw-r--r--Python/pylifecycle.c10
-rw-r--r--Python/pystate.c233
-rw-r--r--Python/pythonrun.c40
-rw-r--r--Python/qsbr.c14
-rw-r--r--Python/remote_debug.h385
-rw-r--r--Python/remote_debugging.c39
-rw-r--r--Python/specialize.c83
-rw-r--r--Python/stackrefs.c14
-rw-r--r--Python/stdlib_module_names.h5
-rw-r--r--Python/symtable.c6
-rw-r--r--Python/sysmodule.c236
-rw-r--r--Python/thread.c82
-rw-r--r--Python/thread_nt.h92
-rw-r--r--Python/thread_pthread.h392
-rw-r--r--Python/traceback.c5
-rw-r--r--README.rst14
-rw-r--r--Tools/build/.ruff.toml2
-rw-r--r--Tools/build/compute-changes.py24
-rw-r--r--Tools/build/deepfreeze.py51
-rw-r--r--Tools/build/generate-build-details.py24
-rw-r--r--Tools/build/generate_sbom.py22
-rw-r--r--Tools/build/generate_stdlib_module_names.py1
-rw-r--r--Tools/build/mypy.ini13
-rw-r--r--Tools/build/umarshal.py11
-rw-r--r--Tools/build/update_file.py24
-rwxr-xr-xTools/build/verify_ensurepip_wheels.py6
-rw-r--r--Tools/c-analyzer/cpython/ignored.tsv3
-rw-r--r--Tools/cases_generator/analyzer.py65
-rw-r--r--Tools/cases_generator/generators_common.py16
-rw-r--r--Tools/cases_generator/interpreter_definition.md2
-rw-r--r--Tools/cases_generator/opcode_metadata_generator.py19
-rw-r--r--Tools/cases_generator/optimizer_generator.py243
-rw-r--r--Tools/cases_generator/parsing.py20
-rw-r--r--Tools/cases_generator/stack.py22
-rw-r--r--Tools/cases_generator/tier2_generator.py2
-rw-r--r--Tools/cases_generator/uop_metadata_generator.py8
-rw-r--r--Tools/clinic/libclinic/converters.py124
-rw-r--r--Tools/ftscalingbench/ftscalingbench.py13
-rwxr-xr-xTools/i18n/makelocalealias.py3
-rw-r--r--Tools/inspection/benchmark_external_inspection.py473
-rw-r--r--Tools/jit/README.md6
-rw-r--r--Tools/jit/_optimizers.py319
-rw-r--r--Tools/jit/_stencils.py67
-rw-r--r--Tools/jit/_targets.py79
-rw-r--r--Tools/jit/build.py25
-rw-r--r--Tools/jit/template.c5
-rw-r--r--Tools/msi/dev/dev_files.wxs2
-rw-r--r--Tools/msi/freethreaded/freethreaded_files.wxs2
-rw-r--r--Tools/msi/lib/lib.wixproj3
-rw-r--r--Tools/msi/lib/lib_files.wxs2
-rwxr-xr-xTools/patchcheck/patchcheck.py46
-rw-r--r--Tools/peg_generator/pegen/build.py2
-rw-r--r--Tools/peg_generator/pegen/c_generator.py60
-rw-r--r--Tools/peg_generator/pegen/parser_generator.py5
-rw-r--r--Tools/requirements-dev.txt6
-rw-r--r--Tools/scripts/summarize_stats.py2
-rw-r--r--Tools/tsan/suppressions_free_threading.txt15
-rw-r--r--Tools/unicode/makeunicodedata.py2
-rw-r--r--Tools/wasm/emscripten/.editorconfig (renamed from Tools/wasm/.editorconfig)0
-rw-r--r--Tools/wasm/emscripten/__main__.py3
-rw-r--r--Tools/wasm/mypy.ini11
-rwxr-xr-xTools/wasm/wasi-env3
-rw-r--r--Tools/wasm/wasi.py373
-rw-r--r--Tools/wasm/wasi/__main__.py374
-rw-r--r--Tools/wasm/wasi/config.site-wasm32-wasi (renamed from Tools/wasm/config.site-wasm32-wasi)0
-rwxr-xr-xTools/wasm/wasm_build.py932
-rwxr-xr-xconfigure888
-rw-r--r--configure.ac256
-rw-r--r--iOS/README.rst2
-rwxr-xr-xiOS/Resources/bin/arm64-apple-ios-simulator-strip2
-rwxr-xr-xiOS/Resources/bin/arm64-apple-ios-strip2
-rwxr-xr-xiOS/Resources/bin/x86_64-apple-ios-simulator-strip2
-rw-r--r--iOS/testbed/__main__.py2
-rw-r--r--iOS/testbed/iOSTestbedTests/iOSTestbedTests.m51
-rw-r--r--pyconfig.h.in43
1534 files changed, 68295 insertions, 26306 deletions
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 64c85c1101e..8e09808f08b 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,5 +1,5 @@
{
- "image": "ghcr.io/python/devcontainer:2024.09.25.11038928730",
+ "image": "ghcr.io/python/devcontainer:2025.05.29.15334414373",
"onCreateCommand": [
// Install common tooling.
"dnf",
diff --git a/.gitattributes b/.gitattributes
index 2f5a030981f..5682b9150a3 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -10,6 +10,7 @@
*.ico binary
*.jpg binary
*.pck binary
+*.pdf binary
*.png binary
*.psd binary
*.tar binary
@@ -67,6 +68,7 @@ PCbuild/readme.txt dos
**/clinic/*.cpp.h generated
**/clinic/*.h.h generated
*_db.h generated
+Doc/c-api/lifecycle.dot.svg generated
Doc/data/stable_abi.dat generated
Doc/library/token-list.inc generated
Include/internal/pycore_ast.h generated
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 11e8acd963a..08d7a80d772 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -35,6 +35,7 @@ Objects/type* @markshannon
Objects/codeobject.c @markshannon
Objects/frameobject.c @markshannon
Objects/call.c @markshannon
+Objects/object.c @ZeroIntensity
Python/ceval*.c @markshannon
Python/ceval*.h @markshannon
Python/codegen.c @markshannon @iritkatriel
@@ -44,8 +45,9 @@ Python/flowgraph.c @markshannon @iritkatriel
Python/instruction_sequence.c @iritkatriel
Python/bytecodes.c @markshannon
Python/optimizer*.c @markshannon
-Python/optimizer_analysis.c @Fidget-Spinner
-Python/optimizer_bytecodes.c @Fidget-Spinner
+Python/optimizer_analysis.c @Fidget-Spinner @tomasr8
+Python/optimizer_bytecodes.c @Fidget-Spinner @tomasr8
+Python/optimizer_symbols.c @tomasr8
Python/symtable.c @JelleZijlstra @carljm
Lib/_pyrepl/* @pablogsal @lysnikolaou @ambv
Lib/test/test_patma.py @brandtbucher
@@ -66,8 +68,8 @@ Doc/_static/** @AA-Turner @hugovk
Doc/tools/** @AA-Turner @hugovk
# runtime state/lifecycle
-**/*pylifecycle* @ericsnowcurrently
-**/*pystate* @ericsnowcurrently
+**/*pylifecycle* @ericsnowcurrently @ZeroIntensity
+**/*pystate* @ericsnowcurrently @ZeroIntensity
**/*preconfig* @ericsnowcurrently
**/*initconfig* @ericsnowcurrently
**/*pathconfig* @ericsnowcurrently
@@ -187,13 +189,13 @@ Include/internal/pycore_time.h @pganssle @abalkin
/Tools/cases_generator/ @markshannon
# AST
-Python/ast.c @isidentical @JelleZijlstra @eclips4
-Python/ast_opt.c @isidentical @eclips4
-Parser/asdl.py @isidentical @JelleZijlstra @eclips4
-Parser/asdl_c.py @isidentical @JelleZijlstra @eclips4
-Lib/ast.py @isidentical @JelleZijlstra @eclips4
-Lib/_ast_unparse.py @isidentical @JelleZijlstra @eclips4
-Lib/test/test_ast/ @eclips4
+Python/ast.c @isidentical @JelleZijlstra @eclips4 @tomasr8
+Python/ast_preprocess.c @isidentical @eclips4 @tomasr8
+Parser/asdl.py @isidentical @JelleZijlstra @eclips4 @tomasr8
+Parser/asdl_c.py @isidentical @JelleZijlstra @eclips4 @tomasr8
+Lib/ast.py @isidentical @JelleZijlstra @eclips4 @tomasr8
+Lib/_ast_unparse.py @isidentical @JelleZijlstra @eclips4 @tomasr8
+Lib/test/test_ast/ @eclips4 @tomasr8
# Mock
/Lib/unittest/mock.py @cjw296
@@ -281,9 +283,13 @@ Doc/howto/clinic.rst @erlend-aasland
# Subinterpreters
**/*interpreteridobject.* @ericsnowcurrently
**/*crossinterp* @ericsnowcurrently
-Lib/test/support/interpreters/ @ericsnowcurrently
Modules/_interp*module.c @ericsnowcurrently
+Lib/test/test__interp*.py @ericsnowcurrently
+Lib/concurrent/interpreters/ @ericsnowcurrently
+Lib/test/support/channels.py @ericsnowcurrently
+Doc/library/concurrent.interpreters.rst @ericsnowcurrently
Lib/test/test_interpreters/ @ericsnowcurrently
+Lib/concurrent/futures/interpreter.py @ericsnowcurrently
# Android
**/*Android* @mhsmith @freakboy3742
@@ -298,7 +304,12 @@ Lib/test/test_interpreters/ @ericsnowcurrently
**/*-ios* @freakboy3742
# WebAssembly
-/Tools/wasm/ @brettcannon @freakboy3742
+Tools/wasm/config.site-wasm32-emscripten @freakboy3742
+/Tools/wasm/README.md @brettcannon @freakboy3742
+/Tools/wasm/wasi-env @brettcannon
+/Tools/wasm/wasi.py @brettcannon
+/Tools/wasm/emscripten @freakboy3742
+/Tools/wasm/wasi @brettcannon
# SBOM
/Misc/externals.spdx.json @sethmlarson
@@ -326,3 +337,11 @@ Modules/_xxtestfuzz/ @ammaraskar
**/*templateobject* @lysnikolaou
**/*templatelib* @lysnikolaou
**/*tstring* @lysnikolaou
+
+# Remote debugging
+Python/remote_debug.h @pablogsal
+Python/remote_debugging.c @pablogsal
+Modules/_remote_debugging_module.c @pablogsal @ambv @1st1
+
+# gettext
+**/*gettext* @tomasr8
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index 7b7810cf696..da70710b7ec 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -40,6 +40,7 @@ body:
- "3.12"
- "3.13"
- "3.14"
+ - "3.15"
- "CPython main branch"
validations:
required: true
diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml
index 58da2dfe0c7..470ad581367 100644
--- a/.github/ISSUE_TEMPLATE/crash.yml
+++ b/.github/ISSUE_TEMPLATE/crash.yml
@@ -33,6 +33,7 @@ body:
- "3.12"
- "3.13"
- "3.14"
+ - "3.15"
- "CPython main branch"
validations:
required: true
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b192508c786..c6171571857 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,7 +15,13 @@ permissions:
contents: read
concurrency:
- group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}-reusable
+ # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency
+ # 'group' must be a key uniquely representing a PR or push event.
+ # github.workflow is the workflow name
+ # github.actor is the user invoking the workflow
+ # github.head_ref is the source branch of the PR or otherwise blank
+ # github.run_id is a unique number for the current run
+ group: ${{ github.workflow }}-${{ github.actor }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
env:
@@ -521,6 +527,14 @@ jobs:
config_hash: ${{ needs.build-context.outputs.config-hash }}
free-threading: ${{ matrix.free-threading }}
+ build-ubsan:
+ name: Undefined behavior sanitizer
+ needs: build-context
+ if: needs.build-context.outputs.run-tests == 'true'
+ uses: ./.github/workflows/reusable-ubsan.yml
+ with:
+ config_hash: ${{ needs.build-context.outputs.config-hash }}
+
cross-build-linux:
name: Cross build Linux
runs-on: ubuntu-latest
diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml
index 116e0c1e945..947badff816 100644
--- a/.github/workflows/jit.yml
+++ b/.github/workflows/jit.yml
@@ -5,6 +5,8 @@ on:
- '**jit**'
- 'Python/bytecodes.c'
- 'Python/optimizer*.c'
+ - 'Python/executor_cases.c.h'
+ - 'Python/optimizer_cases.c.h'
- '!Python/perf_jit_trampoline.c'
- '!**/*.md'
- '!**/*.ini'
@@ -13,6 +15,8 @@ on:
- '**jit**'
- 'Python/bytecodes.c'
- 'Python/optimizer*.c'
+ - 'Python/executor_cases.c.h'
+ - 'Python/optimizer_cases.c.h'
- '!Python/perf_jit_trampoline.c'
- '!**/*.md'
- '!**/*.ini'
diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml
index 908daaf3a60..95133c1338b 100644
--- a/.github/workflows/mypy.yml
+++ b/.github/workflows/mypy.yml
@@ -13,13 +13,18 @@ on:
- "Lib/test/libregrtest/**"
- "Lib/tomllib/**"
- "Misc/mypy/**"
+ - "Tools/build/compute-changes.py"
+ - "Tools/build/deepfreeze.py"
- "Tools/build/generate_sbom.py"
+ - "Tools/build/generate-build-details.py"
+ - "Tools/build/verify_ensurepip_wheels.py"
+ - "Tools/build/update_file.py"
+ - "Tools/build/umarshal.py"
- "Tools/cases_generator/**"
- "Tools/clinic/**"
- "Tools/jit/**"
- "Tools/peg_generator/**"
- "Tools/requirements-dev.txt"
- - "Tools/wasm/**"
workflow_dispatch:
permissions:
@@ -51,7 +56,6 @@ jobs:
"Tools/clinic",
"Tools/jit",
"Tools/peg_generator",
- "Tools/wasm",
]
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/posix-deps-apt.sh b/.github/workflows/posix-deps-apt.sh
index d5538cd9367..44e6a9ce2d0 100755
--- a/.github/workflows/posix-deps-apt.sh
+++ b/.github/workflows/posix-deps-apt.sh
@@ -17,6 +17,7 @@ apt-get -yq install \
libreadline6-dev \
libsqlite3-dev \
libssl-dev \
+ libzstd-dev \
lzma \
lzma-dev \
strace \
@@ -24,3 +25,10 @@ apt-get -yq install \
uuid-dev \
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 install libmpdec-dev
diff --git a/.github/workflows/reusable-context.yml b/.github/workflows/reusable-context.yml
index 73e036a146f..d2668ddcac1 100644
--- a/.github/workflows/reusable-context.yml
+++ b/.github/workflows/reusable-context.yml
@@ -97,6 +97,9 @@ jobs:
run: python Tools/build/compute-changes.py
env:
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GITHUB_EVENT_NAME: ${{ github.event_name }}
+ CCF_TARGET_REF: ${{ github.base_ref || github.event.repository.default_branch }}
+ CCF_HEAD_REF: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Compute hash for config cache key
id: config-hash
diff --git a/.github/workflows/reusable-ubsan.yml b/.github/workflows/reusable-ubsan.yml
new file mode 100644
index 00000000000..cf93932f13b
--- /dev/null
+++ b/.github/workflows/reusable-ubsan.yml
@@ -0,0 +1,74 @@
+name: Reusable Undefined Behavior Sanitizer
+
+on:
+ workflow_call:
+ inputs:
+ config_hash:
+ required: true
+ type: string
+
+env:
+ FORCE_COLOR: 1
+
+jobs:
+ build-ubsan-reusable:
+ name: 'Undefined behavior sanitizer'
+ runs-on: ubuntu-24.04
+ timeout-minutes: 60
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ - name: Runner image version
+ run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
+ - name: Restore config.cache
+ uses: actions/cache@v4
+ with:
+ path: config.cache
+ key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ inputs.config_hash }}
+ - name: Install dependencies
+ run: |
+ sudo ./.github/workflows/posix-deps-apt.sh
+ # Install clang-20
+ wget https://apt.llvm.org/llvm.sh
+ chmod +x llvm.sh
+ sudo ./llvm.sh 20
+ - name: UBSAN option setup
+ run: |
+ echo "UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1" >> "$GITHUB_ENV"
+ echo "CC=clang" >> "$GITHUB_ENV"
+ echo "CXX=clang++" >> "$GITHUB_ENV"
+ - name: Add ccache to PATH
+ run: |
+ echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
+ - name: Configure ccache action
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ save: ${{ github.event_name == 'push' }}
+ max-size: "200M"
+ - name: Configure CPython
+ run: >-
+ ./configure
+ --config-cache
+ --with-undefined-behavior-sanitizer
+ --with-pydebug
+ - name: Set up UBSAN log after configuration
+ run: |
+ echo "UBSAN_OPTIONS=${UBSAN_OPTIONS}:log_path=${GITHUB_WORKSPACE}/ubsan_log" >> "$GITHUB_ENV"
+ - name: Build CPython
+ run: make -j4
+ - name: Display build info
+ run: make pythoninfo
+ - name: Tests
+ run: ./python -m test -j4
+ - name: Display UBSAN logs
+ if: always()
+ run: find "${GITHUB_WORKSPACE}" -name 'ubsan_log.*' | xargs head -n 1000
+ - name: Archive UBSAN logs
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: >-
+ ubsan-logs
+ path: ubsan_log.*
+ if-no-files-found: ignore
diff --git a/.github/workflows/tail-call.yml b/.github/workflows/tail-call.yml
index 4636372e26c..e32cbf0aaa3 100644
--- a/.github/workflows/tail-call.yml
+++ b/.github/workflows/tail-call.yml
@@ -137,4 +137,3 @@ jobs:
CC=clang-20 ./configure --with-tail-call-interp --disable-gil
make all --jobs 4
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
-
diff --git a/.gitignore b/.gitignore
index 2a6f249275c..7aa6272cf8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -131,6 +131,7 @@ Tools/unicode/data/
/autom4te.cache
/build/
/builddir/
+/compile_commands.json
/config.cache
/config.log
/config.status
@@ -171,5 +172,10 @@ Python/frozen_modules/MANIFEST
/python
!/Python/
+# People's custom https://docs.anthropic.com/en/docs/claude-code/memory configs.
+/.claude/
+CLAUDE.local.md
+
+#### main branch only stuff below this line, things to backport go above. ####
# main branch only: ABI files are not checked/maintained.
Doc/data/python*.abi
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7ad829c94d5..86410c46d1d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -34,6 +34,13 @@ repos:
name: Run Black on Tools/jit/
files: ^Tools/jit/
+ - repo: https://github.com/Lucas-C/pre-commit-hooks
+ rev: v1.5.5
+ hooks:
+ - id: remove-tabs
+ types: [python]
+ exclude: ^Tools/c-analyzer/cpython/_parser.py
+
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
@@ -43,10 +50,14 @@ repos:
exclude: ^Lib/test/test_tomllib/
- id: check-yaml
- id: end-of-file-fixer
- types: [python]
+ types_or: [python, yaml]
exclude: Lib/test/tokenizedata/coding20731.py
+ - id: end-of-file-fixer
+ files: '^\.github/CODEOWNERS$'
+ - id: trailing-whitespace
+ types_or: [c, inc, python, rst, yaml]
- id: trailing-whitespace
- types_or: [c, inc, python, rst]
+ files: '^\.github/CODEOWNERS|\.(gram)$'
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.0
diff --git a/.readthedocs.yml b/.readthedocs.yml
index a57de00544e..0a2c3f83453 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -32,4 +32,3 @@ build:
- make -C Doc venv html
- mkdir _readthedocs
- mv Doc/build/html _readthedocs/html
-
diff --git a/Android/README.md b/Android/README.md
index 6cabd6ba5d6..c42eb627006 100644
--- a/Android/README.md
+++ b/Android/README.md
@@ -156,6 +156,10 @@ repository's `Lib` directory will be picked up immediately. Changes in C files,
and architecture-specific files such as sysconfigdata, will not take effect
until you re-run `android.py make-host` or `build`.
+The testbed app can also be used to test third-party packages. For more details,
+run `android.py test --help`, paying attention to the options `--site-packages`,
+`--cwd`, `-c` and `-m`.
+
## Using in your own app
diff --git a/Android/android-env.sh b/Android/android-env.sh
index bab4130c9e9..7b381a013cf 100644
--- a/Android/android-env.sh
+++ b/Android/android-env.sh
@@ -3,7 +3,7 @@
: "${HOST:?}" # GNU target triplet
# You may also override the following:
-: "${api_level:=24}" # Minimum Android API level the build will run on
+: "${ANDROID_API_LEVEL:=24}" # Minimum Android API level the build will run on
: "${PREFIX:-}" # Path in which to find required libraries
@@ -24,7 +24,7 @@ fail() {
# * https://android.googlesource.com/platform/ndk/+/ndk-rXX-release/docs/BuildSystemMaintainers.md
# where XX is the NDK version. Do a diff against the version you're upgrading from, e.g.:
# https://android.googlesource.com/platform/ndk/+/ndk-r25-release..ndk-r26-release/docs/BuildSystemMaintainers.md
-ndk_version=27.1.12297006
+ndk_version=27.2.12479018
ndk=$ANDROID_HOME/ndk/$ndk_version
if ! [ -e "$ndk" ]; then
@@ -43,7 +43,7 @@ fi
toolchain=$(echo "$ndk"/toolchains/llvm/prebuilt/*)
export AR="$toolchain/bin/llvm-ar"
export AS="$toolchain/bin/llvm-as"
-export CC="$toolchain/bin/${clang_triplet}${api_level}-clang"
+export CC="$toolchain/bin/${clang_triplet}${ANDROID_API_LEVEL}-clang"
export CXX="${CC}++"
export LD="$toolchain/bin/ld"
export NM="$toolchain/bin/llvm-nm"
diff --git a/Android/android.py b/Android/android.py
index 3f48b42aa17..551168fc4b2 100755
--- a/Android/android.py
+++ b/Android/android.py
@@ -14,7 +14,7 @@ from asyncio import wait_for
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from glob import glob
-from os.path import basename, relpath
+from os.path import abspath, basename, relpath
from pathlib import Path
from subprocess import CalledProcessError
from tempfile import TemporaryDirectory
@@ -22,9 +22,13 @@ from tempfile import TemporaryDirectory
SCRIPT_NAME = Path(__file__).name
ANDROID_DIR = Path(__file__).resolve().parent
-CHECKOUT = ANDROID_DIR.parent
+PYTHON_DIR = ANDROID_DIR.parent
+in_source_tree = (
+ ANDROID_DIR.name == "Android" and (PYTHON_DIR / "pyconfig.h.in").exists()
+)
+
TESTBED_DIR = ANDROID_DIR / "testbed"
-CROSS_BUILD_DIR = CHECKOUT / "cross-build"
+CROSS_BUILD_DIR = PYTHON_DIR / "cross-build"
HOSTS = ["aarch64-linux-android", "x86_64-linux-android"]
APP_ID = "org.python.testbed"
@@ -76,39 +80,68 @@ def run(command, *, host=None, env=None, log=True, **kwargs):
kwargs.setdefault("check", True)
if env is None:
env = os.environ.copy()
- original_env = env.copy()
if host:
- env_script = ANDROID_DIR / "android-env.sh"
- env_output = subprocess.run(
- f"set -eu; "
- f"HOST={host}; "
- f"PREFIX={subdir(host)}/prefix; "
- f". {env_script}; "
- f"export",
- check=True, shell=True, text=True, stdout=subprocess.PIPE
- ).stdout
-
- for line in env_output.splitlines():
- # We don't require every line to match, as there may be some other
- # output from installing the NDK.
- if match := re.search(
- "^(declare -x |export )?(\\w+)=['\"]?(.*?)['\"]?$", line
- ):
- key, value = match[2], match[3]
- if env.get(key) != value:
- print(line)
- env[key] = value
-
- if env == original_env:
- raise ValueError(f"Found no variables in {env_script.name} output:\n"
- + env_output)
+ host_env = android_env(host)
+ print_env(host_env)
+ env.update(host_env)
if log:
- print(">", " ".join(map(str, command)))
+ print(">", join_command(command))
return subprocess.run(command, env=env, **kwargs)
+# Format a command so it can be copied into a shell. Like shlex.join, but also
+# accepts arguments which are Paths, or a single string/Path outside of a list.
+def join_command(args):
+ if isinstance(args, (str, Path)):
+ return str(args)
+ else:
+ return shlex.join(map(str, args))
+
+
+# Format the environment so it can be pasted into a shell.
+def print_env(env):
+ for key, value in sorted(env.items()):
+ print(f"export {key}={shlex.quote(value)}")
+
+
+def android_env(host):
+ if host:
+ prefix = subdir(host) / "prefix"
+ else:
+ prefix = ANDROID_DIR / "prefix"
+ sysconfig_files = prefix.glob("lib/python*/_sysconfigdata__android_*.py")
+ sysconfig_filename = next(sysconfig_files).name
+ host = re.fullmatch(r"_sysconfigdata__android_(.+).py", sysconfig_filename)[1]
+
+ env_script = ANDROID_DIR / "android-env.sh"
+ env_output = subprocess.run(
+ f"set -eu; "
+ f"export HOST={host}; "
+ f"PREFIX={prefix}; "
+ f". {env_script}; "
+ f"export",
+ check=True, shell=True, capture_output=True, encoding='utf-8',
+ ).stdout
+
+ env = {}
+ for line in env_output.splitlines():
+ # We don't require every line to match, as there may be some other
+ # output from installing the NDK.
+ if match := re.search(
+ "^(declare -x |export )?(\\w+)=['\"]?(.*?)['\"]?$", line
+ ):
+ key, value = match[2], match[3]
+ if os.environ.get(key) != value:
+ env[key] = value
+
+ if not env:
+ raise ValueError(f"Found no variables in {env_script.name} output:\n"
+ + env_output)
+ return env
+
+
def build_python_path():
"""The path to the build Python binary."""
build_dir = subdir("build")
@@ -127,7 +160,7 @@ def configure_build_python(context):
clean("build")
os.chdir(subdir("build", create=True))
- command = [relpath(CHECKOUT / "configure")]
+ command = [relpath(PYTHON_DIR / "configure")]
if context.args:
command.extend(context.args)
run(command)
@@ -139,12 +172,13 @@ def make_build_python(context):
def unpack_deps(host, prefix_dir):
+ os.chdir(prefix_dir)
deps_url = "https://github.com/beeware/cpython-android-source-deps/releases/download"
- for name_ver in ["bzip2-1.0.8-2", "libffi-3.4.4-3", "openssl-3.0.15-4",
+ for name_ver in ["bzip2-1.0.8-3", "libffi-3.4.4-3", "openssl-3.0.15-4",
"sqlite-3.49.1-0", "xz-5.4.6-1"]:
filename = f"{name_ver}-{host}.tar.gz"
download(f"{deps_url}/{name_ver}/{filename}")
- shutil.unpack_archive(filename, prefix_dir)
+ shutil.unpack_archive(filename)
os.remove(filename)
@@ -167,7 +201,7 @@ def configure_host_python(context):
os.chdir(host_dir)
command = [
# Basic cross-compiling configuration
- relpath(CHECKOUT / "configure"),
+ relpath(PYTHON_DIR / "configure"),
f"--host={context.host}",
f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}",
f"--with-build-python={build_python_path()}",
@@ -196,9 +230,12 @@ def make_host_python(context):
for pattern in ("include/python*", "lib/libpython*", "lib/python*"):
delete_glob(f"{prefix_dir}/{pattern}")
+ # The Android environment variables were already captured in the Makefile by
+ # `configure`, and passing them again when running `make` may cause some
+ # flags to be duplicated. So we don't use the `host` argument here.
os.chdir(host_dir)
- run(["make", "-j", str(os.cpu_count())], host=context.host)
- run(["make", "install", f"prefix={prefix_dir}"], host=context.host)
+ run(["make", "-j", str(os.cpu_count())])
+ run(["make", "install", f"prefix={prefix_dir}"])
def build_all(context):
@@ -228,7 +265,12 @@ def setup_sdk():
if not all((android_home / "licenses" / path).exists() for path in [
"android-sdk-arm-dbt-license", "android-sdk-license"
]):
- run([sdkmanager, "--licenses"], text=True, input="y\n" * 100)
+ run(
+ [sdkmanager, "--licenses"],
+ text=True,
+ capture_output=True,
+ input="y\n" * 100,
+ )
# Gradle may install this automatically, but we can't rely on that because
# we need to run adb within the logcat task.
@@ -474,24 +516,49 @@ async def gradle_task(context):
task_prefix = "connected"
env["ANDROID_SERIAL"] = context.connected
+ hidden_output = []
+
+ def log(line):
+ # Gradle may take several minutes to install SDK packages, so it's worth
+ # showing those messages even in non-verbose mode.
+ if context.verbose or line.startswith('Preparing "Install'):
+ sys.stdout.write(line)
+ else:
+ hidden_output.append(line)
+
+ if context.command:
+ mode = "-c"
+ module = context.command
+ else:
+ mode = "-m"
+ module = context.module or "test"
+
args = [
gradlew, "--console", "plain", f"{task_prefix}DebugAndroidTest",
- "-Pandroid.testInstrumentationRunnerArguments.pythonArgs="
- + shlex.join(context.args),
+ ] + [
+ # Build-time properties
+ f"-Ppython.{name}={value}"
+ for name, value in [
+ ("sitePackages", context.site_packages), ("cwd", context.cwd)
+ ] if value
+ ] + [
+ # Runtime properties
+ f"-Pandroid.testInstrumentationRunnerArguments.python{name}={value}"
+ for name, value in [
+ ("Mode", mode), ("Module", module), ("Args", join_command(context.args))
+ ] if value
]
- hidden_output = []
+ if context.verbose >= 2:
+ args.append("--info")
+ log("> " + join_command(args))
+
try:
async with async_process(
*args, cwd=TESTBED_DIR, env=env,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
) as process:
while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
- # Gradle may take several minutes to install SDK packages, so
- # it's worth showing those messages even in non-verbose mode.
- if context.verbose or line.startswith('Preparing "Install'):
- sys.stdout.write(line)
- else:
- hidden_output.append(line)
+ log(line)
status = await wait_for(process.wait(), timeout=1)
if status == 0:
@@ -604,6 +671,10 @@ def package(context):
print(f"Wrote {package_path}")
+def env(context):
+ print_env(android_env(getattr(context, "host", None)))
+
+
# Handle SIGTERM the same way as SIGINT. This ensures that if we're terminated
# by the buildbot worker, we'll make an attempt to clean up our subprocesses.
def install_signal_handler():
@@ -615,36 +686,41 @@ def install_signal_handler():
def parse_args():
parser = argparse.ArgumentParser()
- subcommands = parser.add_subparsers(dest="subcommand")
+ subcommands = parser.add_subparsers(dest="subcommand", required=True)
# Subcommands
- build = subcommands.add_parser("build", help="Build everything")
- configure_build = subcommands.add_parser("configure-build",
- help="Run `configure` for the "
- "build Python")
- make_build = subcommands.add_parser("make-build",
- help="Run `make` for the build Python")
- configure_host = subcommands.add_parser("configure-host",
- help="Run `configure` for Android")
- make_host = subcommands.add_parser("make-host",
- help="Run `make` for Android")
+ build = subcommands.add_parser(
+ "build", help="Run configure-build, make-build, configure-host and "
+ "make-host")
+ configure_build = subcommands.add_parser(
+ "configure-build", help="Run `configure` for the build Python")
subcommands.add_parser(
- "clean", help="Delete all build and prefix directories")
- subcommands.add_parser(
- "build-testbed", help="Build the testbed app")
- test = subcommands.add_parser(
- "test", help="Run the test suite")
+ "make-build", help="Run `make` for the build Python")
+ configure_host = subcommands.add_parser(
+ "configure-host", help="Run `configure` for Android")
+ make_host = subcommands.add_parser(
+ "make-host", help="Run `make` for Android")
+
+ subcommands.add_parser("clean", help="Delete all build directories")
+ subcommands.add_parser("build-testbed", help="Build the testbed app")
+ test = subcommands.add_parser("test", help="Run the testbed app")
package = subcommands.add_parser("package", help="Make a release package")
+ env = subcommands.add_parser("env", help="Print environment variables")
# Common arguments
for subcommand in build, configure_build, configure_host:
subcommand.add_argument(
"--clean", action="store_true", default=False, dest="clean",
- help="Delete the relevant build and prefix directories first")
- for subcommand in [build, configure_host, make_host, package]:
+ help="Delete the relevant build directories first")
+
+ host_commands = [build, configure_host, make_host, package]
+ if in_source_tree:
+ host_commands.append(env)
+ for subcommand in host_commands:
subcommand.add_argument(
"host", metavar="HOST", choices=HOSTS,
help="Host triplet: choices=[%(choices)s]")
+
for subcommand in build, configure_build, configure_host:
subcommand.add_argument("args", nargs="*",
help="Extra arguments to pass to `configure`")
@@ -654,6 +730,7 @@ def parse_args():
"-v", "--verbose", action="count", default=0,
help="Show Gradle output, and non-Python logcat messages. "
"Use twice to include high-volume messages which are rarely useful.")
+
device_group = test.add_mutually_exclusive_group(required=True)
device_group.add_argument(
"--connected", metavar="SERIAL", help="Run on a connected device. "
@@ -661,8 +738,24 @@ def parse_args():
device_group.add_argument(
"--managed", metavar="NAME", help="Run on a Gradle-managed device. "
"These are defined in `managedDevices` in testbed/app/build.gradle.kts.")
+
+ test.add_argument(
+ "--site-packages", metavar="DIR", type=abspath,
+ help="Directory to copy as the app's site-packages.")
test.add_argument(
- "args", nargs="*", help=f"Arguments for `python -m test`. "
+ "--cwd", metavar="DIR", type=abspath,
+ help="Directory to copy as the app's working directory.")
+
+ mode_group = test.add_mutually_exclusive_group()
+ mode_group.add_argument(
+ "-c", dest="command", help="Execute the given Python code.")
+ mode_group.add_argument(
+ "-m", dest="module", help="Execute the module with the given name.")
+ test.epilog = (
+ "If neither -c nor -m are passed, the default is '-m test', which will "
+ "run Python's own test suite.")
+ test.add_argument(
+ "args", nargs="*", help=f"Arguments to add to sys.argv. "
f"Separate them from {SCRIPT_NAME}'s own arguments with `--`.")
return parser.parse_args()
@@ -688,6 +781,7 @@ def main():
"build-testbed": build_testbed,
"test": run_testbed,
"package": package,
+ "env": env,
}
try:
@@ -708,14 +802,9 @@ def print_called_process_error(e):
if not content.endswith("\n"):
stream.write("\n")
- # Format the command so it can be copied into a shell. shlex uses single
- # quotes, so we surround the whole command with double quotes.
- args_joined = (
- e.cmd if isinstance(e.cmd, str)
- else " ".join(shlex.quote(str(arg)) for arg in e.cmd)
- )
+ # shlex uses single quotes, so we surround the command with double quotes.
print(
- f'Command "{args_joined}" returned exit status {e.returncode}'
+ f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}'
)
diff --git a/Android/testbed/app/build.gradle.kts b/Android/testbed/app/build.gradle.kts
index c627cb1b0e0..92cffd61f86 100644
--- a/Android/testbed/app/build.gradle.kts
+++ b/Android/testbed/app/build.gradle.kts
@@ -85,7 +85,7 @@ android {
minSdk = androidEnvFile.useLines {
for (line in it) {
- """api_level:=(\d+)""".toRegex().find(line)?.let {
+ """ANDROID_API_LEVEL:=(\d+)""".toRegex().find(line)?.let {
return@useLines it.groupValues[1].toInt()
}
}
@@ -205,11 +205,29 @@ androidComponents.onVariants { variant ->
into("site-packages") {
from("$projectDir/src/main/python")
+
+ val sitePackages = findProperty("python.sitePackages") as String?
+ if (!sitePackages.isNullOrEmpty()) {
+ if (!file(sitePackages).exists()) {
+ throw GradleException("$sitePackages does not exist")
+ }
+ from(sitePackages)
+ }
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude("**/__pycache__")
}
+
+ into("cwd") {
+ val cwd = findProperty("python.cwd") as String?
+ if (!cwd.isNullOrEmpty()) {
+ if (!file(cwd).exists()) {
+ throw GradleException("$cwd does not exist")
+ }
+ from(cwd)
+ }
+ }
}
}
diff --git a/Android/testbed/app/src/androidTest/java/org/python/testbed/PythonSuite.kt b/Android/testbed/app/src/androidTest/java/org/python/testbed/PythonSuite.kt
index 0e888ab71d8..94be52dd2dc 100644
--- a/Android/testbed/app/src/androidTest/java/org/python/testbed/PythonSuite.kt
+++ b/Android/testbed/app/src/androidTest/java/org/python/testbed/PythonSuite.kt
@@ -17,11 +17,11 @@ class PythonSuite {
fun testPython() {
val start = System.currentTimeMillis()
try {
- val context =
+ val status = PythonTestRunner(
InstrumentationRegistry.getInstrumentation().targetContext
- val args =
- InstrumentationRegistry.getArguments().getString("pythonArgs", "")
- val status = PythonTestRunner(context).run(args)
+ ).run(
+ InstrumentationRegistry.getArguments()
+ )
assertEquals(0, status)
} finally {
// Make sure the process lives long enough for the test script to
diff --git a/Android/testbed/app/src/main/java/org/python/testbed/MainActivity.kt b/Android/testbed/app/src/main/java/org/python/testbed/MainActivity.kt
index c4bf6cbe83d..ef28948486f 100644
--- a/Android/testbed/app/src/main/java/org/python/testbed/MainActivity.kt
+++ b/Android/testbed/app/src/main/java/org/python/testbed/MainActivity.kt
@@ -15,17 +15,29 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
- val status = PythonTestRunner(this).run("-W -uall")
+ val status = PythonTestRunner(this).run("-m", "test", "-W -uall")
findViewById<TextView>(R.id.tvHello).text = "Exit status $status"
}
}
class PythonTestRunner(val context: Context) {
- /** @param args Extra arguments for `python -m test`.
- * @return The Python exit status: zero if the tests passed, nonzero if
- * they failed. */
- fun run(args: String = "") : Int {
+ fun run(instrumentationArgs: Bundle) = run(
+ instrumentationArgs.getString("pythonMode")!!,
+ instrumentationArgs.getString("pythonModule")!!,
+ instrumentationArgs.getString("pythonArgs") ?: "",
+ )
+
+ /** Run Python.
+ *
+ * @param mode Either "-c" or "-m".
+ * @param module Python statements for "-c" mode, or a module name for
+ * "-m" mode.
+ * @param args Arguments to add to sys.argv. Will be parsed by `shlex.split`.
+ * @return The Python exit status: zero on success, nonzero on failure. */
+ fun run(mode: String, module: String, args: String) : Int {
+ Os.setenv("PYTHON_MODE", mode, true)
+ Os.setenv("PYTHON_MODULE", module, true)
Os.setenv("PYTHON_ARGS", args, true)
// Python needs this variable to help it find the temporary directory,
@@ -36,8 +48,9 @@ class PythonTestRunner(val context: Context) {
System.loadLibrary("main_activity")
redirectStdioToLogcat()
- // The main module is in src/main/python/main.py.
- return runPython(pythonHome.toString(), "main")
+ // The main module is in src/main/python. We don't simply call it
+ // "main", as that could clash with third-party test code.
+ return runPython(pythonHome.toString(), "android_testbed_main")
}
private fun extractAssets() : File {
diff --git a/Android/testbed/app/src/main/python/main.py b/Android/testbed/app/src/main/python/android_testbed_main.py
index d6941b14412..31b8e5343a8 100644
--- a/Android/testbed/app/src/main/python/main.py
+++ b/Android/testbed/app/src/main/python/android_testbed_main.py
@@ -26,7 +26,23 @@ import sys
# test_signals in test_threadsignals.py.
signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGUSR1])
+mode = os.environ["PYTHON_MODE"]
+module = os.environ["PYTHON_MODULE"]
sys.argv[1:] = shlex.split(os.environ["PYTHON_ARGS"])
-# The test module will call sys.exit to indicate whether the tests passed.
-runpy.run_module("test")
+cwd = f"{sys.prefix}/cwd"
+if not os.path.exists(cwd):
+ # Empty directories are lost in the asset packing/unpacking process.
+ os.mkdir(cwd)
+os.chdir(cwd)
+
+if mode == "-c":
+ # In -c mode, sys.path starts with an empty string, which means whatever the current
+ # working directory is at the moment of each import.
+ sys.path.insert(0, "")
+ exec(module, {})
+elif mode == "-m":
+ sys.path.insert(0, os.getcwd())
+ runpy.run_module(module, run_name="__main__", alter_sys=True)
+else:
+ raise ValueError(f"unknown mode: {mode}")
diff --git a/Android/testbed/build.gradle.kts b/Android/testbed/build.gradle.kts
index 4d1d6f87594..451517b3f1a 100644
--- a/Android/testbed/build.gradle.kts
+++ b/Android/testbed/build.gradle.kts
@@ -1,5 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id("com.android.application") version "8.6.1" apply false
+ id("com.android.application") version "8.10.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
}
diff --git a/Android/testbed/gradle/wrapper/gradle-wrapper.properties b/Android/testbed/gradle/wrapper/gradle-wrapper.properties
index 36529c89642..5d42fbae084 100644
--- a/Android/testbed/gradle/wrapper/gradle-wrapper.properties
+++ b/Android/testbed/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Mon Feb 19 20:29:06 GMT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/Doc/c-api/allocation.rst b/Doc/c-api/allocation.rst
index 7cbc99ad145..59d913a0462 100644
--- a/Doc/c-api/allocation.rst
+++ b/Doc/c-api/allocation.rst
@@ -16,7 +16,20 @@ Allocating Objects on the Heap
Initialize a newly allocated object *op* with its type and initial
reference. Returns the initialized object. Other fields of the object are
- not affected.
+ not initialized. Despite its name, this function is unrelated to the
+ object's :meth:`~object.__init__` method (:c:member:`~PyTypeObject.tp_init`
+ slot). Specifically, this function does **not** call the object's
+ :meth:`!__init__` method.
+
+ In general, consider this function to be a low-level routine. Use
+ :c:member:`~PyTypeObject.tp_alloc` where possible.
+ For implementing :c:member:`!tp_alloc` for your type, prefer
+ :c:func:`PyType_GenericAlloc` or :c:func:`PyObject_New`.
+
+ .. note::
+
+ This function only initializes the object's memory corresponding to the
+ initial :c:type:`PyObject` structure. It does not zero the rest.
.. c:function:: PyVarObject* PyObject_InitVar(PyVarObject *op, PyTypeObject *type, Py_ssize_t size)
@@ -24,38 +37,107 @@ Allocating Objects on the Heap
This does everything :c:func:`PyObject_Init` does, and also initializes the
length information for a variable-size object.
+ .. note::
+
+ This function only initializes some of the object's memory. It does not
+ zero the rest.
+
.. c:macro:: PyObject_New(TYPE, typeobj)
- Allocate a new Python object using the C structure type *TYPE*
- and the Python type object *typeobj* (``PyTypeObject*``).
- Fields not defined by the Python object header are not initialized.
- The caller will own the only reference to the object
- (i.e. its reference count will be one).
- The size of the memory allocation is determined from the
- :c:member:`~PyTypeObject.tp_basicsize` field of the type object.
+ Allocates a new Python object using the C structure type *TYPE* and the
+ Python type object *typeobj* (``PyTypeObject*``) by calling
+ :c:func:`PyObject_Malloc` to allocate memory and initializing it like
+ :c:func:`PyObject_Init`. The caller will own the only reference to the
+ object (i.e. its reference count will be one).
+
+ Avoid calling this directly to allocate memory for an object; call the type's
+ :c:member:`~PyTypeObject.tp_alloc` slot instead.
+
+ When populating a type's :c:member:`~PyTypeObject.tp_alloc` slot,
+ :c:func:`PyType_GenericAlloc` is preferred over a custom function that
+ simply calls this macro.
+
+ This macro does not call :c:member:`~PyTypeObject.tp_alloc`,
+ :c:member:`~PyTypeObject.tp_new` (:meth:`~object.__new__`), or
+ :c:member:`~PyTypeObject.tp_init` (:meth:`~object.__init__`).
+
+ This cannot be used for objects with :c:macro:`Py_TPFLAGS_HAVE_GC` set in
+ :c:member:`~PyTypeObject.tp_flags`; use :c:macro:`PyObject_GC_New` instead.
+
+ Memory allocated by this macro must be freed with :c:func:`PyObject_Free`
+ (usually called via the object's :c:member:`~PyTypeObject.tp_free` slot).
+
+ .. note::
+
+ The returned memory is not guaranteed to have been completely zeroed
+ before it was initialized.
+
+ .. note::
+
+ This macro does not construct a fully initialized object of the given
+ type; it merely allocates memory and prepares it for further
+ initialization by :c:member:`~PyTypeObject.tp_init`. To construct a
+ fully initialized object, call *typeobj* instead. For example::
- Note that this function is unsuitable if *typeobj* has
- :c:macro:`Py_TPFLAGS_HAVE_GC` set. For such objects,
- use :c:func:`PyObject_GC_New` instead.
+ PyObject *foo = PyObject_CallNoArgs((PyObject *)&PyFoo_Type);
+
+ .. seealso::
+
+ * :c:func:`PyObject_Free`
+ * :c:macro:`PyObject_GC_New`
+ * :c:func:`PyType_GenericAlloc`
+ * :c:member:`~PyTypeObject.tp_alloc`
.. c:macro:: PyObject_NewVar(TYPE, typeobj, size)
- Allocate a new Python object using the C structure type *TYPE* and the
- Python type object *typeobj* (``PyTypeObject*``).
- Fields not defined by the Python object header
- are not initialized. The allocated memory allows for the *TYPE* structure
- plus *size* (``Py_ssize_t``) fields of the size
- given by the :c:member:`~PyTypeObject.tp_itemsize` field of
- *typeobj*. This is useful for implementing objects like tuples, which are
- able to determine their size at construction time. Embedding the array of
- fields into the same allocation decreases the number of allocations,
- improving the memory management efficiency.
+ Like :c:macro:`PyObject_New` except:
+
+ * It allocates enough memory for the *TYPE* structure plus *size*
+ (``Py_ssize_t``) fields of the size given by the
+ :c:member:`~PyTypeObject.tp_itemsize` field of *typeobj*.
+ * The memory is initialized like :c:func:`PyObject_InitVar`.
+
+ This is useful for implementing objects like tuples, which are able to
+ determine their size at construction time. Embedding the array of fields
+ into the same allocation decreases the number of allocations, improving the
+ memory management efficiency.
+
+ Avoid calling this directly to allocate memory for an object; call the type's
+ :c:member:`~PyTypeObject.tp_alloc` slot instead.
+
+ When populating a type's :c:member:`~PyTypeObject.tp_alloc` slot,
+ :c:func:`PyType_GenericAlloc` is preferred over a custom function that
+ simply calls this macro.
+
+ This cannot be used for objects with :c:macro:`Py_TPFLAGS_HAVE_GC` set in
+ :c:member:`~PyTypeObject.tp_flags`; use :c:macro:`PyObject_GC_NewVar`
+ instead.
+
+ Memory allocated by this function must be freed with :c:func:`PyObject_Free`
+ (usually called via the object's :c:member:`~PyTypeObject.tp_free` slot).
+
+ .. note::
+
+ The returned memory is not guaranteed to have been completely zeroed
+ before it was initialized.
+
+ .. note::
+
+ This macro does not construct a fully initialized object of the given
+ type; it merely allocates memory and prepares it for further
+ initialization by :c:member:`~PyTypeObject.tp_init`. To construct a
+ fully initialized object, call *typeobj* instead. For example::
+
+ PyObject *list_instance = PyObject_CallNoArgs((PyObject *)&PyList_Type);
+
+ .. seealso::
- Note that this function is unsuitable if *typeobj* has
- :c:macro:`Py_TPFLAGS_HAVE_GC` set. For such objects,
- use :c:func:`PyObject_GC_NewVar` instead.
+ * :c:func:`PyObject_Free`
+ * :c:macro:`PyObject_GC_NewVar`
+ * :c:func:`PyType_GenericAlloc`
+ * :c:member:`~PyTypeObject.tp_alloc`
.. c:function:: void PyObject_Del(void *op)
@@ -71,6 +153,6 @@ Allocating Objects on the Heap
.. seealso::
- :c:func:`PyModule_Create`
+ :ref:`moduleobjects`
To allocate and create extension modules.
diff --git a/Doc/c-api/arg.rst b/Doc/c-api/arg.rst
index 0b05e868917..ab9f9c4539a 100644
--- a/Doc/c-api/arg.rst
+++ b/Doc/c-api/arg.rst
@@ -274,7 +274,7 @@ small to receive the value.
Convert a Python integer to a C :c:expr:`unsigned long` without
overflow checking.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Use :meth:`~object.__index__` if available.
``L`` (:class:`int`) [long long]
@@ -284,7 +284,7 @@ small to receive the value.
Convert a Python integer to a C :c:expr:`unsigned long long`
without overflow checking.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Use :meth:`~object.__index__` if available.
``n`` (:class:`int`) [:c:type:`Py_ssize_t`]
@@ -380,10 +380,10 @@ Other objects
The *converter* for the ``O&`` format unit in *items* must not store
a borrowed buffer or a borrowed reference.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
:class:`str` and :class:`bytearray` objects no longer accepted as a sequence.
- .. deprecated:: next
+ .. deprecated:: 3.14
Non-tuple sequences are deprecated if *items* contains format units
which store a borrowed buffer or a borrowed reference.
@@ -396,7 +396,7 @@ Other objects
If the argument is not ``None``, it is parsed according to the specified
format unit.
- .. versionadded:: next
+ .. versionadded:: 3.14
A few other characters have a meaning in a format string. These may not occur
inside nested parentheses. They are:
@@ -685,6 +685,13 @@ Building values
``p`` (:class:`bool`) [int]
Convert a C :c:expr:`int` to a Python :class:`bool` object.
+
+ Be aware that this format requires an ``int`` argument.
+ Unlike most other contexts in C, variadic arguments are not coerced to
+ a suitable type automatically.
+ You can convert another type (for example, a pointer or a float) to a
+ suitable ``int`` value using ``(x) ? 1 : 0`` or ``!!x``.
+
.. versionadded:: 3.14
``c`` (:class:`bytes` of length 1) [char]
diff --git a/Doc/c-api/capsule.rst b/Doc/c-api/capsule.rst
index cdb8aa33e9f..64dc4f5275b 100644
--- a/Doc/c-api/capsule.rst
+++ b/Doc/c-api/capsule.rst
@@ -105,9 +105,19 @@ Refer to :ref:`using-capsules` for more information on using these objects.
``module.attribute``. The *name* stored in the capsule must match this
string exactly.
+ This function splits *name* on the ``.`` character, and imports the first
+ element. It then processes further elements using attribute lookups.
+
Return the capsule's internal *pointer* on success. On failure, set an
exception and return ``NULL``.
+ .. note::
+
+ If *name* points to an attribute of some submodule or subpackage, this
+ submodule or subpackage must be previously imported using other means
+ (for example, by using :c:func:`PyImport_ImportModule`) for the
+ attribute lookups to succeed.
+
.. versionchanged:: 3.3
*no_block* has no effect anymore.
diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst
index 6eae24b38fa..42594f063b0 100644
--- a/Doc/c-api/code.rst
+++ b/Doc/c-api/code.rst
@@ -182,7 +182,7 @@ bound into a function.
Type of a code object watcher callback function.
If *event* is ``PY_CODE_EVENT_CREATE``, then the callback is invoked
- after `co` has been fully initialized. Otherwise, the callback is invoked
+ after *co* has been fully initialized. Otherwise, the callback is invoked
before the destruction of *co* takes place, so the prior state of *co*
can be inspected.
diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index c8e1b5c2461..a750cda3e2d 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -749,6 +749,16 @@ Exception Classes
.. versionadded:: 3.2
+.. c:function:: int PyExceptionClass_Check(PyObject *ob)
+
+ Return non-zero if *ob* is an exception class, zero otherwise. This function always succeeds.
+
+
+.. c:function:: const char *PyExceptionClass_Name(PyObject *ob)
+
+ Return :c:member:`~PyTypeObject.tp_name` of the exception class *ob*.
+
+
Exception Objects
=================
@@ -982,6 +992,7 @@ the variables:
.. index::
single: PyExc_BaseException (C var)
+ single: PyExc_BaseExceptionGroup (C var)
single: PyExc_Exception (C var)
single: PyExc_ArithmeticError (C var)
single: PyExc_AssertionError (C var)
@@ -1041,6 +1052,8 @@ the variables:
+=========================================+=================================+==========+
| :c:data:`PyExc_BaseException` | :exc:`BaseException` | [1]_ |
+-----------------------------------------+---------------------------------+----------+
+| :c:data:`PyExc_BaseExceptionGroup` | :exc:`BaseExceptionGroup` | [1]_ |
++-----------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_Exception` | :exc:`Exception` | [1]_ |
+-----------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_ArithmeticError` | :exc:`ArithmeticError` | [1]_ |
@@ -1164,6 +1177,9 @@ the variables:
.. versionadded:: 3.6
:c:data:`PyExc_ModuleNotFoundError`.
+.. versionadded:: 3.11
+ :c:data:`PyExc_BaseExceptionGroup`.
+
These are compatibility aliases to :c:data:`PyExc_OSError`:
.. index::
@@ -1207,6 +1223,7 @@ the variables:
single: PyExc_Warning (C var)
single: PyExc_BytesWarning (C var)
single: PyExc_DeprecationWarning (C var)
+ single: PyExc_EncodingWarning (C var)
single: PyExc_FutureWarning (C var)
single: PyExc_ImportWarning (C var)
single: PyExc_PendingDeprecationWarning (C var)
@@ -1225,6 +1242,8 @@ the variables:
+------------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_DeprecationWarning` | :exc:`DeprecationWarning` | |
+------------------------------------------+---------------------------------+----------+
+| :c:data:`PyExc_EncodingWarning` | :exc:`EncodingWarning` | |
++------------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_FutureWarning` | :exc:`FutureWarning` | |
+------------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_ImportWarning` | :exc:`ImportWarning` | |
@@ -1245,6 +1264,9 @@ the variables:
.. versionadded:: 3.2
:c:data:`PyExc_ResourceWarning`.
+.. versionadded:: 3.10
+ :c:data:`PyExc_EncodingWarning`.
+
Notes:
.. [3]
diff --git a/Doc/c-api/extension-modules.rst b/Doc/c-api/extension-modules.rst
new file mode 100644
index 00000000000..3d331e6ec12
--- /dev/null
+++ b/Doc/c-api/extension-modules.rst
@@ -0,0 +1,247 @@
+.. highlight:: c
+
+.. _extension-modules:
+
+Defining extension modules
+--------------------------
+
+A C extension for CPython is a shared library (for example, a ``.so`` file
+on Linux, ``.pyd`` DLL on Windows), which is loadable into the Python process
+(for example, it is compiled with compatible compiler settings), and which
+exports an :ref:`initialization function <extension-export-hook>`.
+
+To be importable by default (that is, by
+:py:class:`importlib.machinery.ExtensionFileLoader`),
+the shared library must be available on :py:attr:`sys.path`,
+and must be named after the module name plus an extension listed in
+:py:attr:`importlib.machinery.EXTENSION_SUFFIXES`.
+
+.. note::
+
+ Building, packaging and distributing extension modules is best done with
+ third-party tools, and is out of scope of this document.
+ One suitable tool is Setuptools, whose documentation can be found at
+ https://setuptools.pypa.io/en/latest/setuptools.html.
+
+Normally, the initialization function returns a module definition initialized
+using :c:func:`PyModuleDef_Init`.
+This allows splitting the creation process into several phases:
+
+- Before any substantial code is executed, Python can determine which
+ capabilities the module supports, and it can adjust the environment or
+ refuse loading an incompatible extension.
+- By default, Python itself creates the module object -- that is, it does
+ the equivalent of :py:meth:`object.__new__` for classes.
+ It also sets initial attributes like :attr:`~module.__package__` and
+ :attr:`~module.__loader__`.
+- Afterwards, the module object is initialized using extension-specific
+ code -- the equivalent of :py:meth:`~object.__init__` on classes.
+
+This is called *multi-phase initialization* to distinguish it from the legacy
+(but still supported) *single-phase initialization* scheme,
+where the initialization function returns a fully constructed module.
+See the :ref:`single-phase-initialization section below <single-phase-initialization>`
+for details.
+
+.. versionchanged:: 3.5
+
+ Added support for multi-phase initialization (:pep:`489`).
+
+
+Multiple module instances
+.........................
+
+By default, extension modules are not singletons.
+For example, if the :py:attr:`sys.modules` entry is removed and the module
+is re-imported, a new module object is created, and typically populated with
+fresh method and type objects.
+The old module is subject to normal garbage collection.
+This mirrors the behavior of pure-Python modules.
+
+Additional module instances may be created in
+:ref:`sub-interpreters <sub-interpreter-support>`
+or after Python runtime reinitialization
+(:c:func:`Py_Finalize` and :c:func:`Py_Initialize`).
+In these cases, sharing Python objects between module instances would likely
+cause crashes or undefined behavior.
+
+To avoid such issues, each instance of an extension module should
+be *isolated*: changes to one instance should not implicitly affect the others,
+and all state owned by the module, including references to Python objects,
+should be specific to a particular module instance.
+See :ref:`isolating-extensions-howto` for more details and a practical guide.
+
+A simpler way to avoid these issues is
+:ref:`raising an error on repeated initialization <isolating-extensions-optout>`.
+
+All modules are expected to support
+:ref:`sub-interpreters <sub-interpreter-support>`, or otherwise explicitly
+signal a lack of support.
+This is usually achieved by isolation or blocking repeated initialization,
+as above.
+A module may also be limited to the main interpreter using
+the :c:data:`Py_mod_multiple_interpreters` slot.
+
+
+.. _extension-export-hook:
+
+Initialization function
+.......................
+
+The initialization function defined by an extension module has the
+following signature:
+
+.. c:function:: PyObject* PyInit_modulename(void)
+
+Its name should be :samp:`PyInit_{<name>}`, with ``<name>`` replaced by the
+name of the module.
+
+For modules with ASCII-only names, the function must instead be named
+:samp:`PyInit_{<name>}`, with ``<name>`` replaced by the name of the module.
+When using :ref:`multi-phase-initialization`, non-ASCII module names
+are allowed. In this case, the initialization function name is
+:samp:`PyInitU_{<name>}`, with ``<name>`` encoded using Python's
+*punycode* encoding with hyphens replaced by underscores. In Python:
+
+.. code-block:: python
+
+ def initfunc_name(name):
+ try:
+ suffix = b'_' + name.encode('ascii')
+ except UnicodeEncodeError:
+ suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
+ return b'PyInit' + suffix
+
+It is recommended to define the initialization function using a helper macro:
+
+.. c:macro:: PyMODINIT_FUNC
+
+ Declare an extension module initialization function.
+ This macro:
+
+ * specifies the :c:expr:`PyObject*` return type,
+ * adds any special linkage declarations required by the platform, and
+ * for C++, declares the function as ``extern "C"``.
+
+For example, a module called ``spam`` would be defined like this::
+
+ static struct PyModuleDef spam_module = {
+ .m_base = PyModuleDef_HEAD_INIT,
+ .m_name = "spam",
+ ...
+ };
+
+ PyMODINIT_FUNC
+ PyInit_spam(void)
+ {
+ return PyModuleDef_Init(&spam_module);
+ }
+
+It is possible to export multiple modules from a single shared library by
+defining multiple initialization functions. However, importing them requires
+using symbolic links or a custom importer, because by default only the
+function corresponding to the filename is found.
+See the `Multiple modules in one library <https://peps.python.org/pep-0489/#multiple-modules-in-one-library>`__
+section in :pep:`489` for details.
+
+The initialization function is typically the only non-\ ``static``
+item defined in the module's C source.
+
+
+.. _multi-phase-initialization:
+
+Multi-phase initialization
+..........................
+
+Normally, the :ref:`initialization function <extension-export-hook>`
+(``PyInit_modulename``) returns a :c:type:`PyModuleDef` instance with
+non-``NULL`` :c:member:`~PyModuleDef.m_slots`.
+Before it is returned, the ``PyModuleDef`` instance must be initialized
+using the following function:
+
+
+.. c:function:: PyObject* PyModuleDef_Init(PyModuleDef *def)
+
+ Ensure a module definition is a properly initialized Python object that
+ correctly reports its type and a reference count.
+
+ Return *def* cast to ``PyObject*``, or ``NULL`` if an error occurred.
+
+ Calling this function is required for :ref:`multi-phase-initialization`.
+ It should not be used in other contexts.
+
+ Note that Python assumes that ``PyModuleDef`` structures are statically
+ allocated.
+ This function may return either a new reference or a borrowed one;
+ this reference must not be released.
+
+ .. versionadded:: 3.5
+
+
+.. _single-phase-initialization:
+
+Legacy single-phase initialization
+..................................
+
+.. attention::
+ Single-phase initialization is a legacy mechanism to initialize extension
+ modules, with known drawbacks and design flaws. Extension module authors
+ are encouraged to use multi-phase initialization instead.
+
+In single-phase initialization, the
+:ref:`initialization function <extension-export-hook>` (``PyInit_modulename``)
+should create, populate and return a module object.
+This is typically done using :c:func:`PyModule_Create` and functions like
+:c:func:`PyModule_AddObjectRef`.
+
+Single-phase initialization differs from the :ref:`default <multi-phase-initialization>`
+in the following ways:
+
+* Single-phase modules are, or rather *contain*, “singletons”.
+
+ When the module is first initialized, Python saves the contents of
+ the module's ``__dict__`` (that is, typically, the module's functions and
+ types).
+
+ For subsequent imports, Python does not call the initialization function
+ again.
+ Instead, it creates a new module object with a new ``__dict__``, and copies
+ the saved contents to it.
+ For example, given a single-phase module ``_testsinglephase``
+ [#testsinglephase]_ that defines a function ``sum`` and an exception class
+ ``error``:
+
+ .. code-block:: python
+
+ >>> import sys
+ >>> import _testsinglephase as one
+ >>> del sys.modules['_testsinglephase']
+ >>> import _testsinglephase as two
+ >>> one is two
+ False
+ >>> one.__dict__ is two.__dict__
+ False
+ >>> one.sum is two.sum
+ True
+ >>> one.error is two.error
+ True
+
+ The exact behavior should be considered a CPython implementation detail.
+
+* To work around the fact that ``PyInit_modulename`` does not take a *spec*
+ argument, some state of the import machinery is saved and applied to the
+ first suitable module created during the ``PyInit_modulename`` call.
+ Specifically, when a sub-module is imported, this mechanism prepends the
+ parent package name to the name of the module.
+
+ A single-phase ``PyInit_modulename`` function should create “its” module
+ object as soon as possible, before any other module objects can be created.
+
+* Non-ASCII module names (``PyInitU_modulename``) are not supported.
+
+* Single-phase modules support module lookup functions like
+ :c:func:`PyState_FindModule`.
+
+.. [#testsinglephase] ``_testsinglephase`` is an internal module used
+ in CPython's self-test suite; your installation may or may not
+ include it.
diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst
index 58792edeed2..5fb8567ef8c 100644
--- a/Doc/c-api/function.rst
+++ b/Doc/c-api/function.rst
@@ -95,6 +95,13 @@ There are a few functions specific to Python functions.
.. versionadded:: 3.12
+
+.. c:function:: PyObject* PyFunction_GetKwDefaults(PyObject *op)
+
+ Return the keyword-only argument default values of the function object *op*. This can be a
+ dictionary of arguments or ``NULL``.
+
+
.. c:function:: PyObject* PyFunction_GetClosure(PyObject *op)
Return the closure associated with the function object *op*. This can be ``NULL``
@@ -123,6 +130,19 @@ There are a few functions specific to Python functions.
Raises :exc:`SystemError` and returns ``-1`` on failure.
+.. c:function:: PyObject *PyFunction_GET_CODE(PyObject *op)
+ PyObject *PyFunction_GET_GLOBALS(PyObject *op)
+ PyObject *PyFunction_GET_MODULE(PyObject *op)
+ PyObject *PyFunction_GET_DEFAULTS(PyObject *op)
+ PyObject *PyFunction_GET_KW_DEFAULTS(PyObject *op)
+ PyObject *PyFunction_GET_CLOSURE(PyObject *op)
+ PyObject *PyFunction_GET_ANNOTATIONS(PyObject *op)
+
+ These functions are similar to their ``PyFunction_Get*`` counterparts, but
+ do not do type checking. Passing anything other than an instance of
+ :c:data:`PyFunction_Type` is undefined behavior.
+
+
.. c:function:: int PyFunction_AddWatcher(PyFunction_WatchCallback callback)
Register *callback* as a function watcher for the current interpreter.
@@ -169,7 +189,7 @@ There are a few functions specific to Python functions.
unpredictable effects, including infinite recursion.
If *event* is ``PyFunction_EVENT_CREATE``, then the callback is invoked
- after `func` has been fully initialized. Otherwise, the callback is invoked
+ after *func* has been fully initialized. Otherwise, the callback is invoked
before the modification to *func* takes place, so the prior state of *func*
can be inspected. The runtime is permitted to optimize away the creation of
function objects when possible. In such cases no event will be emitted.
diff --git a/Doc/c-api/gcsupport.rst b/Doc/c-api/gcsupport.rst
index d1f0982b818..f6fa52b36c5 100644
--- a/Doc/c-api/gcsupport.rst
+++ b/Doc/c-api/gcsupport.rst
@@ -57,11 +57,49 @@ rules:
Analogous to :c:macro:`PyObject_New` but for container objects with the
:c:macro:`Py_TPFLAGS_HAVE_GC` flag set.
+ Do not call this directly to allocate memory for an object; call the type's
+ :c:member:`~PyTypeObject.tp_alloc` slot instead.
+
+ When populating a type's :c:member:`~PyTypeObject.tp_alloc` slot,
+ :c:func:`PyType_GenericAlloc` is preferred over a custom function that
+ simply calls this macro.
+
+ Memory allocated by this macro must be freed with
+ :c:func:`PyObject_GC_Del` (usually called via the object's
+ :c:member:`~PyTypeObject.tp_free` slot).
+
+ .. seealso::
+
+ * :c:func:`PyObject_GC_Del`
+ * :c:macro:`PyObject_New`
+ * :c:func:`PyType_GenericAlloc`
+ * :c:member:`~PyTypeObject.tp_alloc`
+
+
.. c:macro:: PyObject_GC_NewVar(TYPE, typeobj, size)
Analogous to :c:macro:`PyObject_NewVar` but for container objects with the
:c:macro:`Py_TPFLAGS_HAVE_GC` flag set.
+ Do not call this directly to allocate memory for an object; call the type's
+ :c:member:`~PyTypeObject.tp_alloc` slot instead.
+
+ When populating a type's :c:member:`~PyTypeObject.tp_alloc` slot,
+ :c:func:`PyType_GenericAlloc` is preferred over a custom function that
+ simply calls this macro.
+
+ Memory allocated by this macro must be freed with
+ :c:func:`PyObject_GC_Del` (usually called via the object's
+ :c:member:`~PyTypeObject.tp_free` slot).
+
+ .. seealso::
+
+ * :c:func:`PyObject_GC_Del`
+ * :c:macro:`PyObject_NewVar`
+ * :c:func:`PyType_GenericAlloc`
+ * :c:member:`~PyTypeObject.tp_alloc`
+
+
.. c:function:: PyObject* PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *type, size_t extra_size)
Analogous to :c:macro:`PyObject_GC_New` but allocates *extra_size*
@@ -73,6 +111,10 @@ rules:
The extra data will be deallocated with the object, but otherwise it is
not managed by Python.
+ Memory allocated by this function must be freed with
+ :c:func:`PyObject_GC_Del` (usually called via the object's
+ :c:member:`~PyTypeObject.tp_free` slot).
+
.. warning::
The function is marked as unstable because the final mechanism
for reserving extra data after an instance is not yet decided.
@@ -136,6 +178,21 @@ rules:
Releases memory allocated to an object using :c:macro:`PyObject_GC_New` or
:c:macro:`PyObject_GC_NewVar`.
+ Do not call this directly to free an object's memory; call the type's
+ :c:member:`~PyTypeObject.tp_free` slot instead.
+
+ Do not use this for memory allocated by :c:macro:`PyObject_New`,
+ :c:macro:`PyObject_NewVar`, or related allocation functions; use
+ :c:func:`PyObject_Free` instead.
+
+ .. seealso::
+
+ * :c:func:`PyObject_Free` is the non-GC equivalent of this function.
+ * :c:macro:`PyObject_GC_New`
+ * :c:macro:`PyObject_GC_NewVar`
+ * :c:func:`PyType_GenericAlloc`
+ * :c:member:`~PyTypeObject.tp_free`
+
.. c:function:: void PyObject_GC_UnTrack(void *op)
@@ -180,9 +237,9 @@ provided. In order to use this macro, the :c:member:`~PyTypeObject.tp_traverse`
must name its arguments exactly *visit* and *arg*:
-.. c:function:: void Py_VISIT(PyObject *o)
+.. c:macro:: Py_VISIT(o)
- If *o* is not ``NULL``, call the *visit* callback, with arguments *o*
+ If the :c:expr:`PyObject *` *o* is not ``NULL``, call the *visit* callback, with arguments *o*
and *arg*. If *visit* returns a non-zero value, then return it.
Using this macro, :c:member:`~PyTypeObject.tp_traverse` handlers
look like::
diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst
index 1cab3ce3061..8eabc0406b1 100644
--- a/Doc/c-api/import.rst
+++ b/Doc/c-api/import.rst
@@ -16,19 +16,6 @@ Importing Modules
This is a wrapper around :c:func:`PyImport_Import()` which takes a
:c:expr:`const char *` as an argument instead of a :c:expr:`PyObject *`.
-.. c:function:: PyObject* PyImport_ImportModuleNoBlock(const char *name)
-
- This function is a deprecated alias of :c:func:`PyImport_ImportModule`.
-
- .. versionchanged:: 3.3
- This function used to fail immediately when the import lock was held
- by another thread. In Python 3.3 though, the locking scheme switched
- to per-module locks for most purposes, so this function's special
- behaviour isn't needed anymore.
-
- .. deprecated-removed:: 3.13 3.15
- Use :c:func:`PyImport_ImportModule` instead.
-
.. c:function:: PyObject* PyImport_ImportModuleEx(const char *name, PyObject *globals, PyObject *locals, PyObject *fromlist)
diff --git a/Doc/c-api/index.rst b/Doc/c-api/index.rst
index ba56b03c6ac..e9df2a304d9 100644
--- a/Doc/c-api/index.rst
+++ b/Doc/c-api/index.rst
@@ -17,6 +17,7 @@ document the API functions in detail.
veryhigh.rst
refcounting.rst
exceptions.rst
+ extension-modules.rst
utilities.rst
abstract.rst
concrete.rst
diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst
index 52f64a61006..409539dec17 100644
--- a/Doc/c-api/init.rst
+++ b/Doc/c-api/init.rst
@@ -77,10 +77,7 @@ The following functions can be safely called before Python is initialized:
Despite their apparent similarity to some of the functions listed above,
the following functions **should not be called** before the interpreter has
- been initialized: :c:func:`Py_EncodeLocale`, :c:func:`Py_GetPath`,
- :c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`,
- :c:func:`Py_GetProgramFullPath`, :c:func:`Py_GetPythonHome`,
- :c:func:`Py_GetProgramName`, :c:func:`PyEval_InitThreads`, and
+ been initialized: :c:func:`Py_EncodeLocale`, :c:func:`PyEval_InitThreads`, and
:c:func:`Py_RunMain`.
@@ -145,9 +142,6 @@ to 1 and ``-bb`` sets :c:data:`Py_BytesWarningFlag` to 2.
:c:member:`PyConfig.pathconfig_warnings` should be used instead, see
:ref:`Python Initialization Configuration <init-config>`.
- Suppress error messages when calculating the module search path in
- :c:func:`Py_GetPath`.
-
Private flag used by ``_freeze_module`` and ``frozenmain`` programs.
.. deprecated-removed:: 3.12 3.15
@@ -203,7 +197,7 @@ to 1 and ``-bb`` sets :c:data:`Py_BytesWarningFlag` to 2.
Set by the :option:`-i` option.
- .. deprecated:: 3.12
+ .. deprecated-removed:: 3.12 3.15
.. c:var:: int Py_IsolatedFlag
@@ -498,17 +492,8 @@ Initializing and finalizing the interpreter
strings other than those passed in (however, the contents of the strings
pointed to by the argument list are not modified).
- The return value will be ``0`` if the interpreter exits normally (i.e.,
- without an exception), ``1`` if the interpreter exits due to an exception,
- or ``2`` if the argument list does not represent a valid Python command
- line.
-
- Note that if an otherwise unhandled :exc:`SystemExit` is raised, this
- function will not return ``1``, but exit the process, as long as
- ``Py_InspectFlag`` is not set. If ``Py_InspectFlag`` is set, execution will
- drop into the interactive Python prompt, at which point a second otherwise
- unhandled :exc:`SystemExit` will still exit the process, while any other
- means of exiting will set the return value as described above.
+ The return value is ``2`` if the argument list does not represent a valid
+ Python command line, and otherwise the same as :c:func:`Py_RunMain`.
In terms of the CPython runtime configuration APIs documented in the
:ref:`runtime configuration <init-config>` section (and without accounting
@@ -545,23 +530,18 @@ Initializing and finalizing the interpreter
If :c:member:`PyConfig.inspect` is not set (the default), the return value
will be ``0`` if the interpreter exits normally (that is, without raising
- an exception), or ``1`` if the interpreter exits due to an exception. If an
- otherwise unhandled :exc:`SystemExit` is raised, the function will immediately
- exit the process instead of returning ``1``.
+ an exception), the exit status of an unhandled :exc:`SystemExit`, or ``1``
+ for any other unhandled exception.
If :c:member:`PyConfig.inspect` is set (such as when the :option:`-i` option
is used), rather than returning when the interpreter exits, execution will
instead resume in an interactive Python prompt (REPL) using the ``__main__``
module's global namespace. If the interpreter exited with an exception, it
is immediately raised in the REPL session. The function return value is
- then determined by the way the *REPL session* terminates: returning ``0``
- if the session terminates without raising an unhandled exception, exiting
- immediately for an unhandled :exc:`SystemExit`, and returning ``1`` for
- any other unhandled exception.
+ then determined by the way the *REPL session* terminates: ``0``, ``1``, or
+ the status of a :exc:`SystemExit`, as specified above.
- This function always finalizes the Python interpreter regardless of whether
- it returns a value or immediately exits the process due to an unhandled
- :exc:`SystemExit` exception.
+ This function always finalizes the Python interpreter before it returns.
See :ref:`Python Configuration <init-python-config>` for an example of a
customized Python that always runs in isolated mode using
@@ -586,7 +566,6 @@ Process-wide parameters
.. index::
single: Py_Initialize()
single: main()
- single: Py_GetPath()
This API is kept for backward compatibility: setting
:c:member:`PyConfig.program_name` should be used instead, see :ref:`Python
@@ -596,7 +575,7 @@ Process-wide parameters
the first time, if it is called at all. It tells the interpreter the value
of the ``argv[0]`` argument to the :c:func:`main` function of the program
(converted to wide characters).
- This is used by :c:func:`Py_GetPath` and some other functions below to find
+ This is used by some other functions below to find
the Python run-time libraries relative to the interpreter executable. The
default value is ``'python'``. The argument should point to a
zero-terminated wide character string in static storage whose contents will not
@@ -609,146 +588,6 @@ Process-wide parameters
.. deprecated-removed:: 3.11 3.15
-.. c:function:: wchar_t* Py_GetProgramName()
-
- Return the program name set with :c:member:`PyConfig.program_name`, or the default.
- The returned string points into static storage; the caller should not modify its
- value.
-
- This function should not be called before :c:func:`Py_Initialize`, otherwise
- it returns ``NULL``.
-
- .. versionchanged:: 3.10
- It now returns ``NULL`` if called before :c:func:`Py_Initialize`.
-
- .. deprecated-removed:: 3.13 3.15
- Use :c:func:`PyConfig_Get("executable") <PyConfig_Get>`
- (:data:`sys.executable`) instead.
-
-
-.. c:function:: wchar_t* Py_GetPrefix()
-
- Return the *prefix* for installed platform-independent files. This is derived
- through a number of complicated rules from the program name set with
- :c:member:`PyConfig.program_name` and some environment variables; for example, if the
- program name is ``'/usr/local/bin/python'``, the prefix is ``'/usr/local'``. The
- returned string points into static storage; the caller should not modify its
- value. This corresponds to the :makevar:`prefix` variable in the top-level
- :file:`Makefile` and the :option:`--prefix` argument to the :program:`configure`
- script at build time. The value is available to Python code as ``sys.base_prefix``.
- It is only useful on Unix. See also the next function.
-
- This function should not be called before :c:func:`Py_Initialize`, otherwise
- it returns ``NULL``.
-
- .. versionchanged:: 3.10
- It now returns ``NULL`` if called before :c:func:`Py_Initialize`.
-
- .. deprecated-removed:: 3.13 3.15
- Use :c:func:`PyConfig_Get("base_prefix") <PyConfig_Get>`
- (:data:`sys.base_prefix`) instead. Use :c:func:`PyConfig_Get("prefix")
- <PyConfig_Get>` (:data:`sys.prefix`) if :ref:`virtual environments
- <venv-def>` need to be handled.
-
-
-.. c:function:: wchar_t* Py_GetExecPrefix()
-
- Return the *exec-prefix* for installed platform-*dependent* files. This is
- derived through a number of complicated rules from the program name set with
- :c:member:`PyConfig.program_name` and some environment variables; for example, if the
- program name is ``'/usr/local/bin/python'``, the exec-prefix is
- ``'/usr/local'``. The returned string points into static storage; the caller
- should not modify its value. This corresponds to the :makevar:`exec_prefix`
- variable in the top-level :file:`Makefile` and the ``--exec-prefix``
- argument to the :program:`configure` script at build time. The value is
- available to Python code as ``sys.base_exec_prefix``. It is only useful on
- Unix.
-
- Background: The exec-prefix differs from the prefix when platform dependent
- files (such as executables and shared libraries) are installed in a different
- directory tree. In a typical installation, platform dependent files may be
- installed in the :file:`/usr/local/plat` subtree while platform independent may
- be installed in :file:`/usr/local`.
-
- Generally speaking, a platform is a combination of hardware and software
- families, e.g. Sparc machines running the Solaris 2.x operating system are
- considered the same platform, but Intel machines running Solaris 2.x are another
- platform, and Intel machines running Linux are yet another platform. Different
- major revisions of the same operating system generally also form different
- platforms. Non-Unix operating systems are a different story; the installation
- strategies on those systems are so different that the prefix and exec-prefix are
- meaningless, and set to the empty string. Note that compiled Python bytecode
- files are platform independent (but not independent from the Python version by
- which they were compiled!).
-
- System administrators will know how to configure the :program:`mount` or
- :program:`automount` programs to share :file:`/usr/local` between platforms
- while having :file:`/usr/local/plat` be a different filesystem for each
- platform.
-
- This function should not be called before :c:func:`Py_Initialize`, otherwise
- it returns ``NULL``.
-
- .. versionchanged:: 3.10
- It now returns ``NULL`` if called before :c:func:`Py_Initialize`.
-
- .. deprecated-removed:: 3.13 3.15
- Use :c:func:`PyConfig_Get("base_exec_prefix") <PyConfig_Get>`
- (:data:`sys.base_exec_prefix`) instead. Use
- :c:func:`PyConfig_Get("exec_prefix") <PyConfig_Get>`
- (:data:`sys.exec_prefix`) if :ref:`virtual environments <venv-def>` need
- to be handled.
-
-.. c:function:: wchar_t* Py_GetProgramFullPath()
-
- .. index::
- single: executable (in module sys)
-
- Return the full program name of the Python executable; this is computed as a
- side-effect of deriving the default module search path from the program name
- (set by :c:member:`PyConfig.program_name`). The returned string points into
- static storage; the caller should not modify its value. The value is available
- to Python code as ``sys.executable``.
-
- This function should not be called before :c:func:`Py_Initialize`, otherwise
- it returns ``NULL``.
-
- .. versionchanged:: 3.10
- It now returns ``NULL`` if called before :c:func:`Py_Initialize`.
-
- .. deprecated-removed:: 3.13 3.15
- Use :c:func:`PyConfig_Get("executable") <PyConfig_Get>`
- (:data:`sys.executable`) instead.
-
-
-.. c:function:: wchar_t* Py_GetPath()
-
- .. index::
- triple: module; search; path
- single: path (in module sys)
-
- Return the default module search path; this is computed from the program name
- (set by :c:member:`PyConfig.program_name`) and some environment variables.
- The returned string consists of a series of directory names separated by a
- platform dependent delimiter character. The delimiter character is ``':'``
- on Unix and macOS, ``';'`` on Windows. The returned string points into
- static storage; the caller should not modify its value. The list
- :data:`sys.path` is initialized with this value on interpreter startup; it
- can be (and usually is) modified later to change the search path for loading
- modules.
-
- This function should not be called before :c:func:`Py_Initialize`, otherwise
- it returns ``NULL``.
-
- .. XXX should give the exact rules
-
- .. versionchanged:: 3.10
- It now returns ``NULL`` if called before :c:func:`Py_Initialize`.
-
- .. deprecated-removed:: 3.13 3.15
- Use :c:func:`PyConfig_Get("module_search_paths") <PyConfig_Get>`
- (:data:`sys.path`) instead.
-
.. c:function:: const char* Py_GetVersion()
Return the version of this Python interpreter. This is a string that looks
@@ -919,23 +758,6 @@ Process-wide parameters
.. deprecated-removed:: 3.11 3.15
-.. c:function:: wchar_t* Py_GetPythonHome()
-
- Return the default "home", that is, the value set by
- :c:member:`PyConfig.home`, or the value of the :envvar:`PYTHONHOME`
- environment variable if it is set.
-
- This function should not be called before :c:func:`Py_Initialize`, otherwise
- it returns ``NULL``.
-
- .. versionchanged:: 3.10
- It now returns ``NULL`` if called before :c:func:`Py_Initialize`.
-
- .. deprecated-removed:: 3.13 3.15
- Use :c:func:`PyConfig_Get("home") <PyConfig_Get>` or the
- :envvar:`PYTHONHOME` environment variable instead.
-
-
.. _threads:
Thread State and the Global Interpreter Lock
@@ -1083,8 +905,36 @@ Note that the ``PyGILState_*`` functions assume there is only one global
interpreter (created automatically by :c:func:`Py_Initialize`). Python
supports the creation of additional interpreters (using
:c:func:`Py_NewInterpreter`), but mixing multiple interpreters and the
-``PyGILState_*`` API is unsupported.
+``PyGILState_*`` API is unsupported. This is because :c:func:`PyGILState_Ensure`
+and similar functions default to :term:`attaching <attached thread state>` a
+:term:`thread state` for the main interpreter, meaning that the thread can't safely
+interact with the calling subinterpreter.
+
+Supporting subinterpreters in non-Python threads
+------------------------------------------------
+
+If you would like to support subinterpreters with non-Python created threads, you
+must use the ``PyThreadState_*`` API instead of the traditional ``PyGILState_*``
+API.
+
+In particular, you must store the interpreter state from the calling
+function and pass it to :c:func:`PyThreadState_New`, which will ensure that
+the :term:`thread state` is targeting the correct interpreter::
+
+ /* The return value of PyInterpreterState_Get() from the
+ function that created this thread. */
+ PyInterpreterState *interp = ThreadData->interp;
+ PyThreadState *tstate = PyThreadState_New(interp);
+ PyThreadState_Swap(tstate);
+
+ /* GIL of the subinterpreter is now held.
+ Perform Python actions here. */
+ result = CallSomeFunction();
+ /* evaluate result or handle exception */
+ /* Destroy the thread state. No Python API allowed beyond this point. */
+ PyThreadState_Clear(tstate);
+ PyThreadState_DeleteCurrent();
.. _fork-and-threads:
@@ -1261,6 +1111,10 @@ code, or when embedding the Python interpreter:
.. seealso:
:c:func:`PyEval_ReleaseThread`
+ .. note::
+ Similar to :c:func:`PyGILState_Ensure`, this function will hang the
+ thread if the runtime is finalizing.
+
The following functions use thread-local storage, and are not compatible
with sub-interpreters:
@@ -1287,10 +1141,10 @@ with sub-interpreters:
When the function returns, there will be an :term:`attached thread state`
and the thread will be able to call arbitrary Python code. Failure is a fatal error.
- .. note::
- Calling this function from a thread when the runtime is finalizing will
- hang the thread until the program exits, even if the thread was not
- created by Python. Refer to
+ .. warning::
+ Calling this function when the runtime is finalizing is unsafe. Doing
+ so will either hang the thread until the program ends, or fully crash
+ the interpreter in rare cases. Refer to
:ref:`cautions-regarding-runtime-finalization` for more details.
.. versionchanged:: 3.14
@@ -1307,7 +1161,6 @@ with sub-interpreters:
Every call to :c:func:`PyGILState_Ensure` must be matched by a call to
:c:func:`PyGILState_Release` on the same thread.
-
.. c:function:: PyThreadState* PyGILState_GetThisThreadState()
Get the :term:`attached thread state` for this thread. May return ``NULL`` if no
@@ -1315,20 +1168,30 @@ with sub-interpreters:
always has such a thread-state, even if no auto-thread-state call has been
made on the main thread. This is mainly a helper/diagnostic function.
- .. seealso: :c:func:`PyThreadState_Get``
+ .. note::
+ This function does not account for :term:`thread states <thread state>` created
+ by something other than :c:func:`PyGILState_Ensure` (such as :c:func:`PyThreadState_New`).
+ Prefer :c:func:`PyThreadState_Get` or :c:func:`PyThreadState_GetUnchecked`
+ for most cases.
+ .. seealso: :c:func:`PyThreadState_Get``
.. c:function:: int PyGILState_Check()
Return ``1`` if the current thread is holding the :term:`GIL` and ``0`` otherwise.
This function can be called from any thread at any time.
- Only if it has had its Python thread state initialized and currently is
- holding the :term:`GIL` will it return ``1``.
+ Only if it has had its :term:`thread state <attached thread state>` initialized
+ via :c:func:`PyGILState_Ensure` will it return ``1``.
This is mainly a helper/diagnostic function. It can be useful
for example in callback contexts or memory allocation functions when
knowing that the :term:`GIL` is locked can allow the caller to perform sensitive
actions or otherwise behave differently.
+ .. note::
+ If the current Python process has ever created a subinterpreter, this
+ function will *always* return ``1``. Prefer :c:func:`PyThreadState_GetUnchecked`
+ for most cases.
+
.. versionadded:: 3.4
@@ -1387,7 +1250,7 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
.. c:function:: void PyInterpreterState_Clear(PyInterpreterState *interp)
Reset all information in an interpreter state object. There must be
- an :term:`attached thread state` for the the interpreter.
+ an :term:`attached thread state` for the interpreter.
.. audit-event:: cpython.PyInterpreterState_Clear "" c.PyInterpreterState_Clear
@@ -2414,6 +2277,18 @@ The C-API provides a basic mutual exclusion lock.
.. versionadded:: 3.13
+.. c:function:: int PyMutex_IsLocked(PyMutex *m)
+
+ Returns non-zero if the mutex *m* is currently locked, zero otherwise.
+
+ .. note::
+
+ This function is intended for use in assertions and debugging only and
+ should not be used to make concurrency control decisions, as the lock
+ state may change immediately after the check.
+
+ .. versionadded:: next
+
.. _python-critical-section-api:
Python Critical Section API
diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index bc5b236393b..4fd10224262 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -363,7 +363,7 @@ Configuration Options
- Read-only
* - ``"import_time"``
- :c:member:`import_time <PyConfig.import_time>`
- - ``bool``
+ - ``int``
- Read-only
* - ``"inspect"``
- :c:member:`inspect <PyConfig.inspect>`
@@ -1477,13 +1477,19 @@ PyConfig
.. c:member:: int import_time
- If non-zero, profile import time.
+ If ``1``, profile import time.
+ If ``2``, include additional output that indicates
+ when an imported module has already been loaded.
- Set the ``1`` by the :option:`-X importtime <-X>` option and the
+ Set by the :option:`-X importtime <-X>` option and the
:envvar:`PYTHONPROFILEIMPORTTIME` environment variable.
Default: ``0``.
+ .. versionchanged:: 3.14
+
+ Added support for ``import_time = 2``
+
.. c:member:: int inspect
Enter interactive mode after executing a script or a command.
@@ -2105,7 +2111,7 @@ initialization::
/* Specify sys.path explicitly */
/* If you want to modify the default set of paths, finish
- initialization first and then use PySys_GetObject("path") */
+ initialization first and then use PySys_GetAttrString("path") */
config.module_search_paths_set = 1;
status = PyWideStringList_Append(&config.module_search_paths,
L"/path/to/stdlib");
diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst
index c8c60eb9f48..acce3dc215d 100644
--- a/Doc/c-api/intro.rst
+++ b/Doc/c-api/intro.rst
@@ -111,33 +111,11 @@ Useful macros
=============
Several useful macros are defined in the Python header files. Many are
-defined closer to where they are useful (e.g. :c:macro:`Py_RETURN_NONE`).
+defined closer to where they are useful (for example, :c:macro:`Py_RETURN_NONE`,
+:c:macro:`PyMODINIT_FUNC`).
Others of a more general utility are defined here. This is not necessarily a
complete listing.
-.. c:macro:: PyMODINIT_FUNC
-
- Declare an extension module ``PyInit`` initialization function. The function
- return type is :c:expr:`PyObject*`. The macro declares any special linkage
- declarations required by the platform, and for C++ declares the function as
- ``extern "C"``.
-
- The initialization function must be named :samp:`PyInit_{name}`, where
- *name* is the name of the module, and should be the only non-\ ``static``
- item defined in the module file. Example::
-
- static struct PyModuleDef spam_module = {
- PyModuleDef_HEAD_INIT,
- .m_name = "spam",
- ...
- };
-
- PyMODINIT_FUNC
- PyInit_spam(void)
- {
- return PyModule_Create(&spam_module);
- }
-
.. c:macro:: Py_ABS(x)
@@ -779,20 +757,11 @@ found along :envvar:`PATH`.) The user can override this behavior by setting the
environment variable :envvar:`PYTHONHOME`, or insert additional directories in
front of the standard path by setting :envvar:`PYTHONPATH`.
-.. index::
- single: Py_GetPath (C function)
- single: Py_GetPrefix (C function)
- single: Py_GetExecPrefix (C function)
- single: Py_GetProgramFullPath (C function)
-
The embedding application can steer the search by setting
:c:member:`PyConfig.program_name` *before* calling
:c:func:`Py_InitializeFromConfig`. Note that
:envvar:`PYTHONHOME` still overrides this and :envvar:`PYTHONPATH` is still
-inserted in front of the standard path. An application that requires total
-control has to provide its own implementation of :c:func:`Py_GetPath`,
-:c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`, and
-:c:func:`Py_GetProgramFullPath` (all defined in :file:`Modules/getpath.c`).
+inserted in front of the standard path.
.. index:: single: Py_IsInitialized (C function)
@@ -826,14 +795,17 @@ frequently used builds will be described in the remainder of this section.
Compiling the interpreter with the :c:macro:`!Py_DEBUG` macro defined produces
what is generally meant by :ref:`a debug build of Python <debug-build>`.
-:c:macro:`!Py_DEBUG` is enabled in the Unix build by adding
-:option:`--with-pydebug` to the :file:`./configure` command.
-It is also implied by the presence of the
-not-Python-specific :c:macro:`!_DEBUG` macro. When :c:macro:`!Py_DEBUG` is enabled
-in the Unix build, compiler optimization is disabled.
+
+On Unix, :c:macro:`!Py_DEBUG` can be enabled by adding :option:`--with-pydebug`
+to the :file:`./configure` command. This will also disable compiler optimization.
+
+On Windows, selecting a debug build (e.g., by passing the :option:`-d` option to
+:file:`PCbuild/build.bat`) automatically enables :c:macro:`!Py_DEBUG`.
+Additionally, the presence of the not-Python-specific :c:macro:`!_DEBUG` macro,
+when defined by the compiler, will also implicitly enable :c:macro:`!Py_DEBUG`.
In addition to the reference count debugging described below, extra checks are
-performed, see :ref:`Python Debug Build <debug-build>`.
+performed. See :ref:`Python Debug Build <debug-build>` for more details.
Defining :c:macro:`Py_TRACE_REFS` enables reference tracing
(see the :option:`configure --with-trace-refs option <--with-trace-refs>`).
@@ -844,3 +816,41 @@ after every statement run by the interpreter.)
Please refer to :file:`Misc/SpecialBuilds.txt` in the Python source distribution
for more detailed information.
+
+
+.. _c-api-tools:
+
+Recommended third party tools
+=============================
+
+The following third party tools offer both simpler and more sophisticated
+approaches to creating C, C++ and Rust extensions for Python:
+
+* `Cython <https://cython.org/>`_
+* `cffi <https://cffi.readthedocs.io>`_
+* `HPy <https://hpyproject.org/>`_
+* `nanobind <https://github.com/wjakob/nanobind>`_ (C++)
+* `Numba <https://numba.pydata.org/>`_
+* `pybind11 <https://pybind11.readthedocs.io/>`_ (C++)
+* `PyO3 <https://pyo3.rs/>`_ (Rust)
+* `SWIG <https://www.swig.org>`_
+
+Using tools such as these can help avoid writing code that is tightly bound to
+a particular version of CPython, avoid reference counting errors, and focus
+more on your own code than on using the CPython API. In general, new versions
+of Python can be supported by updating the tool, and your code will often use
+newer and more efficient APIs automatically. Some tools also support compiling
+for other implementations of Python from a single set of sources.
+
+These projects are not supported by the same people who maintain Python, and
+issues need to be raised with the projects directly. Remember to check that the
+project is still maintained and supported, as the list above may become
+outdated.
+
+.. seealso::
+
+ `Python Packaging User Guide: Binary Extensions <https://packaging.python.org/guides/packaging-binary-extensions/>`_
+ The Python Packaging User Guide not only covers several available
+ tools that simplify the creation of binary extensions, but also
+ discusses the various reasons why creating an extension module may be
+ desirable in the first place.
diff --git a/Doc/c-api/lifecycle.dot b/Doc/c-api/lifecycle.dot
new file mode 100644
index 00000000000..dca9f87e9e0
--- /dev/null
+++ b/Doc/c-api/lifecycle.dot
@@ -0,0 +1,156 @@
+digraph "Life Events" {
+ graph [
+ fontnames="svg"
+ fontsize=12.0
+ id="life_events_graph"
+ layout="dot"
+ margin="0,0"
+ ranksep=0.25
+ stylesheet="lifecycle.dot.css"
+ ]
+ node [
+ fontname="Courier"
+ fontsize=12.0
+ ]
+ edge [
+ fontname="Times-Italic"
+ fontsize=12.0
+ ]
+
+ "start" [fontname="Times-Italic" shape=plain label=< start > style=invis]
+ {
+ rank="same"
+ "tp_new" [href="typeobj.html#c.PyTypeObject.tp_new" target="_top"]
+ "tp_alloc" [href="typeobj.html#c.PyTypeObject.tp_alloc" target="_top"]
+ }
+ "tp_init" [href="typeobj.html#c.PyTypeObject.tp_init" target="_top"]
+ "reachable" [fontname="Times-Italic" shape=box]
+ "tp_traverse" [
+ href="typeobj.html#c.PyTypeObject.tp_traverse"
+ ordering="in"
+ target="_top"
+ ]
+ "finalized?" [
+ fontname="Times-Italic"
+ label=<marked as<br/>finalized?>
+ ordering="in"
+ shape=diamond
+ tooltip="marked as finalized?"
+ ]
+ "tp_finalize" [
+ href="typeobj.html#c.PyTypeObject.tp_finalize"
+ ordering="in"
+ target="_top"
+ ]
+ "tp_clear" [href="typeobj.html#c.PyTypeObject.tp_clear" target="_top"]
+ "uncollectable" [
+ fontname="Times-Italic"
+ label=<uncollectable<br/>(leaked)>
+ shape=box
+ tooltip="uncollectable (leaked)"
+ ]
+ "tp_dealloc" [
+ href="typeobj.html#c.PyTypeObject.tp_dealloc"
+ ordering="in"
+ target="_top"
+ ]
+ "tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"]
+
+ "start" -> "tp_new" [
+ label=< type call >
+ ]
+ "tp_new" -> "tp_alloc" [
+ label=< direct call > arrowhead=empty
+ labeltooltip="tp_new to tp_alloc: direct call"
+ tooltip="tp_new to tp_alloc: direct call"
+ ]
+ "tp_new" -> "tp_init" [tooltip="tp_new to tp_init"]
+ "tp_init" -> "reachable" [tooltip="tp_init to reachable"]
+ "reachable" -> "tp_traverse" [
+ dir="back"
+ label=< not in a <br/> cyclic <br/> isolate >
+ labeltooltip="tp_traverse to reachable: not in a cyclic isolate"
+ tooltip="tp_traverse to reachable: not in a cyclic isolate"
+ ]
+ "reachable" -> "tp_traverse" [
+ label=< periodic <br/> cyclic isolate <br/> detection >
+ labeltooltip="reachable to tp_traverse: periodic cyclic isolate detection"
+ tooltip="reachable to tp_traverse: periodic cyclic isolate detection"
+ ]
+ "reachable" -> "tp_init" [tooltip="reachable to tp_init"]
+ "reachable" -> "tp_finalize" [
+ dir="back"
+ label=< resurrected <br/> (maybe remove <br/> finalized mark) >
+ labeltooltip="tp_finalize to reachable: resurrected (maybe remove finalized mark)"
+ tooltip="tp_finalize to reachable: resurrected (maybe remove finalized mark)"
+ ]
+ "tp_traverse" -> "finalized?" [
+ label=< cyclic <br/> isolate >
+ labeltooltip="tp_traverse to finalized?: cyclic isolate"
+ tooltip="tp_traverse to finalized?: cyclic isolate"
+ ]
+ "reachable" -> "finalized?" [
+ label=< no refs >
+ labeltooltip="reachable to finalized?: no refs"
+ tooltip="reachable to finalized?: no refs"
+ ]
+ "finalized?" -> "tp_finalize" [
+ label=< no (mark <br/> as finalized) >
+ labeltooltip="finalized? to tp_finalize: no (mark as finalized)"
+ tooltip="finalized? to tp_finalize: no (mark as finalized)"
+ ]
+ "finalized?" -> "tp_clear" [
+ label=< yes >
+ labeltooltip="finalized? to tp_clear: yes"
+ tooltip="finalized? to tp_clear: yes"
+ ]
+ "tp_finalize" -> "tp_clear" [
+ label=< no refs or <br/> cyclic isolate >
+ labeltooltip="tp_finalize to tp_clear: no refs or cyclic isolate"
+ tooltip="tp_finalize to tp_clear: no refs or cyclic isolate"
+ ]
+ "tp_finalize" -> "tp_dealloc" [
+ arrowtail=empty
+ dir="back"
+ href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc"
+ style=dashed
+ label=< recommended<br/> call (see<br/> explanation)>
+ labeltooltip="tp_dealloc to tp_finalize: recommended call (see explanation)"
+ target="_top"
+ tooltip="tp_dealloc to tp_finalize: recommended call (see explanation)"
+ ]
+ "tp_finalize" -> "tp_dealloc" [
+ label=< no refs >
+ labeltooltip="tp_finalize to tp_dealloc: no refs"
+ tooltip="tp_finalize to tp_dealloc: no refs"
+ ]
+ "tp_clear" -> "tp_dealloc" [
+ label=< no refs >
+ labeltooltip="tp_clear to tp_dealloc: no refs"
+ tooltip="tp_clear to tp_dealloc: no refs"
+ ]
+ "tp_clear" -> "uncollectable" [
+ label=< cyclic <br/> isolate >
+ labeltooltip="tp_clear to uncollectable: cyclic isolate"
+ tooltip="tp_clear to uncollectable: cyclic isolate"
+ ]
+ "uncollectable" -> "tp_dealloc" [
+ style=invis
+ tooltip="uncollectable to tp_dealloc"
+ ]
+ "reachable" -> "uncollectable" [
+ label=< cyclic <br/> isolate <br/> (no GC <br/> support) >
+ labeltooltip="reachable to uncollectable: cyclic isolate (no GC support)"
+ tooltip="reachable to uncollectable: cyclic isolate (no GC support)"
+ ]
+ "reachable" -> "tp_dealloc" [
+ label=< no refs>
+ labeltooltip="reachable to tp_dealloc: no refs"
+ ]
+ "tp_dealloc" -> "tp_free" [
+ arrowhead=empty
+ label=< direct call >
+ labeltooltip="tp_dealloc to tp_free: direct call"
+ tooltip="tp_dealloc to tp_free: direct call"
+ ]
+}
diff --git a/Doc/c-api/lifecycle.dot.css b/Doc/c-api/lifecycle.dot.css
new file mode 100644
index 00000000000..3abf95b74da
--- /dev/null
+++ b/Doc/c-api/lifecycle.dot.css
@@ -0,0 +1,21 @@
+#life_events_graph {
+ --svg-fgcolor: currentcolor;
+ --svg-bgcolor: transparent;
+}
+#life_events_graph a {
+ color: inherit;
+}
+#life_events_graph [stroke="black"] {
+ stroke: var(--svg-fgcolor);
+}
+#life_events_graph text,
+#life_events_graph [fill="black"] {
+ fill: var(--svg-fgcolor);
+}
+#life_events_graph [fill="white"] {
+ fill: var(--svg-bgcolor);
+}
+#life_events_graph [fill="none"] {
+ /* On links, setting fill will make the entire shape clickable */
+ fill: var(--svg-bgcolor);
+}
diff --git a/Doc/c-api/lifecycle.dot.pdf b/Doc/c-api/lifecycle.dot.pdf
new file mode 100644
index 00000000000..ed5b5039c83
--- /dev/null
+++ b/Doc/c-api/lifecycle.dot.pdf
Binary files differ
diff --git a/Doc/c-api/lifecycle.dot.svg b/Doc/c-api/lifecycle.dot.svg
new file mode 100644
index 00000000000..7ace27dfcba
--- /dev/null
+++ b/Doc/c-api/lifecycle.dot.svg
@@ -0,0 +1,374 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?xml-stylesheet href="lifecycle.dot.css" type="text/css"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.2.0 (0)
+ -->
+<!-- Title: Life Events Pages: 1 -->
+<svg width="465pt" height="845pt"
+ viewBox="0.00 0.00 465.30 845.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="life_events_graph" class="graph" transform="scale(1 1) rotate(0) translate(4 841)">
+<title>Life Events</title>
+<polygon fill="white" stroke="none" points="-4,4 -4,-841 461.3,-841 461.3,4 -4,4"/>
+<!-- start -->
+<!-- tp_new -->
+<g id="life_events_graph_node2" class="node">
+<title>tp_new</title>
+<g id="a_life_events_graph_node2"><a xlink:href="typeobj.html#c.PyTypeObject.tp_new" xlink:title="tp_new" target="_top">
+<ellipse fill="none" stroke="black" cx="192.8" cy="-772.5" rx="38.8" ry="18"/>
+<text text-anchor="middle" x="192.8" y="-768.23" font-family="monospace,monospace" font-size="12.00">tp_new</text>
+</a>
+</g>
+</g>
+<!-- start&#45;&gt;tp_new -->
+<g id="life_events_graph_edge1" class="edge">
+<title>start&#45;&gt;tp_new</title>
+<g id="a_life_events_graph_edge1"><a xlink:title="start to tp_new: type call">
+<path fill="none" stroke="black" d="M192.8,-822.95C192.8,-817.85 192.8,-810.09 192.8,-802.22"/>
+<polygon fill="black" stroke="black" points="196.3,-802.42 192.8,-792.42 189.3,-802.42 196.3,-802.42"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge1&#45;label"><a xlink:title="start to tp_new: type call">
+<text text-anchor="start" x="192.8" y="-802.35" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;&#160;&#160;type call &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_alloc -->
+<g id="life_events_graph_node3" class="node">
+<title>tp_alloc</title>
+<g id="a_life_events_graph_node3"><a xlink:href="typeobj.html#c.PyTypeObject.tp_alloc" xlink:title="tp_alloc" target="_top">
+<ellipse fill="none" stroke="black" cx="373.8" cy="-772.5" rx="48.34" ry="18"/>
+<text text-anchor="middle" x="373.8" y="-768.23" font-family="monospace,monospace" font-size="12.00">tp_alloc</text>
+</a>
+</g>
+</g>
+<!-- tp_new&#45;&gt;tp_alloc -->
+<g id="life_events_graph_edge2" class="edge">
+<title>tp_new&#45;&gt;tp_alloc</title>
+<g id="a_life_events_graph_edge2"><a xlink:title="tp_new to tp_alloc: direct call">
+<path fill="none" stroke="black" d="M232.07,-772.5C256,-772.5 287.05,-772.5 313.98,-772.5"/>
+<polygon fill="none" stroke="black" points="313.73,-776 323.73,-772.5 313.73,-769 313.73,-776"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge2&#45;label"><a xlink:title="tp_new to tp_alloc: direct call">
+<text text-anchor="start" x="240.65" y="-778.35" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;direct call &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_init -->
+<g id="life_events_graph_node4" class="node">
+<title>tp_init</title>
+<g id="a_life_events_graph_node4"><a xlink:href="typeobj.html#c.PyTypeObject.tp_init" xlink:title="tp_init" target="_top">
+<ellipse fill="none" stroke="black" cx="192.8" cy="-717.5" rx="43.57" ry="18"/>
+<text text-anchor="middle" x="192.8" y="-713.23" font-family="monospace,monospace" font-size="12.00">tp_init</text>
+</a>
+</g>
+</g>
+<!-- tp_new&#45;&gt;tp_init -->
+<g id="life_events_graph_edge3" class="edge">
+<title>tp_new&#45;&gt;tp_init</title>
+<g id="a_life_events_graph_edge3"><a xlink:title="tp_new to tp_init">
+<path fill="none" stroke="black" d="M192.8,-754.15C192.8,-751.83 192.8,-749.42 192.8,-746.98"/>
+<polygon fill="black" stroke="black" points="196.3,-747.23 192.8,-737.23 189.3,-747.23 196.3,-747.23"/>
+</a>
+</g>
+</g>
+<!-- reachable -->
+<g id="life_events_graph_node5" class="node">
+<title>reachable</title>
+<polygon fill="none" stroke="black" points="230.8,-680.5 154.8,-680.5 154.8,-644.5 230.8,-644.5 230.8,-680.5"/>
+<text text-anchor="middle" x="192.8" y="-658.23" font-family="serif,serif" font-style="italic" font-size="12.00">reachable</text>
+</g>
+<!-- tp_init&#45;&gt;reachable -->
+<g id="life_events_graph_edge4" class="edge">
+<title>tp_init&#45;&gt;reachable</title>
+<g id="a_life_events_graph_edge4"><a xlink:title="tp_init to reachable">
+<path fill="none" stroke="black" d="M186.44,-699.44C186.24,-697.12 186.11,-694.69 186.07,-692.24"/>
+<polygon fill="black" stroke="black" points="189.56,-692.51 186.37,-682.41 182.56,-692.29 189.56,-692.51"/>
+</a>
+</g>
+</g>
+<!-- reachable&#45;&gt;tp_init -->
+<g id="life_events_graph_edge7" class="edge">
+<title>reachable&#45;&gt;tp_init</title>
+<g id="a_life_events_graph_edge7"><a xlink:title="reachable to tp_init">
+<path fill="none" stroke="black" d="M199.18,-680.89C199.37,-683.22 199.49,-685.65 199.53,-688.11"/>
+<polygon fill="black" stroke="black" points="196.04,-687.81 199.2,-697.93 203.04,-688.05 196.04,-687.81"/>
+</a>
+</g>
+</g>
+<!-- tp_traverse -->
+<g id="life_events_graph_node6" class="node">
+<title>tp_traverse</title>
+<g id="a_life_events_graph_node6"><a xlink:href="typeobj.html#c.PyTypeObject.tp_traverse" xlink:title="tp_traverse" target="_top">
+<ellipse fill="none" stroke="black" cx="136.8" cy="-565.75" rx="62.65" ry="18"/>
+<text text-anchor="middle" x="136.8" y="-561.48" font-family="monospace,monospace" font-size="12.00">tp_traverse</text>
+</a>
+</g>
+</g>
+<!-- reachable&#45;&gt;tp_traverse -->
+<g id="life_events_graph_edge5" class="edge">
+<title>reachable&#45;&gt;tp_traverse</title>
+<g id="a_life_events_graph_edge5"><a xlink:title="tp_traverse to reachable: not in a cyclic isolate">
+<path fill="none" stroke="black" d="M143.43,-658.77C108.3,-655.68 65.38,-649.16 54.05,-635.5 41.91,-620.88 42.8,-608.07 54.05,-592.75 60.55,-583.89 70.07,-577.97 80.37,-574.03"/>
+<polygon fill="black" stroke="black" points="142.76,-662.23 153.01,-659.54 143.32,-655.25 142.76,-662.23"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge5&#45;label"><a xlink:title="tp_traverse to reachable: not in a cyclic isolate">
+<text text-anchor="start" x="54.05" y="-624.1" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;not in a &#160;</text>
+<text text-anchor="start" x="59.67" y="-609.85" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;cyclic &#160;</text>
+<text text-anchor="start" x="57.05" y="-595.6" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;isolate &#160;</text>
+</a>
+</g>
+</g>
+<!-- reachable&#45;&gt;tp_traverse -->
+<g id="life_events_graph_edge6" class="edge">
+<title>reachable&#45;&gt;tp_traverse</title>
+<g id="a_life_events_graph_edge6"><a xlink:title="reachable to tp_traverse: periodic cyclic isolate detection">
+<path fill="none" stroke="black" d="M154.41,-650.07C147.94,-646.44 142.04,-641.69 138.05,-635.5 130.52,-623.82 129.57,-608.56 130.79,-595.38"/>
+<polygon fill="black" stroke="black" points="134.25,-595.91 132.17,-585.52 127.31,-594.94 134.25,-595.91"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge6&#45;label"><a xlink:title="reachable to tp_traverse: periodic cyclic isolate detection">
+<text text-anchor="start" x="154.17" y="-624.1" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;periodic &#160;</text>
+<text text-anchor="start" x="138.05" y="-609.85" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;cyclic isolate &#160;&#160;</text>
+<text text-anchor="start" x="151.17" y="-595.6" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;detection &#160;</text>
+</a>
+</g>
+</g>
+<!-- finalized? -->
+<g id="life_events_graph_node7" class="node">
+<title>finalized?</title>
+<g id="a_life_events_graph_node7"><a xlink:title="marked as finalized?">
+<polygon fill="none" stroke="black" points="191.8,-487 112.05,-450.5 191.8,-414 271.55,-450.5 191.8,-487"/>
+<text text-anchor="start" x="159.92" y="-453.35" font-family="serif,serif" font-style="italic" font-size="12.00">marked as</text>
+<text text-anchor="start" x="162.92" y="-439.1" font-family="serif,serif" font-style="italic" font-size="12.00">finalized?</text>
+</a>
+</g>
+</g>
+<!-- reachable&#45;&gt;finalized? -->
+<g id="life_events_graph_edge10" class="edge">
+<title>reachable&#45;&gt;finalized?</title>
+<g id="a_life_events_graph_edge10"><a xlink:title="reachable to finalized?: no refs">
+<path fill="none" stroke="black" d="M227.72,-644.32C230.51,-641.73 232.96,-638.8 234.8,-635.5 244.04,-618.9 235.48,-611.74 234.8,-592.75 233.24,-549.67 243.64,-536.1 227.8,-496 226.37,-492.38 224.53,-488.82 222.45,-485.4"/>
+<polygon fill="black" stroke="black" points="225.47,-483.62 216.91,-477.39 219.72,-487.61 225.47,-483.62"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge10&#45;label"><a xlink:title="reachable to finalized?: no refs">
+<text text-anchor="start" x="236.45" y="-561.48" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;no refs &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_finalize -->
+<g id="life_events_graph_node8" class="node">
+<title>tp_finalize</title>
+<g id="a_life_events_graph_node8"><a xlink:href="typeobj.html#c.PyTypeObject.tp_finalize" xlink:title="tp_finalize" target="_top">
+<ellipse fill="none" stroke="black" cx="122.8" cy="-321" rx="62.65" ry="18"/>
+<text text-anchor="middle" x="122.8" y="-316.73" font-family="monospace,monospace" font-size="12.00">tp_finalize</text>
+</a>
+</g>
+</g>
+<!-- reachable&#45;&gt;tp_finalize -->
+<g id="life_events_graph_edge8" class="edge">
+<title>reachable&#45;&gt;tp_finalize</title>
+<g id="a_life_events_graph_edge8"><a xlink:title="tp_finalize to reachable: resurrected (maybe remove finalized mark)">
+<path fill="none" stroke="black" d="M142.86,-659.6C103.8,-656.96 53.97,-650.63 40.8,-635.5 -37.32,-545.75 69.61,-390.31 109.14,-338.99"/>
+<polygon fill="black" stroke="black" points="142.62,-663.09 152.82,-660.2 143.05,-656.11 142.62,-663.09"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge8&#45;label"><a xlink:title="tp_finalize to reachable: resurrected (maybe remove finalized mark)">
+<text text-anchor="start" x="33.68" y="-527.35" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;resurrected &#160;</text>
+<text text-anchor="start" x="22.43" y="-513.1" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;(maybe remove &#160;</text>
+<text text-anchor="start" x="23.18" y="-498.85" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;finalized mark) &#160;</text>
+</a>
+</g>
+</g>
+<!-- uncollectable -->
+<g id="life_events_graph_node10" class="node">
+<title>uncollectable</title>
+<g id="a_life_events_graph_node10"><a xlink:title="uncollectable (leaked)">
+<polygon fill="none" stroke="black" points="371.92,-159.75 275.67,-159.75 275.67,-123.25 371.92,-123.25 371.92,-159.75"/>
+<text text-anchor="start" x="283.67" y="-144.35" font-family="serif,serif" font-style="italic" font-size="12.00">uncollectable</text>
+<text text-anchor="start" x="299.42" y="-130.1" font-family="serif,serif" font-style="italic" font-size="12.00">(leaked)</text>
+</a>
+</g>
+</g>
+<!-- reachable&#45;&gt;uncollectable -->
+<g id="life_events_graph_edge19" class="edge">
+<title>reachable&#45;&gt;uncollectable</title>
+<g id="a_life_events_graph_edge19"><a xlink:title="reachable to uncollectable: cyclic isolate (no GC support)">
+<path fill="none" stroke="black" d="M231.2,-652.03C270.79,-639.69 326.8,-613.9 326.8,-566.75 326.8,-566.75 326.8,-566.75 326.8,-237.5 326.8,-215.3 325.97,-190.2 325.18,-171.37"/>
+<polygon fill="black" stroke="black" points="328.68,-171.35 324.75,-161.52 321.69,-171.66 328.68,-171.35"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge19&#45;label"><a xlink:title="reachable to uncollectable: cyclic isolate (no GC support)">
+<text text-anchor="start" x="335.05" y="-393.6" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;cyclic &#160;</text>
+<text text-anchor="start" x="332.42" y="-379.35" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;isolate &#160;</text>
+<text text-anchor="start" x="331.3" y="-365.1" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;(no GC &#160;</text>
+<text text-anchor="start" x="326.8" y="-350.85" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;support) &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_dealloc -->
+<g id="life_events_graph_node11" class="node">
+<title>tp_dealloc</title>
+<g id="a_life_events_graph_node11"><a xlink:href="typeobj.html#c.PyTypeObject.tp_dealloc" xlink:title="tp_dealloc" target="_top">
+<ellipse fill="none" stroke="black" cx="200.8" cy="-86.25" rx="57.88" ry="18"/>
+<text text-anchor="middle" x="200.8" y="-81.97" font-family="monospace,monospace" font-size="12.00">tp_dealloc</text>
+</a>
+</g>
+</g>
+<!-- reachable&#45;&gt;tp_dealloc -->
+<g id="life_events_graph_edge20" class="edge">
+<title>reachable&#45;&gt;tp_dealloc</title>
+<path fill="none" stroke="black" d="M231.23,-661.18C293.08,-658.43 407.8,-643.03 407.8,-566.75 407.8,-566.75 407.8,-566.75 407.8,-140.5 407.8,-111.22 329.12,-97.8 268.77,-91.82"/>
+<polygon fill="black" stroke="black" points="269.15,-88.34 258.87,-90.89 268.5,-95.31 269.15,-88.34"/>
+<g id="a_life_events_graph_edge20&#45;label"><a xlink:title="reachable to tp_dealloc: no refs">
+<text text-anchor="start" x="407.8" y="-316.73" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;no refs</text>
+</a>
+</g>
+</g>
+<!-- tp_traverse&#45;&gt;finalized? -->
+<g id="life_events_graph_edge9" class="edge">
+<title>tp_traverse&#45;&gt;finalized?</title>
+<g id="a_life_events_graph_edge9"><a xlink:title="tp_traverse to finalized?: cyclic isolate">
+<path fill="none" stroke="black" d="M145.15,-547.55C152.4,-532.62 163.18,-510.43 172.55,-491.13"/>
+<polygon fill="black" stroke="black" points="175.7,-492.66 176.92,-482.14 169.4,-489.61 175.7,-492.66"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge9&#45;label"><a xlink:title="tp_traverse to finalized?: cyclic isolate">
+<text text-anchor="start" x="171.85" y="-520.23" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;cyclic &#160;</text>
+<text text-anchor="start" x="169.22" y="-505.98" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;isolate &#160;</text>
+</a>
+</g>
+</g>
+<!-- finalized?&#45;&gt;tp_finalize -->
+<g id="life_events_graph_edge11" class="edge">
+<title>finalized?&#45;&gt;tp_finalize</title>
+<g id="a_life_events_graph_edge11"><a xlink:title="finalized? to tp_finalize: no (mark as finalized)">
+<path fill="none" stroke="black" d="M172.89,-422.6C169.14,-416.89 165.34,-410.82 162.05,-405 151.89,-387.08 141.99,-366.11 134.68,-349.73"/>
+<polygon fill="black" stroke="black" points="137.89,-348.35 130.66,-340.61 131.48,-351.17 137.89,-348.35"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge11&#45;label"><a xlink:title="finalized? to tp_finalize: no (mark as finalized)">
+<text text-anchor="start" x="170.67" y="-379.35" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;no (mark &#160;</text>
+<text text-anchor="start" x="162.05" y="-365.1" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;as finalized) &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_clear -->
+<g id="life_events_graph_node9" class="node">
+<title>tp_clear</title>
+<g id="a_life_events_graph_node9"><a xlink:href="typeobj.html#c.PyTypeObject.tp_clear" xlink:title="tp_clear" target="_top">
+<ellipse fill="none" stroke="black" cx="222.8" cy="-238.5" rx="48.34" ry="18"/>
+<text text-anchor="middle" x="222.8" y="-234.22" font-family="monospace,monospace" font-size="12.00">tp_clear</text>
+</a>
+</g>
+</g>
+<!-- finalized?&#45;&gt;tp_clear -->
+<g id="life_events_graph_edge12" class="edge">
+<title>finalized?&#45;&gt;tp_clear</title>
+<g id="a_life_events_graph_edge12"><a xlink:title="finalized? to tp_clear: yes">
+<path fill="none" stroke="black" d="M227.56,-430.1C236.46,-423.41 244.86,-415.02 249.8,-405 277.22,-349.39 274.06,-322.55 249.8,-265.5 249.7,-265.27 249.6,-265.04 249.49,-264.81"/>
+<polygon fill="black" stroke="black" points="252.41,-262.88 243.93,-256.52 246.6,-266.78 252.41,-262.88"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge12&#45;label"><a xlink:title="finalized? to tp_clear: yes">
+<text text-anchor="start" x="269.2" y="-316.73" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;yes &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_finalize&#45;&gt;tp_clear -->
+<g id="life_events_graph_edge13" class="edge">
+<title>tp_finalize&#45;&gt;tp_clear</title>
+<g id="a_life_events_graph_edge13"><a xlink:title="tp_finalize to tp_clear: no refs or cyclic isolate">
+<path fill="none" stroke="black" d="M130.02,-302.72C135.75,-290.85 144.8,-275.49 156.8,-265.5 161.95,-261.21 167.9,-257.57 174.07,-254.49"/>
+<polygon fill="black" stroke="black" points="175.46,-257.71 183.18,-250.45 172.62,-251.31 175.46,-257.71"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge13&#45;label"><a xlink:title="tp_finalize to tp_clear: no refs or cyclic isolate">
+<text text-anchor="start" x="164.3" y="-282.6" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;no refs or &#160;&#160;</text>
+<text text-anchor="start" x="156.8" y="-268.35" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;cyclic isolate &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_finalize&#45;&gt;tp_dealloc -->
+<g id="life_events_graph_edge14" class="edge">
+<title>tp_finalize&#45;&gt;tp_dealloc</title>
+<g id="a_life_events_graph_edge14"><a xlink:href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" xlink:title="tp_dealloc to tp_finalize: recommended call (see explanation)" target="_top">
+<path fill="none" stroke="black" stroke-dasharray="5,2" d="M85.85,-298.52C42.09,-270.18 -21.4,-218.11 7.8,-168.75 36.22,-120.7 99.95,-100.97 146.42,-92.87"/>
+<polygon fill="none" stroke="black" points="83.78,-301.35 94.11,-303.72 87.52,-295.43 83.78,-301.35"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge14&#45;label"><a xlink:href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" xlink:title="tp_dealloc to tp_finalize: recommended call (see explanation)" target="_top">
+<text text-anchor="start" x="7.8" y="-200.1" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;recommended</text>
+<text text-anchor="start" x="25.8" y="-185.85" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;call (see</text>
+<text text-anchor="start" x="13.05" y="-171.6" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;explanation)</text>
+</a>
+</g>
+</g>
+<!-- tp_finalize&#45;&gt;tp_dealloc -->
+<g id="life_events_graph_edge15" class="edge">
+<title>tp_finalize&#45;&gt;tp_dealloc</title>
+<g id="a_life_events_graph_edge15"><a xlink:title="tp_finalize to tp_dealloc: no refs">
+<path fill="none" stroke="black" d="M123.03,-302.58C123.95,-273.77 128.08,-214.78 146.05,-168.75 153.95,-148.5 167.56,-128.2 179.24,-112.92"/>
+<polygon fill="black" stroke="black" points="181.81,-115.32 185.25,-105.3 176.31,-110.98 181.81,-115.32"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge15&#45;label"><a xlink:title="tp_finalize to tp_dealloc: no refs">
+<text text-anchor="start" x="146.05" y="-185.85" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;&#160;no refs &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_clear&#45;&gt;uncollectable -->
+<g id="life_events_graph_edge17" class="edge">
+<title>tp_clear&#45;&gt;uncollectable</title>
+<g id="a_life_events_graph_edge17"><a xlink:title="tp_clear to uncollectable: cyclic isolate">
+<path fill="none" stroke="black" d="M227.75,-220.38C232.99,-205 242.67,-182.74 258.05,-168.75 260.43,-166.58 263.02,-164.58 265.74,-162.73"/>
+<polygon fill="black" stroke="black" points="267.27,-165.89 274.12,-157.81 263.73,-159.86 267.27,-165.89"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge17&#45;label"><a xlink:title="tp_clear to uncollectable: cyclic isolate">
+<text text-anchor="start" x="260.67" y="-192.97" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;cyclic &#160;</text>
+<text text-anchor="start" x="258.05" y="-178.72" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;isolate &#160;</text>
+</a>
+</g>
+</g>
+<!-- tp_clear&#45;&gt;tp_dealloc -->
+<g id="life_events_graph_edge16" class="edge">
+<title>tp_clear&#45;&gt;tp_dealloc</title>
+<g id="a_life_events_graph_edge16"><a xlink:title="tp_clear to tp_dealloc: no refs">
+<path fill="none" stroke="black" d="M219.7,-220.24C216.92,-204.51 212.83,-180.61 209.8,-159.75 207.7,-145.34 205.67,-129.26 204.07,-115.92"/>
+<polygon fill="black" stroke="black" points="207.56,-115.59 202.91,-106.07 200.61,-116.41 207.56,-115.59"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge16&#45;label"><a xlink:title="tp_clear to tp_dealloc: no refs">
+<text text-anchor="start" x="209.8" y="-137.22" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;no refs &#160;</text>
+</a>
+</g>
+</g>
+<!-- uncollectable&#45;&gt;tp_dealloc -->
+<!-- tp_free -->
+<g id="life_events_graph_node12" class="node">
+<title>tp_free</title>
+<g id="a_life_events_graph_node12"><a xlink:href="typeobj.html#c.PyTypeObject.tp_free" xlink:title="tp_free" target="_top">
+<ellipse fill="none" stroke="black" cx="200.8" cy="-18" rx="43.57" ry="18"/>
+<text text-anchor="middle" x="200.8" y="-13.72" font-family="monospace,monospace" font-size="12.00">tp_free</text>
+</a>
+</g>
+</g>
+<!-- tp_dealloc&#45;&gt;tp_free -->
+<g id="life_events_graph_edge21" class="edge">
+<title>tp_dealloc&#45;&gt;tp_free</title>
+<g id="a_life_events_graph_edge21"><a xlink:title="tp_dealloc to tp_free: direct call">
+<path fill="none" stroke="black" d="M200.8,-67.84C200.8,-61.63 200.8,-54.46 200.8,-47.56"/>
+<polygon fill="none" stroke="black" points="204.3,-47.57 200.8,-37.57 197.3,-47.57 204.3,-47.57"/>
+</a>
+</g>
+<g id="a_life_events_graph_edge21&#45;label"><a xlink:title="tp_dealloc to tp_free: direct call">
+<text text-anchor="start" x="200.8" y="-47.85" font-family="serif,serif" font-style="italic" font-size="12.00"> &#160;&#160;&#160;direct call &#160;</text>
+</a>
+</g>
+</g>
+</g>
+</svg>
diff --git a/Doc/c-api/lifecycle.rst b/Doc/c-api/lifecycle.rst
new file mode 100644
index 00000000000..5a170862a26
--- /dev/null
+++ b/Doc/c-api/lifecycle.rst
@@ -0,0 +1,271 @@
+.. highlight:: c
+
+.. _life-cycle:
+
+Object Life Cycle
+=================
+
+This section explains how a type's slots relate to each other throughout the
+life of an object. It is not intended to be a complete canonical reference for
+the slots; instead, refer to the slot-specific documentation in
+:ref:`type-structs` for details about a particular slot.
+
+
+Life Events
+-----------
+
+The figure below illustrates the order of events that can occur throughout an
+object's life. An arrow from *A* to *B* indicates that event *B* can occur
+after event *A* has occurred, with the arrow's label indicating the condition
+that must be true for *B* to occur after *A*.
+
+.. only:: html and not epub
+
+ .. raw:: html
+
+ <style type="text/css">
+
+ .. raw:: html
+ :file: lifecycle.dot.css
+
+ .. raw:: html
+
+ </style>
+
+ .. raw:: html
+ :file: lifecycle.dot.svg
+
+ .. raw:: html
+
+ <script>
+ (() => {
+ const g = document.getElementById('life_events_graph');
+ const title = g.querySelector(':scope > title');
+ title.id = 'life-events-graph-title';
+ const svg = g.closest('svg');
+ svg.role = 'img';
+ svg.setAttribute('aria-describedby',
+ 'life-events-graph-description');
+ svg.setAttribute('aria-labelledby', 'life-events-graph-title');
+ })();
+ </script>
+
+.. only:: epub or not (html or latex)
+
+ .. image:: lifecycle.dot.svg
+ :align: center
+ :class: invert-in-dark-mode
+ :alt: Diagram showing events in an object's life. Explained in detail below.
+
+.. only:: latex
+
+ .. image:: lifecycle.dot.pdf
+ :align: center
+ :class: invert-in-dark-mode
+ :alt: Diagram showing events in an object's life. Explained in detail below.
+
+.. container::
+ :name: life-events-graph-description
+
+ Explanation:
+
+ * When a new object is constructed by calling its type:
+
+ #. :c:member:`~PyTypeObject.tp_new` is called to create a new object.
+ #. :c:member:`~PyTypeObject.tp_alloc` is directly called by
+ :c:member:`~PyTypeObject.tp_new` to allocate the memory for the new
+ object.
+ #. :c:member:`~PyTypeObject.tp_init` initializes the newly created object.
+ :c:member:`!tp_init` can be called again to re-initialize an object, if
+ desired. The :c:member:`!tp_init` call can also be skipped entirely,
+ for example by Python code calling :py:meth:`~object.__new__`.
+
+ * After :c:member:`!tp_init` completes, the object is ready to use.
+ * Some time after the last reference to an object is removed:
+
+ #. If an object is not marked as *finalized*, it might be finalized by
+ marking it as *finalized* and calling its
+ :c:member:`~PyTypeObject.tp_finalize` function. Python does
+ *not* finalize an object when the last reference to it is deleted; use
+ :c:func:`PyObject_CallFinalizerFromDealloc` to ensure that
+ :c:member:`~PyTypeObject.tp_finalize` is always called.
+ #. If the object is marked as finalized,
+ :c:member:`~PyTypeObject.tp_clear` might be called by the garbage collector
+ to clear references held by the object. It is *not* called when the
+ object's reference count reaches zero.
+ #. :c:member:`~PyTypeObject.tp_dealloc` is called to destroy the object.
+ To avoid code duplication, :c:member:`~PyTypeObject.tp_dealloc` typically
+ calls into :c:member:`~PyTypeObject.tp_clear` to free up the object's
+ references.
+ #. When :c:member:`~PyTypeObject.tp_dealloc` finishes object destruction,
+ it directly calls :c:member:`~PyTypeObject.tp_free` (usually set to
+ :c:func:`PyObject_Free` or :c:func:`PyObject_GC_Del` automatically as
+ appropriate for the type) to deallocate the memory.
+
+ * The :c:member:`~PyTypeObject.tp_finalize` function is permitted to add a
+ reference to the object if desired. If it does, the object is
+ *resurrected*, preventing its pending destruction. (Only
+ :c:member:`!tp_finalize` is allowed to resurrect an object;
+ :c:member:`~PyTypeObject.tp_clear` and
+ :c:member:`~PyTypeObject.tp_dealloc` cannot without calling into
+ :c:member:`!tp_finalize`.) Resurrecting an object may
+ or may not cause the object's *finalized* mark to be removed. Currently,
+ Python does not remove the *finalized* mark from a resurrected object if
+ it supports garbage collection (i.e., the :c:macro:`Py_TPFLAGS_HAVE_GC`
+ flag is set) but does remove the mark if the object does not support
+ garbage collection; either or both of these behaviors may change in the
+ future.
+ * :c:member:`~PyTypeObject.tp_dealloc` can optionally call
+ :c:member:`~PyTypeObject.tp_finalize` via
+ :c:func:`PyObject_CallFinalizerFromDealloc` if it wishes to reuse that
+ code to help with object destruction. This is recommended because it
+ guarantees that :c:member:`!tp_finalize` is always called before
+ destruction. See the :c:member:`~PyTypeObject.tp_dealloc` documentation
+ for example code.
+ * If the object is a member of a :term:`cyclic isolate` and either
+ :c:member:`~PyTypeObject.tp_clear` fails to break the reference cycle or
+ the cyclic isolate is not detected (perhaps :func:`gc.disable` was called,
+ or the :c:macro:`Py_TPFLAGS_HAVE_GC` flag was erroneously omitted in one
+ of the involved types), the objects remain indefinitely uncollectable
+ (they "leak"). See :data:`gc.garbage`.
+
+ If the object is marked as supporting garbage collection (the
+ :c:macro:`Py_TPFLAGS_HAVE_GC` flag is set in
+ :c:member:`~PyTypeObject.tp_flags`), the following events are also possible:
+
+ * The garbage collector occasionally calls
+ :c:member:`~PyTypeObject.tp_traverse` to identify :term:`cyclic isolates
+ <cyclic isolate>`.
+ * When the garbage collector discovers a :term:`cyclic isolate`, it
+ finalizes one of the objects in the group by marking it as *finalized* and
+ calling its :c:member:`~PyTypeObject.tp_finalize` function, if it has one.
+ This repeats until the cyclic isolate doesn't exist or all of the objects
+ have been finalized.
+ * :c:member:`~PyTypeObject.tp_finalize` is permitted to resurrect the object
+ by adding a reference from outside the :term:`cyclic isolate`. The new
+ reference causes the group of objects to no longer form a cyclic isolate
+ (the reference cycle may still exist, but if it does the objects are no
+ longer isolated).
+ * When the garbage collector discovers a :term:`cyclic isolate` and all of
+ the objects in the group have already been marked as *finalized*, the
+ garbage collector clears one or more of the uncleared objects in the group
+ (possibly concurrently) by calling each's
+ :c:member:`~PyTypeObject.tp_clear` function. This repeats as long as the
+ cyclic isolate still exists and not all of the objects have been cleared.
+
+
+Cyclic Isolate Destruction
+--------------------------
+
+Listed below are the stages of life of a hypothetical :term:`cyclic isolate`
+that continues to exist after each member object is finalized or cleared. It
+is a memory leak if a cyclic isolate progresses through all of these stages; it should
+vanish once all objects are cleared, if not sooner. A cyclic isolate can
+vanish either because the reference cycle is broken or because the objects are
+no longer isolated due to finalizer resurrection (see
+:c:member:`~PyTypeObject.tp_finalize`).
+
+0. **Reachable** (not yet a cyclic isolate): All objects are in their normal,
+ reachable state. A reference cycle could exist, but an external reference
+ means the objects are not yet isolated.
+#. **Unreachable but consistent:** The final reference from outside the cyclic
+ group of objects has been removed, causing the objects to become isolated
+ (thus a cyclic isolate is born). None of the group's objects have been
+ finalized or cleared yet. The cyclic isolate remains at this stage until
+ some future run of the garbage collector (not necessarily the next run
+ because the next run might not scan every object).
+#. **Mix of finalized and not finalized:** Objects in a cyclic isolate are
+ finalized one at a time, which means that there is a period of time when the
+ cyclic isolate is composed of a mix of finalized and non-finalized objects.
+ Finalization order is unspecified, so it can appear random. A finalized
+ object must behave in a sane manner when non-finalized objects interact with
+ it, and a non-finalized object must be able to tolerate the finalization of
+ an arbitrary subset of its referents.
+#. **All finalized:** All objects in a cyclic isolate are finalized before any
+ of them are cleared.
+#. **Mix of finalized and cleared:** The objects can be cleared serially or
+ concurrently (but with the :term:`GIL` held); either way, some will finish
+ before others. A finalized object must be able to tolerate the clearing of
+ a subset of its referents. :pep:`442` calls this stage "cyclic trash".
+#. **Leaked:** If a cyclic isolate still exists after all objects in the group
+ have been finalized and cleared, then the objects remain indefinitely
+ uncollectable (see :data:`gc.garbage`). It is a bug if a cyclic isolate
+ reaches this stage---it means the :c:member:`~PyTypeObject.tp_clear` methods
+ of the participating objects have failed to break the reference cycle as
+ required.
+
+If :c:member:`~PyTypeObject.tp_clear` did not exist, then Python would have no
+way to safely break a reference cycle. Simply destroying an object in a cyclic
+isolate would result in a dangling pointer, triggering undefined behavior when
+an object referencing the destroyed object is itself destroyed. The clearing
+step makes object destruction a two-phase process: first
+:c:member:`~PyTypeObject.tp_clear` is called to partially destroy the objects
+enough to detangle them from each other, then
+:c:member:`~PyTypeObject.tp_dealloc` is called to complete the destruction.
+
+Unlike clearing, finalization is not a phase of destruction. A finalized
+object must still behave properly by continuing to fulfill its design
+contracts. An object's finalizer is allowed to execute arbitrary Python code,
+and is even allowed to prevent the impending destruction by adding a reference.
+The finalizer is only related to destruction by call order---if it runs, it runs
+before destruction, which starts with :c:member:`~PyTypeObject.tp_clear` (if
+called) and concludes with :c:member:`~PyTypeObject.tp_dealloc`.
+
+The finalization step is not necessary to safely reclaim the objects in a
+cyclic isolate, but its existence makes it easier to design types that behave
+in a sane manner when objects are cleared. Clearing an object might
+necessarily leave it in a broken, partially destroyed state---it might be
+unsafe to call any of the cleared object's methods or access any of its
+attributes. With finalization, only finalized objects can possibly interact
+with cleared objects; non-finalized objects are guaranteed to interact with
+only non-cleared (but potentially finalized) objects.
+
+To summarize the possible interactions:
+
+* A non-finalized object might have references to or from non-finalized and
+ finalized objects, but not to or from cleared objects.
+* A finalized object might have references to or from non-finalized, finalized,
+ and cleared objects.
+* A cleared object might have references to or from finalized and cleared
+ objects, but not to or from non-finalized objects.
+
+Without any reference cycles, an object can be simply destroyed once its last
+reference is deleted; the finalization and clearing steps are not necessary to
+safely reclaim unused objects. However, it can be useful to automatically call
+:c:member:`~PyTypeObject.tp_finalize` and :c:member:`~PyTypeObject.tp_clear`
+before destruction anyway because type design is simplified when all objects
+always experience the same series of events regardless of whether they
+participated in a cyclic isolate. Python currently only calls
+:c:member:`~PyTypeObject.tp_finalize` and :c:member:`~PyTypeObject.tp_clear` as
+needed to destroy a cyclic isolate; this may change in a future version.
+
+
+Functions
+---------
+
+To allocate and free memory, see :ref:`allocating-objects`.
+
+
+.. c:function:: void PyObject_CallFinalizer(PyObject *op)
+
+ Finalizes the object as described in :c:member:`~PyTypeObject.tp_finalize`.
+ Call this function (or :c:func:`PyObject_CallFinalizerFromDealloc`) instead
+ of calling :c:member:`~PyTypeObject.tp_finalize` directly because this
+ function may deduplicate multiple calls to :c:member:`!tp_finalize`.
+ Currently, calls are only deduplicated if the type supports garbage
+ collection (i.e., the :c:macro:`Py_TPFLAGS_HAVE_GC` flag is set); this may
+ change in the future.
+
+
+.. c:function:: int PyObject_CallFinalizerFromDealloc(PyObject *op)
+
+ Same as :c:func:`PyObject_CallFinalizer` but meant to be called at the
+ beginning of the object's destructor (:c:member:`~PyTypeObject.tp_dealloc`).
+ There must not be any references to the object. If the object's finalizer
+ resurrects the object, this function returns -1; no further destruction
+ should happen. Otherwise, this function returns 0 and destruction can
+ continue normally.
+
+ .. seealso::
+
+ :c:member:`~PyTypeObject.tp_dealloc` for example code.
diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst
index 25d9e62e387..2d0bda76697 100644
--- a/Doc/c-api/long.rst
+++ b/Doc/c-api/long.rst
@@ -439,7 +439,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
All *n_bytes* of the buffer are written: large buffers are padded with
zeroes.
- If the returned value is greater than than *n_bytes*, the value was
+ If the returned value is greater than *n_bytes*, the value was
truncated: as many of the lowest bits of the value as could fit are written,
and the higher bits are ignored. This matches the typical behavior
of a C-style downcast.
diff --git a/Doc/c-api/memory.rst b/Doc/c-api/memory.rst
index 64ae35daa70..61fa49f8681 100644
--- a/Doc/c-api/memory.rst
+++ b/Doc/c-api/memory.rst
@@ -376,6 +376,24 @@ The :ref:`default object allocator <default-memory-allocators>` uses the
If *p* is ``NULL``, no operation is performed.
+ Do not call this directly to free an object's memory; call the type's
+ :c:member:`~PyTypeObject.tp_free` slot instead.
+
+ Do not use this for memory allocated by :c:macro:`PyObject_GC_New` or
+ :c:macro:`PyObject_GC_NewVar`; use :c:func:`PyObject_GC_Del` instead.
+
+ .. seealso::
+
+ * :c:func:`PyObject_GC_Del` is the equivalent of this function for memory
+ allocated by types that support garbage collection.
+ * :c:func:`PyObject_Malloc`
+ * :c:func:`PyObject_Realloc`
+ * :c:func:`PyObject_Calloc`
+ * :c:macro:`PyObject_New`
+ * :c:macro:`PyObject_NewVar`
+ * :c:func:`PyType_GenericAlloc`
+ * :c:member:`~PyTypeObject.tp_free`
+
.. _default-memory-allocators:
diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst
index f7f4d37d4c7..c8edcecc5b4 100644
--- a/Doc/c-api/module.rst
+++ b/Doc/c-api/module.rst
@@ -127,25 +127,36 @@ Module Objects
unencodable filenames, use :c:func:`PyModule_GetFilenameObject` instead.
-.. _initializing-modules:
+.. _pymoduledef:
-Initializing C modules
-^^^^^^^^^^^^^^^^^^^^^^
+Module definitions
+------------------
-Modules objects are usually created from extension modules (shared libraries
-which export an initialization function), or compiled-in modules
-(where the initialization function is added using :c:func:`PyImport_AppendInittab`).
-See :ref:`building` or :ref:`extending-with-embedding` for details.
+The functions in the previous section work on any module object, including
+modules imported from Python code.
-The initialization function can either pass a module definition instance
-to :c:func:`PyModule_Create`, and return the resulting module object,
-or request "multi-phase initialization" by returning the definition struct itself.
+Modules defined using the C API typically use a *module definition*,
+:c:type:`PyModuleDef` -- a statically allocated, constant “description" of
+how a module should be created.
+
+The definition is usually used to define an extension's “main” module object
+(see :ref:`extension-modules` for details).
+It is also used to
+:ref:`create extension modules dynamically <moduledef-dynamic>`.
+
+Unlike :c:func:`PyModule_New`, the definition allows management of
+*module state* -- a piece of memory that is allocated and cleared together
+with the module object.
+Unlike the module's Python attributes, Python code cannot replace or delete
+data stored in module state.
.. c:type:: PyModuleDef
The module definition struct, which holds all information needed to create
- a module object. There is usually only one statically initialized variable
- of this type for each module.
+ a module object.
+ This structure must be statically allocated (or be otherwise guaranteed
+ to be valid while any modules created from it exist).
+ Usually, there is only one variable of this type for each extension module.
.. c:member:: PyModuleDef_Base m_base
@@ -170,13 +181,15 @@ or request "multi-phase initialization" by returning the definition struct itsel
and freed when the module object is deallocated, after the
:c:member:`~PyModuleDef.m_free` function has been called, if present.
- Setting ``m_size`` to ``-1`` means that the module does not support
- sub-interpreters, because it has global state.
-
Setting it to a non-negative value means that the module can be
re-initialized and specifies the additional amount of memory it requires
- for its state. Non-negative ``m_size`` is required for multi-phase
- initialization.
+ for its state.
+
+ Setting ``m_size`` to ``-1`` means that the module does not support
+ sub-interpreters, because it has global state.
+ Negative ``m_size`` is only allowed when using
+ :ref:`legacy single-phase initialization <single-phase-initialization>`
+ or when :ref:`creating modules dynamically <moduledef-dynamic>`.
See :PEP:`3121` for more details.
@@ -189,7 +202,7 @@ or request "multi-phase initialization" by returning the definition struct itsel
An array of slot definitions for multi-phase initialization, terminated by
a ``{0, NULL}`` entry.
- When using single-phase initialization, *m_slots* must be ``NULL``.
+ When using legacy single-phase initialization, *m_slots* must be ``NULL``.
.. versionchanged:: 3.5
@@ -249,78 +262,9 @@ or request "multi-phase initialization" by returning the definition struct itsel
.. versionchanged:: 3.9
No longer called before the module state is allocated.
-Single-phase initialization
-...........................
-
-The module initialization function may create and return the module object
-directly. This is referred to as "single-phase initialization", and uses one
-of the following two module creation functions:
-
-.. c:function:: PyObject* PyModule_Create(PyModuleDef *def)
-
- Create a new module object, given the definition in *def*. This behaves
- like :c:func:`PyModule_Create2` with *module_api_version* set to
- :c:macro:`PYTHON_API_VERSION`.
-
-.. c:function:: PyObject* PyModule_Create2(PyModuleDef *def, int module_api_version)
-
- Create a new module object, given the definition in *def*, assuming the
- API version *module_api_version*. If that version does not match the version
- of the running interpreter, a :exc:`RuntimeWarning` is emitted.
-
- Return ``NULL`` with an exception set on error.
-
- .. note::
-
- Most uses of this function should be using :c:func:`PyModule_Create`
- instead; only use this if you are sure you need it.
-
-Before it is returned from in the initialization function, the resulting module
-object is typically populated using functions like :c:func:`PyModule_AddObjectRef`.
-
-.. _multi-phase-initialization:
-
-Multi-phase initialization
-..........................
-
-An alternate way to specify extensions is to request "multi-phase initialization".
-Extension modules created this way behave more like Python modules: the
-initialization is split between the *creation phase*, when the module object
-is created, and the *execution phase*, when it is populated.
-The distinction is similar to the :py:meth:`!__new__` and :py:meth:`!__init__` methods
-of classes.
-
-Unlike modules created using single-phase initialization, these modules are not
-singletons: if the *sys.modules* entry is removed and the module is re-imported,
-a new module object is created, and the old module is subject to normal garbage
-collection -- as with Python modules.
-By default, multiple modules created from the same definition should be
-independent: changes to one should not affect the others.
-This means that all state should be specific to the module object (using e.g.
-using :c:func:`PyModule_GetState`), or its contents (such as the module's
-:attr:`~object.__dict__` or individual classes created with :c:func:`PyType_FromSpec`).
-
-All modules created using multi-phase initialization are expected to support
-:ref:`sub-interpreters <sub-interpreter-support>`. Making sure multiple modules
-are independent is typically enough to achieve this.
-
-To request multi-phase initialization, the initialization function
-(PyInit_modulename) returns a :c:type:`PyModuleDef` instance with non-empty
-:c:member:`~PyModuleDef.m_slots`. Before it is returned, the ``PyModuleDef``
-instance must be initialized with the following function:
-
-.. c:function:: PyObject* PyModuleDef_Init(PyModuleDef *def)
-
- Ensures a module definition is a properly initialized Python object that
- correctly reports its type and reference count.
-
- Returns *def* cast to ``PyObject*``, or ``NULL`` if an error occurred.
-
- .. versionadded:: 3.5
-
-The *m_slots* member of the module definition must point to an array of
-``PyModuleDef_Slot`` structures:
+Module slots
+............
.. c:type:: PyModuleDef_Slot
@@ -334,8 +278,6 @@ The *m_slots* member of the module definition must point to an array of
.. versionadded:: 3.5
-The *m_slots* array must be terminated by a slot with id 0.
-
The available slot types are:
.. c:macro:: Py_mod_create
@@ -446,21 +388,48 @@ The available slot types are:
.. versionadded:: 3.13
-See :PEP:`489` for more details on multi-phase initialization.
-Low-level module creation functions
-...................................
+.. _moduledef-dynamic:
-The following functions are called under the hood when using multi-phase
-initialization. They can be used directly, for example when creating module
-objects dynamically. Note that both ``PyModule_FromDefAndSpec`` and
-``PyModule_ExecDef`` must be called to fully initialize a module.
+Creating extension modules dynamically
+--------------------------------------
+
+The following functions may be used to create a module outside of an
+extension's :ref:`initialization function <extension-export-hook>`.
+They are also used in
+:ref:`single-phase initialization <single-phase-initialization>`.
+
+.. c:function:: PyObject* PyModule_Create(PyModuleDef *def)
+
+ Create a new module object, given the definition in *def*.
+ This is a macro that calls :c:func:`PyModule_Create2` with
+ *module_api_version* set to :c:macro:`PYTHON_API_VERSION`, or
+ to :c:macro:`PYTHON_ABI_VERSION` if using the
+ :ref:`limited API <limited-c-api>`.
+
+.. c:function:: PyObject* PyModule_Create2(PyModuleDef *def, int module_api_version)
+
+ Create a new module object, given the definition in *def*, assuming the
+ API version *module_api_version*. If that version does not match the version
+ of the running interpreter, a :exc:`RuntimeWarning` is emitted.
+
+ Return ``NULL`` with an exception set on error.
+
+ This function does not support slots.
+ The :c:member:`~PyModuleDef.m_slots` member of *def* must be ``NULL``.
+
+
+ .. note::
+
+ Most uses of this function should be using :c:func:`PyModule_Create`
+ instead; only use this if you are sure you need it.
.. c:function:: PyObject * PyModule_FromDefAndSpec(PyModuleDef *def, PyObject *spec)
- Create a new module object, given the definition in *def* and the
- ModuleSpec *spec*. This behaves like :c:func:`PyModule_FromDefAndSpec2`
- with *module_api_version* set to :c:macro:`PYTHON_API_VERSION`.
+ This macro calls :c:func:`PyModule_FromDefAndSpec2` with
+ *module_api_version* set to :c:macro:`PYTHON_API_VERSION`, or
+ to :c:macro:`PYTHON_ABI_VERSION` if using the
+ :ref:`limited API <limited-c-api>`.
.. versionadded:: 3.5
@@ -473,6 +442,10 @@ objects dynamically. Note that both ``PyModule_FromDefAndSpec`` and
Return ``NULL`` with an exception set on error.
+ Note that this does not process execution slots (:c:data:`Py_mod_exec`).
+ Both ``PyModule_FromDefAndSpec`` and ``PyModule_ExecDef`` must be called
+ to fully initialize a module.
+
.. note::
Most uses of this function should be using :c:func:`PyModule_FromDefAndSpec`
@@ -486,35 +459,29 @@ objects dynamically. Note that both ``PyModule_FromDefAndSpec`` and
.. versionadded:: 3.5
-.. c:function:: int PyModule_SetDocString(PyObject *module, const char *docstring)
+.. c:macro:: PYTHON_API_VERSION
- Set the docstring for *module* to *docstring*.
- This function is called automatically when creating a module from
- ``PyModuleDef``, using either ``PyModule_Create`` or
- ``PyModule_FromDefAndSpec``.
+ The C API version. Defined for backwards compatibility.
- .. versionadded:: 3.5
+ Currently, this constant is not updated in new Python versions, and is not
+ useful for versioning. This may change in the future.
-.. c:function:: int PyModule_AddFunctions(PyObject *module, PyMethodDef *functions)
+.. c:macro:: PYTHON_ABI_VERSION
- Add the functions from the ``NULL`` terminated *functions* array to *module*.
- Refer to the :c:type:`PyMethodDef` documentation for details on individual
- entries (due to the lack of a shared module namespace, module level
- "functions" implemented in C typically receive the module as their first
- parameter, making them similar to instance methods on Python classes).
- This function is called automatically when creating a module from
- ``PyModuleDef``, using either ``PyModule_Create`` or
- ``PyModule_FromDefAndSpec``.
+ Defined as ``3`` for backwards compatibility.
+
+ Currently, this constant is not updated in new Python versions, and is not
+ useful for versioning. This may change in the future.
- .. versionadded:: 3.5
Support functions
-.................
+-----------------
-The module initialization function (if using single phase initialization) or
-a function called from a module execution slot (if using multi-phase
-initialization), can use the following functions to help initialize the module
-state:
+The following functions are provided to help initialize a module
+state.
+They are intended for a module's execution slots (:c:data:`Py_mod_exec`),
+the initialization function for legacy :ref:`single-phase initialization <single-phase-initialization>`,
+or code that creates modules dynamically.
.. c:function:: int PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value)
@@ -663,12 +630,39 @@ state:
.. versionadded:: 3.9
+.. c:function:: int PyModule_AddFunctions(PyObject *module, PyMethodDef *functions)
+
+ Add the functions from the ``NULL`` terminated *functions* array to *module*.
+ Refer to the :c:type:`PyMethodDef` documentation for details on individual
+ entries (due to the lack of a shared module namespace, module level
+ "functions" implemented in C typically receive the module as their first
+ parameter, making them similar to instance methods on Python classes).
+
+ This function is called automatically when creating a module from
+ ``PyModuleDef`` (such as when using :ref:`multi-phase-initialization`,
+ ``PyModule_Create``, or ``PyModule_FromDefAndSpec``).
+ Some module authors may prefer defining functions in multiple
+ :c:type:`PyMethodDef` arrays; in that case they should call this function
+ directly.
+
+ .. versionadded:: 3.5
+
+.. c:function:: int PyModule_SetDocString(PyObject *module, const char *docstring)
+
+ Set the docstring for *module* to *docstring*.
+ This function is called automatically when creating a module from
+ ``PyModuleDef`` (such as when using :ref:`multi-phase-initialization`,
+ ``PyModule_Create``, or ``PyModule_FromDefAndSpec``).
+
+ .. versionadded:: 3.5
+
.. c:function:: int PyUnstable_Module_SetGIL(PyObject *module, void *gil)
Indicate that *module* does or does not support running without the global
interpreter lock (GIL), using one of the values from
:c:macro:`Py_mod_gil`. It must be called during *module*'s initialization
- function. If this function is not called during module initialization, the
+ function when using :ref:`single-phase-initialization`.
+ If this function is not called during module initialization, the
import machinery assumes the module does not support running without the
GIL. This function is only available in Python builds configured with
:option:`--disable-gil`.
@@ -677,10 +671,11 @@ state:
.. versionadded:: 3.13
-Module lookup
-^^^^^^^^^^^^^
+Module lookup (single-phase initialization)
+...........................................
-Single-phase initialization creates singleton modules that can be looked up
+The legacy :ref:`single-phase initialization <single-phase-initialization>`
+initialization scheme creates singleton modules that can be looked up
in the context of the current interpreter. This allows the module object to be
retrieved later with only a reference to the module definition.
@@ -701,7 +696,8 @@ since multiple such modules can be created from a single definition.
Only effective on modules created using single-phase initialization.
- Python calls ``PyState_AddModule`` automatically after importing a module,
+ Python calls ``PyState_AddModule`` automatically after importing a module
+ that uses :ref:`single-phase initialization <single-phase-initialization>`,
so it is unnecessary (but harmless) to call it from module initialization
code. An explicit call is needed only if the module's own init code
subsequently calls ``PyState_FindModule``.
@@ -709,6 +705,9 @@ since multiple such modules can be created from a single definition.
mechanisms (either by calling it directly, or by referring to its
implementation for details of the required state updates).
+ If a module was attached previously using the same *def*, it is replaced
+ by the new *module*.
+
The caller must have an :term:`attached thread state`.
Return ``-1`` with an exception set on error, ``0`` on success.
diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst
index efad4d215b1..21fa1491b33 100644
--- a/Doc/c-api/object.rst
+++ b/Doc/c-api/object.rst
@@ -197,6 +197,13 @@ Object Protocol
in favour of using :c:func:`PyObject_DelAttr`, but there are currently no
plans to remove it.
+ The function must not be called with ``NULL`` *v* and an an exception set.
+ This case can arise from forgetting ``NULL`` checks and would delete the
+ attribute.
+
+ .. versionchanged:: next
+ Must not be called with NULL value if an exception is set.
+
.. c:function:: int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v)
@@ -207,6 +214,10 @@ Object Protocol
If *v* is ``NULL``, the attribute is deleted, but this feature is
deprecated in favour of using :c:func:`PyObject_DelAttrString`.
+ The function must not be called with ``NULL`` *v* and an an exception set.
+ This case can arise from forgetting ``NULL`` checks and would delete the
+ attribute.
+
The number of different attribute names passed to this function
should be kept small, usually by using a statically allocated string
as *attr_name*.
@@ -215,6 +226,10 @@ Object Protocol
For more details, see :c:func:`PyUnicode_InternFromString`, which may be
used internally to create a key object.
+ .. versionchanged:: next
+ Must not be called with NULL value if an exception is set.
+
+
.. c:function:: int PyObject_GenericSetAttr(PyObject *o, PyObject *name, PyObject *value)
Generic attribute setter and deleter function that is meant
@@ -737,3 +752,21 @@ Object Protocol
caller must hold a :term:`strong reference` to *obj* when calling this.
.. versionadded:: 3.14
+
+.. c:function:: int PyUnstable_Object_IsUniquelyReferenced(PyObject *op)
+
+ Determine if *op* only has one reference.
+
+ On GIL-enabled builds, this function is equivalent to
+ :c:expr:`Py_REFCNT(op) == 1`.
+
+ On a :term:`free threaded <free threading>` build, this checks if *op*'s
+ :term:`reference count` is equal to one and additionally checks if *op*
+ is only used by this thread. :c:expr:`Py_REFCNT(op) == 1` is **not**
+ thread-safe on free threaded builds; prefer this function.
+
+ The caller must hold an :term:`attached thread state`, despite the fact
+ that this function doesn't call into the Python interpreter. This function
+ cannot fail.
+
+ .. versionadded:: 3.14
diff --git a/Doc/c-api/objimpl.rst b/Doc/c-api/objimpl.rst
index 8bd8c107c98..83de4248039 100644
--- a/Doc/c-api/objimpl.rst
+++ b/Doc/c-api/objimpl.rst
@@ -12,6 +12,7 @@ object types.
.. toctree::
allocation.rst
+ lifecycle.rst
structures.rst
typeobj.rst
gcsupport.rst
diff --git a/Doc/c-api/refcounting.rst b/Doc/c-api/refcounting.rst
index 83febcf70a5..57a0728d4e9 100644
--- a/Doc/c-api/refcounting.rst
+++ b/Doc/c-api/refcounting.rst
@@ -23,7 +23,14 @@ of Python objects.
Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count.
- See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
+ .. note::
+
+ On :term:`free threaded <free threading>` builds of Python, returning 1
+ isn't sufficient to determine if it's safe to treat *o* as having no
+ access by other threads. Use :c:func:`PyUnstable_Object_IsUniquelyReferenced`
+ for that instead.
+
+ See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
.. versionchanged:: 3.10
:c:func:`Py_REFCNT()` is changed to the inline static function.
@@ -203,7 +210,7 @@ of Python objects.
Py_SETREF(dst, src);
- That arranges to set *dst* to *src* _before_ releasing the reference
+ That arranges to set *dst* to *src* *before* releasing the reference
to the old value of *dst*, so that any code triggered as a side-effect
of *dst* getting torn down no longer believes *dst* points
to a valid object.
diff --git a/Doc/c-api/stable.rst b/Doc/c-api/stable.rst
index 124e58cf950..9b65e0b8d23 100644
--- a/Doc/c-api/stable.rst
+++ b/Doc/c-api/stable.rst
@@ -51,6 +51,7 @@ It is generally intended for specialized, low-level tools like debuggers.
Projects that use this API are expected to follow
CPython development and spend extra effort adjusting to changes.
+.. _stable-application-binary-interface:
Stable Application Binary Interface
===================================
diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst
index b3c89800e38..b34936dd55e 100644
--- a/Doc/c-api/sys.rst
+++ b/Doc/c-api/sys.rst
@@ -258,10 +258,57 @@ These are utility functions that make functionality from the :mod:`sys` module
accessible to C code. They all work with the current interpreter thread's
:mod:`sys` module's dict, which is contained in the internal thread state structure.
+.. c:function:: PyObject *PySys_GetAttr(PyObject *name)
+
+ Get the attribute *name* of the :mod:`sys` module.
+ Return a :term:`strong reference`.
+ Raise :exc:`RuntimeError` and return ``NULL`` if it does not exist or
+ if the :mod:`sys` module cannot be found.
+
+ If the non-existing object should not be treated as a failure, you can use
+ :c:func:`PySys_GetOptionalAttr` instead.
+
+ .. versionadded:: next
+
+.. c:function:: PyObject *PySys_GetAttrString(const char *name)
+
+ This is the same as :c:func:`PySys_GetAttr`, but *name* is
+ specified as a :c:expr:`const char*` UTF-8 encoded bytes string,
+ rather than a :c:expr:`PyObject*`.
+
+ If the non-existing object should not be treated as a failure, you can use
+ :c:func:`PySys_GetOptionalAttrString` instead.
+
+ .. versionadded:: next
+
+.. c:function:: int PySys_GetOptionalAttr(PyObject *name, PyObject **result)
+
+ Variant of :c:func:`PySys_GetAttr` which doesn't raise
+ exception if the object does not exist.
+
+ * Set *\*result* to a new :term:`strong reference` to the object and
+ return ``1`` if the object exists.
+ * Set *\*result* to ``NULL`` and return ``0`` without setting an exception
+ if the object does not exist.
+ * Set an exception, set *\*result* to ``NULL``, and return ``-1``,
+ if an error occurred.
+
+ .. versionadded:: next
+
+.. c:function:: int PySys_GetOptionalAttrString(const char *name, PyObject **result)
+
+ This is the same as :c:func:`PySys_GetOptionalAttr`, but *name* is
+ specified as a :c:expr:`const char*` UTF-8 encoded bytes string,
+ rather than a :c:expr:`PyObject*`.
+
+ .. versionadded:: next
+
.. c:function:: PyObject *PySys_GetObject(const char *name)
- Return the object *name* from the :mod:`sys` module or ``NULL`` if it does
- not exist, without setting an exception.
+ Similar to :c:func:`PySys_GetAttrString`, but return a :term:`borrowed
+ reference` and return ``NULL`` *without* setting exception on failure.
+
+ Preserves exception that was set before the call.
.. c:function:: int PySys_SetObject(const char *name, PyObject *v)
diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index ec2867b0ce0..5bdbff4e0ad 100644
--- a/Doc/c-api/type.rst
+++ b/Doc/c-api/type.rst
@@ -151,14 +151,29 @@ Type Objects
.. c:function:: PyObject* PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
- Generic handler for the :c:member:`~PyTypeObject.tp_alloc` slot of a type object. Use
- Python's default memory allocation mechanism to allocate a new instance and
- initialize all its contents to ``NULL``.
+ Generic handler for the :c:member:`~PyTypeObject.tp_alloc` slot of a type
+ object. Uses Python's default memory allocation mechanism to allocate memory
+ for a new instance, zeros the memory, then initializes the memory as if by
+ calling :c:func:`PyObject_Init` or :c:func:`PyObject_InitVar`.
+
+ Do not call this directly to allocate memory for an object; call the type's
+ :c:member:`~PyTypeObject.tp_alloc` slot instead.
+
+ For types that support garbage collection (i.e., the
+ :c:macro:`Py_TPFLAGS_HAVE_GC` flag is set), this function behaves like
+ :c:macro:`PyObject_GC_New` or :c:macro:`PyObject_GC_NewVar` (except the
+ memory is guaranteed to be zeroed before initialization), and should be
+ paired with :c:func:`PyObject_GC_Del` in :c:member:`~PyTypeObject.tp_free`.
+ Otherwise, it behaves like :c:macro:`PyObject_New` or
+ :c:macro:`PyObject_NewVar` (except the memory is guaranteed to be zeroed
+ before initialization) and should be paired with :c:func:`PyObject_Free` in
+ :c:member:`~PyTypeObject.tp_free`.
.. c:function:: PyObject* PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
- Generic handler for the :c:member:`~PyTypeObject.tp_new` slot of a type object. Create a
- new instance using the type's :c:member:`~PyTypeObject.tp_alloc` slot.
+ Generic handler for the :c:member:`~PyTypeObject.tp_new` slot of a type
+ object. Creates a new instance using the type's
+ :c:member:`~PyTypeObject.tp_alloc` slot and returns the resulting object.
.. c:function:: int PyType_Ready(PyTypeObject *type)
@@ -267,6 +282,10 @@ Type Objects
and other places where a method's defining class cannot be passed using the
:c:type:`PyCMethod` calling convention.
+ The returned reference is :term:`borrowed <borrowed reference>` from *type*,
+ and will be valid as long as you hold a reference to *type*.
+ Do not release it with :c:func:`Py_DECREF` or similar.
+
.. versionadded:: 3.11
.. c:function:: int PyType_GetBaseByToken(PyTypeObject *type, void *token, PyTypeObject **result)
diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst
index 3b9f07778d5..af2bead3bb5 100644
--- a/Doc/c-api/typeobj.rst
+++ b/Doc/c-api/typeobj.rst
@@ -79,7 +79,7 @@ Quick Reference
| :c:member:`~PyTypeObject.tp_setattro` | :c:type:`setattrofunc` | __setattr__, | X | X | | G |
| | | __delattr__ | | | | |
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
- | :c:member:`~PyTypeObject.tp_as_buffer` | :c:type:`PyBufferProcs` * | | | | | % |
+ | :c:member:`~PyTypeObject.tp_as_buffer` | :c:type:`PyBufferProcs` * | :ref:`sub-slots` | | | | % |
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
| :c:member:`~PyTypeObject.tp_flags` | unsigned long | | X | X | | ? |
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
@@ -325,9 +325,10 @@ sub-slots
+---------------------------------------------------------+-----------------------------------+---------------+
| |
+---------------------------------------------------------+-----------------------------------+---------------+
- | :c:member:`~PyBufferProcs.bf_getbuffer` | :c:func:`getbufferproc` | |
+ | :c:member:`~PyBufferProcs.bf_getbuffer` | :c:func:`getbufferproc` | __buffer__ |
+---------------------------------------------------------+-----------------------------------+---------------+
- | :c:member:`~PyBufferProcs.bf_releasebuffer` | :c:func:`releasebufferproc` | |
+ | :c:member:`~PyBufferProcs.bf_releasebuffer` | :c:func:`releasebufferproc` | __release_\ |
+ | | | buffer\__ |
+---------------------------------------------------------+-----------------------------------+---------------+
.. _slot-typedefs-table:
@@ -676,77 +677,142 @@ and :c:data:`PyType_Type` effectively act as defaults.)
.. c:member:: destructor PyTypeObject.tp_dealloc
- A pointer to the instance destructor function. This function must be defined
- unless the type guarantees that its instances will never be deallocated (as is
- the case for the singletons ``None`` and ``Ellipsis``). The function signature is::
+ A pointer to the instance destructor function. The function signature is::
void tp_dealloc(PyObject *self);
- The destructor function is called by the :c:func:`Py_DECREF` and
- :c:func:`Py_XDECREF` macros when the new reference count is zero. At this point,
- the instance is still in existence, but there are no references to it. The
- destructor function should free all references which the instance owns, free all
- memory buffers owned by the instance (using the freeing function corresponding
- to the allocation function used to allocate the buffer), and call the type's
- :c:member:`~PyTypeObject.tp_free` function. If the type is not subtypable
- (doesn't have the :c:macro:`Py_TPFLAGS_BASETYPE` flag bit set), it is
- permissible to call the object deallocator directly instead of via
- :c:member:`~PyTypeObject.tp_free`. The object deallocator should be the one used to allocate the
- instance; this is normally :c:func:`PyObject_Free` if the instance was allocated
- using :c:macro:`PyObject_New` or :c:macro:`PyObject_NewVar`, or
- :c:func:`PyObject_GC_Del` if the instance was allocated using
- :c:macro:`PyObject_GC_New` or :c:macro:`PyObject_GC_NewVar`.
-
- If the type supports garbage collection (has the :c:macro:`Py_TPFLAGS_HAVE_GC`
- flag bit set), the destructor should call :c:func:`PyObject_GC_UnTrack`
- before clearing any member fields.
+ The destructor function should remove all references which the instance owns
+ (e.g., call :c:func:`Py_CLEAR`), free all memory buffers owned by the
+ instance, and call the type's :c:member:`~PyTypeObject.tp_free` function to
+ free the object itself.
+
+ If you may call functions that may set the error indicator, you must use
+ :c:func:`PyErr_GetRaisedException` and :c:func:`PyErr_SetRaisedException`
+ to ensure you don't clobber a preexisting error indicator (the deallocation
+ could have occurred while processing a different error):
.. code-block:: c
static void
- foo_dealloc(PyObject *op)
+ foo_dealloc(foo_object *self)
{
+ PyObject *et, *ev, *etb;
+ PyObject *exc = PyErr_GetRaisedException();
+ ...
+ PyErr_SetRaisedException(exc);
+ }
+
+ The dealloc handler itself must not raise an exception; if it hits an error
+ case it should call :c:func:`PyErr_FormatUnraisable` to log (and clear) an
+ unraisable exception.
+
+ No guarantees are made about when an object is destroyed, except:
+
+ * Python will destroy an object immediately or some time after the final
+ reference to the object is deleted, unless its finalizer
+ (:c:member:`~PyTypeObject.tp_finalize`) subsequently resurrects the
+ object.
+ * An object will not be destroyed while it is being automatically finalized
+ (:c:member:`~PyTypeObject.tp_finalize`) or automatically cleared
+ (:c:member:`~PyTypeObject.tp_clear`).
+
+ CPython currently destroys an object immediately from :c:func:`Py_DECREF`
+ when the new reference count is zero, but this may change in a future
+ version.
+
+ It is recommended to call :c:func:`PyObject_CallFinalizerFromDealloc` at the
+ beginning of :c:member:`!tp_dealloc` to guarantee that the object is always
+ finalized before destruction.
+
+ If the type supports garbage collection (the :c:macro:`Py_TPFLAGS_HAVE_GC`
+ flag is set), the destructor should call :c:func:`PyObject_GC_UnTrack`
+ before clearing any member fields.
+
+ It is permissible to call :c:member:`~PyTypeObject.tp_clear` from
+ :c:member:`!tp_dealloc` to reduce code duplication and to guarantee that the
+ object is always cleared before destruction. Beware that
+ :c:member:`!tp_clear` might have already been called.
+
+ If the type is heap allocated (:c:macro:`Py_TPFLAGS_HEAPTYPE`), the
+ deallocator should release the owned reference to its type object (via
+ :c:func:`Py_DECREF`) after calling the type deallocator. See the example
+ code below.::
+
+ static void
+ foo_dealloc(PyObject *op)
+ {
foo_object *self = (foo_object *) op;
PyObject_GC_UnTrack(self);
Py_CLEAR(self->ref);
Py_TYPE(self)->tp_free(self);
- }
+ }
- Finally, if the type is heap allocated (:c:macro:`Py_TPFLAGS_HEAPTYPE`), the
- deallocator should release the owned reference to its type object
- (via :c:func:`Py_DECREF`) after
- calling the type deallocator. In order to avoid dangling pointers, the
- recommended way to achieve this is:
+ :c:member:`!tp_dealloc` must leave the exception status unchanged. If it
+ needs to call something that might raise an exception, the exception state
+ must be backed up first and restored later (after logging any exceptions
+ with :c:func:`PyErr_WriteUnraisable`).
- .. code-block:: c
+ Example::
- static void
- foo_dealloc(PyObject *op)
- {
- PyTypeObject *tp = Py_TYPE(op);
- // free references and buffers here
- tp->tp_free(op);
- Py_DECREF(tp);
- }
+ static void
+ foo_dealloc(PyObject *self)
+ {
+ PyObject *exc = PyErr_GetRaisedException();
- .. warning::
+ if (PyObject_CallFinalizerFromDealloc(self) < 0) {
+ // self was resurrected.
+ goto done;
+ }
+
+ PyTypeObject *tp = Py_TYPE(self);
+
+ if (tp->tp_flags & Py_TPFLAGS_HAVE_GC) {
+ PyObject_GC_UnTrack(self);
+ }
+
+ // Optional, but convenient to avoid code duplication.
+ if (tp->tp_clear && tp->tp_clear(self) < 0) {
+ PyErr_WriteUnraisable(self);
+ }
- In a garbage collected Python, :c:member:`!tp_dealloc` may be called from
- any Python thread, not just the thread which created the object (if the
- object becomes part of a refcount cycle, that cycle might be collected by
- a garbage collection on any thread). This is not a problem for Python
- API calls, since the thread on which :c:member:`!tp_dealloc` is called
- with an :term:`attached thread state`. However, if the object being
- destroyed in turn destroys objects from some other C or C++ library, care
- should be taken to ensure that destroying those objects on the thread
- which called :c:member:`!tp_dealloc` will not violate any assumptions of
- the library.
+ // Any additional destruction goes here.
+
+ tp->tp_free(self);
+ self = NULL; // In case PyErr_WriteUnraisable() is called below.
+
+ if (tp->tp_flags & Py_TPFLAGS_HEAPTYPE) {
+ Py_CLEAR(tp);
+ }
+
+ done:
+ // Optional, if something was called that might have raised an
+ // exception.
+ if (PyErr_Occurred()) {
+ PyErr_WriteUnraisable(self);
+ }
+ PyErr_SetRaisedException(exc);
+ }
+
+ :c:member:`!tp_dealloc` may be called from
+ any Python thread, not just the thread which created the object (if the
+ object becomes part of a refcount cycle, that cycle might be collected by
+ a garbage collection on any thread). This is not a problem for Python
+ API calls, since the thread on which :c:member:`!tp_dealloc` is called
+ with an :term:`attached thread state`. However, if the object being
+ destroyed in turn destroys objects from some other C library, care
+ should be taken to ensure that destroying those objects on the thread
+ which called :c:member:`!tp_dealloc` will not violate any assumptions of
+ the library.
**Inheritance:**
This field is inherited by subtypes.
+ .. seealso::
+
+ :ref:`life-cycle` for details about how this slot relates to other slots.
+
.. c:member:: Py_ssize_t PyTypeObject.tp_vectorcall_offset
@@ -1137,11 +1203,11 @@ and :c:data:`PyType_Type` effectively act as defaults.)
.. c:macro:: Py_TPFLAGS_HAVE_GC
This bit is set when the object supports garbage collection. If this bit
- is set, instances must be created using :c:macro:`PyObject_GC_New` and
- destroyed using :c:func:`PyObject_GC_Del`. More information in section
- :ref:`supporting-cycle-detection`. This bit also implies that the
- GC-related fields :c:member:`~PyTypeObject.tp_traverse` and :c:member:`~PyTypeObject.tp_clear` are present in
- the type object.
+ is set, memory for new instances (see :c:member:`~PyTypeObject.tp_alloc`)
+ must be allocated using :c:macro:`PyObject_GC_New` or
+ :c:func:`PyType_GenericAlloc` and deallocated (see
+ :c:member:`~PyTypeObject.tp_free`) using :c:func:`PyObject_GC_Del`. More
+ information in section :ref:`supporting-cycle-detection`.
**Inheritance:**
@@ -1192,7 +1258,7 @@ and :c:data:`PyType_Type` effectively act as defaults.)
.. c:macro:: Py_TPFLAGS_MANAGED_DICT
- This bit indicates that instances of the class have a `~object.__dict__`
+ This bit indicates that instances of the class have a :attr:`~object.__dict__`
attribute, and that the space for the dictionary is managed by the VM.
If this flag is set, :c:macro:`Py_TPFLAGS_HAVE_GC` should also be set.
@@ -1478,6 +1544,11 @@ and :c:data:`PyType_Type` effectively act as defaults.)
heap-allocated superclass).
If they do not, the type object may not be garbage-collected.
+ .. note::
+
+ The :c:member:`~PyTypeObject.tp_traverse` function can be called from any
+ thread.
+
.. versionchanged:: 3.9
Heap-allocated types are expected to visit ``Py_TYPE(self)`` in
@@ -1497,20 +1568,101 @@ and :c:data:`PyType_Type` effectively act as defaults.)
.. c:member:: inquiry PyTypeObject.tp_clear
- An optional pointer to a clear function for the garbage collector. This is only
- used if the :c:macro:`Py_TPFLAGS_HAVE_GC` flag bit is set. The signature is::
+ An optional pointer to a clear function. The signature is::
int tp_clear(PyObject *);
- The :c:member:`~PyTypeObject.tp_clear` member function is used to break reference cycles in cyclic
- garbage detected by the garbage collector. Taken together, all :c:member:`~PyTypeObject.tp_clear`
- functions in the system must combine to break all reference cycles. This is
- subtle, and if in any doubt supply a :c:member:`~PyTypeObject.tp_clear` function. For example,
- the tuple type does not implement a :c:member:`~PyTypeObject.tp_clear` function, because it's
- possible to prove that no reference cycle can be composed entirely of tuples.
- Therefore the :c:member:`~PyTypeObject.tp_clear` functions of other types must be sufficient to
- break any cycle containing a tuple. This isn't immediately obvious, and there's
- rarely a good reason to avoid implementing :c:member:`~PyTypeObject.tp_clear`.
+ The purpose of this function is to break reference cycles that are causing a
+ :term:`cyclic isolate` so that the objects can be safely destroyed. A
+ cleared object is a partially destroyed object; the object is not obligated
+ to satisfy design invariants held during normal use.
+
+ :c:member:`!tp_clear` does not need to delete references to objects that
+ can't participate in reference cycles, such as Python strings or Python
+ integers. However, it may be convenient to clear all references, and write
+ the type's :c:member:`~PyTypeObject.tp_dealloc` function to invoke
+ :c:member:`!tp_clear` to avoid code duplication. (Beware that
+ :c:member:`!tp_clear` might have already been called. Prefer calling
+ idempotent functions like :c:func:`Py_CLEAR`.)
+
+ Any non-trivial cleanup should be performed in
+ :c:member:`~PyTypeObject.tp_finalize` instead of :c:member:`!tp_clear`.
+
+ .. note::
+
+ If :c:member:`!tp_clear` fails to break a reference cycle then the
+ objects in the :term:`cyclic isolate` may remain indefinitely
+ uncollectable ("leak"). See :data:`gc.garbage`.
+
+ .. note::
+
+ Referents (direct and indirect) might have already been cleared; they are
+ not guaranteed to be in a consistent state.
+
+ .. note::
+
+ The :c:member:`~PyTypeObject.tp_clear` function can be called from any
+ thread.
+
+ .. note::
+
+ An object is not guaranteed to be automatically cleared before its
+ destructor (:c:member:`~PyTypeObject.tp_dealloc`) is called.
+
+ This function differs from the destructor
+ (:c:member:`~PyTypeObject.tp_dealloc`) in the following ways:
+
+ * The purpose of clearing an object is to remove references to other objects
+ that might participate in a reference cycle. The purpose of the
+ destructor, on the other hand, is a superset: it must release *all*
+ resources it owns, including references to objects that cannot participate
+ in a reference cycle (e.g., integers) as well as the object's own memory
+ (by calling :c:member:`~PyTypeObject.tp_free`).
+ * When :c:member:`!tp_clear` is called, other objects might still hold
+ references to the object being cleared. Because of this,
+ :c:member:`!tp_clear` must not deallocate the object's own memory
+ (:c:member:`~PyTypeObject.tp_free`). The destructor, on the other hand,
+ is only called when no (strong) references exist, and as such, must
+ safely destroy the object itself by deallocating it.
+ * :c:member:`!tp_clear` might never be automatically called. An object's
+ destructor, on the other hand, will be automatically called some time
+ after the object becomes unreachable (i.e., either there are no references
+ to the object or the object is a member of a :term:`cyclic isolate`).
+
+ No guarantees are made about when, if, or how often Python automatically
+ clears an object, except:
+
+ * Python will not automatically clear an object if it is reachable, i.e.,
+ there is a reference to it and it is not a member of a :term:`cyclic
+ isolate`.
+ * Python will not automatically clear an object if it has not been
+ automatically finalized (see :c:member:`~PyTypeObject.tp_finalize`). (If
+ the finalizer resurrected the object, the object may or may not be
+ automatically finalized again before it is cleared.)
+ * If an object is a member of a :term:`cyclic isolate`, Python will not
+ automatically clear it if any member of the cyclic isolate has not yet
+ been automatically finalized (:c:member:`~PyTypeObject.tp_finalize`).
+ * Python will not destroy an object until after any automatic calls to its
+ :c:member:`!tp_clear` function have returned. This ensures that the act
+ of breaking a reference cycle does not invalidate the ``self`` pointer
+ while :c:member:`!tp_clear` is still executing.
+ * Python will not automatically call :c:member:`!tp_clear` multiple times
+ concurrently.
+
+ CPython currently only automatically clears objects as needed to break
+ reference cycles in a :term:`cyclic isolate`, but future versions might
+ clear objects regularly before their destruction.
+
+ Taken together, all :c:member:`~PyTypeObject.tp_clear` functions in the
+ system must combine to break all reference cycles. This is subtle, and if
+ in any doubt supply a :c:member:`~PyTypeObject.tp_clear` function. For
+ example, the tuple type does not implement a
+ :c:member:`~PyTypeObject.tp_clear` function, because it's possible to prove
+ that no reference cycle can be composed entirely of tuples. Therefore the
+ :c:member:`~PyTypeObject.tp_clear` functions of other types are responsible
+ for breaking any cycle containing a tuple. This isn't immediately obvious,
+ and there's rarely a good reason to avoid implementing
+ :c:member:`~PyTypeObject.tp_clear`.
Implementations of :c:member:`~PyTypeObject.tp_clear` should drop the instance's references to
those of its members that may be Python objects, and set its pointers to those
@@ -1545,18 +1697,6 @@ and :c:data:`PyType_Type` effectively act as defaults.)
PyObject_ClearManagedDict((PyObject*)self);
- Note that :c:member:`~PyTypeObject.tp_clear` is not *always* called
- before an instance is deallocated. For example, when reference counting
- is enough to determine that an object is no longer used, the cyclic garbage
- collector is not involved and :c:member:`~PyTypeObject.tp_dealloc` is
- called directly.
-
- Because the goal of :c:member:`~PyTypeObject.tp_clear` functions is to break reference cycles,
- it's not necessary to clear contained objects like Python strings or Python
- integers, which can't participate in reference cycles. On the other hand, it may
- be convenient to clear all contained Python objects, and write the type's
- :c:member:`~PyTypeObject.tp_dealloc` function to invoke :c:member:`~PyTypeObject.tp_clear`.
-
More information about Python's garbage collection scheme can be found in
section :ref:`supporting-cycle-detection`.
@@ -1569,6 +1709,10 @@ and :c:data:`PyType_Type` effectively act as defaults.)
:c:member:`~PyTypeObject.tp_clear` are all inherited from the base type if they are all zero in
the subtype.
+ .. seealso::
+
+ :ref:`life-cycle` for details about how this slot relates to other slots.
+
.. c:member:: richcmpfunc PyTypeObject.tp_richcompare
@@ -1945,18 +2089,17 @@ and :c:data:`PyType_Type` effectively act as defaults.)
**Inheritance:**
- This field is inherited by static subtypes, but not by dynamic
- subtypes (subtypes created by a class statement).
+ Static subtypes inherit this slot, which will be
+ :c:func:`PyType_GenericAlloc` if inherited from :class:`object`.
+
+ :ref:`Heap subtypes <heap-types>` do not inherit this slot.
**Default:**
- For dynamic subtypes, this field is always set to
- :c:func:`PyType_GenericAlloc`, to force a standard heap
- allocation strategy.
+ For heap subtypes, this field is always set to
+ :c:func:`PyType_GenericAlloc`.
- For static subtypes, :c:data:`PyBaseObject_Type` uses
- :c:func:`PyType_GenericAlloc`. That is the recommended value
- for all statically defined types.
+ For static subtypes, this slot is inherited (see above).
.. c:member:: newfunc PyTypeObject.tp_new
@@ -2004,20 +2147,27 @@ and :c:data:`PyType_Type` effectively act as defaults.)
void tp_free(void *self);
- An initializer that is compatible with this signature is :c:func:`PyObject_Free`.
+ This function must free the memory allocated by
+ :c:member:`~PyTypeObject.tp_alloc`.
**Inheritance:**
- This field is inherited by static subtypes, but not by dynamic
- subtypes (subtypes created by a class statement)
+ Static subtypes inherit this slot, which will be :c:func:`PyObject_Free` if
+ inherited from :class:`object`. Exception: If the type supports garbage
+ collection (i.e., the :c:macro:`Py_TPFLAGS_HAVE_GC` flag is set in
+ :c:member:`~PyTypeObject.tp_flags`) and it would inherit
+ :c:func:`PyObject_Free`, then this slot is not inherited but instead defaults
+ to :c:func:`PyObject_GC_Del`.
+
+ :ref:`Heap subtypes <heap-types>` do not inherit this slot.
**Default:**
- In dynamic subtypes, this field is set to a deallocator suitable to
- match :c:func:`PyType_GenericAlloc` and the value of the
- :c:macro:`Py_TPFLAGS_HAVE_GC` flag bit.
+ For :ref:`heap subtypes <heap-types>`, this slot defaults to a deallocator suitable to match
+ :c:func:`PyType_GenericAlloc` and the value of the
+ :c:macro:`Py_TPFLAGS_HAVE_GC` flag.
- For static subtypes, :c:data:`PyBaseObject_Type` uses :c:func:`PyObject_Free`.
+ For static subtypes, this slot is inherited (see above).
.. c:member:: inquiry PyTypeObject.tp_is_gc
@@ -2144,29 +2294,138 @@ and :c:data:`PyType_Type` effectively act as defaults.)
.. c:member:: destructor PyTypeObject.tp_finalize
- An optional pointer to an instance finalization function. Its signature is::
+ An optional pointer to an instance finalization function. This is the C
+ implementation of the :meth:`~object.__del__` special method. Its signature
+ is::
void tp_finalize(PyObject *self);
- If :c:member:`~PyTypeObject.tp_finalize` is set, the interpreter calls it once when
- finalizing an instance. It is called either from the garbage
- collector (if the instance is part of an isolated reference cycle) or
- just before the object is deallocated. Either way, it is guaranteed
- to be called before attempting to break reference cycles, ensuring
- that it finds the object in a sane state.
+ The primary purpose of finalization is to perform any non-trivial cleanup
+ that must be performed before the object is destroyed, while the object and
+ any other objects it directly or indirectly references are still in a
+ consistent state. The finalizer is allowed to execute
+ arbitrary Python code.
+
+ Before Python automatically finalizes an object, some of the object's direct
+ or indirect referents might have themselves been automatically finalized.
+ However, none of the referents will have been automatically cleared
+ (:c:member:`~PyTypeObject.tp_clear`) yet.
- :c:member:`~PyTypeObject.tp_finalize` should not mutate the current exception status;
- therefore, a recommended way to write a non-trivial finalizer is::
+ Other non-finalized objects might still be using a finalized object, so the
+ finalizer must leave the object in a sane state (e.g., invariants are still
+ met).
+
+ .. note::
+
+ After Python automatically finalizes an object, Python might start
+ automatically clearing (:c:member:`~PyTypeObject.tp_clear`) the object
+ and its referents (direct and indirect). Cleared objects are not
+ guaranteed to be in a consistent state; a finalized object must be able
+ to tolerate cleared referents.
+
+ .. note::
+
+ An object is not guaranteed to be automatically finalized before its
+ destructor (:c:member:`~PyTypeObject.tp_dealloc`) is called. It is
+ recommended to call :c:func:`PyObject_CallFinalizerFromDealloc` at the
+ beginning of :c:member:`!tp_dealloc` to guarantee that the object is
+ always finalized before destruction.
+
+ .. note::
+
+ The :c:member:`~PyTypeObject.tp_finalize` function can be called from any
+ thread, although the :term:`GIL` will be held.
+
+ .. note::
+
+ The :c:member:`!tp_finalize` function can be called during shutdown,
+ after some global variables have been deleted. See the documentation of
+ the :meth:`~object.__del__` method for details.
+
+ When Python finalizes an object, it behaves like the following algorithm:
+
+ #. Python might mark the object as *finalized*. Currently, Python always
+ marks objects whose type supports garbage collection (i.e., the
+ :c:macro:`Py_TPFLAGS_HAVE_GC` flag is set in
+ :c:member:`~PyTypeObject.tp_flags`) and never marks other types of
+ objects; this might change in a future version.
+ #. If the object is not marked as *finalized* and its
+ :c:member:`!tp_finalize` finalizer function is non-``NULL``, the
+ finalizer function is called.
+ #. If the finalizer function was called and the finalizer made the object
+ reachable (i.e., there is a reference to the object and it is not a
+ member of a :term:`cyclic isolate`), then the finalizer is said to have
+ *resurrected* the object. It is unspecified whether the finalizer can
+ also resurrect the object by adding a new reference to the object that
+ does not make it reachable, i.e., the object is (still) a member of a
+ cyclic isolate.
+ #. If the finalizer resurrected the object, the object's pending destruction
+ is canceled and the object's *finalized* mark might be removed if
+ present. Currently, Python never removes the *finalized* mark; this
+ might change in a future version.
+
+ *Automatic finalization* refers to any finalization performed by Python
+ except via calls to :c:func:`PyObject_CallFinalizer` or
+ :c:func:`PyObject_CallFinalizerFromDealloc`. No guarantees are made about
+ when, if, or how often an object is automatically finalized, except:
+
+ * Python will not automatically finalize an object if it is reachable, i.e.,
+ there is a reference to it and it is not a member of a :term:`cyclic
+ isolate`.
+ * Python will not automatically finalize an object if finalizing it would
+ not mark the object as *finalized*. Currently, this applies to objects
+ whose type does not support garbage collection, i.e., the
+ :c:macro:`Py_TPFLAGS_HAVE_GC` flag is not set. Such objects can still be
+ manually finalized by calling :c:func:`PyObject_CallFinalizer` or
+ :c:func:`PyObject_CallFinalizerFromDealloc`.
+ * Python will not automatically finalize any two members of a :term:`cyclic
+ isolate` concurrently.
+ * Python will not automatically finalize an object after it has
+ automatically cleared (:c:member:`~PyTypeObject.tp_clear`) the object.
+ * If an object is a member of a :term:`cyclic isolate`, Python will not
+ automatically finalize it after automatically clearing (see
+ :c:member:`~PyTypeObject.tp_clear`) any other member.
+ * Python will automatically finalize every member of a :term:`cyclic
+ isolate` before it automatically clears (see
+ :c:member:`~PyTypeObject.tp_clear`) any of them.
+ * If Python is going to automatically clear an object
+ (:c:member:`~PyTypeObject.tp_clear`), it will automatically finalize the
+ object first.
+
+ Python currently only automatically finalizes objects that are members of a
+ :term:`cyclic isolate`, but future versions might finalize objects regularly
+ before their destruction.
+
+ To manually finalize an object, do not call this function directly; call
+ :c:func:`PyObject_CallFinalizer` or
+ :c:func:`PyObject_CallFinalizerFromDealloc` instead.
+
+ :c:member:`~PyTypeObject.tp_finalize` should leave the current exception
+ status unchanged. The recommended way to write a non-trivial finalizer is
+ to back up the exception at the beginning by calling
+ :c:func:`PyErr_GetRaisedException` and restore the exception at the end by
+ calling :c:func:`PyErr_SetRaisedException`. If an exception is encountered
+ in the middle of the finalizer, log and clear it with
+ :c:func:`PyErr_WriteUnraisable` or :c:func:`PyErr_FormatUnraisable`. For
+ example::
static void
- local_finalize(PyObject *self)
+ foo_finalize(PyObject *self)
{
- /* Save the current exception, if any. */
+ // Save the current exception, if any.
PyObject *exc = PyErr_GetRaisedException();
- /* ... */
+ // ...
- /* Restore the saved exception. */
+ if (do_something_that_might_raise() != success_indicator) {
+ PyErr_WriteUnraisable(self);
+ goto done;
+ }
+
+ done:
+ // Restore the saved exception. This silently discards any exception
+ // raised above, so be sure to call PyErr_WriteUnraisable first if
+ // necessary.
PyErr_SetRaisedException(exc);
}
@@ -2182,7 +2441,13 @@ and :c:data:`PyType_Type` effectively act as defaults.)
:c:macro:`Py_TPFLAGS_HAVE_FINALIZE` flags bit in order for this field to be
used. This is no longer required.
- .. seealso:: "Safe object finalization" (:pep:`442`)
+ .. seealso::
+
+ * :pep:`442`: "Safe object finalization"
+ * :ref:`life-cycle` for details about how this slot relates to other
+ slots.
+ * :c:func:`PyObject_CallFinalizer`
+ * :c:func:`PyObject_CallFinalizerFromDealloc`
.. c:member:: vectorcallfunc PyTypeObject.tp_vectorcall
diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst
index 95987e872ce..84fee05cb4c 100644
--- a/Doc/c-api/unicode.rst
+++ b/Doc/c-api/unicode.rst
@@ -191,6 +191,22 @@ access to internal read-only data of Unicode objects:
.. versionadded:: 3.2
+.. c:function:: Py_hash_t PyUnstable_Unicode_GET_CACHED_HASH(PyObject *str)
+
+ If the hash of *str*, as returned by :c:func:`PyObject_Hash`, has been
+ cached and is immediately available, return it.
+ Otherwise, return ``-1`` *without* setting an exception.
+
+ If *str* is not a string (that is, if ``PyUnicode_Check(obj)``
+ is false), the behavior is undefined.
+
+ This function never fails with an exception.
+
+ Note that there are no guarantees on when an object's hash is cached,
+ and the (non-)existence of a cached hash does not imply that the string has
+ any other properties.
+
+
Unicode Character Properties
""""""""""""""""""""""""""""
@@ -645,6 +661,17 @@ APIs:
difference being that it decrements the reference count of *right* by one.
+.. c:function:: PyObject* PyUnicode_BuildEncodingMap(PyObject* string)
+
+ Return a mapping suitable for decoding a custom single-byte encoding.
+ Given a Unicode string *string* of up to 256 characters representing an encoding
+ table, returns either a compact internal mapping object or a dictionary
+ mapping character ordinals to byte values. Raises a :exc:`TypeError` and
+ return ``NULL`` on invalid input.
+
+ .. versionadded:: 3.2
+
+
.. c:function:: const char* PyUnicode_GetDefaultEncoding(void)
Return the name of the default string encoding, ``"utf-8"``.
@@ -1450,10 +1477,6 @@ the user settings on the machine running the codec.
.. versionadded:: 3.3
-Methods & Slots
-"""""""""""""""
-
-
.. _unicodemethodsandslots:
Methods and Slot Functions
@@ -1715,10 +1738,6 @@ They all return ``NULL`` or ``-1`` if an exception occurs.
from user input, prefer calling :c:func:`PyUnicode_FromString` and
:c:func:`PyUnicode_InternInPlace` directly.
- .. impl-detail::
-
- Strings interned this way are made :term:`immortal`.
-
.. c:function:: unsigned int PyUnicode_CHECK_INTERNED(PyObject *str)
@@ -1795,9 +1814,24 @@ object.
See also :c:func:`PyUnicodeWriter_DecodeUTF8Stateful`.
+.. c:function:: int PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, const char *str, Py_ssize_t size)
+
+ Write the ASCII string *str* into *writer*.
+
+ *size* is the string length in bytes. If *size* is equal to ``-1``, call
+ ``strlen(str)`` to get the string length.
+
+ *str* must only contain ASCII characters. The behavior is undefined if
+ *str* contains non-ASCII characters.
+
+ On success, return ``0``.
+ On error, set an exception, leave the writer unchanged, and return ``-1``.
+
+ .. versionadded:: 3.14
+
.. c:function:: int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size)
- Writer the wide string *str* into *writer*.
+ Write the wide string *str* into *writer*.
*size* is a number of wide characters. If *size* is equal to ``-1``, call
``wcslen(str)`` to get the string length.
diff --git a/Doc/conf.py b/Doc/conf.py
index 467961dd5e2..161c2986441 100644
--- a/Doc/conf.py
+++ b/Doc/conf.py
@@ -79,6 +79,10 @@ version, release = import_module('patchlevel').get_version_info()
rst_epilog = f"""
.. |python_version_literal| replace:: ``Python {version}``
.. |python_x_dot_y_literal| replace:: ``python{version}``
+.. |python_x_dot_y_t_literal| replace:: ``python{version}t``
+.. |python_x_dot_y_t_literal_config| replace:: ``python{version}t-config``
+.. |x_dot_y_b2_literal| replace:: ``{version}.0b2``
+.. |applications_python_version_literal| replace:: ``/Applications/Python {version}/``
.. |usr_local_bin_python_x_dot_y_literal| replace:: ``/usr/local/bin/python{version}``
.. Apparently this how you hack together a formatted link:
@@ -234,6 +238,7 @@ nitpick_ignore += [
('c:data', 'PyExc_AssertionError'),
('c:data', 'PyExc_AttributeError'),
('c:data', 'PyExc_BaseException'),
+ ('c:data', 'PyExc_BaseExceptionGroup'),
('c:data', 'PyExc_BlockingIOError'),
('c:data', 'PyExc_BrokenPipeError'),
('c:data', 'PyExc_BufferError'),
@@ -287,6 +292,7 @@ nitpick_ignore += [
# C API: Standard Python warning classes
('c:data', 'PyExc_BytesWarning'),
('c:data', 'PyExc_DeprecationWarning'),
+ ('c:data', 'PyExc_EncodingWarning'),
('c:data', 'PyExc_FutureWarning'),
('c:data', 'PyExc_ImportWarning'),
('c:data', 'PyExc_PendingDeprecationWarning'),
@@ -308,7 +314,6 @@ nitpick_ignore += [
('py:attr', '__annotations__'),
('py:meth', '__missing__'),
('py:attr', '__wrapped__'),
- ('py:attr', 'decimal.Context.clamp'),
('py:meth', 'index'), # list.index, tuple.index, etc.
]
diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat
index ca99b9e6d37..144c5608e07 100644
--- a/Doc/data/refcounts.dat
+++ b/Doc/data/refcounts.dat
@@ -963,21 +963,45 @@ PyFunction_Check:PyObject*:o:0:
PyFunction_GetAnnotations:PyObject*::0:
PyFunction_GetAnnotations:PyObject*:op:0:
+PyFunction_GET_ANNOTATIONS:PyObject*::0:
+PyFunction_GET_ANNOTATIONS:PyObject*:op:0:
+
PyFunction_GetClosure:PyObject*::0:
PyFunction_GetClosure:PyObject*:op:0:
+PyFunction_GET_CLOSURE:PyObject*::0:
+PyFunction_GET_CLOSURE:PyObject*:op:0:
+
PyFunction_GetCode:PyObject*::0:
PyFunction_GetCode:PyObject*:op:0:
+PyFunction_GET_CODE:PyObject*::0:
+PyFunction_GET_CODE:PyObject*:op:0:
+
PyFunction_GetDefaults:PyObject*::0:
PyFunction_GetDefaults:PyObject*:op:0:
+PyFunction_GET_DEFAULTS:PyObject*::0:
+PyFunction_GET_DEFAULTS:PyObject*:op:0:
+
+PyFunction_GetKwDefaults:PyObject*::0:
+PyFunction_GetKwDefaults:PyObject*:op:0:
+
+PyFunction_GET_KW_DEFAULTS:PyObject*::0:
+PyFunction_GET_KW_DEFAULTS:PyObject*:op:0:
+
PyFunction_GetGlobals:PyObject*::0:
PyFunction_GetGlobals:PyObject*:op:0:
+PyFunction_GET_GLOBALS:PyObject*::0:
+PyFunction_GET_GLOBALS:PyObject*:op:0:
+
PyFunction_GetModule:PyObject*::0:
PyFunction_GetModule:PyObject*:op:0:
+PyFunction_GET_MODULE:PyObject*::0:
+PyFunction_GET_MODULE:PyObject*:op:0:
+
PyFunction_New:PyObject*::+1:
PyFunction_New:PyObject*:code:+1:
PyFunction_New:PyObject*:globals:+1:
@@ -1093,9 +1117,6 @@ PyImport_ImportModuleLevelObject:PyObject*:locals:0:???
PyImport_ImportModuleLevelObject:PyObject*:fromlist:0:???
PyImport_ImportModuleLevelObject:int:level::
-PyImport_ImportModuleNoBlock:PyObject*::+1:
-PyImport_ImportModuleNoBlock:const char*:name::
-
PyImport_ReloadModule:PyObject*::+1:
PyImport_ReloadModule:PyObject*:m:0:
@@ -1492,9 +1513,6 @@ PyModule_SetDocString:int:::
PyModule_SetDocString:PyObject*:module:0:
PyModule_SetDocString:const char*:docstring::
-PyModuleDef_Init:PyObject*::0:
-PyModuleDef_Init:PyModuleDef*:def::
-
PyNumber_Absolute:PyObject*::+1:
PyNumber_Absolute:PyObject*:o:0:
@@ -2391,6 +2409,10 @@ PyType_GetFlags:PyTypeObject*:type:0:
PyType_GetName:PyObject*::+1:
PyType_GetName:PyTypeObject*:type:0:
+PyType_GetModuleByDef:PyObject*::0:
+PyType_GetModuleByDef:PyTypeObject*:type:0:
+PyType_GetModuleByDef:PyModuleDef*:def::
+
PyType_GetQualName:PyObject*::+1:
PyType_GetQualName:PyTypeObject*:type:0:
@@ -2781,6 +2803,9 @@ PyUnicode_AppendAndDel:void:::
PyUnicode_AppendAndDel:PyObject**:p_left:0:
PyUnicode_AppendAndDel:PyObject*:right:-1:
+PyUnicode_BuildEncodingMap:PyObject*::+1:
+PyUnicode_BuildEncodingMap:PyObject*:string:::
+
PyUnicode_GetDefaultEncoding:const char*:::
PyUnicode_GetDefaultEncoding::void::
@@ -3007,18 +3032,8 @@ Py_GetCompiler:const char*:::
Py_GetCopyright:const char*:::
-Py_GetExecPrefix:wchar_t*:::
-
-Py_GetPath:wchar_t*:::
-
Py_GetPlatform:const char*:::
-Py_GetPrefix:wchar_t*:::
-
-Py_GetProgramFullPath:wchar_t*:::
-
-Py_GetProgramName:wchar_t*:::
-
Py_GetVersion:const char*:::
Py_INCREF:void:::
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 3d68487d07b..0d0dfb38432 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -323,7 +323,6 @@ func,PyImport_ImportFrozenModuleObject,3.7,,
func,PyImport_ImportModule,3.2,,
func,PyImport_ImportModuleLevel,3.2,,
func,PyImport_ImportModuleLevelObject,3.7,,
-func,PyImport_ImportModuleNoBlock,3.2,,
func,PyImport_ReloadModule,3.2,,
func,PyIndex_Check,3.8,,
type,PyInterpreterState,3.2,,opaque
@@ -629,7 +628,11 @@ func,PySys_Audit,3.13,,
func,PySys_AuditTuple,3.13,,
func,PySys_FormatStderr,3.2,,
func,PySys_FormatStdout,3.2,,
+func,PySys_GetAttr,3.15,,
+func,PySys_GetAttrString,3.15,,
func,PySys_GetObject,3.2,,
+func,PySys_GetOptionalAttr,3.15,,
+func,PySys_GetOptionalAttrString,3.15,,
func,PySys_GetXOptions,3.7,,
func,PySys_ResetWarnOptions,3.2,,
func,PySys_SetArgv,3.2,,
@@ -740,11 +743,7 @@ func,PyUnicode_Append,3.2,,
func,PyUnicode_AppendAndDel,3.2,,
func,PyUnicode_AsASCIIString,3.2,,
func,PyUnicode_AsCharmapString,3.2,,
-func,PyUnicode_AsDecodedObject,3.2,,
-func,PyUnicode_AsDecodedUnicode,3.2,,
-func,PyUnicode_AsEncodedObject,3.2,,
func,PyUnicode_AsEncodedString,3.2,,
-func,PyUnicode_AsEncodedUnicode,3.2,,
func,PyUnicode_AsLatin1String,3.2,,
func,PyUnicode_AsMBCSString,3.7,on Windows,
func,PyUnicode_AsRawUnicodeEscapeString,3.2,,
@@ -862,13 +861,7 @@ func,Py_GetCompiler,3.2,,
func,Py_GetConstant,3.13,,
func,Py_GetConstantBorrowed,3.13,,
func,Py_GetCopyright,3.2,,
-func,Py_GetExecPrefix,3.2,,
-func,Py_GetPath,3.2,,
func,Py_GetPlatform,3.2,,
-func,Py_GetPrefix,3.2,,
-func,Py_GetProgramFullPath,3.2,,
-func,Py_GetProgramName,3.2,,
-func,Py_GetPythonHome,3.2,,
func,Py_GetRecursionLimit,3.2,,
func,Py_GetVersion,3.2,,
data,Py_HasFileSystemDefaultEncoding,3.2,,
diff --git a/Doc/deprecations/c-api-pending-removal-in-3.15.rst b/Doc/deprecations/c-api-pending-removal-in-3.15.rst
index a5cc8f1d5b3..a3e335ecaf4 100644
--- a/Doc/deprecations/c-api-pending-removal-in-3.15.rst
+++ b/Doc/deprecations/c-api-pending-removal-in-3.15.rst
@@ -1,8 +1,7 @@
Pending removal in Python 3.15
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-* The bundled copy of ``libmpdecimal``.
-* The :c:func:`PyImport_ImportModuleNoBlock`:
+* The :c:func:`!PyImport_ImportModuleNoBlock`:
Use :c:func:`PyImport_ImportModule` instead.
* :c:func:`PyWeakref_GetObject` and :c:func:`PyWeakref_GET_OBJECT`:
Use :c:func:`PyWeakref_GetRef` instead. The `pythoncapi-compat project
@@ -22,27 +21,27 @@ Pending removal in Python 3.15
may return a type other than :class:`bytes`, such as :class:`str`.
* Python initialization functions, deprecated in Python 3.13:
- * :c:func:`Py_GetPath`:
+ * :c:func:`!Py_GetPath`:
Use :c:func:`PyConfig_Get("module_search_paths") <PyConfig_Get>`
(:data:`sys.path`) instead.
- * :c:func:`Py_GetPrefix`:
+ * :c:func:`!Py_GetPrefix`:
Use :c:func:`PyConfig_Get("base_prefix") <PyConfig_Get>`
(:data:`sys.base_prefix`) instead. Use :c:func:`PyConfig_Get("prefix")
<PyConfig_Get>` (:data:`sys.prefix`) if :ref:`virtual environments
<venv-def>` need to be handled.
- * :c:func:`Py_GetExecPrefix`:
+ * :c:func:`!Py_GetExecPrefix`:
Use :c:func:`PyConfig_Get("base_exec_prefix") <PyConfig_Get>`
(:data:`sys.base_exec_prefix`) instead. Use
:c:func:`PyConfig_Get("exec_prefix") <PyConfig_Get>`
(:data:`sys.exec_prefix`) if :ref:`virtual environments <venv-def>` need to
be handled.
- * :c:func:`Py_GetProgramFullPath`:
+ * :c:func:`!Py_GetProgramFullPath`:
Use :c:func:`PyConfig_Get("executable") <PyConfig_Get>`
(:data:`sys.executable`) instead.
- * :c:func:`Py_GetProgramName`:
+ * :c:func:`!Py_GetProgramName`:
Use :c:func:`PyConfig_Get("executable") <PyConfig_Get>`
(:data:`sys.executable`) instead.
- * :c:func:`Py_GetPythonHome`:
+ * :c:func:`!Py_GetPythonHome`:
Use :c:func:`PyConfig_Get("home") <PyConfig_Get>` or the
:envvar:`PYTHONHOME` environment variable instead.
diff --git a/Doc/deprecations/c-api-pending-removal-in-3.16.rst b/Doc/deprecations/c-api-pending-removal-in-3.16.rst
new file mode 100644
index 00000000000..9453f83799c
--- /dev/null
+++ b/Doc/deprecations/c-api-pending-removal-in-3.16.rst
@@ -0,0 +1,4 @@
+Pending removal in Python 3.16
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* The bundled copy of ``libmpdec``.
diff --git a/Doc/deprecations/index.rst b/Doc/deprecations/index.rst
index bb78f7b3607..d064f2bec42 100644
--- a/Doc/deprecations/index.rst
+++ b/Doc/deprecations/index.rst
@@ -7,6 +7,8 @@ Deprecations
.. include:: pending-removal-in-3.17.rst
+.. include:: pending-removal-in-3.19.rst
+
.. include:: pending-removal-in-future.rst
C API deprecations
diff --git a/Doc/deprecations/pending-removal-in-3.14.rst b/Doc/deprecations/pending-removal-in-3.14.rst
index 6159fa48848..9aac10840a6 100644
--- a/Doc/deprecations/pending-removal-in-3.14.rst
+++ b/Doc/deprecations/pending-removal-in-3.14.rst
@@ -78,7 +78,7 @@ Pending removal in Python 3.14
:meth:`~pathlib.PurePath.relative_to`: passing additional arguments is
deprecated.
-* :mod:`pkgutil`: :func:`!pkgutil.find_loader` and :func:!pkgutil.get_loader`
+* :mod:`pkgutil`: :func:`!pkgutil.find_loader` and :func:`!pkgutil.get_loader`
now raise :exc:`DeprecationWarning`;
use :func:`importlib.util.find_spec` instead.
(Contributed by Nikita Sobolev in :gh:`97850`.)
diff --git a/Doc/deprecations/pending-removal-in-3.15.rst b/Doc/deprecations/pending-removal-in-3.15.rst
index 7b32275ad86..c5ca599bb04 100644
--- a/Doc/deprecations/pending-removal-in-3.15.rst
+++ b/Doc/deprecations/pending-removal-in-3.15.rst
@@ -20,7 +20,7 @@ Pending removal in Python 3.15
* :mod:`http.server`:
- * The obsolete and rarely used :class:`~http.server.CGIHTTPRequestHandler`
+ * The obsolete and rarely used :class:`!CGIHTTPRequestHandler`
has been deprecated since Python 3.13.
No direct replacement exists.
*Anything* is better than CGI to interface
@@ -51,7 +51,7 @@ Pending removal in Python 3.15
* :mod:`platform`:
- * :func:`~platform.java_ver` has been deprecated since Python 3.13.
+ * :func:`!platform.java_ver` has been deprecated since Python 3.13.
This function is only useful for Jython support, has a confusing API,
and is largely untested.
@@ -85,15 +85,23 @@ Pending removal in Python 3.15
has been deprecated since Python 3.13.
Use the class-based syntax or the functional syntax instead.
+ * When using the functional syntax of :class:`~typing.TypedDict`\s, failing
+ to pass a value to the *fields* parameter (``TD = TypedDict("TD")``) or
+ passing ``None`` (``TD = TypedDict("TD", None)``) has been deprecated
+ since Python 3.13.
+ Use ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``
+ to create a TypedDict with zero field.
+
* The :func:`typing.no_type_check_decorator` decorator function
has been deprecated since Python 3.13.
After eight years in the :mod:`typing` module,
it has yet to be supported by any major type checker.
+* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules.
+
* :mod:`wave`:
- * The :meth:`~wave.Wave_read.getmark`, :meth:`!setmark`,
- and :meth:`~wave.Wave_read.getmarkers` methods of
+ * The ``getmark()``, ``setmark()`` and ``getmarkers()`` methods of
the :class:`~wave.Wave_read` and :class:`~wave.Wave_write` classes
have been deprecated since Python 3.13.
diff --git a/Doc/deprecations/pending-removal-in-3.19.rst b/Doc/deprecations/pending-removal-in-3.19.rst
new file mode 100644
index 00000000000..25f9cba390d
--- /dev/null
+++ b/Doc/deprecations/pending-removal-in-3.19.rst
@@ -0,0 +1,24 @@
+Pending removal in Python 3.19
+------------------------------
+
+* :mod:`ctypes`:
+
+ * Implicitly switching to the MSVC-compatible struct layout by setting
+ :attr:`~ctypes.Structure._pack_` but not :attr:`~ctypes.Structure._layout_`
+ on non-Windows platforms.
+
+* :mod:`hashlib`:
+
+ - In hash function constructors such as :func:`~hashlib.new` or the
+ direct hash-named constructors such as :func:`~hashlib.md5` and
+ :func:`~hashlib.sha256`, their optional initial data parameter could
+ also be passed a keyword argument named ``data=`` or ``string=`` in
+ various :mod:`!hashlib` implementations.
+
+ Support for the ``string`` keyword argument name is now deprecated
+ and slated for removal in Python 3.19.
+
+ Before Python 3.13, the ``string`` keyword parameter was not correctly
+ supported depending on the backend implementation of hash functions.
+ Prefer passing the initial data as a positional argument for maximum
+ backwards compatibility.
diff --git a/Doc/deprecations/pending-removal-in-future.rst b/Doc/deprecations/pending-removal-in-future.rst
index 4c4a368baca..edb672ed8ad 100644
--- a/Doc/deprecations/pending-removal-in-future.rst
+++ b/Doc/deprecations/pending-removal-in-future.rst
@@ -89,8 +89,6 @@ although there is currently no date scheduled for their removal.
underscore.
(Contributed by Serhiy Storchaka in :gh:`91760`.)
-* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules.
-
* :mod:`shutil`: :func:`~shutil.rmtree`'s *onerror* parameter is deprecated in
Python 3.12; use the *onexc* parameter instead.
diff --git a/Doc/extending/building.rst b/Doc/extending/building.rst
index ddde567f6f3..098dde39ea5 100644
--- a/Doc/extending/building.rst
+++ b/Doc/extending/building.rst
@@ -6,41 +6,10 @@
Building C and C++ Extensions
*****************************
-A C extension for CPython is a shared library (e.g. a ``.so`` file on Linux,
-``.pyd`` on Windows), which exports an *initialization function*.
+A C extension for CPython is a shared library (for example, a ``.so`` file on
+Linux, ``.pyd`` on Windows), which exports an *initialization function*.
-To be importable, the shared library must be available on :envvar:`PYTHONPATH`,
-and must be named after the module name, with an appropriate extension.
-When using setuptools, the correct filename is generated automatically.
-
-The initialization function has the signature:
-
-.. c:function:: PyObject* PyInit_modulename(void)
-
-It returns either a fully initialized module, or a :c:type:`PyModuleDef`
-instance. See :ref:`initializing-modules` for details.
-
-.. highlight:: python
-
-For modules with ASCII-only names, the function must be named
-``PyInit_<modulename>``, with ``<modulename>`` replaced by the name of the
-module. When using :ref:`multi-phase-initialization`, non-ASCII module names
-are allowed. In this case, the initialization function name is
-``PyInitU_<modulename>``, with ``<modulename>`` encoded using Python's
-*punycode* encoding with hyphens replaced by underscores. In Python::
-
- def initfunc_name(name):
- try:
- suffix = b'_' + name.encode('ascii')
- except UnicodeEncodeError:
- suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
- return b'PyInit' + suffix
-
-It is possible to export multiple modules from a single shared library by
-defining multiple initialization functions. However, importing them requires
-using symbolic links or a custom importer, because by default only the
-function corresponding to the filename is found.
-See the *"Multiple modules in one library"* section in :pep:`489` for details.
+See :ref:`extension-modules` for details.
.. highlight:: c
@@ -51,7 +20,11 @@ See the *"Multiple modules in one library"* section in :pep:`489` for details.
Building C and C++ Extensions with setuptools
=============================================
-Python 3.12 and newer no longer come with distutils. Please refer to the
-``setuptools`` documentation at
-https://setuptools.readthedocs.io/en/latest/setuptools.html
-to learn more about how build and distribute C/C++ extensions with setuptools.
+
+Building, packaging and distributing extension modules is best done with
+third-party tools, and is out of scope of this document.
+One suitable tool is Setuptools, whose documentation can be found at
+https://setuptools.pypa.io/en/latest/setuptools.html.
+
+The :mod:`distutils` module, which was included in the standard library
+until Python 3.12, is now maintained as part of Setuptools.
diff --git a/Doc/extending/embedding.rst b/Doc/extending/embedding.rst
index b777862da79..cb41889437c 100644
--- a/Doc/extending/embedding.rst
+++ b/Doc/extending/embedding.rst
@@ -245,21 +245,23 @@ Python extension. For example::
return PyLong_FromLong(numargs);
}
- static PyMethodDef EmbMethods[] = {
+ static PyMethodDef emb_module_methods[] = {
{"numargs", emb_numargs, METH_VARARGS,
"Return the number of arguments received by the process."},
{NULL, NULL, 0, NULL}
};
- static PyModuleDef EmbModule = {
- PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
- NULL, NULL, NULL, NULL
+ static struct PyModuleDef emb_module = {
+ .m_base = PyModuleDef_HEAD_INIT,
+ .m_name = "emb",
+ .m_size = 0,
+ .m_methods = emb_module_methods,
};
static PyObject*
PyInit_emb(void)
{
- return PyModule_Create(&EmbModule);
+ return PyModuleDef_Init(&emb_module);
}
Insert the above code just above the :c:func:`main` function. Also, insert the
diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst
index b0493bed75b..fd634956746 100644
--- a/Doc/extending/extending.rst
+++ b/Doc/extending/extending.rst
@@ -203,31 +203,57 @@ function usually raises :c:data:`PyExc_TypeError`. If you have an argument whos
value must be in a particular range or must satisfy other conditions,
:c:data:`PyExc_ValueError` is appropriate.
-You can also define a new exception that is unique to your module. For this, you
-usually declare a static object variable at the beginning of your file::
+You can also define a new exception that is unique to your module.
+The simplest way to do this is to declare a static global object variable at
+the beginning of the file::
- static PyObject *SpamError;
+ static PyObject *SpamError = NULL;
-and initialize it in your module's initialization function (:c:func:`!PyInit_spam`)
-with an exception object::
+and initialize it by calling :c:func:`PyErr_NewException` in the module's
+:c:data:`Py_mod_exec` function (:c:func:`!spam_module_exec`)::
- PyMODINIT_FUNC
- PyInit_spam(void)
- {
- PyObject *m;
+ SpamError = PyErr_NewException("spam.error", NULL, NULL);
- m = PyModule_Create(&spammodule);
- if (m == NULL)
- return NULL;
+Since :c:data:`!SpamError` is a global variable, it will be overwitten every time
+the module is reinitialized, when the :c:data:`Py_mod_exec` function is called.
+
+For now, let's avoid the issue: we will block repeated initialization by raising an
+:py:exc:`ImportError`::
+ static PyObject *SpamError = NULL;
+
+ static int
+ spam_module_exec(PyObject *m)
+ {
+ if (SpamError != NULL) {
+ PyErr_SetString(PyExc_ImportError,
+ "cannot initialize spam module more than once");
+ return -1;
+ }
SpamError = PyErr_NewException("spam.error", NULL, NULL);
- if (PyModule_AddObjectRef(m, "error", SpamError) < 0) {
- Py_CLEAR(SpamError);
- Py_DECREF(m);
- return NULL;
+ if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) {
+ return -1;
}
- return m;
+ return 0;
+ }
+
+ static PyModuleDef_Slot spam_module_slots[] = {
+ {Py_mod_exec, spam_module_exec},
+ {0, NULL}
+ };
+
+ static struct PyModuleDef spam_module = {
+ .m_base = PyModuleDef_HEAD_INIT,
+ .m_name = "spam",
+ .m_size = 0, // non-negative
+ .m_slots = spam_module_slots,
+ };
+
+ PyMODINIT_FUNC
+ PyInit_spam(void)
+ {
+ return PyModuleDef_Init(&spam_module);
}
Note that the Python name for the exception object is :exc:`!spam.error`. The
@@ -242,6 +268,11 @@ needed to ensure that it will not be discarded, causing :c:data:`!SpamError` to
become a dangling pointer. Should it become a dangling pointer, C code which
raises the exception could cause a core dump or other unintended side effects.
+For now, the :c:func:`Py_DECREF` call to remove this reference is missing.
+Even when the Python interpreter shuts down, the global :c:data:`!SpamError`
+variable will not be garbage-collected. It will "leak".
+We did, however, ensure that this will happen at most once per process.
+
We discuss the use of :c:macro:`PyMODINIT_FUNC` as a function return type later in this
sample.
@@ -318,7 +349,7 @@ The Module's Method Table and Initialization Function
I promised to show how :c:func:`!spam_system` is called from Python programs.
First, we need to list its name and address in a "method table"::
- static PyMethodDef SpamMethods[] = {
+ static PyMethodDef spam_methods[] = {
...
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
@@ -343,13 +374,10 @@ function.
The method table must be referenced in the module definition structure::
- static struct PyModuleDef spammodule = {
- PyModuleDef_HEAD_INIT,
- "spam", /* name of module */
- spam_doc, /* module documentation, may be NULL */
- -1, /* size of per-interpreter state of the module,
- or -1 if the module keeps state in global variables. */
- SpamMethods
+ static struct PyModuleDef spam_module = {
+ ...
+ .m_methods = spam_methods,
+ ...
};
This structure, in turn, must be passed to the interpreter in the module's
@@ -360,23 +388,17 @@ only non-\ ``static`` item defined in the module file::
PyMODINIT_FUNC
PyInit_spam(void)
{
- return PyModule_Create(&spammodule);
+ return PyModuleDef_Init(&spam_module);
}
Note that :c:macro:`PyMODINIT_FUNC` declares the function as ``PyObject *`` return type,
declares any special linkage declarations required by the platform, and for C++
declares the function as ``extern "C"``.
-When the Python program imports module :mod:`!spam` for the first time,
-:c:func:`!PyInit_spam` is called. (See below for comments about embedding Python.)
-It calls :c:func:`PyModule_Create`, which returns a module object, and
-inserts built-in function objects into the newly created module based upon the
-table (an array of :c:type:`PyMethodDef` structures) found in the module definition.
-:c:func:`PyModule_Create` returns a pointer to the module object
-that it creates. It may abort with a fatal error for
-certain errors, or return ``NULL`` if the module could not be initialized
-satisfactorily. The init function must return the module object to its caller,
-so that it then gets inserted into ``sys.modules``.
+:c:func:`!PyInit_spam` is called when each interpreter imports its module
+:mod:`!spam` for the first time. (See below for comments about embedding Python.)
+A pointer to the module definition must be returned via :c:func:`PyModuleDef_Init`,
+so that the import machinery can create the module and store it in ``sys.modules``.
When embedding Python, the :c:func:`!PyInit_spam` function is not called
automatically unless there's an entry in the :c:data:`PyImport_Inittab` table.
@@ -433,23 +455,19 @@ optionally followed by an import of the module::
.. note::
- Removing entries from ``sys.modules`` or importing compiled modules into
- multiple interpreters within a process (or following a :c:func:`fork` without an
- intervening :c:func:`exec`) can create problems for some extension modules.
- Extension module authors should exercise caution when initializing internal data
- structures.
+ If you declare a global variable or a local static one, the module may
+ experience unintended side-effects on re-initialisation, for example when
+ removing entries from ``sys.modules`` or importing compiled modules into
+ multiple interpreters within a process
+ (or following a :c:func:`fork` without an intervening :c:func:`exec`).
+ If module state is not yet fully :ref:`isolated <isolating-extensions-howto>`,
+ authors should consider marking the module as having no support for subinterpreters
+ (via :c:macro:`Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED`).
A more substantial example module is included in the Python source distribution
-as :file:`Modules/xxmodule.c`. This file may be used as a template or simply
+as :file:`Modules/xxlimited.c`. This file may be used as a template or simply
read as an example.
-.. note::
-
- Unlike our ``spam`` example, ``xxmodule`` uses *multi-phase initialization*
- (new in Python 3.5), where a PyModuleDef structure is returned from
- ``PyInit_spam``, and creation of the module is left to the import machinery.
- For details on multi-phase initialization, see :PEP:`489`.
-
.. _compilation:
@@ -790,18 +808,17 @@ Philbrick (philbrick@hks.com)::
{NULL, NULL, 0, NULL} /* sentinel */
};
- static struct PyModuleDef keywdargmodule = {
- PyModuleDef_HEAD_INIT,
- "keywdarg",
- NULL,
- -1,
- keywdarg_methods
+ static struct PyModuleDef keywdarg_module = {
+ .m_base = PyModuleDef_HEAD_INIT,
+ .m_name = "keywdarg",
+ .m_size = 0,
+ .m_methods = keywdarg_methods,
};
PyMODINIT_FUNC
PyInit_keywdarg(void)
{
- return PyModule_Create(&keywdargmodule);
+ return PyModuleDef_Init(&keywdarg_module);
}
@@ -1072,8 +1089,9 @@ why his :meth:`!__del__` methods would fail...
The second case of problems with a borrowed reference is a variant involving
threads. Normally, multiple threads in the Python interpreter can't get in each
-other's way, because there is a global lock protecting Python's entire object
-space. However, it is possible to temporarily release this lock using the macro
+other's way, because there is a :term:`global lock <global interpreter lock>`
+protecting Python's entire object space.
+However, it is possible to temporarily release this lock using the macro
:c:macro:`Py_BEGIN_ALLOW_THREADS`, and to re-acquire it using
:c:macro:`Py_END_ALLOW_THREADS`. This is common around blocking I/O calls, to
let other threads use the processor while waiting for the I/O to complete.
@@ -1259,20 +1277,15 @@ two more lines must be added::
#include "spammodule.h"
The ``#define`` is used to tell the header file that it is being included in the
-exporting module, not a client module. Finally, the module's initialization
-function must take care of initializing the C API pointer array::
+exporting module, not a client module. Finally, the module's :c:data:`mod_exec
+<Py_mod_exec>` function must take care of initializing the C API pointer array::
- PyMODINIT_FUNC
- PyInit_spam(void)
+ static int
+ spam_module_exec(PyObject *m)
{
- PyObject *m;
static void *PySpam_API[PySpam_API_pointers];
PyObject *c_api_object;
- m = PyModule_Create(&spammodule);
- if (m == NULL)
- return NULL;
-
/* Initialize the C API pointer array */
PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;
@@ -1280,11 +1293,10 @@ function must take care of initializing the C API pointer array::
c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);
if (PyModule_Add(m, "_C_API", c_api_object) < 0) {
- Py_DECREF(m);
- return NULL;
+ return -1;
}
- return m;
+ return 0;
}
Note that ``PySpam_API`` is declared ``static``; otherwise the pointer
@@ -1343,20 +1355,16 @@ like this::
All that a client module must do in order to have access to the function
:c:func:`!PySpam_System` is to call the function (or rather macro)
-:c:func:`!import_spam` in its initialization function::
+:c:func:`!import_spam` in its :c:data:`mod_exec <Py_mod_exec>` function::
- PyMODINIT_FUNC
- PyInit_client(void)
+ static int
+ client_module_exec(PyObject *m)
{
- PyObject *m;
-
- m = PyModule_Create(&clientmodule);
- if (m == NULL)
- return NULL;
- if (import_spam() < 0)
- return NULL;
+ if (import_spam() < 0) {
+ return -1;
+ }
/* additional initialization can happen here */
- return m;
+ return 0;
}
The main disadvantage of this approach is that the file :file:`spammodule.h` is
diff --git a/Doc/extending/index.rst b/Doc/extending/index.rst
index 01b4df6d44a..4cc2c96d8d5 100644
--- a/Doc/extending/index.rst
+++ b/Doc/extending/index.rst
@@ -26,19 +26,9 @@ Recommended third party tools
=============================
This guide only covers the basic tools for creating extensions provided
-as part of this version of CPython. Third party tools like
-`Cython <https://cython.org/>`_, `cffi <https://cffi.readthedocs.io>`_,
-`SWIG <https://www.swig.org>`_ and `Numba <https://numba.pydata.org/>`_
-offer both simpler and more sophisticated approaches to creating C and C++
-extensions for Python.
-
-.. seealso::
-
- `Python Packaging User Guide: Binary Extensions <https://packaging.python.org/guides/packaging-binary-extensions/>`_
- The Python Packaging User Guide not only covers several available
- tools that simplify the creation of binary extensions, but also
- discusses the various reasons why creating an extension module may be
- desirable in the first place.
+as part of this version of CPython. Some :ref:`third party tools
+<c-api-tools>` offer both simpler and more sophisticated approaches to creating
+C and C++ extensions for Python.
Creating extensions without third party tools
@@ -49,6 +39,10 @@ assistance from third party tools. It is intended primarily for creators
of those tools, rather than being a recommended way to create your own
C extensions.
+.. seealso::
+
+ :pep:`489` -- Multi-phase extension module initialization
+
.. toctree::
:maxdepth: 2
:numbered:
diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst
index 3fc91841416..3bbee33bd50 100644
--- a/Doc/extending/newtypes_tutorial.rst
+++ b/Doc/extending/newtypes_tutorial.rst
@@ -55,8 +55,10 @@ from the previous chapter. This file defines three things:
#. How the :class:`!Custom` **type** behaves: this is the ``CustomType`` struct,
which defines a set of flags and function pointers that the interpreter
inspects when specific operations are requested.
-#. How to initialize the :mod:`!custom` module: this is the ``PyInit_custom``
- function and the associated ``custommodule`` struct.
+#. How to define and execute the :mod:`!custom` module: this is the
+ ``PyInit_custom`` function and the associated ``custom_module`` struct for
+ defining the module, and the ``custom_module_exec`` function to set up
+ a fresh module object.
The first bit is::
@@ -171,18 +173,18 @@ implementation provided by the API function :c:func:`PyType_GenericNew`. ::
.tp_new = PyType_GenericNew,
Everything else in the file should be familiar, except for some code in
-:c:func:`!PyInit_custom`::
+:c:func:`!custom_module_exec`::
- if (PyType_Ready(&CustomType) < 0)
- return;
+ if (PyType_Ready(&CustomType) < 0) {
+ return -1;
+ }
This initializes the :class:`!Custom` type, filling in a number of members
to the appropriate default values, including :c:member:`~PyObject.ob_type` that we initially
set to ``NULL``. ::
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
- Py_DECREF(m);
- return NULL;
+ return -1;
}
This adds the type to the module dictionary. This allows us to create
@@ -275,7 +277,7 @@ be an instance of a subclass.
The explicit cast to ``CustomObject *`` above is needed because we defined
``Custom_dealloc`` to take a ``PyObject *`` argument, as the ``tp_dealloc``
function pointer expects to receive a ``PyObject *`` argument.
- By assigning to the the ``tp_dealloc`` slot of a type, we declare
+ By assigning to the ``tp_dealloc`` slot of a type, we declare
that it can only be called with instances of our ``CustomObject``
class, so the cast to ``(CustomObject *)`` is safe.
This is object-oriented polymorphism, in C!
@@ -875,27 +877,22 @@ but let the base class handle it by calling its own :c:member:`~PyTypeObject.tp_
The :c:type:`PyTypeObject` struct supports a :c:member:`~PyTypeObject.tp_base`
specifying the type's concrete base class. Due to cross-platform compiler
issues, you can't fill that field directly with a reference to
-:c:type:`PyList_Type`; it should be done later in the module initialization
+:c:type:`PyList_Type`; it should be done in the :c:data:`Py_mod_exec`
function::
- PyMODINIT_FUNC
- PyInit_sublist(void)
+ static int
+ sublist_module_exec(PyObject *m)
{
- PyObject* m;
SubListType.tp_base = &PyList_Type;
- if (PyType_Ready(&SubListType) < 0)
- return NULL;
-
- m = PyModule_Create(&sublistmodule);
- if (m == NULL)
- return NULL;
+ if (PyType_Ready(&SubListType) < 0) {
+ return -1;
+ }
if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
- Py_DECREF(m);
- return NULL;
+ return -1;
}
- return m;
+ return 0;
}
Before calling :c:func:`PyType_Ready`, the type structure must have the
diff --git a/Doc/extending/windows.rst b/Doc/extending/windows.rst
index 56aa44e4e58..a97c6182553 100644
--- a/Doc/extending/windows.rst
+++ b/Doc/extending/windows.rst
@@ -121,7 +121,7 @@ When creating DLLs in Windows, you can use the CPython library in two ways:
:file:`Python.h` triggers an implicit, configure-aware link with the
library. The header file chooses :file:`pythonXY_d.lib` for Debug,
:file:`pythonXY.lib` for Release, and :file:`pythonX.lib` for Release with
- the `Limited API <stable-application-binary-interface>`_ enabled.
+ the :ref:`Limited API <stable-application-binary-interface>` enabled.
To build two DLLs, spam and ni (which uses C functions found in spam), you
could use these commands::
diff --git a/Doc/faq/design.rst b/Doc/faq/design.rst
index e2710fab9cf..c758c019ca4 100644
--- a/Doc/faq/design.rst
+++ b/Doc/faq/design.rst
@@ -420,10 +420,12 @@ strings representing the files in the current directory. Functions which
operate on this output would generally not break if you added another file or
two to the directory.
-Tuples are immutable, meaning that once a tuple has been created, you can't
-replace any of its elements with a new value. Lists are mutable, meaning that
-you can always change a list's elements. Only immutable elements can be used as
-dictionary keys, and hence only tuples and not lists can be used as keys.
+Tuples are :term:`immutable`, meaning that once a tuple has been created, you can't
+replace any of its elements with a new value. Lists are :term:`mutable`, meaning that
+you can always change a list's elements. Only :term:`hashable` objects can
+be used as dictionary keys. Most immutable types are hashable, which is why
+tuples, but not lists, can be used as keys. Note, however, that a tuple is
+only hashable if all of its elements are hashable.
How are lists implemented in CPython?
diff --git a/Doc/faq/extending.rst b/Doc/faq/extending.rst
index 3147fda7c37..1d5abed2317 100644
--- a/Doc/faq/extending.rst
+++ b/Doc/faq/extending.rst
@@ -37,24 +37,9 @@ Writing C is hard; are there any alternatives?
----------------------------------------------
There are a number of alternatives to writing your own C extensions, depending
-on what you're trying to do.
-
-.. XXX make sure these all work
-
-`Cython <https://cython.org>`_ and its relative `Pyrex
-<https://www.csse.canterbury.ac.nz/greg.ewing/python/Pyrex/>`_ are compilers
-that accept a slightly modified form of Python and generate the corresponding
-C code. Cython and Pyrex make it possible to write an extension without having
-to learn Python's C API.
-
-If you need to interface to some C or C++ library for which no Python extension
-currently exists, you can try wrapping the library's data types and functions
-with a tool such as `SWIG <https://www.swig.org>`_. `SIP
-<https://github.com/Python-SIP/sip>`__, `CXX
-<https://cxx.sourceforge.net/>`_ `Boost
-<https://www.boost.org/libs/python/doc/index.html>`_, or `Weave
-<https://github.com/scipy/weave>`_ are also
-alternatives for wrapping C++ libraries.
+on what you're trying to do. :ref:`Recommended third party tools <c-api-tools>`
+offer both simpler and more sophisticated approaches to creating C and C++
+extensions for Python.
How can I execute arbitrary Python statements from C?
diff --git a/Doc/glossary.rst b/Doc/glossary.rst
index 0b26e18efd7..c5c7994f126 100644
--- a/Doc/glossary.rst
+++ b/Doc/glossary.rst
@@ -355,6 +355,12 @@ Glossary
tasks (see :mod:`asyncio`) associate each task with a context which
becomes the current context whenever the task starts or resumes execution.
+ cyclic isolate
+ A subgroup of one or more objects that reference each other in a reference
+ cycle, but are not referenced by objects outside the group. The goal of
+ the :term:`cyclic garbage collector <garbage collection>` is to identify these groups and break the reference
+ cycles so that the memory can be reclaimed.
+
decorator
A function returning another function, usually applied as a function
transformation using the ``@wrapper`` syntax. Common examples for
diff --git a/Doc/howto/annotations.rst b/Doc/howto/annotations.rst
index 78f3704ba5d..d7deb6c6bc1 100644
--- a/Doc/howto/annotations.rst
+++ b/Doc/howto/annotations.rst
@@ -248,4 +248,9 @@ quirks by using :func:`annotationlib.get_annotations` on Python 3.14+ or
:func:`inspect.get_annotations` on Python 3.10+. On earlier versions of
Python, you can avoid these bugs by accessing the annotations from the
class's :attr:`~type.__dict__`
-(e.g., ``cls.__dict__.get('__annotations__', None)``).
+(for example, ``cls.__dict__.get('__annotations__', None)``).
+
+In some versions of Python, instances of classes may have an ``__annotations__``
+attribute. However, this is not supported functionality. If you need the
+annotations of an instance, you can use :func:`type` to access its class
+(for example, ``annotationlib.get_annotations(type(myinstance))`` on Python 3.14+).
diff --git a/Doc/howto/cporting.rst b/Doc/howto/cporting.rst
index 7773620b40b..cf857aed042 100644
--- a/Doc/howto/cporting.rst
+++ b/Doc/howto/cporting.rst
@@ -14,13 +14,11 @@ We recommend the following resources for porting extension modules to Python 3:
module.
* The `Porting guide`_ from the *py3c* project provides opinionated
suggestions with supporting code.
-* The `Cython`_ and `CFFI`_ libraries offer abstractions over
- Python's C API.
+* :ref:`Recommended third party tools <c-api-tools>` offer abstractions over
+ the Python's C API.
Extensions generally need to be re-written to use one of them,
but the library then handles differences between various Python
versions and implementations.
.. _Migrating C extensions: http://python3porting.com/cextensions.html
.. _Porting guide: https://py3c.readthedocs.io/en/latest/guide.html
-.. _Cython: https://cython.org/
-.. _CFFI: https://cffi.readthedocs.io/en/latest/
diff --git a/Doc/howto/curses.rst b/Doc/howto/curses.rst
index 6994a5328e8..816639552d7 100644
--- a/Doc/howto/curses.rst
+++ b/Doc/howto/curses.rst
@@ -161,6 +161,8 @@ your terminal won't be left in a funny state on exception and you'll be
able to read the exception's message and traceback.
+.. _windows-and-pads:
+
Windows and Pads
================
diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst
index 3f6ee517050..02b45879ccf 100644
--- a/Doc/howto/free-threading-extensions.rst
+++ b/Doc/howto/free-threading-extensions.rst
@@ -6,8 +6,8 @@
C API Extension Support for Free Threading
******************************************
-Starting with the 3.13 release, CPython has experimental support for running
-with the :term:`global interpreter lock` (GIL) disabled in a configuration
+Starting with the 3.13 release, CPython has support for running with
+the :term:`global interpreter lock` (GIL) disabled in a configuration
called :term:`free threading`. This document describes how to adapt C API
extensions to support free threading.
@@ -23,6 +23,14 @@ You can use it to enable code that only runs under the free-threaded build::
/* code that only runs in the free-threaded build */
#endif
+.. note::
+
+ On Windows, this macro is not defined automatically, but must be specified
+ to the compiler when building. The :func:`sysconfig.get_config_var` function
+ can be used to determine whether the current running interpreter had the
+ macro defined.
+
+
Module Initialization
=====================
@@ -388,7 +396,7 @@ The wheels, shared libraries, and binaries are indicated by a ``t`` suffix.
free-threaded build, with the ``t`` suffix, such as ``python3.13t``.
* `pypa/cibuildwheel <https://github.com/pypa/cibuildwheel>`_ supports the
free-threaded build if you set
- `CIBW_FREE_THREADED_SUPPORT <https://cibuildwheel.pypa.io/en/stable/options/#free-threaded-support>`_.
+ `CIBW_ENABLE to cpython-freethreading <https://cibuildwheel.pypa.io/en/stable/options/#enable>`_.
Limited C API and Stable ABI
............................
diff --git a/Doc/howto/free-threading-python.rst b/Doc/howto/free-threading-python.rst
index f7a894ac2cd..24069617c47 100644
--- a/Doc/howto/free-threading-python.rst
+++ b/Doc/howto/free-threading-python.rst
@@ -1,18 +1,21 @@
.. _freethreading-python-howto:
-**********************************************
-Python experimental support for free threading
-**********************************************
+*********************************
+Python support for free threading
+*********************************
-Starting with the 3.13 release, CPython has experimental support for a build of
+Starting with the 3.13 release, CPython has support for a build of
Python called :term:`free threading` where the :term:`global interpreter lock`
(GIL) is disabled. Free-threaded execution allows for full utilization of the
available processing power by running threads in parallel on available CPU cores.
While not all software will benefit from this automatically, programs
designed with threading in mind will run faster on multi-core hardware.
-**The free-threaded mode is experimental** and work is ongoing to improve it:
-expect some bugs and a substantial single-threaded performance hit.
+The free-threaded mode is working and continues to be improved, but
+there is some additional overhead in single-threaded workloads compared
+to the regular build. Additionally, third-party packages, in particular ones
+with an :term:`extension module`, may not be ready for use in a
+free-threaded build, and will re-enable the :term:`GIL`.
This document describes the implications of free threading
for Python code. See :ref:`freethreading-extensions-howto` for information on
@@ -32,7 +35,7 @@ optionally support installing free-threaded Python binaries. The installers
are available at https://www.python.org/downloads/.
For information on other platforms, see the `Installing a Free-Threaded Python
-<https://py-free-threading.github.io/installing_cpython/>`_, a
+<https://py-free-threading.github.io/installing-cpython/>`_, a
community-maintained installation guide for installing free-threaded Python.
When building CPython from source, the :option:`--disable-gil` configure option
@@ -43,7 +46,7 @@ Identifying free-threaded Python
================================
To check if the current interpreter supports free-threading, :option:`python -VV <-V>`
-and :data:`sys.version` contain "experimental free-threading build".
+and :data:`sys.version` contain "free-threading build".
The new :func:`sys._is_gil_enabled` function can be used to check whether
the GIL is actually disabled in the running process.
diff --git a/Doc/howto/functional.rst b/Doc/howto/functional.rst
index 1f0608fb0fc..053558e3890 100644
--- a/Doc/howto/functional.rst
+++ b/Doc/howto/functional.rst
@@ -372,8 +372,8 @@ have the form::
for expr2 in sequence2
if condition2
for expr3 in sequence3
- ...
if condition3
+ ...
for exprN in sequenceN
if conditionN )
@@ -602,7 +602,7 @@ generators:
raise an exception inside the generator; the exception is raised by the
``yield`` expression where the generator's execution is paused.
-* :meth:`~generator.close` raises a :exc:`GeneratorExit` exception inside the
+* :meth:`~generator.close` sends a :exc:`GeneratorExit` exception to the
generator to terminate the iteration. On receiving this exception, the
generator's code must either raise :exc:`GeneratorExit` or
:exc:`StopIteration`; catching the exception and doing anything else is
@@ -1217,7 +1217,7 @@ flow inside a program. The book uses Scheme for its examples, but many of the
design approaches described in these chapters are applicable to functional-style
Python code.
-https://www.defmacro.org/ramblings/fp.html: A general introduction to functional
+https://defmacro.org/2006/06/19/fp.html: A general introduction to functional
programming that uses Java examples and has a lengthy historical introduction.
https://en.wikipedia.org/wiki/Functional_programming: General Wikipedia entry
diff --git a/Doc/howto/isolating-extensions.rst b/Doc/howto/isolating-extensions.rst
index a636e06bda8..7da6dc8a397 100644
--- a/Doc/howto/isolating-extensions.rst
+++ b/Doc/howto/isolating-extensions.rst
@@ -168,7 +168,7 @@ possible, consider explicit locking.
If it is necessary to use process-global state, the simplest way to
avoid issues with multiple interpreters is to explicitly prevent a
module from being loaded more than once per process—see
-`Opt-Out: Limiting to One Module Object per Process`_.
+:ref:`isolating-extensions-optout`.
Managing Per-Module State
@@ -207,6 +207,8 @@ An example of a module with per-module state is currently available as
example module initialization shown at the bottom of the file.
+.. _isolating-extensions-optout:
+
Opt-Out: Limiting to One Module Object per Process
--------------------------------------------------
@@ -215,21 +217,36 @@ multiple interpreters correctly. If this is not yet the case for your
module, you can explicitly make your module loadable only once per
process. For example::
+ // A process-wide flag
static int loaded = 0;
+ // Mutex to provide thread safety (only needed for free-threaded Python)
+ static PyMutex modinit_mutex = {0};
+
static int
exec_module(PyObject* module)
{
+ PyMutex_Lock(&modinit_mutex);
if (loaded) {
+ PyMutex_Unlock(&modinit_mutex);
PyErr_SetString(PyExc_ImportError,
"cannot load module more than once per process");
return -1;
}
loaded = 1;
+ PyMutex_Unlock(&modinit_mutex);
// ... rest of initialization
}
+If your module's :c:member:`PyModuleDef.m_clear` function is able to prepare
+for future re-initialization, it should clear the ``loaded`` flag.
+In this case, your module won't support multiple instances existing
+*concurrently*, but it will, for example, support being loaded after
+Python runtime shutdown (:c:func:`Py_FinalizeEx`) and re-initialization
+(:c:func:`Py_Initialize`).
+
+
Module State Access from Functions
----------------------------------
@@ -436,7 +453,7 @@ Avoiding ``PyObject_New``
GC-tracked objects need to be allocated using GC-aware functions.
-If you use use :c:func:`PyObject_New` or :c:func:`PyObject_NewVar`:
+If you use :c:func:`PyObject_New` or :c:func:`PyObject_NewVar`:
- Get and call type's :c:member:`~PyTypeObject.tp_alloc` slot, if possible.
That is, replace ``TYPE *o = PyObject_New(TYPE, typeobj)`` with::
@@ -609,8 +626,7 @@ Open Issues
Several issues around per-module state and heap types are still open.
-Discussions about improving the situation are best held on the `capi-sig
-mailing list <https://mail.python.org/mailman3/lists/capi-sig.python.org/>`__.
+Discussions about improving the situation are best held on the `discuss forum under c-api tag <https://discuss.python.org/c/core-dev/c-api/30>`__.
Per-Class Scope
diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst
index 661d6c290f6..7d64a02358a 100644
--- a/Doc/howto/logging-cookbook.rst
+++ b/Doc/howto/logging-cookbook.rst
@@ -626,7 +626,7 @@ which, when run, will produce:
of each message with the handler's level, and only passes a message to a
handler if it's appropriate to do so.
-.. versionchanged:: next
+.. versionchanged:: 3.14
The :class:`QueueListener` can be started (and stopped) via the
:keyword:`with` statement. For example:
diff --git a/Doc/howto/perf_profiling.rst b/Doc/howto/perf_profiling.rst
index b579d776576..96d757ac452 100644
--- a/Doc/howto/perf_profiling.rst
+++ b/Doc/howto/perf_profiling.rst
@@ -92,7 +92,7 @@ Then we can use ``perf report`` to analyze the data:
| | | |
| | | |--51.67%--_PyEval_EvalFrameDefault
| | | | |
- | | | | |--11.52%--_PyLong_Add
+ | | | | |--11.52%--_PyCompactLong_Add
| | | | | |
| | | | | |--2.97%--_PyObject_Malloc
...
@@ -142,7 +142,7 @@ Instead, if we run the same experiment with ``perf`` support enabled we get:
| | | |
| | | |--51.81%--_PyEval_EvalFrameDefault
| | | | |
- | | | | |--13.77%--_PyLong_Add
+ | | | | |--13.77%--_PyCompactLong_Add
| | | | | |
| | | | | |--3.26%--_PyObject_Malloc
diff --git a/Doc/howto/regex.rst b/Doc/howto/regex.rst
index e543f6d5657..7486a378dbb 100644
--- a/Doc/howto/regex.rst
+++ b/Doc/howto/regex.rst
@@ -1016,7 +1016,9 @@ extension. This regular expression matches ``foo.bar`` and
Now, consider complicating the problem a bit; what if you want to match
filenames where the extension is not ``bat``? Some incorrect attempts:
-``.*[.][^b].*$`` The first attempt above tries to exclude ``bat`` by requiring
+``.*[.][^b].*$``
+
+The first attempt above tries to exclude ``bat`` by requiring
that the first character of the extension is not a ``b``. This is wrong,
because the pattern also doesn't match ``foo.bar``.
@@ -1043,7 +1045,9 @@ confusing.
A negative lookahead cuts through all this confusion:
-``.*[.](?!bat$)[^.]*$`` The negative lookahead means: if the expression ``bat``
+``.*[.](?!bat$)[^.]*$``
+
+The negative lookahead means: if the expression ``bat``
doesn't match at this point, try the rest of the pattern; if ``bat$`` does
match, the whole pattern will fail. The trailing ``$`` is required to ensure
that something like ``sample.batch``, where the extension only starts with
diff --git a/Doc/howto/urllib2.rst b/Doc/howto/urllib2.rst
index 33a2a7ea89e..d79d1abe8d0 100644
--- a/Doc/howto/urllib2.rst
+++ b/Doc/howto/urllib2.rst
@@ -245,75 +245,27 @@ codes in the 100--299 range indicate success, you will usually only see error
codes in the 400--599 range.
:attr:`http.server.BaseHTTPRequestHandler.responses` is a useful dictionary of
-response codes in that shows all the response codes used by :rfc:`2616`. The
-dictionary is reproduced here for convenience ::
+response codes that shows all the response codes used by :rfc:`2616`.
+An excerpt from the dictionary is shown below ::
- # Table mapping response codes to messages; entries have the
- # form {code: (shortmessage, longmessage)}.
responses = {
- 100: ('Continue', 'Request received, please continue'),
- 101: ('Switching Protocols',
- 'Switching to new protocol; obey Upgrade header'),
-
- 200: ('OK', 'Request fulfilled, document follows'),
- 201: ('Created', 'Document created, URL follows'),
- 202: ('Accepted',
- 'Request accepted, processing continues off-line'),
- 203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
- 204: ('No Content', 'Request fulfilled, nothing follows'),
- 205: ('Reset Content', 'Clear input form for further input.'),
- 206: ('Partial Content', 'Partial content follows.'),
-
- 300: ('Multiple Choices',
- 'Object has several resources -- see URI list'),
- 301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
- 302: ('Found', 'Object moved temporarily -- see URI list'),
- 303: ('See Other', 'Object moved -- see Method and URL list'),
- 304: ('Not Modified',
- 'Document has not changed since given time'),
- 305: ('Use Proxy',
- 'You must use proxy specified in Location to access this '
- 'resource.'),
- 307: ('Temporary Redirect',
- 'Object moved temporarily -- see URI list'),
-
- 400: ('Bad Request',
- 'Bad request syntax or unsupported method'),
- 401: ('Unauthorized',
- 'No permission -- see authorization schemes'),
- 402: ('Payment Required',
- 'No payment -- see charging schemes'),
- 403: ('Forbidden',
- 'Request forbidden -- authorization will not help'),
- 404: ('Not Found', 'Nothing matches the given URI'),
- 405: ('Method Not Allowed',
- 'Specified method is invalid for this server.'),
- 406: ('Not Acceptable', 'URI not available in preferred format.'),
- 407: ('Proxy Authentication Required', 'You must authenticate with '
- 'this proxy before proceeding.'),
- 408: ('Request Timeout', 'Request timed out; try again later.'),
- 409: ('Conflict', 'Request conflict.'),
- 410: ('Gone',
- 'URI no longer exists and has been permanently removed.'),
- 411: ('Length Required', 'Client must specify Content-Length.'),
- 412: ('Precondition Failed', 'Precondition in headers is false.'),
- 413: ('Request Entity Too Large', 'Entity is too large.'),
- 414: ('Request-URI Too Long', 'URI is too long.'),
- 415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
- 416: ('Requested Range Not Satisfiable',
- 'Cannot satisfy request range.'),
- 417: ('Expectation Failed',
- 'Expect condition could not be satisfied.'),
-
- 500: ('Internal Server Error', 'Server got itself in trouble'),
- 501: ('Not Implemented',
- 'Server does not support this operation'),
- 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
- 503: ('Service Unavailable',
- 'The server cannot process the request due to a high load'),
- 504: ('Gateway Timeout',
- 'The gateway server did not receive a timely response'),
- 505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
+ ...
+ <HTTPStatus.OK: 200>: ('OK', 'Request fulfilled, document follows'),
+ ...
+ <HTTPStatus.FORBIDDEN: 403>: ('Forbidden',
+ 'Request forbidden -- authorization will '
+ 'not help'),
+ <HTTPStatus.NOT_FOUND: 404>: ('Not Found',
+ 'Nothing matches the given URI'),
+ ...
+ <HTTPStatus.IM_A_TEAPOT: 418>: ("I'm a Teapot",
+ 'Server refuses to brew coffee because '
+ 'it is a teapot'),
+ ...
+ <HTTPStatus.SERVICE_UNAVAILABLE: 503>: ('Service Unavailable',
+ 'The server cannot process the '
+ 'request due to a high load'),
+ ...
}
When an error is raised the server responds by returning an HTTP error code
diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c
index 5253f879360..039a1a72193 100644
--- a/Doc/includes/newtypes/custom.c
+++ b/Doc/includes/newtypes/custom.c
@@ -16,28 +16,37 @@ static PyTypeObject CustomType = {
.tp_new = PyType_GenericNew,
};
-static PyModuleDef custommodule = {
+static int
+custom_module_exec(PyObject *m)
+{
+ if (PyType_Ready(&CustomType) < 0) {
+ return -1;
+ }
+
+ if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyModuleDef_Slot custom_module_slots[] = {
+ {Py_mod_exec, custom_module_exec},
+ // Just use this while using static types
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+ {0, NULL}
+};
+
+static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom",
.m_doc = "Example module that creates an extension type.",
- .m_size = -1,
+ .m_size = 0,
+ .m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom(void)
{
- PyObject *m;
- if (PyType_Ready(&CustomType) < 0)
- return NULL;
-
- m = PyModule_Create(&custommodule);
- if (m == NULL)
- return NULL;
-
- if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
- return m;
+ return PyModuleDef_Init(&custom_module);
}
diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c
index a87917583ca..1ff8e707d1b 100644
--- a/Doc/includes/newtypes/custom2.c
+++ b/Doc/includes/newtypes/custom2.c
@@ -106,28 +106,36 @@ static PyTypeObject CustomType = {
.tp_methods = Custom_methods,
};
-static PyModuleDef custommodule = {
- .m_base =PyModuleDef_HEAD_INIT,
+static int
+custom_module_exec(PyObject *m)
+{
+ if (PyType_Ready(&CustomType) < 0) {
+ return -1;
+ }
+
+ if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyModuleDef_Slot custom_module_slots[] = {
+ {Py_mod_exec, custom_module_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+ {0, NULL}
+};
+
+static PyModuleDef custom_module = {
+ .m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom2",
.m_doc = "Example module that creates an extension type.",
- .m_size = -1,
+ .m_size = 0,
+ .m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom2(void)
{
- PyObject *m;
- if (PyType_Ready(&CustomType) < 0)
- return NULL;
-
- m = PyModule_Create(&custommodule);
- if (m == NULL)
- return NULL;
-
- if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
- return m;
+ return PyModuleDef_Init(&custom_module);
}
diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c
index 854034d4066..22f50eb0e1d 100644
--- a/Doc/includes/newtypes/custom3.c
+++ b/Doc/includes/newtypes/custom3.c
@@ -151,28 +151,36 @@ static PyTypeObject CustomType = {
.tp_getset = Custom_getsetters,
};
-static PyModuleDef custommodule = {
+static int
+custom_module_exec(PyObject *m)
+{
+ if (PyType_Ready(&CustomType) < 0) {
+ return -1;
+ }
+
+ if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyModuleDef_Slot custom_module_slots[] = {
+ {Py_mod_exec, custom_module_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+ {0, NULL}
+};
+
+static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom3",
.m_doc = "Example module that creates an extension type.",
- .m_size = -1,
+ .m_size = 0,
+ .m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom3(void)
{
- PyObject *m;
- if (PyType_Ready(&CustomType) < 0)
- return NULL;
-
- m = PyModule_Create(&custommodule);
- if (m == NULL)
- return NULL;
-
- if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
- return m;
+ return PyModuleDef_Init(&custom_module);
}
diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c
index a0a1eeb2891..07585aff598 100644
--- a/Doc/includes/newtypes/custom4.c
+++ b/Doc/includes/newtypes/custom4.c
@@ -170,28 +170,36 @@ static PyTypeObject CustomType = {
.tp_getset = Custom_getsetters,
};
-static PyModuleDef custommodule = {
+static int
+custom_module_exec(PyObject *m)
+{
+ if (PyType_Ready(&CustomType) < 0) {
+ return -1;
+ }
+
+ if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyModuleDef_Slot custom_module_slots[] = {
+ {Py_mod_exec, custom_module_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+ {0, NULL}
+};
+
+static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom4",
.m_doc = "Example module that creates an extension type.",
- .m_size = -1,
+ .m_size = 0,
+ .m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom4(void)
{
- PyObject *m;
- if (PyType_Ready(&CustomType) < 0)
- return NULL;
-
- m = PyModule_Create(&custommodule);
- if (m == NULL)
- return NULL;
-
- if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
- return m;
+ return PyModuleDef_Init(&custom_module);
}
diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c
index 00664f34541..b784456a4ef 100644
--- a/Doc/includes/newtypes/sublist.c
+++ b/Doc/includes/newtypes/sublist.c
@@ -31,7 +31,7 @@ SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
}
static PyTypeObject SubListType = {
- PyVarObject_HEAD_INIT(NULL, 0)
+ .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "sublist.SubList",
.tp_doc = PyDoc_STR("SubList objects"),
.tp_basicsize = sizeof(SubListObject),
@@ -41,29 +41,37 @@ static PyTypeObject SubListType = {
.tp_methods = SubList_methods,
};
-static PyModuleDef sublistmodule = {
- PyModuleDef_HEAD_INIT,
+static int
+sublist_module_exec(PyObject *m)
+{
+ SubListType.tp_base = &PyList_Type;
+ if (PyType_Ready(&SubListType) < 0) {
+ return -1;
+ }
+
+ if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyModuleDef_Slot sublist_module_slots[] = {
+ {Py_mod_exec, sublist_module_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+ {0, NULL}
+};
+
+static PyModuleDef sublist_module = {
+ .m_base = PyModuleDef_HEAD_INIT,
.m_name = "sublist",
.m_doc = "Example module that creates an extension type.",
- .m_size = -1,
+ .m_size = 0,
+ .m_slots = sublist_module_slots,
};
PyMODINIT_FUNC
PyInit_sublist(void)
{
- PyObject *m;
- SubListType.tp_base = &PyList_Type;
- if (PyType_Ready(&SubListType) < 0)
- return NULL;
-
- m = PyModule_Create(&sublistmodule);
- if (m == NULL)
- return NULL;
-
- if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
- return m;
+ return PyModuleDef_Init(&sublist_module);
}
diff --git a/Doc/installing/index.rst b/Doc/installing/index.rst
index a46c1caefe4..3a485a43a5a 100644
--- a/Doc/installing/index.rst
+++ b/Doc/installing/index.rst
@@ -188,7 +188,7 @@ switch::
Once the Development & Deployment part of PPUG is fleshed out, some of
those sections should be linked from new questions here (most notably,
we should have a question about avoiding depending on PyPI that links to
- https://packaging.python.org/en/latest/mirrors/)
+ https://packaging.python.org/en/latest/guides/index-mirrors-and-caches/)
Common installation issues
diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst
index 7946cd3a3ce..7dfc11449a6 100644
--- a/Doc/library/annotationlib.rst
+++ b/Doc/library/annotationlib.rst
@@ -40,7 +40,7 @@ The :func:`get_annotations` function is the main entry point for
retrieving annotations. Given a function, class, or module, it returns
an annotations dictionary in the requested format. This module also provides
functionality for working directly with the :term:`annotate function`
-that is used to evaluate annotations, such as :func:`get_annotate_function`
+that is used to evaluate annotations, such as :func:`get_annotate_from_class_namespace`
and :func:`call_annotate_function`, as well as the
:func:`call_evaluate_function` function for working with
:term:`evaluate functions <evaluate function>`.
@@ -127,16 +127,27 @@ Classes
Values are the result of evaluating the annotation expressions.
- .. attribute:: FORWARDREF
+ .. attribute:: VALUE_WITH_FAKE_GLOBALS
:value: 2
+ Special value used to signal that an annotate function is being
+ evaluated in a special environment with fake globals. When passed this
+ value, annotate functions should either return the same value as for
+ the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
+ to signal that they do not support execution in this environment.
+ This format is only used internally and should not be passed to
+ the functions in this module.
+
+ .. attribute:: FORWARDREF
+ :value: 3
+
Values are real annotation values (as per :attr:`Format.VALUE` format)
for defined values, and :class:`ForwardRef` proxies for undefined
- values. Real objects may contain references to, :class:`ForwardRef`
+ values. Real objects may contain references to :class:`ForwardRef`
proxy objects.
.. attribute:: STRING
- :value: 3
+ :value: 4
Values are the text string of the annotation as it appears in the
source code, up to modifications including, but not restricted to,
@@ -144,17 +155,6 @@ Classes
The exact values of these strings may change in future versions of Python.
- .. attribute:: VALUE_WITH_FAKE_GLOBALS
- :value: 4
-
- Special value used to signal that an annotate function is being
- evaluated in a special environment with fake globals. When passed this
- value, annotate functions should either return the same value as for
- the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
- to signal that they do not support execution in this environment.
- This format is only used internally and should not be passed to
- the functions in this module.
-
.. versionadded:: 3.14
.. class:: ForwardRef
@@ -172,14 +172,21 @@ Classes
:class:`~ForwardRef`. The string may not be exactly equivalent
to the original source.
- .. method:: evaluate(*, owner=None, globals=None, locals=None, type_params=None)
+ .. method:: evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)
Evaluate the forward reference, returning its value.
- This may throw an exception, such as :exc:`NameError`, if the forward
+ If the *format* argument is :attr:`~Format.VALUE` (the default),
+ this method may throw an exception, such as :exc:`NameError`, if the forward
reference refers to a name that cannot be resolved. The arguments to this
method can be used to provide bindings for names that would otherwise
- be undefined.
+ be undefined. If the *format* argument is :attr:`~Format.FORWARDREF`,
+ the method will never throw an exception, but may return a :class:`~ForwardRef`
+ instance. For example, if the forward reference object contains the code
+ ``list[undefined]``, where ``undefined`` is a name that is not defined,
+ evaluating it with the :attr:`~Format.FORWARDREF` format will return
+ ``list[ForwardRef('undefined')]``. If the *format* argument is
+ :attr:`~Format.STRING`, the method will return :attr:`~ForwardRef.__forward_arg__`.
The *owner* parameter provides the preferred mechanism for passing scope
information to this method. The owner of a :class:`~ForwardRef` is the
@@ -204,6 +211,10 @@ Classes
means may not have any information about their scope, so passing
arguments to this method may be necessary to evaluate them successfully.
+ If no *owner*, *globals*, *locals*, or *type_params* are provided and the
+ :class:`~ForwardRef` does not contain information about its origin,
+ empty globals and locals dictionaries are used.
+
.. versionadded:: 3.14
@@ -300,15 +311,13 @@ Functions
.. versionadded:: 3.14
-.. function:: get_annotate_function(obj)
+.. function:: get_annotate_from_class_namespace(namespace)
- Retrieve the :term:`annotate function` for *obj*. Return :const:`!None`
- if *obj* does not have an annotate function. *obj* may be a class, function,
- module, or a namespace dictionary for a class. The last case is useful during
- class creation, e.g. in the ``__new__`` method of a metaclass.
-
- This is usually equivalent to accessing the :attr:`~object.__annotate__`
- attribute of *obj*, but access through this public function is preferred.
+ Retrieve the :term:`annotate function` from a class namespace dictionary *namespace*.
+ Return :const:`!None` if the namespace does not contain an annotate function.
+ This is primarily useful before the class has been fully created (e.g., in a metaclass);
+ after the class exists, the annotate function can be retrieved with ``cls.__annotate__``.
+ See :ref:`below <annotationlib-metaclass>` for an example using this function in a metaclass.
.. versionadded:: 3.14
@@ -407,3 +416,190 @@ Functions
.. versionadded:: 3.14
+
+Recipes
+-------
+
+.. _annotationlib-metaclass:
+
+Using annotations in a metaclass
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A :ref:`metaclass <metaclasses>` may want to inspect or even modify the annotations
+in a class body during class creation. Doing so requires retrieving annotations
+from the class namespace dictionary. For classes created with
+``from __future__ import annotations``, the annotations will be in the ``__annotations__``
+key of the dictionary. For other classes with annotations,
+:func:`get_annotate_from_class_namespace` can be used to get the
+annotate function, and :func:`call_annotate_function` can be used to call it and
+retrieve the annotations. Using the :attr:`~Format.FORWARDREF` format will usually
+be best, because this allows the annotations to refer to names that cannot yet be
+resolved when the class is created.
+
+To modify the annotations, it is best to create a wrapper annotate function
+that calls the original annotate function, makes any necessary adjustments, and
+returns the result.
+
+Below is an example of a metaclass that filters out all :class:`typing.ClassVar`
+annotations from the class and puts them in a separate attribute:
+
+.. code-block:: python
+
+ import annotationlib
+ import typing
+
+ class ClassVarSeparator(type):
+ def __new__(mcls, name, bases, ns):
+ if "__annotations__" in ns: # from __future__ import annotations
+ annotations = ns["__annotations__"]
+ classvar_keys = {
+ key for key, value in annotations.items()
+ # Use string comparison for simplicity; a more robust solution
+ # could use annotationlib.ForwardRef.evaluate
+ if value.startswith("ClassVar")
+ }
+ classvars = {key: annotations[key] for key in classvar_keys}
+ ns["__annotations__"] = {
+ key: value for key, value in annotations.items()
+ if key not in classvar_keys
+ }
+ wrapped_annotate = None
+ elif annotate := annotationlib.get_annotate_from_class_namespace(ns):
+ annotations = annotationlib.call_annotate_function(
+ annotate, format=annotationlib.Format.FORWARDREF
+ )
+ classvar_keys = {
+ key for key, value in annotations.items()
+ if typing.get_origin(value) is typing.ClassVar
+ }
+ classvars = {key: annotations[key] for key in classvar_keys}
+
+ def wrapped_annotate(format):
+ annos = annotationlib.call_annotate_function(annotate, format, owner=typ)
+ return {key: value for key, value in annos.items() if key not in classvar_keys}
+
+ else: # no annotations
+ classvars = {}
+ wrapped_annotate = None
+ typ = super().__new__(mcls, name, bases, ns)
+
+ if wrapped_annotate is not None:
+ # Wrap the original __annotate__ with a wrapper that removes ClassVars
+ typ.__annotate__ = wrapped_annotate
+ typ.classvars = classvars # Store the ClassVars in a separate attribute
+ return typ
+
+
+Limitations of the ``STRING`` format
+------------------------------------
+
+The :attr:`~Format.STRING` format is meant to approximate the source code
+of the annotation, but the implementation strategy used means that it is not
+always possible to recover the exact source code.
+
+First, the stringifier of course cannot recover any information that is not present in
+the compiled code, including comments, whitespace, parenthesization, and operations that
+get simplified by the compiler.
+
+Second, the stringifier can intercept almost all operations that involve names looked
+up in some scope, but it cannot intercept operations that operate fully on constants.
+As a corollary, this also means it is not safe to request the ``STRING`` format on
+untrusted code: Python is powerful enough that it is possible to achieve arbitrary
+code execution even with no access to any globals or builtins. For example:
+
+.. code-block:: pycon
+
+ >>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
+ ...
+ >>> annotationlib.get_annotations(f, format=annotationlib.Format.SOURCE)
+ Hello world
+ {'x': 'None'}
+
+.. note::
+ This particular example works as of the time of writing, but it relies on
+ implementation details and is not guaranteed to work in the future.
+
+Among the different kinds of expressions that exist in Python,
+as represented by the :mod:`ast` module, some expressions are supported,
+meaning that the ``STRING`` format can generally recover the original source code;
+others are unsupported, meaning that they may result in incorrect output or an error.
+
+The following are supported (sometimes with caveats):
+
+* :class:`ast.BinOp`
+* :class:`ast.UnaryOp`
+
+ * :class:`ast.Invert` (``~``), :class:`ast.UAdd` (``+``), and :class:`ast.USub` (``-``) are supported
+ * :class:`ast.Not` (``not``) is not supported
+
+* :class:`ast.Dict` (except when using ``**`` unpacking)
+* :class:`ast.Set`
+* :class:`ast.Compare`
+
+ * :class:`ast.Eq` and :class:`ast.NotEq` are supported
+ * :class:`ast.Lt`, :class:`ast.LtE`, :class:`ast.Gt`, and :class:`ast.GtE` are supported, but the operand may be flipped
+ * :class:`ast.Is`, :class:`ast.IsNot`, :class:`ast.In`, and :class:`ast.NotIn` are not supported
+
+* :class:`ast.Call` (except when using ``**`` unpacking)
+* :class:`ast.Constant` (though not the exact representation of the constant; for example, escape
+ sequences in strings are lost; hexadecimal numbers are converted to decimal)
+* :class:`ast.Attribute` (assuming the value is not a constant)
+* :class:`ast.Subscript` (assuming the value is not a constant)
+* :class:`ast.Starred` (``*`` unpacking)
+* :class:`ast.Name`
+* :class:`ast.List`
+* :class:`ast.Tuple`
+* :class:`ast.Slice`
+
+The following are unsupported, but throw an informative error when encountered by the
+stringifier:
+
+* :class:`ast.FormattedValue` (f-strings; error is not detected if conversion specifiers like ``!r``
+ are used)
+* :class:`ast.JoinedStr` (f-strings)
+
+The following are unsupported and result in incorrect output:
+
+* :class:`ast.BoolOp` (``and`` and ``or``)
+* :class:`ast.IfExp`
+* :class:`ast.Lambda`
+* :class:`ast.ListComp`
+* :class:`ast.SetComp`
+* :class:`ast.DictComp`
+* :class:`ast.GeneratorExp`
+
+The following are disallowed in annotation scopes and therefore not relevant:
+
+* :class:`ast.NamedExpr` (``:=``)
+* :class:`ast.Await`
+* :class:`ast.Yield`
+* :class:`ast.YieldFrom`
+
+
+Limitations of the ``FORWARDREF`` format
+----------------------------------------
+
+The :attr:`~Format.FORWARDREF` format aims to produce real values as much
+as possible, with anything that cannot be resolved replaced with
+:class:`ForwardRef` objects. It is affected by broadly the same Limitations
+as the :attr:`~Format.STRING` format: annotations that perform operations on
+literals or that use unsupported expression types may raise exceptions when
+evaluated using the :attr:`~Format.FORWARDREF` format.
+
+Below are a few examples of the behavior with unsupported expressions:
+
+.. code-block:: pycon
+
+ >>> from annotationlib import get_annotations, Format
+ >>> def zerodiv(x: 1 / 0): ...
+ >>> get_annotations(zerodiv, format=Format.STRING)
+ Traceback (most recent call last):
+ ...
+ ZeroDivisionError: division by zero
+ >>> get_annotations(zerodiv, format=Format.FORWARDREF)
+ Traceback (most recent call last):
+ ...
+ ZeroDivisionError: division by zero
+ >>> def ifexp(x: 1 if y else 0): ...
+ >>> get_annotations(ifexp, format=Format.STRING)
+ {'x': '1'}
diff --git a/Doc/library/archiving.rst b/Doc/library/archiving.rst
index c9284949af4..da0b3f8c3e7 100644
--- a/Doc/library/archiving.rst
+++ b/Doc/library/archiving.rst
@@ -5,13 +5,15 @@ Data Compression and Archiving
******************************
The modules described in this chapter support data compression with the zlib,
-gzip, bzip2 and lzma algorithms, and the creation of ZIP- and tar-format
+gzip, bzip2, lzma, and zstd algorithms, and the creation of ZIP- and tar-format
archives. See also :ref:`archiving-operations` provided by the :mod:`shutil`
module.
.. toctree::
+ compression.rst
+ compression.zstd.rst
zlib.rst
gzip.rst
bz2.rst
diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index feccc635ff7..a03d88092db 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -641,7 +641,7 @@ keyword argument::
>>> parser = argparse.ArgumentParser(description='Process some integers.')
>>> parser.color = True
-.. versionadded:: next
+.. versionadded:: 3.14
The add_argument() method
@@ -955,7 +955,7 @@ See also :ref:`specifying-ambiguous-arguments`. The supported values are:
.. index:: single: + (plus); in argparse module
-* ``'+'``. Just like ``'*'``, all command-line args present are gathered into a
+* ``'+'``. Just like ``'*'``, all command-line arguments present are gathered into a
list. Additionally, an error message will be generated if there wasn't at
least one command-line argument present. For example::
@@ -2122,12 +2122,15 @@ Partial parsing
.. method:: ArgumentParser.parse_known_args(args=None, namespace=None)
- Sometimes a script may only parse a few of the command-line arguments, passing
- the remaining arguments on to another script or program. In these cases, the
- :meth:`~ArgumentParser.parse_known_args` method can be useful. It works much like
- :meth:`~ArgumentParser.parse_args` except that it does not produce an error when
- extra arguments are present. Instead, it returns a two item tuple containing
- the populated namespace and the list of remaining argument strings.
+ Sometimes a script only needs to handle a specific set of command-line
+ arguments, leaving any unrecognized arguments for another script or program.
+ In these cases, the :meth:`~ArgumentParser.parse_known_args` method can be
+ useful.
+
+ This method works similarly to :meth:`~ArgumentParser.parse_args`, but it does
+ not raise an error for extra, unrecognized arguments. Instead, it parses the
+ known arguments and returns a two item tuple that contains the populated
+ namespace and the list of any unrecognized arguments.
::
diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst
index 776c63d1f0f..ef6c62dca1e 100644
--- a/Doc/library/ast.rst
+++ b/Doc/library/ast.rst
@@ -1,4 +1,4 @@
-:mod:`!ast` --- Abstract Syntax Trees
+:mod:`!ast` --- Abstract syntax trees
=====================================
.. module:: ast
@@ -29,7 +29,7 @@ compiled into a Python code object using the built-in :func:`compile` function.
.. _abstract-grammar:
-Abstract Grammar
+Abstract grammar
----------------
The abstract grammar is currently defined as follows:
@@ -252,12 +252,11 @@ Root nodes
>>> print(ast.dump(ast.parse('(int, str) -> List[int]', mode='func_type'), indent=4))
FunctionType(
argtypes=[
- Name(id='int', ctx=Load()),
- Name(id='str', ctx=Load())],
+ Name(id='int'),
+ Name(id='str')],
returns=Subscript(
- value=Name(id='List', ctx=Load()),
- slice=Name(id='int', ctx=Load()),
- ctx=Load()))
+ value=Name(id='List'),
+ slice=Name(id='int')))
.. versionadded:: 3.8
@@ -268,9 +267,9 @@ Literals
.. class:: Constant(value)
A constant value. The ``value`` attribute of the ``Constant`` literal contains the
- Python object it represents. The values represented can be simple types
- such as a number, string or ``None``, but also immutable container types
- (tuples and frozensets) if all of their elements are constant.
+ 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`.
.. doctest::
@@ -312,14 +311,14 @@ Literals
values=[
Constant(value='sin('),
FormattedValue(
- value=Name(id='a', ctx=Load()),
+ value=Name(id='a'),
conversion=-1),
Constant(value=') is '),
FormattedValue(
value=Call(
- func=Name(id='sin', ctx=Load()),
+ func=Name(id='sin'),
args=[
- Name(id='a', ctx=Load())]),
+ Name(id='a')]),
conversion=-1,
format_spec=JoinedStr(
values=[
@@ -341,16 +340,14 @@ Literals
elts=[
Constant(value=1),
Constant(value=2),
- Constant(value=3)],
- ctx=Load()))
+ Constant(value=3)]))
>>> print(ast.dump(ast.parse('(1, 2, 3)', mode='eval'), indent=4))
Expression(
body=Tuple(
elts=[
Constant(value=1),
Constant(value=2),
- Constant(value=3)],
- ctx=Load()))
+ Constant(value=3)]))
.. class:: Set(elts)
@@ -388,7 +385,7 @@ Literals
None],
values=[
Constant(value=1),
- Name(id='d', ctx=Load())]))
+ Name(id='d')]))
Variables
@@ -414,7 +411,7 @@ Variables
Module(
body=[
Expr(
- value=Name(id='a', ctx=Load()))])
+ value=Name(id='a'))])
>>> print(ast.dump(ast.parse('a = 1'), indent=4))
Module(
@@ -452,7 +449,7 @@ Variables
value=Name(id='b', ctx=Store()),
ctx=Store())],
ctx=Store())],
- value=Name(id='it', ctx=Load()))])
+ value=Name(id='it'))])
.. _ast-expressions:
@@ -475,7 +472,7 @@ Expressions
Expr(
value=UnaryOp(
op=USub(),
- operand=Name(id='a', ctx=Load())))])
+ operand=Name(id='a')))])
.. class:: UnaryOp(op, operand)
@@ -498,7 +495,7 @@ Expressions
Expression(
body=UnaryOp(
op=Not(),
- operand=Name(id='x', ctx=Load())))
+ operand=Name(id='x')))
.. class:: BinOp(left, op, right)
@@ -511,9 +508,9 @@ Expressions
>>> print(ast.dump(ast.parse('x + y', mode='eval'), indent=4))
Expression(
body=BinOp(
- left=Name(id='x', ctx=Load()),
+ left=Name(id='x'),
op=Add(),
- right=Name(id='y', ctx=Load())))
+ right=Name(id='y')))
.. class:: Add
@@ -549,8 +546,8 @@ Expressions
body=BoolOp(
op=Or(),
values=[
- Name(id='x', ctx=Load()),
- Name(id='y', ctx=Load())]))
+ Name(id='x'),
+ Name(id='y')]))
.. class:: And
@@ -575,7 +572,7 @@ Expressions
LtE(),
Lt()],
comparators=[
- Name(id='a', ctx=Load()),
+ Name(id='a'),
Constant(value=10)]))
@@ -609,18 +606,17 @@ Expressions
>>> print(ast.dump(ast.parse('func(a, b=c, *d, **e)', mode='eval'), indent=4))
Expression(
body=Call(
- func=Name(id='func', ctx=Load()),
+ func=Name(id='func'),
args=[
- Name(id='a', ctx=Load()),
+ Name(id='a'),
Starred(
- value=Name(id='d', ctx=Load()),
- ctx=Load())],
+ value=Name(id='d'))],
keywords=[
keyword(
arg='b',
- value=Name(id='c', ctx=Load())),
+ value=Name(id='c')),
keyword(
- value=Name(id='e', ctx=Load()))]))
+ value=Name(id='e'))]))
.. class:: keyword(arg, value)
@@ -639,9 +635,9 @@ Expressions
>>> print(ast.dump(ast.parse('a if b else c', mode='eval'), indent=4))
Expression(
body=IfExp(
- test=Name(id='b', ctx=Load()),
- body=Name(id='a', ctx=Load()),
- orelse=Name(id='c', ctx=Load())))
+ test=Name(id='b'),
+ body=Name(id='a'),
+ orelse=Name(id='c')))
.. class:: Attribute(value, attr, ctx)
@@ -656,9 +652,8 @@ Expressions
>>> print(ast.dump(ast.parse('snake.colour', mode='eval'), indent=4))
Expression(
body=Attribute(
- value=Name(id='snake', ctx=Load()),
- attr='colour',
- ctx=Load()))
+ value=Name(id='snake'),
+ attr='colour'))
.. class:: NamedExpr(target, value)
@@ -694,15 +689,13 @@ Subscripting
>>> print(ast.dump(ast.parse('l[1:2, 3]', mode='eval'), indent=4))
Expression(
body=Subscript(
- value=Name(id='l', ctx=Load()),
+ value=Name(id='l'),
slice=Tuple(
elts=[
Slice(
lower=Constant(value=1),
upper=Constant(value=2)),
- Constant(value=3)],
- ctx=Load()),
- ctx=Load()))
+ Constant(value=3)])))
.. class:: Slice(lower, upper, step)
@@ -716,11 +709,10 @@ Subscripting
>>> print(ast.dump(ast.parse('l[1:2]', mode='eval'), indent=4))
Expression(
body=Subscript(
- value=Name(id='l', ctx=Load()),
+ value=Name(id='l'),
slice=Slice(
lower=Constant(value=1),
- upper=Constant(value=2)),
- ctx=Load()))
+ upper=Constant(value=2))))
Comprehensions
@@ -745,11 +737,11 @@ Comprehensions
... ))
Expression(
body=ListComp(
- elt=Name(id='x', ctx=Load()),
+ elt=Name(id='x'),
generators=[
comprehension(
target=Name(id='x', ctx=Store()),
- iter=Name(id='numbers', ctx=Load()),
+ iter=Name(id='numbers'),
is_async=0)]))
>>> print(ast.dump(
... ast.parse('{x: x**2 for x in numbers}', mode='eval'),
@@ -757,15 +749,15 @@ Comprehensions
... ))
Expression(
body=DictComp(
- key=Name(id='x', ctx=Load()),
+ key=Name(id='x'),
value=BinOp(
- left=Name(id='x', ctx=Load()),
+ left=Name(id='x'),
op=Pow(),
right=Constant(value=2)),
generators=[
comprehension(
target=Name(id='x', ctx=Store()),
- iter=Name(id='numbers', ctx=Load()),
+ iter=Name(id='numbers'),
is_async=0)]))
>>> print(ast.dump(
... ast.parse('{x for x in numbers}', mode='eval'),
@@ -773,11 +765,11 @@ Comprehensions
... ))
Expression(
body=SetComp(
- elt=Name(id='x', ctx=Load()),
+ elt=Name(id='x'),
generators=[
comprehension(
target=Name(id='x', ctx=Store()),
- iter=Name(id='numbers', ctx=Load()),
+ iter=Name(id='numbers'),
is_async=0)]))
@@ -798,17 +790,17 @@ Comprehensions
Expression(
body=ListComp(
elt=Call(
- func=Name(id='ord', ctx=Load()),
+ func=Name(id='ord'),
args=[
- Name(id='c', ctx=Load())]),
+ Name(id='c')]),
generators=[
comprehension(
target=Name(id='line', ctx=Store()),
- iter=Name(id='file', ctx=Load()),
+ iter=Name(id='file'),
is_async=0),
comprehension(
target=Name(id='c', ctx=Store()),
- iter=Name(id='line', ctx=Load()),
+ iter=Name(id='line'),
is_async=0)]))
>>> print(ast.dump(ast.parse('(n**2 for n in it if n>5 if n<10)', mode='eval'),
@@ -816,22 +808,22 @@ Comprehensions
Expression(
body=GeneratorExp(
elt=BinOp(
- left=Name(id='n', ctx=Load()),
+ left=Name(id='n'),
op=Pow(),
right=Constant(value=2)),
generators=[
comprehension(
target=Name(id='n', ctx=Store()),
- iter=Name(id='it', ctx=Load()),
+ iter=Name(id='it'),
ifs=[
Compare(
- left=Name(id='n', ctx=Load()),
+ left=Name(id='n'),
ops=[
Gt()],
comparators=[
Constant(value=5)]),
Compare(
- left=Name(id='n', ctx=Load()),
+ left=Name(id='n'),
ops=[
Lt()],
comparators=[
@@ -842,11 +834,11 @@ Comprehensions
... indent=4)) # Async comprehension
Expression(
body=ListComp(
- elt=Name(id='i', ctx=Load()),
+ elt=Name(id='i'),
generators=[
comprehension(
target=Name(id='i', ctx=Store()),
- iter=Name(id='soc', ctx=Load()),
+ iter=Name(id='soc'),
is_async=1)]))
@@ -888,7 +880,7 @@ Statements
Name(id='a', ctx=Store()),
Name(id='b', ctx=Store())],
ctx=Store())],
- value=Name(id='c', ctx=Load()))])
+ value=Name(id='c'))])
.. class:: AnnAssign(target, annotation, value, simple)
@@ -911,7 +903,7 @@ Statements
body=[
AnnAssign(
target=Name(id='c', ctx=Store()),
- annotation=Name(id='int', ctx=Load()),
+ annotation=Name(id='int'),
simple=1)])
>>> print(ast.dump(ast.parse('(a): int = 1'), indent=4)) # Annotation with parenthesis
@@ -919,7 +911,7 @@ Statements
body=[
AnnAssign(
target=Name(id='a', ctx=Store()),
- annotation=Name(id='int', ctx=Load()),
+ annotation=Name(id='int'),
value=Constant(value=1),
simple=0)])
@@ -928,10 +920,10 @@ Statements
body=[
AnnAssign(
target=Attribute(
- value=Name(id='a', ctx=Load()),
+ value=Name(id='a'),
attr='b',
ctx=Store()),
- annotation=Name(id='int', ctx=Load()),
+ annotation=Name(id='int'),
simple=0)])
>>> print(ast.dump(ast.parse('a[1]: int'), indent=4)) # Subscript annotation
@@ -939,10 +931,10 @@ Statements
body=[
AnnAssign(
target=Subscript(
- value=Name(id='a', ctx=Load()),
+ value=Name(id='a'),
slice=Constant(value=1),
ctx=Store()),
- annotation=Name(id='int', ctx=Load()),
+ annotation=Name(id='int'),
simple=0)])
@@ -979,8 +971,8 @@ Statements
Module(
body=[
Raise(
- exc=Name(id='x', ctx=Load()),
- cause=Name(id='y', ctx=Load()))])
+ exc=Name(id='x'),
+ cause=Name(id='y'))])
.. class:: Assert(test, msg)
@@ -994,8 +986,8 @@ Statements
Module(
body=[
Assert(
- test=Name(id='x', ctx=Load()),
- msg=Name(id='y', ctx=Load()))])
+ test=Name(id='x'),
+ msg=Name(id='y'))])
.. class:: Delete(targets)
@@ -1041,7 +1033,7 @@ Statements
body=[
TypeAlias(
name=Name(id='Alias', ctx=Store()),
- value=Name(id='int', ctx=Load()))])
+ value=Name(id='int'))])
.. versionadded:: 3.12
@@ -1134,13 +1126,13 @@ Control flow
Module(
body=[
If(
- test=Name(id='x', ctx=Load()),
+ test=Name(id='x'),
body=[
Expr(
value=Constant(value=Ellipsis))],
orelse=[
If(
- test=Name(id='y', ctx=Load()),
+ test=Name(id='y'),
body=[
Expr(
value=Constant(value=Ellipsis))],
@@ -1174,7 +1166,7 @@ Control flow
body=[
For(
target=Name(id='x', ctx=Store()),
- iter=Name(id='y', ctx=Load()),
+ iter=Name(id='y'),
body=[
Expr(
value=Constant(value=Ellipsis))],
@@ -1199,7 +1191,7 @@ Control flow
Module(
body=[
While(
- test=Name(id='x', ctx=Load()),
+ test=Name(id='x'),
body=[
Expr(
value=Constant(value=Ellipsis))],
@@ -1227,11 +1219,11 @@ Control flow
body=[
For(
target=Name(id='a', ctx=Store()),
- iter=Name(id='b', ctx=Load()),
+ iter=Name(id='b'),
body=[
If(
test=Compare(
- left=Name(id='a', ctx=Load()),
+ left=Name(id='a'),
ops=[
Gt()],
comparators=[
@@ -1269,12 +1261,12 @@ Control flow
value=Constant(value=Ellipsis))],
handlers=[
ExceptHandler(
- type=Name(id='Exception', ctx=Load()),
+ type=Name(id='Exception'),
body=[
Expr(
value=Constant(value=Ellipsis))]),
ExceptHandler(
- type=Name(id='OtherException', ctx=Load()),
+ type=Name(id='OtherException'),
name='e',
body=[
Expr(
@@ -1309,7 +1301,7 @@ Control flow
value=Constant(value=Ellipsis))],
handlers=[
ExceptHandler(
- type=Name(id='Exception', ctx=Load()),
+ type=Name(id='Exception'),
body=[
Expr(
value=Constant(value=Ellipsis))])])])
@@ -1337,12 +1329,12 @@ Control flow
body=[
Expr(
value=BinOp(
- left=Name(id='a', ctx=Load()),
+ left=Name(id='a'),
op=Add(),
right=Constant(value=1)))],
handlers=[
ExceptHandler(
- type=Name(id='TypeError', ctx=Load()),
+ type=Name(id='TypeError'),
body=[
Pass()])])])
@@ -1375,18 +1367,18 @@ Control flow
With(
items=[
withitem(
- context_expr=Name(id='a', ctx=Load()),
+ context_expr=Name(id='a'),
optional_vars=Name(id='b', ctx=Store())),
withitem(
- context_expr=Name(id='c', ctx=Load()),
+ context_expr=Name(id='c'),
optional_vars=Name(id='d', ctx=Store()))],
body=[
Expr(
value=Call(
- func=Name(id='something', ctx=Load()),
+ func=Name(id='something'),
args=[
- Name(id='b', ctx=Load()),
- Name(id='d', ctx=Load())]))])])
+ Name(id='b'),
+ Name(id='d')]))])])
Pattern matching
@@ -1426,14 +1418,14 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchSequence(
patterns=[
MatchAs(name='x')]),
guard=Compare(
- left=Name(id='x', ctx=Load()),
+ left=Name(id='x'),
ops=[
Gt()],
comparators=[
@@ -1443,7 +1435,7 @@ Pattern matching
value=Constant(value=Ellipsis))]),
match_case(
pattern=MatchClass(
- cls=Name(id='tuple', ctx=Load())),
+ cls=Name(id='tuple')),
body=[
Expr(
value=Constant(value=Ellipsis))])])])
@@ -1467,7 +1459,7 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchValue(
@@ -1494,7 +1486,7 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchSingleton(value=None),
@@ -1521,7 +1513,7 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchSequence(
@@ -1554,7 +1546,7 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchSequence(
@@ -1603,7 +1595,7 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchMapping(
@@ -1653,11 +1645,11 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchClass(
- cls=Name(id='Point2D', ctx=Load()),
+ cls=Name(id='Point2D'),
patterns=[
MatchValue(
value=Constant(value=0)),
@@ -1668,7 +1660,7 @@ Pattern matching
value=Constant(value=Ellipsis))]),
match_case(
pattern=MatchClass(
- cls=Name(id='Point3D', ctx=Load()),
+ cls=Name(id='Point3D'),
kwd_attrs=[
'x',
'y',
@@ -1709,7 +1701,7 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchAs(
@@ -1746,7 +1738,7 @@ Pattern matching
Module(
body=[
Match(
- subject=Name(id='x', ctx=Load()),
+ subject=Name(id='x'),
cases=[
match_case(
pattern=MatchOr(
@@ -1786,7 +1778,7 @@ Type annotations
body=[
AnnAssign(
target=Name(id='x', ctx=Store()),
- annotation=Name(id='bool', ctx=Load()),
+ annotation=Name(id='bool'),
value=Constant(value=1),
simple=1)],
type_ignores=[
@@ -1824,12 +1816,11 @@ aliases.
type_params=[
TypeVar(
name='T',
- bound=Name(id='int', ctx=Load()),
- default_value=Name(id='bool', ctx=Load()))],
+ bound=Name(id='int'),
+ default_value=Name(id='bool'))],
value=Subscript(
- value=Name(id='list', ctx=Load()),
- slice=Name(id='T', ctx=Load()),
- ctx=Load()))])
+ value=Name(id='list'),
+ slice=Name(id='T')))])
.. versionadded:: 3.12
@@ -1854,17 +1845,14 @@ aliases.
name='P',
default_value=List(
elts=[
- Name(id='int', ctx=Load()),
- Name(id='str', ctx=Load())],
- ctx=Load()))],
+ Name(id='int'),
+ Name(id='str')]))],
value=Subscript(
- value=Name(id='Callable', ctx=Load()),
+ value=Name(id='Callable'),
slice=Tuple(
elts=[
- Name(id='P', ctx=Load()),
- Name(id='int', ctx=Load())],
- ctx=Load()),
- ctx=Load()))])
+ Name(id='P'),
+ Name(id='int')])))])
.. versionadded:: 3.12
@@ -1885,18 +1873,13 @@ aliases.
TypeAlias(
name=Name(id='Alias', ctx=Store()),
type_params=[
- TypeVarTuple(
- name='Ts',
- default_value=Tuple(ctx=Load()))],
+ TypeVarTuple(name='Ts', default_value=Tuple())],
value=Subscript(
- value=Name(id='tuple', ctx=Load()),
+ value=Name(id='tuple'),
slice=Tuple(
elts=[
Starred(
- value=Name(id='Ts', ctx=Load()),
- ctx=Load())],
- ctx=Load()),
- ctx=Load()))])
+ value=Name(id='Ts'))])))])
.. versionadded:: 3.12
@@ -2001,8 +1984,8 @@ Function and class definitions
body=[
Pass()],
decorator_list=[
- Name(id='decorator1', ctx=Load()),
- Name(id='decorator2', ctx=Load())],
+ Name(id='decorator1'),
+ Name(id='decorator2')],
returns=Constant(value='return annotation'))])
@@ -2032,14 +2015,14 @@ Function and class definitions
body=[
Expr(
value=Yield(
- value=Name(id='x', ctx=Load())))])
+ value=Name(id='x')))])
>>> print(ast.dump(ast.parse('yield from x'), indent=4))
Module(
body=[
Expr(
value=YieldFrom(
- value=Name(id='x', ctx=Load())))])
+ value=Name(id='x')))])
.. class:: Global(names)
@@ -2094,17 +2077,17 @@ Function and class definitions
ClassDef(
name='Foo',
bases=[
- Name(id='base1', ctx=Load()),
- Name(id='base2', ctx=Load())],
+ Name(id='base1'),
+ Name(id='base2')],
keywords=[
keyword(
arg='metaclass',
- value=Name(id='meta', ctx=Load()))],
+ value=Name(id='meta'))],
body=[
Pass()],
decorator_list=[
- Name(id='decorator1', ctx=Load()),
- Name(id='decorator2', ctx=Load())])])
+ Name(id='decorator1'),
+ Name(id='decorator2')])])
.. versionchanged:: 3.12
Added ``type_params``.
@@ -2141,7 +2124,7 @@ Async and await
Expr(
value=Await(
value=Call(
- func=Name(id='other_func', ctx=Load()))))])])
+ func=Name(id='other_func'))))])])
.. class:: AsyncFor(target, iter, body, orelse, type_comment)
@@ -2156,10 +2139,10 @@ Async and await
of :class:`ast.operator`, :class:`ast.unaryop`, :class:`ast.cmpop`,
:class:`ast.boolop` and :class:`ast.expr_context`) on the returned tree
will be singletons. Changes to one will be reflected in all other
- occurrences of the same value (e.g. :class:`ast.Add`).
+ occurrences of the same value (for example, :class:`ast.Add`).
-:mod:`ast` Helpers
+:mod:`ast` helpers
------------------
Apart from the node classes, the :mod:`ast` module defines these utility functions
@@ -2402,7 +2385,7 @@ and classes for traversing abstract syntax trees:
def visit_Name(self, node):
return Subscript(
- value=Name(id='data', ctx=Load()),
+ value=Name(id='data'),
slice=Constant(value=node.id),
ctx=node.ctx
)
@@ -2445,8 +2428,26 @@ and classes for traversing abstract syntax trees:
indents that many spaces per level. If *indent* is a string (such as ``"\t"``),
that string is used to indent each level.
- If *show_empty* is ``False`` (the default), empty lists and fields that are ``None``
- will be omitted from the output.
+ If *show_empty* is false (the default), optional empty lists and
+ ``Load()`` values will be omitted from the output.
+ Optional ``None`` values are always omitted.
+
+ .. doctest::
+
+ >>> tree = ast.parse('print(None)', '?', 'eval')
+ >>> print(ast.dump(tree, indent=4))
+ Expression(
+ body=Call(
+ func=Name(id='print'),
+ args=[
+ Constant(value=None)]))
+ >>> print(ast.dump(tree, indent=4, show_empty=True))
+ Expression(
+ body=Call(
+ func=Name(id='print', ctx=Load()),
+ args=[
+ Constant(value=None)],
+ keywords=[]))
.. versionchanged:: 3.9
Added the *indent* option.
@@ -2454,37 +2455,13 @@ and classes for traversing abstract syntax trees:
.. versionchanged:: 3.13
Added the *show_empty* option.
- .. doctest::
-
- >>> print(ast.dump(ast.parse("""\
- ... async def f():
- ... await other_func()
- ... """), indent=4, show_empty=True))
- Module(
- body=[
- AsyncFunctionDef(
- name='f',
- args=arguments(
- posonlyargs=[],
- args=[],
- kwonlyargs=[],
- kw_defaults=[],
- defaults=[]),
- body=[
- Expr(
- value=Await(
- value=Call(
- func=Name(id='other_func', ctx=Load()),
- args=[],
- keywords=[])))],
- decorator_list=[],
- type_params=[])],
- type_ignores=[])
+ .. versionchanged:: next
+ Omit optional ``Load()`` values by default.
.. _ast-compiler-flags:
-Compiler Flags
+Compiler flags
--------------
The following flags may be passed to :func:`compile` in order to change
@@ -2533,7 +2510,7 @@ effects on the compilation of a program:
.. _ast-cli:
-Command-Line Usage
+Command-line usage
------------------
.. versionadded:: 3.9
@@ -2572,6 +2549,28 @@ The following options are accepted:
Indentation of nodes in AST (number of spaces).
+.. option:: --feature-version <version>
+
+ Python version in the format 3.x (for example, 3.10). Defaults to the
+ current version of the interpreter.
+
+ .. versionadded:: 3.14
+
+.. option:: -O <level>
+ --optimize <level>
+
+ Optimization level for parser. Defaults to no optimization.
+
+ .. versionadded:: 3.14
+
+.. option:: --show-empty
+
+ Show empty lists and fields that are ``None``. Defaults to not showing empty
+ objects.
+
+ .. versionadded:: 3.14
+
+
If :file:`infile` is specified its contents are parsed to AST and dumped
to stdout. Otherwise, the content is read from stdin.
diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst
index 44b507a9811..7831b613bd4 100644
--- a/Doc/library/asyncio-dev.rst
+++ b/Doc/library/asyncio-dev.rst
@@ -46,10 +46,6 @@ In addition to enabling the debug mode, consider also:
When the debug mode is enabled:
-* asyncio checks for :ref:`coroutines that were not awaited
- <asyncio-coroutine-not-scheduled>` and logs them; this mitigates
- the "forgotten await" pitfall.
-
* Many non-threadsafe asyncio APIs (such as :meth:`loop.call_soon` and
:meth:`loop.call_at` methods) raise an exception if they are called
from a wrong thread.
diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
index 8f561744fe4..91970c28239 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -361,7 +361,7 @@ Creating Futures and Tasks
.. versionadded:: 3.5.2
-.. method:: loop.create_task(coro, *, name=None, context=None)
+.. method:: loop.create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)
Schedule the execution of :ref:`coroutine <coroutine>` *coro*.
Return a :class:`Task` object.
@@ -370,6 +370,10 @@ Creating Futures and Tasks
for interoperability. In this case, the result type is a subclass
of :class:`Task`.
+ The full function signature is largely the same as that of the
+ :class:`Task` constructor (or factory) - all of the keyword arguments to
+ this function are passed through to that interface.
+
If the *name* argument is provided and not ``None``, it is set as
the name of the task using :meth:`Task.set_name`.
@@ -377,12 +381,27 @@ Creating Futures and Tasks
custom :class:`contextvars.Context` for the *coro* to run in.
The current context copy is created when no *context* is provided.
+ An optional keyword-only *eager_start* argument allows specifying
+ if the task should execute eagerly during the call to create_task,
+ or be scheduled later. If *eager_start* is not passed the mode set
+ by :meth:`loop.set_task_factory` will be used.
+
.. versionchanged:: 3.8
Added the *name* parameter.
.. versionchanged:: 3.11
Added the *context* parameter.
+ .. versionchanged:: 3.13.3
+ Added ``kwargs`` which passes on arbitrary extra parameters, including ``name`` and ``context``.
+
+ .. versionchanged:: 3.13.4
+ Rolled back the change that passes on *name* and *context* (if it is None),
+ while still passing on other arbitrary keyword arguments (to avoid breaking backwards compatibility with 3.13.3).
+
+ .. versionchanged:: 3.14
+ All *kwargs* are now passed on. The *eager_start* parameter works with eager task factories.
+
.. method:: loop.set_task_factory(factory)
Set a task factory that will be used by
@@ -394,6 +413,16 @@ Creating Futures and Tasks
event loop, and *coro* is a coroutine object. The callable
must pass on all *kwargs*, and return a :class:`asyncio.Task`-compatible object.
+ .. versionchanged:: 3.13.3
+ Required that all *kwargs* are passed on to :class:`asyncio.Task`.
+
+ .. versionchanged:: 3.13.4
+ *name* is no longer passed to task factories. *context* is no longer passed
+ to task factories if it is ``None``.
+
+ .. versionchanged:: 3.14
+ *name* and *context* are now unconditionally passed on to task factories again.
+
.. method:: loop.get_task_factory()
Return a task factory or ``None`` if the default one is in use.
diff --git a/Doc/library/asyncio-stream.rst b/Doc/library/asyncio-stream.rst
index c56166cabb9..90c90862ca1 100644
--- a/Doc/library/asyncio-stream.rst
+++ b/Doc/library/asyncio-stream.rst
@@ -171,13 +171,17 @@ and work with streams:
.. function:: start_unix_server(client_connected_cb, path=None, \
*, limit=None, sock=None, backlog=100, ssl=None, \
ssl_handshake_timeout=None, \
- ssl_shutdown_timeout=None, start_serving=True)
+ ssl_shutdown_timeout=None, start_serving=True, cleanup_socket=True)
:async:
Start a Unix socket server.
Similar to :func:`start_server` but works with Unix sockets.
+ If *cleanup_socket* is true then the Unix socket will automatically
+ be removed from the filesystem when the server is closed, unless the
+ socket has been replaced after the server has been created.
+
See also the documentation of :meth:`loop.create_unix_server`.
.. note::
@@ -198,6 +202,9 @@ and work with streams:
.. versionchanged:: 3.11
Added the *ssl_shutdown_timeout* parameter.
+ .. versionchanged:: 3.13
+ Added the *cleanup_socket* parameter.
+
StreamReader
============
diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index 59acce1990a..b19ffa8213a 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -238,18 +238,24 @@ Creating Tasks
-----------------------------------------------
-.. function:: create_task(coro, *, name=None, context=None)
+.. function:: create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)
Wrap the *coro* :ref:`coroutine <coroutine>` into a :class:`Task`
and schedule its execution. Return the Task object.
- If *name* is not ``None``, it is set as the name of the task using
- :meth:`Task.set_name`.
+ The full function signature is largely the same as that of the
+ :class:`Task` constructor (or factory) - all of the keyword arguments to
+ this function are passed through to that interface.
An optional keyword-only *context* argument allows specifying a
custom :class:`contextvars.Context` for the *coro* to run in.
The current context copy is created when no *context* is provided.
+ An optional keyword-only *eager_start* argument allows specifying
+ if the task should execute eagerly during the call to create_task,
+ or be scheduled later. If *eager_start* is not passed the mode set
+ by :meth:`loop.set_task_factory` will be used.
+
The task is executed in the loop returned by :func:`get_running_loop`,
:exc:`RuntimeError` is raised if there is no running loop in
current thread.
@@ -290,6 +296,9 @@ Creating Tasks
.. versionchanged:: 3.11
Added the *context* parameter.
+ .. versionchanged:: 3.14
+ Added the *eager_start* parameter by passing on all *kwargs*.
+
Task Cancellation
=================
@@ -330,7 +339,7 @@ and reliable way to wait for all tasks in the group to finish.
.. versionadded:: 3.11
- .. method:: create_task(coro, *, name=None, context=None)
+ .. method:: create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)
Create a task in this task group.
The signature matches that of :func:`asyncio.create_task`.
@@ -342,6 +351,10 @@ and reliable way to wait for all tasks in the group to finish.
Close the given coroutine if the task group is not active.
+ .. versionchanged:: 3.14
+
+ Passes on all *kwargs* to :meth:`loop.create_task`
+
Example::
async def main():
diff --git a/Doc/library/audit_events.rst b/Doc/library/audit_events.rst
index d2377a38d78..73a58092024 100644
--- a/Doc/library/audit_events.rst
+++ b/Doc/library/audit_events.rst
@@ -48,5 +48,5 @@ public API of CPython:
| ctypes.PyObj_FromPtr | ``obj`` |
+----------------------------+-------------------------------------------+
-.. versionadded:: next
+.. versionadded:: 3.14
The ``_posixsubprocess.fork_exec`` internal audit event.
diff --git a/Doc/library/base64.rst b/Doc/library/base64.rst
index 834ab2536e6..529a7242443 100644
--- a/Doc/library/base64.rst
+++ b/Doc/library/base64.rst
@@ -15,14 +15,9 @@
This module provides functions for encoding binary data to printable
ASCII characters and decoding such encodings back to binary data.
-It provides encoding and decoding functions for the encodings specified in
-:rfc:`4648`, which defines the Base16, Base32, and Base64 algorithms,
-and for the de-facto standard Ascii85 and Base85 encodings.
-
-The :rfc:`4648` encodings are suitable for encoding binary data so that it can be
-safely sent by email, used as parts of URLs, or included as part of an HTTP
-POST request. The encoding algorithm is not the same as the
-:program:`uuencode` program.
+This includes the :ref:`encodings specified in <base64-rfc-4648>`
+:rfc:`4648` (Base64, Base32 and Base16)
+and the non-standard :ref:`Base85 encodings <base64-base-85>`.
There are two interfaces provided by this module. The modern interface
supports encoding :term:`bytes-like objects <bytes-like object>` to ASCII
@@ -30,7 +25,7 @@ supports encoding :term:`bytes-like objects <bytes-like object>` to ASCII
strings containing ASCII to :class:`bytes`. Both base-64 alphabets
defined in :rfc:`4648` (normal, and URL- and filesystem-safe) are supported.
-The legacy interface does not support decoding from strings, but it does
+The :ref:`legacy interface <base64-legacy>` does not support decoding from strings, but it does
provide functions for encoding and decoding to and from :term:`file objects
<file object>`. It only supports the Base64 standard alphabet, and it adds
newlines every 76 characters as per :rfc:`2045`. Note that if you are looking
@@ -46,7 +41,15 @@ package instead.
Any :term:`bytes-like objects <bytes-like object>` are now accepted by all
encoding and decoding functions in this module. Ascii85/Base85 support added.
-The modern interface provides:
+
+.. _base64-rfc-4648:
+
+RFC 4648 Encodings
+------------------
+
+The :rfc:`4648` encodings are suitable for encoding binary data so that it can be
+safely sent by email, used as parts of URLs, or included as part of an HTTP
+POST request.
.. function:: b64encode(s, altchars=None)
@@ -181,6 +184,26 @@ The modern interface provides:
incorrectly padded or if there are non-alphabet characters present in the
input.
+.. _base64-base-85:
+
+Base85 Encodings
+-----------------
+
+Base85 encoding is not formally specified but rather a de facto standard,
+thus different systems perform the encoding differently.
+
+The :func:`a85encode` and :func:`b85encode` functions in this module are two implementations of
+the de facto standard. You should call the function with the Base85
+implementation used by the software you intend to work with.
+
+The two functions present in this module differ in how they handle the following:
+
+* Whether to include enclosing ``<~`` and ``~>`` markers
+* Whether to include newline characters
+* The set of ASCII characters used for encoding
+* Handling of null bytes
+
+Refer to the documentation of the individual functions for more information.
.. function:: a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False)
@@ -262,7 +285,10 @@ The modern interface provides:
.. versionadded:: 3.13
-The legacy interface:
+.. _base64-legacy:
+
+Legacy Interface
+----------------
.. function:: decode(input, output)
diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst
index 39090e36ed9..b292d828841 100644
--- a/Doc/library/calendar.rst
+++ b/Doc/library/calendar.rst
@@ -251,7 +251,7 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
3) specifies the number of months per row. *css* is the name for the
cascading style sheet to be used. :const:`None` can be passed if no style
sheet should be used. *encoding* specifies the encoding to be used for the
- output (defaulting to the system default encoding).
+ output (defaulting to ``'utf-8'``).
.. method:: formatmonthname(theyear, themonth, withyear=True)
diff --git a/Doc/library/cmdline.rst b/Doc/library/cmdline.rst
index f7ae2133a70..16c67ddbf7c 100644
--- a/Doc/library/cmdline.rst
+++ b/Doc/library/cmdline.rst
@@ -1,3 +1,5 @@
+.. _library-cmdline:
+
++++++++++++++++++++++++++++++++++++
Modules command-line interface (CLI)
++++++++++++++++++++++++++++++++++++
diff --git a/Doc/library/code.rst b/Doc/library/code.rst
index 8f7692df9fb..52587c4dd8f 100644
--- a/Doc/library/code.rst
+++ b/Doc/library/code.rst
@@ -22,6 +22,12 @@ build applications which provide an interactive interpreter prompt.
it defaults to a newly created dictionary with key ``'__name__'`` set to
``'__console__'`` and key ``'__doc__'`` set to ``None``.
+ Note that functions and classes objects created under an
+ :class:`!InteractiveInterpreter` instance will belong to the namespace
+ specified by *locals*.
+ They are only pickleable if *locals* is the namespace of an existing
+ module.
+
.. class:: InteractiveConsole(locals=None, filename="<console>", local_exit=False)
diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst
index 14f6547e4e0..b231fa568cf 100644
--- a/Doc/library/codecs.rst
+++ b/Doc/library/codecs.rst
@@ -53,6 +53,14 @@ any codec:
:exc:`UnicodeDecodeError`). Refer to :ref:`codec-base-classes` for more
information on codec error handling.
+.. function:: charmap_build(string)
+
+ Return a mapping suitable for encoding with a custom single-byte encoding.
+ Given a :class:`str` *string* of up to 256 characters representing a
+ decoding table, returns either a compact internal mapping object
+ ``EncodingMap`` or a :class:`dictionary <dict>` mapping character ordinals
+ to byte values. Raises a :exc:`TypeError` on invalid input.
+
The full details for each codec can also be looked up directly:
.. function:: lookup(encoding, /)
@@ -208,7 +216,7 @@ wider range of codecs when working with binary files:
.. versionchanged:: 3.11
The ``'U'`` mode has been removed.
- .. deprecated:: next
+ .. deprecated:: 3.14
:func:`codecs.open` has been superseded by :func:`open`.
diff --git a/Doc/library/compileall.rst b/Doc/library/compileall.rst
index c42288419c4..ebbbf857e71 100644
--- a/Doc/library/compileall.rst
+++ b/Doc/library/compileall.rst
@@ -56,11 +56,18 @@ compile Python sources.
executed.
.. option:: -s strip_prefix
+
+ Remove the given prefix from paths recorded in the ``.pyc`` files.
+ Paths are made relative to the prefix.
+
+ This option can be used with ``-p`` but not with ``-d``.
+
.. option:: -p prepend_prefix
- Remove (``-s``) or append (``-p``) the given prefix of paths
- recorded in the ``.pyc`` files.
- Cannot be combined with ``-d``.
+ Prepend the given prefix to paths recorded in the ``.pyc`` files.
+ Use ``-p /`` to make the paths absolute.
+
+ This option can be used with ``-s`` but not with ``-d``.
.. option:: -x regex
diff --git a/Doc/library/compression.rst b/Doc/library/compression.rst
new file mode 100644
index 00000000000..618b4a3c2bd
--- /dev/null
+++ b/Doc/library/compression.rst
@@ -0,0 +1,18 @@
+The :mod:`!compression` package
+===============================
+
+.. versionadded:: 3.14
+
+The :mod:`!compression` package contains the canonical compression modules
+containing interfaces to several different compression algorithms. Some of
+these modules have historically been available as separate modules; those will
+continue to be available under their original names for compatibility reasons,
+and will not be removed without a deprecation cycle. The use of modules in
+:mod:`!compression` is encouraged where practical.
+
+* :mod:`!compression.bz2` -- Re-exports :mod:`bz2`
+* :mod:`!compression.gzip` -- Re-exports :mod:`gzip`
+* :mod:`!compression.lzma` -- Re-exports :mod:`lzma`
+* :mod:`!compression.zlib` -- Re-exports :mod:`zlib`
+* :mod:`compression.zstd` -- Wrapper for the Zstandard compression library
+
diff --git a/Doc/library/compression.zstd.rst b/Doc/library/compression.zstd.rst
new file mode 100644
index 00000000000..a901403621b
--- /dev/null
+++ b/Doc/library/compression.zstd.rst
@@ -0,0 +1,897 @@
+:mod:`!compression.zstd` --- Compression compatible with the Zstandard format
+=============================================================================
+
+.. module:: compression.zstd
+ :synopsis: Low-level interface to compression and decompression routines in
+ the zstd library.
+
+.. versionadded:: 3.14
+
+**Source code:** :source:`Lib/compression/zstd/__init__.py`
+
+--------------
+
+This module provides classes and functions for compressing and decompressing
+data using the Zstandard (or *zstd*) compression algorithm. The
+`zstd manual <https://facebook.github.io/zstd/doc/api_manual_latest.html>`__
+describes Zstandard as "a fast lossless compression algorithm, targeting
+real-time compression scenarios at zlib-level and better compression ratios."
+Also included is a file interface that supports reading and writing the
+contents of ``.zst`` files created by the :program:`zstd` utility, as well as
+raw zstd compressed streams.
+
+The :mod:`!compression.zstd` module contains:
+
+* The :func:`.open` function and :class:`ZstdFile` class for reading and
+ writing compressed files.
+* The :class:`ZstdCompressor` and :class:`ZstdDecompressor` classes for
+ incremental (de)compression.
+* The :func:`compress` and :func:`decompress` functions for one-shot
+ (de)compression.
+* The :func:`train_dict` and :func:`finalize_dict` functions and the
+ :class:`ZstdDict` class to train and manage Zstandard dictionaries.
+* The :class:`CompressionParameter`, :class:`DecompressionParameter`, and
+ :class:`Strategy` classes for setting advanced (de)compression parameters.
+
+
+Exceptions
+----------
+
+.. exception:: ZstdError
+
+ This exception is raised when an error occurs during compression or
+ decompression, or while initializing the (de)compressor state.
+
+
+Reading and writing compressed files
+------------------------------------
+
+.. function:: open(file, /, mode='rb', *, level=None, options=None, \
+ zstd_dict=None, encoding=None, errors=None, newline=None)
+
+ Open a Zstandard-compressed file in binary or text mode, returning a
+ :term:`file object`.
+
+ The *file* argument can be either a file name (given as a
+ :class:`str`, :class:`bytes` or :term:`path-like <path-like object>`
+ object), in which case the named file is opened, or it can be an existing
+ file object to read from or write to.
+
+ The mode argument can be either ``'rb'`` for reading (default), ``'wb'`` for
+ overwriting, ``'ab'`` for appending, or ``'xb'`` for exclusive creation.
+ These can equivalently be given as ``'r'``, ``'w'``, ``'a'``, and ``'x'``
+ respectively. You may also open in text mode with ``'rt'``, ``'wt'``,
+ ``'at'``, and ``'xt'`` respectively.
+
+ When reading, the *options* argument can be a dictionary providing advanced
+ decompression parameters; see :class:`DecompressionParameter` for detailed
+ information about supported
+ parameters. The *zstd_dict* argument is a :class:`ZstdDict` instance to be
+ used during decompression. When reading, if the *level*
+ argument is not None, a :exc:`!TypeError` will be raised.
+
+ When writing, the *options* argument can be a dictionary
+ providing advanced decompression parameters; see
+ :class:`CompressionParameter` for detailed information about supported
+ parameters. The *level* argument is the compression level to use when
+ writing compressed data. Only one of *level* or *options* may be non-None.
+ The *zstd_dict* argument is a :class:`ZstdDict` instance to be used during
+ compression.
+
+ In binary mode, this function is equivalent to the :class:`ZstdFile`
+ constructor: ``ZstdFile(file, mode, ...)``. In this case, the
+ *encoding*, *errors*, and *newline* parameters must not be provided.
+
+ In text mode, a :class:`ZstdFile` object is created, and wrapped in an
+ :class:`io.TextIOWrapper` instance with the specified encoding, error
+ handling behavior, and line endings.
+
+
+.. class:: ZstdFile(file, /, mode='rb', *, level=None, options=None, \
+ zstd_dict=None)
+
+ Open a Zstandard-compressed file in binary mode.
+
+ A :class:`ZstdFile` can wrap an already-open :term:`file object`, or operate
+ directly on a named file. The *file* argument specifies either the file
+ object to wrap, or the name of the file to open (as a :class:`str`,
+ :class:`bytes` or :term:`path-like <path-like object>` object). If
+ wrapping an existing file object, the wrapped file will not be closed when
+ the :class:`ZstdFile` is closed.
+
+ The *mode* argument can be either ``'rb'`` for reading (default), ``'wb'``
+ for overwriting, ``'xb'`` for exclusive creation, or ``'ab'`` for appending.
+ These can equivalently be given as ``'r'``, ``'w'``, ``'x'`` and ``'a'``
+ respectively.
+
+ If *file* is a file object (rather than an actual file name), a mode of
+ ``'w'`` does not truncate the file, and is instead equivalent to ``'a'``.
+
+ When reading, the *options* argument can be a dictionary
+ providing advanced decompression parameters; see
+ :class:`DecompressionParameter` for detailed information about supported
+ parameters. The *zstd_dict* argument is a :class:`ZstdDict` instance to be
+ used during decompression. When reading, if the *level*
+ argument is not None, a :exc:`!TypeError` will be raised.
+
+ When writing, the *options* argument can be a dictionary
+ providing advanced decompression parameters; see
+ :class:`CompressionParameter` for detailed information about supported
+ parameters. The *level* argument is the compression level to use when
+ writing compressed data. Only one of *level* or *options* may be passed. The
+ *zstd_dict* argument is a :class:`ZstdDict` instance to be used during
+ compression.
+
+ :class:`!ZstdFile` supports all the members specified by
+ :class:`io.BufferedIOBase`, except for :meth:`~io.BufferedIOBase.detach`
+ and :meth:`~io.IOBase.truncate`.
+ Iteration and the :keyword:`with` statement are supported.
+
+ The following method and attributes are also provided:
+
+ .. method:: peek(size=-1)
+
+ Return buffered data without advancing the file position. At least one
+ byte of data will be returned, unless EOF has been reached. The exact
+ number of bytes returned is unspecified (the *size* argument is ignored).
+
+ .. note:: While calling :meth:`peek` does not change the file position of
+ the :class:`ZstdFile`, it may change the position of the underlying
+ file object (for example, if the :class:`ZstdFile` was constructed by
+ passing a file object for *file*).
+
+ .. attribute:: mode
+
+ ``'rb'`` for reading and ``'wb'`` for writing.
+
+ .. attribute:: name
+
+ The name of the Zstandard file. Equivalent to the :attr:`~io.FileIO.name`
+ attribute of the underlying :term:`file object`.
+
+
+Compressing and decompressing data in memory
+--------------------------------------------
+
+.. function:: compress(data, level=None, options=None, zstd_dict=None)
+
+ Compress *data* (a :term:`bytes-like object`), returning the compressed
+ data as a :class:`bytes` object.
+
+ The *level* argument is an integer controlling the level of
+ compression. *level* is an alternative to setting
+ :attr:`CompressionParameter.compression_level` in *options*. Use
+ :meth:`~CompressionParameter.bounds` on
+ :attr:`~CompressionParameter.compression_level` to get the values that can
+ be passed for *level*. If advanced compression options are needed, the
+ *level* argument must be omitted and in the *options* dictionary the
+ :attr:`!CompressionParameter.compression_level` parameter should be set.
+
+ The *options* argument is a Python dictionary containing advanced
+ compression parameters. The valid keys and values for compression parameters
+ are documented as part of the :class:`CompressionParameter` documentation.
+
+ The *zstd_dict* argument is an instance of :class:`ZstdDict`
+ containing trained data to improve compression efficiency. The
+ function :func:`train_dict` can be used to generate a Zstandard dictionary.
+
+
+.. function:: decompress(data, zstd_dict=None, options=None)
+
+ Decompress *data* (a :term:`bytes-like object`), returning the uncompressed
+ data as a :class:`bytes` object.
+
+ The *options* argument is a Python dictionary containing advanced
+ decompression parameters. The valid keys and values for compression
+ parameters are documented as part of the :class:`DecompressionParameter`
+ documentation.
+
+ The *zstd_dict* argument is an instance of :class:`ZstdDict`
+ containing trained data used during compression. This must be
+ the same Zstandard dictionary used during compression.
+
+ If *data* is the concatenation of multiple distinct compressed frames,
+ decompress all of these frames, and return the concatenation of the results.
+
+
+.. class:: ZstdCompressor(level=None, options=None, zstd_dict=None)
+
+ Create a compressor object, which can be used to compress data
+ incrementally.
+
+ For a more convenient way of compressing a single chunk of data, see the
+ module-level function :func:`compress`.
+
+ The *level* argument is an integer controlling the level of
+ compression. *level* is an alternative to setting
+ :attr:`CompressionParameter.compression_level` in *options*. Use
+ :meth:`~CompressionParameter.bounds` on
+ :attr:`~CompressionParameter.compression_level` to get the values that can
+ be passed for *level*. If advanced compression options are needed, the
+ *level* argument must be omitted and in the *options* dictionary the
+ :attr:`!CompressionParameter.compression_level` parameter should be set.
+
+ The *options* argument is a Python dictionary containing advanced
+ compression parameters. The valid keys and values for compression parameters
+ are documented as part of the :class:`CompressionParameter` documentation.
+
+ The *zstd_dict* argument is an optional instance of :class:`ZstdDict`
+ containing trained data to improve compression efficiency. The
+ function :func:`train_dict` can be used to generate a Zstandard dictionary.
+
+
+ .. method:: compress(data, mode=ZstdCompressor.CONTINUE)
+
+ Compress *data* (a :term:`bytes-like object`), returning a :class:`bytes`
+ object with compressed data if possible, or otherwise an empty
+ :class:`!bytes` object. Some of *data* may be buffered internally, for
+ use in later calls to :meth:`!compress` and :meth:`~.flush`. The returned
+ data should be concatenated with the output of any previous calls to
+ :meth:`~.compress`.
+
+ The *mode* argument is a :class:`ZstdCompressor` attribute, either
+ :attr:`~.CONTINUE`, :attr:`~.FLUSH_BLOCK`,
+ or :attr:`~.FLUSH_FRAME`.
+
+ When all data has been provided to the compressor, call the
+ :meth:`~.flush` method to finish the compression process. If
+ :meth:`~.compress` is called with *mode* set to :attr:`~.FLUSH_FRAME`,
+ :meth:`~.flush` should not be called, as it would write out a new empty
+ frame.
+
+ .. method:: flush(mode=ZstdCompressor.FLUSH_FRAME)
+
+ Finish the compression process, returning a :class:`bytes` object
+ containing any data stored in the compressor's internal buffers.
+
+ The *mode* argument is a :class:`ZstdCompressor` attribute, either
+ :attr:`~.FLUSH_BLOCK`, or :attr:`~.FLUSH_FRAME`.
+
+ .. method:: set_pledged_input_size(size)
+
+ Specify the amount of uncompressed data *size* that will be provided for
+ the next frame. *size* will be written into the frame header of the next
+ frame unless :attr:`CompressionParameter.content_size_flag` is ``False``
+ or ``0``. A size of ``0`` means that the frame is empty. If *size* is
+ ``None``, the frame header will omit the frame size. Frames that include
+ the uncompressed data size require less memory to decompress, especially
+ at higher compression levels.
+
+ If :attr:`last_mode` is not :attr:`FLUSH_FRAME`, a
+ :exc:`ValueError` is raised as the compressor is not at the start of
+ a frame. If the pledged size does not match the actual size of data
+ provided to :meth:`.compress`, future calls to :meth:`!compress` or
+ :meth:`flush` may raise :exc:`ZstdError` and the last chunk of data may
+ be lost.
+
+ After :meth:`flush` or :meth:`.compress` are called with mode
+ :attr:`FLUSH_FRAME`, the next frame will not include the frame size into
+ the header unless :meth:`!set_pledged_input_size` is called again.
+
+ .. attribute:: CONTINUE
+
+ Collect more data for compression, which may or may not generate output
+ immediately. This mode optimizes the compression ratio by maximizing the
+ amount of data per block and frame.
+
+ .. attribute:: FLUSH_BLOCK
+
+ Complete and write a block to the data stream. The data returned so far
+ can be immediately decompressed. Past data can still be referenced in
+ future blocks generated by calls to :meth:`~.compress`,
+ improving compression.
+
+ .. attribute:: FLUSH_FRAME
+
+ Complete and write out a frame. Future data provided to
+ :meth:`~.compress` will be written into a new frame and
+ *cannot* reference past data.
+
+ .. attribute:: last_mode
+
+ The last mode passed to either :meth:`~.compress` or :meth:`~.flush`.
+ The value can be one of :attr:`~.CONTINUE`, :attr:`~.FLUSH_BLOCK`, or
+ :attr:`~.FLUSH_FRAME`. The initial value is :attr:`~.FLUSH_FRAME`,
+ signifying that the compressor is at the start of a new frame.
+
+
+.. class:: ZstdDecompressor(zstd_dict=None, options=None)
+
+ Create a decompressor object, which can be used to decompress data
+ incrementally.
+
+ For a more convenient way of decompressing an entire compressed stream at
+ once, see the module-level function :func:`decompress`.
+
+ The *options* argument is a Python dictionary containing advanced
+ decompression parameters. The valid keys and values for compression
+ parameters are documented as part of the :class:`DecompressionParameter`
+ documentation.
+
+ The *zstd_dict* argument is an instance of :class:`ZstdDict`
+ containing trained data used during compression. This must be
+ the same Zstandard dictionary used during compression.
+
+ .. note::
+ This class does not transparently handle inputs containing multiple
+ compressed frames, unlike the :func:`decompress` function and
+ :class:`ZstdFile` class. To decompress a multi-frame input, you should
+ use :func:`decompress`, :class:`ZstdFile` if working with a
+ :term:`file object`, or multiple :class:`!ZstdDecompressor` instances.
+
+ .. method:: decompress(data, max_length=-1)
+
+ Decompress *data* (a :term:`bytes-like object`), returning
+ uncompressed data as bytes. Some of *data* may be buffered
+ internally, for use in later calls to :meth:`!decompress`.
+ The returned data should be concatenated with the output of any previous
+ calls to :meth:`!decompress`.
+
+ 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
+ :meth:`~.decompress` may provide *data* as ``b''`` to obtain
+ more of the output.
+
+ If all of the input data was decompressed and returned (either
+ because this was less than *max_length* bytes, or because
+ *max_length* was negative), the :attr:`~.needs_input` attribute
+ will be set to ``True``.
+
+ Attempting to decompress data after the end of a frame will raise a
+ :exc:`ZstdError`. Any data found after the end of the frame is ignored
+ and saved in the :attr:`~.unused_data` attribute.
+
+ .. attribute:: eof
+
+ ``True`` if the end-of-stream marker has been reached.
+
+ .. attribute:: unused_data
+
+ Data found after the end of the compressed stream.
+
+ Before the end of the stream is reached, this will be ``b''``.
+
+ .. attribute:: needs_input
+
+ ``False`` if the :meth:`.decompress` method can provide more
+ decompressed data before requiring new compressed input.
+
+
+Zstandard dictionaries
+----------------------
+
+
+.. function:: train_dict(samples, dict_size)
+
+ Train a Zstandard dictionary, returning a :class:`ZstdDict` instance.
+ Zstandard dictionaries enable more efficient compression of smaller sizes
+ of data, which is traditionally difficult to compress due to less
+ repetition. If you are compressing multiple similar groups of data (such as
+ similar files), Zstandard dictionaries can improve compression ratios and
+ speed significantly.
+
+ The *samples* argument (an iterable of :class:`bytes` objects), is the
+ population of samples used to train the Zstandard dictionary.
+
+ The *dict_size* argument, an integer, is the maximum size (in bytes) the
+ Zstandard dictionary should be. The Zstandard documentation suggests an
+ absolute maximum of no more than 100 KB, but the maximum can often be smaller
+ depending on the data. Larger dictionaries generally slow down compression,
+ but improve compression ratios. Smaller dictionaries lead to faster
+ compression, but reduce the compression ratio.
+
+
+.. function:: finalize_dict(zstd_dict, /, samples, dict_size, level)
+
+ An advanced function for converting a "raw content" Zstandard dictionary into
+ a regular Zstandard dictionary. "Raw content" dictionaries are a sequence of
+ bytes that do not need to follow the structure of a normal Zstandard
+ dictionary.
+
+ The *zstd_dict* argument is a :class:`ZstdDict` instance with
+ the :attr:`~ZstdDict.dict_content` containing the raw dictionary contents.
+
+ The *samples* argument (an iterable of :class:`bytes` objects), contains
+ sample data for generating the Zstandard dictionary.
+
+ The *dict_size* argument, an integer, is the maximum size (in bytes) the
+ Zstandard dictionary should be. See :func:`train_dict` for
+ suggestions on the maximum dictionary size.
+
+ The *level* argument (an integer) is the compression level expected to be
+ passed to the compressors using this dictionary. The dictionary information
+ varies for each compression level, so tuning for the proper compression
+ level can make compression more efficient.
+
+
+.. class:: ZstdDict(dict_content, /, *, is_raw=False)
+
+ A wrapper around Zstandard dictionaries. Dictionaries can be used to improve
+ the compression of many small chunks of data. Use :func:`train_dict` if you
+ need to train a new dictionary from sample data.
+
+ The *dict_content* argument (a :term:`bytes-like object`), is the already
+ trained dictionary information.
+
+ The *is_raw* argument, a boolean, is an advanced parameter controlling the
+ meaning of *dict_content*. ``True`` means *dict_content* is a "raw content"
+ dictionary, without any format restrictions. ``False`` means *dict_content*
+ is an ordinary Zstandard dictionary, created from Zstandard functions,
+ for example, :func:`train_dict` or the external :program:`zstd` CLI.
+
+ When passing a :class:`!ZstdDict` to a function, the
+ :attr:`!as_digested_dict` and :attr:`!as_undigested_dict` attributes can
+ control how the dictionary is loaded by passing them as the ``zstd_dict``
+ argument, for example, ``compress(data, zstd_dict=zd.as_digested_dict)``.
+ Digesting a dictionary is a costly operation that occurs when loading a
+ Zstandard dictionary. When making multiple calls to compression or
+ decompression, passing a digested dictionary will reduce the overhead of
+ loading the dictionary.
+
+ .. list-table:: Difference for compression
+ :widths: 10 14 10
+ :header-rows: 1
+
+ * -
+ - Digested dictionary
+ - Undigested dictionary
+ * - Advanced parameters of the compressor which may be overridden by
+ the dictionary's parameters
+ - ``window_log``, ``hash_log``, ``chain_log``, ``search_log``,
+ ``min_match``, ``target_length``, ``strategy``,
+ ``enable_long_distance_matching``, ``ldm_hash_log``,
+ ``ldm_min_match``, ``ldm_bucket_size_log``, ``ldm_hash_rate_log``,
+ and some non-public parameters.
+ - None
+ * - :class:`!ZstdDict` internally caches the dictionary
+ - Yes. It's faster when loading a digested dictionary again with the
+ same compression level.
+ - No. If you wish to load an undigested dictionary multiple times,
+ consider reusing a compressor object.
+
+ If passing a :class:`!ZstdDict` without any attribute, an undigested
+ dictionary is passed by default when compressing and a digested dictionary
+ is generated if necessary and passed by default when decompressing.
+
+ .. attribute:: dict_content
+
+ The content of the Zstandard dictionary, a ``bytes`` object. It's the
+ same as the *dict_content* argument in the ``__init__`` method. It can
+ be used with other programs, such as the ``zstd`` CLI program.
+
+ .. attribute:: dict_id
+
+ Identifier of the Zstandard dictionary, a non-negative int value.
+
+ Non-zero means the dictionary is ordinary, created by Zstandard
+ functions and following the Zstandard format.
+
+ ``0`` means a "raw content" dictionary, free of any format restriction,
+ used for advanced users.
+
+ .. note::
+
+ The meaning of ``0`` for :attr:`!ZstdDict.dict_id` is different
+ from the ``dictionary_id`` attribute to the :func:`get_frame_info`
+ function.
+
+ .. attribute:: as_digested_dict
+
+ Load as a digested dictionary.
+
+ .. attribute:: as_undigested_dict
+
+ Load as an undigested dictionary.
+
+
+Advanced parameter control
+--------------------------
+
+.. class:: CompressionParameter()
+
+ An :class:`~enum.IntEnum` containing the advanced compression parameter
+ keys that can be used when compressing data.
+
+ The :meth:`~.bounds` method can be used on any attribute to get the valid
+ values for that parameter.
+
+ Parameters are optional; any omitted parameter will have it's value selected
+ automatically.
+
+ Example getting the lower and upper bound of :attr:`~.compression_level`::
+
+ lower, upper = CompressionParameter.compression_level.bounds()
+
+ Example setting the :attr:`~.window_log` to the maximum size::
+
+ _lower, upper = CompressionParameter.window_log.bounds()
+ options = {CompressionParameter.window_log: upper}
+ compress(b'venezuelan beaver cheese', options=options)
+
+ .. method:: bounds()
+
+ Return the tuple of int bounds, ``(lower, upper)``, of a compression
+ parameter. This method should be called on the attribute you wish to
+ retrieve the bounds of. For example, to get the valid values for
+ :attr:`~.compression_level`, one may check the result of
+ ``CompressionParameter.compression_level.bounds()``.
+
+ Both the lower and upper bounds are inclusive.
+
+ .. attribute:: compression_level
+
+ A high-level means of setting other compression parameters that affect
+ the speed and ratio of compressing data.
+
+ Regular compression levels are greater than ``0``. Values greater than
+ ``20`` are considered "ultra" compression and require more memory than
+ other levels. Negative values can be used to trade off faster compression
+ for worse compression ratios.
+
+ Setting the level to zero uses :attr:`COMPRESSION_LEVEL_DEFAULT`.
+
+ .. attribute:: window_log
+
+ Maximum allowed back-reference distance the compressor can use when
+ compressing data, expressed as power of two, ``1 << window_log`` bytes.
+ This parameter greatly influences the memory usage of compression. Higher
+ values require more memory but gain better compression values.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: hash_log
+
+ Size of the initial probe table, as a power of two. The resulting memory
+ usage is ``1 << (hash_log+2)`` bytes. Larger tables improve compression
+ ratio of strategies <= :attr:`~Strategy.dfast`, and improve compression
+ speed of strategies > :attr:`~Strategy.dfast`.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: chain_log
+
+ Size of the multi-probe search table, as a power of two. The resulting
+ memory usage is ``1 << (chain_log+2)`` bytes. Larger tables result in
+ better and slower compression. This parameter has no effect for the
+ :attr:`~Strategy.fast` strategy. It's still useful when using
+ :attr:`~Strategy.dfast` strategy, in which case it defines a secondary
+ probe table.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: search_log
+
+ Number of search attempts, as a power of two. More attempts result in
+ better and slower compression. This parameter is useless for
+ :attr:`~Strategy.fast` and :attr:`~Strategy.dfast` strategies.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: min_match
+
+ Minimum size of searched matches. Larger values increase compression and
+ decompression speed, but decrease ratio. Note that Zstandard can still
+ find matches of smaller size, it just tweaks its search algorithm to look
+ for this size and larger. For all strategies < :attr:`~Strategy.btopt`,
+ the effective minimum is ``4``; for all strategies
+ > :attr:`~Strategy.fast`, the effective maximum is ``6``.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: target_length
+
+ The impact of this field depends on the selected :class:`Strategy`.
+
+ For strategies :attr:`~Strategy.btopt`, :attr:`~Strategy.btultra` and
+ :attr:`~Strategy.btultra2`, the value is the length of a match
+ considered "good enough" to stop searching. Larger values make
+ compression ratios better, but compresses slower.
+
+ For strategy :attr:`~Strategy.fast`, it is the distance between match
+ sampling. Larger values make compression faster, but with a worse
+ compression ratio.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: strategy
+
+ The higher the value of selected strategy, the more complex the
+ compression technique used by zstd, resulting in higher compression
+ ratios but slower compression.
+
+ .. seealso:: :class:`Strategy`
+
+ .. attribute:: enable_long_distance_matching
+
+ Long distance matching can be used to improve compression for large
+ inputs by finding large matches at greater distances. It increases memory
+ usage and window size.
+
+ ``True`` or ``1`` enable long distance matching while ``False`` or ``0``
+ disable it.
+
+ Enabling this parameter increases default
+ :attr:`~CompressionParameter.window_log` to 128 MiB except when expressly
+ set to a different value. This setting is enabled by default if
+ :attr:`!window_log` >= 128 MiB and the compression
+ strategy >= :attr:`~Strategy.btopt` (compression level 16+).
+
+ .. attribute:: ldm_hash_log
+
+ Size of the table for long distance matching, as a power of two. Larger
+ values increase memory usage and compression ratio, but decrease
+ compression speed.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: ldm_min_match
+
+ Minimum match size for long distance matcher. Larger or too small values
+ can often decrease the compression ratio.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: ldm_bucket_size_log
+
+ Log size of each bucket in the long distance matcher hash table for
+ collision resolution. Larger values improve collision resolution but
+ decrease compression speed.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: ldm_hash_rate_log
+
+ Frequency of inserting/looking up entries into the long distance matcher
+ hash table. Larger values improve compression speed. Deviating far from
+ the default value will likely result in a compression ratio decrease.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: content_size_flag
+
+ Write the size of the data to be compressed into the Zstandard frame
+ header when known prior to compressing.
+
+ This flag only takes effect under the following scenarios:
+
+ * Calling :func:`compress` for one-shot compression
+ * Providing all of the data to be compressed in the frame in a single
+ :meth:`ZstdCompressor.compress` call, with the
+ :attr:`ZstdCompressor.FLUSH_FRAME` mode.
+ * Calling :meth:`ZstdCompressor.set_pledged_input_size` with the exact
+ amount of data that will be provided to the compressor prior to any
+ calls to :meth:`ZstdCompressor.compress` for the current frame.
+ :meth:`!ZstdCompressor.set_pledged_input_size` must be called for each
+ new frame.
+
+ All other compression calls may not write the size information into the
+ frame header.
+
+ ``True`` or ``1`` enable the content size flag while ``False`` or ``0``
+ disable it.
+
+ .. attribute:: checksum_flag
+
+ A four-byte checksum using XXHash64 of the uncompressed content is
+ written at the end of each frame. Zstandard's decompression code verifies
+ the checksum. If there is a mismatch a :class:`ZstdError` exception is
+ raised.
+
+ ``True`` or ``1`` enable checksum generation while ``False`` or ``0``
+ disable it.
+
+ .. attribute:: dict_id_flag
+
+ When compressing with a :class:`ZstdDict`, the dictionary's ID is written
+ into the frame header.
+
+ ``True`` or ``1`` enable storing the dictionary ID while ``False`` or
+ ``0`` disable it.
+
+ .. attribute:: nb_workers
+
+ Select how many threads will be spawned to compress in parallel. When
+ :attr:`!nb_workers` > 0, enables multi-threaded compression, a value of
+ ``1`` means "one-thread multi-threaded mode". More workers improve speed,
+ but also increase memory usage and slightly reduce compression ratio.
+
+ A value of zero disables multi-threading.
+
+ .. attribute:: job_size
+
+ Size of a compression job, in bytes. This value is enforced only when
+ :attr:`~CompressionParameter.nb_workers` >= 1. Each compression job is
+ completed in parallel, so this value can indirectly impact the number of
+ active threads.
+
+ A value of zero causes the value to be selected automatically.
+
+ .. attribute:: overlap_log
+
+ Sets how much data is reloaded from previous jobs (threads) for new jobs
+ to be used by the look behind window during compression. This value is
+ only used when :attr:`~CompressionParameter.nb_workers` >= 1. Acceptable
+ values vary from 0 to 9.
+
+ * 0 means dynamically set the overlap amount
+ * 1 means no overlap
+ * 9 means use a full window size from the previous job
+
+ Each increment halves/doubles the overlap size. "8" means an overlap of
+ ``window_size/2``, "7" means an overlap of ``window_size/4``, etc.
+
+.. class:: DecompressionParameter()
+
+ An :class:`~enum.IntEnum` containing the advanced decompression parameter
+ keys that can be used when decompressing data. Parameters are optional; any
+ omitted parameter will have it's value selected automatically.
+
+ The :meth:`~.bounds` method can be used on any attribute to get the valid
+ values for that parameter.
+
+ Example setting the :attr:`~.window_log_max` to the maximum size::
+
+ data = compress(b'Some very long buffer of bytes...')
+
+ _lower, upper = DecompressionParameter.window_log_max.bounds()
+
+ options = {DecompressionParameter.window_log_max: upper}
+ decompress(data, options=options)
+
+ .. method:: bounds()
+
+ Return the tuple of int bounds, ``(lower, upper)``, of a decompression
+ parameter. This method should be called on the attribute you wish to
+ retrieve the bounds of.
+
+ Both the lower and upper bounds are inclusive.
+
+ .. attribute:: window_log_max
+
+ The base-two logarithm of the maximum size of the window used during
+ decompression. This can be useful to limit the amount of memory used when
+ decompressing data. A larger maximum window size leads to faster
+ decompression.
+
+ A value of zero causes the value to be selected automatically.
+
+
+.. class:: Strategy()
+
+ An :class:`~enum.IntEnum` containing strategies for compression.
+ Higher-numbered strategies correspond to more complex and slower
+ compression.
+
+ .. note::
+
+ The values of attributes of :class:`!Strategy` are not necessarily stable
+ across zstd versions. Only the ordering of the attributes may be relied
+ upon. The attributes are listed below in order.
+
+ The following strategies are available:
+
+ .. attribute:: fast
+
+ .. attribute:: dfast
+
+ .. attribute:: greedy
+
+ .. attribute:: lazy
+
+ .. attribute:: lazy2
+
+ .. attribute:: btlazy2
+
+ .. attribute:: btopt
+
+ .. attribute:: btultra
+
+ .. attribute:: btultra2
+
+
+Miscellaneous
+-------------
+
+.. function:: get_frame_info(frame_buffer)
+
+ Retrieve a :class:`FrameInfo` object containing metadata about a Zstandard
+ frame. Frames contain metadata related to the compressed data they hold.
+
+
+.. class:: FrameInfo
+
+ Metadata related to a Zstandard frame.
+
+ .. attribute:: decompressed_size
+
+ The size of the decompressed contents of the frame.
+
+ .. attribute:: dictionary_id
+
+ An integer representing the Zstandard dictionary ID needed for
+ decompressing the frame. ``0`` means the dictionary ID was not
+ recorded in the frame header. This may mean that a Zstandard dictionary
+ is not needed, or that the ID of a required dictionary was not recorded.
+
+
+.. attribute:: COMPRESSION_LEVEL_DEFAULT
+
+ The default compression level for Zstandard: ``3``.
+
+
+.. attribute:: zstd_version_info
+
+ Version number of the runtime zstd library as a tuple of integers
+ (major, minor, release).
+
+
+Examples
+--------
+
+Reading in a compressed file:
+
+.. code-block:: python
+
+ from compression import zstd
+
+ with zstd.open("file.zst") as f:
+ file_content = f.read()
+
+Creating a compressed file:
+
+.. code-block:: python
+
+ from compression import zstd
+
+ data = b"Insert Data Here"
+ with zstd.open("file.zst", "w") as f:
+ f.write(data)
+
+Compressing data in memory:
+
+.. code-block:: python
+
+ from compression import zstd
+
+ data_in = b"Insert Data Here"
+ data_out = zstd.compress(data_in)
+
+Incremental compression:
+
+.. code-block:: python
+
+ from compression import zstd
+
+ comp = zstd.ZstdCompressor()
+ out1 = comp.compress(b"Some data\n")
+ out2 = comp.compress(b"Another piece of data\n")
+ out3 = comp.compress(b"Even more data\n")
+ out4 = comp.flush()
+ # Concatenate all the partial results:
+ result = b"".join([out1, out2, out3, out4])
+
+Writing compressed data to an already-open file:
+
+.. code-block:: python
+
+ from compression import zstd
+
+ with open("myfile", "wb") as f:
+ f.write(b"This data will not be compressed\n")
+ with zstd.open(f, "w") as zstf:
+ zstf.write(b"This *will* be compressed\n")
+ f.write(b"Not compressed\n")
+
+Creating a compressed file using compression parameters:
+
+.. code-block:: python
+
+ from compression import zstd
+
+ options = {
+ zstd.CompressionParameter.checksum_flag: 1
+ }
+ with zstd.open("file.zst", "w", options=options) as f:
+ f.write(b"Mind if I squeeze in?")
diff --git a/Doc/library/concurrency.rst b/Doc/library/concurrency.rst
index 5be1a1106b0..18f9443cbfe 100644
--- a/Doc/library/concurrency.rst
+++ b/Doc/library/concurrency.rst
@@ -18,6 +18,7 @@ multitasking). Here's an overview:
multiprocessing.shared_memory.rst
concurrent.rst
concurrent.futures.rst
+ concurrent.interpreters.rst
subprocess.rst
sched.rst
queue.rst
diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst
index 7efae9e628b..dd92765038c 100644
--- a/Doc/library/concurrent.futures.rst
+++ b/Doc/library/concurrent.futures.rst
@@ -6,8 +6,9 @@
.. versionadded:: 3.2
-**Source code:** :source:`Lib/concurrent/futures/thread.py`
-and :source:`Lib/concurrent/futures/process.py`
+**Source code:** :source:`Lib/concurrent/futures/thread.py`,
+:source:`Lib/concurrent/futures/process.py`,
+and :source:`Lib/concurrent/futures/interpreter.py`
--------------
@@ -264,7 +265,7 @@ Each worker's interpreter is isolated from all the other interpreters.
"Isolated" means each interpreter has its own runtime state and
operates completely independently. For example, if you redirect
:data:`sys.stdout` in one interpreter, it will not be automatically
-redirected any other interpreter. If you import a module in one
+redirected to any other interpreter. If you import a module in one
interpreter, it is not automatically imported in any other. You
would need to import the module separately in interpreter where
you need it. In fact, each module imported in an interpreter is
@@ -286,7 +287,7 @@ efficient alternative is to serialize with :mod:`pickle` and then send
the bytes over a shared :mod:`socket <socket>` or
:func:`pipe <os.pipe>`.
-.. class:: InterpreterPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=(), shared=None)
+.. class:: InterpreterPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())
A :class:`ThreadPoolExecutor` subclass that executes calls asynchronously
using a pool of at most *max_workers* threads. Each thread runs
@@ -304,20 +305,9 @@ the bytes over a shared :mod:`socket <socket>` or
interpreter.
.. note::
- Functions defined in the ``__main__`` module cannot be pickled
- and thus cannot be used.
-
- .. note::
The executor may replace uncaught exceptions from *initializer*
with :class:`~concurrent.futures.interpreter.ExecutionFailed`.
- The optional *shared* argument is a :class:`dict` of objects that all
- interpreters in the pool share. The *shared* items are added to each
- interpreter's ``__main__`` module. Not all objects are shareable.
- Shareable objects include the builtin singletons, :class:`str`
- and :class:`bytes`, and :class:`memoryview`. See :pep:`734`
- for more info.
-
Other caveats from parent :class:`ThreadPoolExecutor` apply here.
:meth:`~Executor.submit` and :meth:`~Executor.map` work like normal,
@@ -325,10 +315,6 @@ except the worker serializes the callable and arguments using
:mod:`pickle` when sending them to its interpreter. The worker
likewise serializes the return value when sending it back.
-.. note::
- Functions defined in the ``__main__`` module cannot be pickled
- and thus cannot be used.
-
When a worker's current task raises an uncaught exception, the worker
always tries to preserve the exception as-is. If that is successful
then it also sets the ``__cause__`` to a corresponding
diff --git a/Doc/library/concurrent.interpreters.rst b/Doc/library/concurrent.interpreters.rst
new file mode 100644
index 00000000000..524d505bcf1
--- /dev/null
+++ b/Doc/library/concurrent.interpreters.rst
@@ -0,0 +1,387 @@
+:mod:`!concurrent.interpreters` --- Multiple interpreters in the same process
+=============================================================================
+
+.. module:: concurrent.interpreters
+ :synopsis: Multiple interpreters in the same process
+
+.. moduleauthor:: Eric Snow <ericsnowcurrently@gmail.com>
+.. sectionauthor:: Eric Snow <ericsnowcurrently@gmail.com>
+
+.. versionadded:: 3.14
+
+**Source code:** :source:`Lib/concurrent/interpreters.py`
+
+--------------
+
+The :mod:`!concurrent.interpreters` module constructs higher-level
+interfaces on top of the lower level :mod:`!_interpreters` module.
+
+The module is primarily meant to provide a basic API for managing
+interpreters (AKA "subinterpreters") and running things in them.
+Running mostly involves switching to an interpreter (in the current
+thread) and calling a function in that execution context.
+
+For concurrency, interpreters themselves (and this module) don't
+provide much more than isolation, which on its own isn't useful.
+Actual concurrency is available separately through
+:mod:`threads <threading>` See `below <interp-concurrency_>`_
+
+.. seealso::
+
+ :class:`~concurrent.futures.InterpreterPoolExecutor`
+ combines threads with interpreters in a familiar interface.
+
+ .. XXX Add references to the upcoming HOWTO docs in the seealso block.
+
+ :ref:`isolating-extensions-howto`
+ how to update an extension module to support multiple interpreters
+
+ :pep:`554`
+
+ :pep:`734`
+
+ :pep:`684`
+
+.. XXX Why do we disallow multiple interpreters on WASM?
+
+.. include:: ../includes/wasm-notavail.rst
+
+
+Key details
+-----------
+
+Before we dive in further, there are a small number of details
+to keep in mind about using multiple interpreters:
+
+* `isolated <interp-isolation_>`_, by default
+* no implicit threads
+* not all PyPI packages support use in multiple interpreters yet
+
+.. XXX Are there other relevant details to list?
+
+
+.. _interpreters-intro:
+
+Introduction
+------------
+
+An "interpreter" is effectively the execution context of the Python
+runtime. It contains all of the state the runtime needs to execute
+a program. This includes things like the import state and builtins.
+(Each thread, even if there's only the main thread, has some extra
+runtime state, in addition to the current interpreter, related to
+the current exception and the bytecode eval loop.)
+
+The concept and functionality of the interpreter have been a part of
+Python since version 2.2, but the feature was only available through
+the C-API and not well known, and the `isolation <interp-isolation_>`_
+was relatively incomplete until version 3.12.
+
+.. _interp-isolation:
+
+Multiple Interpreters and Isolation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A Python implementation may support using multiple interpreters in the
+same process. CPython has this support. Each interpreter is
+effectively isolated from the others (with a limited number of
+carefully managed process-global exceptions to the rule).
+
+That isolation is primarily useful as a strong separation between
+distinct logical components of a program, where you want to have
+careful control of how those components interact.
+
+.. note::
+
+ Interpreters in the same process can technically never be strictly
+ isolated from one another since there are few restrictions on memory
+ access within the same process. The Python runtime makes a best
+ effort at isolation but extension modules may easily violate that.
+ Therefore, do not use multiple interpreters in security-sensitive
+ situations, where they shouldn't have access to each other's data.
+
+Running in an Interpreter
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Running in a different interpreter involves switching to it in the
+current thread and then calling some function. The runtime will
+execute the function using the current interpreter's state. The
+:mod:`!concurrent.interpreters` module provides a basic API for
+creating and managing interpreters, as well as the switch-and-call
+operation.
+
+No other threads are automatically started for the operation.
+There is `a helper <interp-call-in-thread_>`_ for that though.
+There is another dedicated helper for calling the builtin
+:func:`exec` in an interpreter.
+
+When :func:`exec` (or :func:`eval`) are called in an interpreter,
+they run using the interpreter's :mod:`!__main__` module as the
+"globals" namespace. The same is true for functions that aren't
+associated with any module. This is the same as how scripts invoked
+from the command-line run in the :mod:`!__main__` module.
+
+
+.. _interp-concurrency:
+
+Concurrency and Parallelism
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+As noted earlier, interpreters do not provide any concurrency
+on their own. They strictly represent the isolated execution
+context the runtime will use *in the current thread*. That isolation
+makes them similar to processes, but they still enjoy in-process
+efficiency, like threads.
+
+All that said, interpreters do naturally support certain flavors of
+concurrency, as a powerful side effect of that isolation.
+There's a powerful side effect of that isolation. It enables a
+different approach to concurrency than you can take with async or
+threads. It's a similar concurrency model to CSP or the actor model,
+a model which is relatively easy to reason about.
+
+You can take advantage of that concurrency model in a single thread,
+switching back and forth between interpreters, Stackless-style.
+However, this model is more useful when you combine interpreters
+with multiple threads. This mostly involves starting a new thread,
+where you switch to another interpreter and run what you want there.
+
+Each actual thread in Python, even if you're only running in the main
+thread, has its own *current* execution context. Multiple threads can
+use the same interpreter or different ones.
+
+At a high level, you can think of the combination of threads and
+interpreters as threads with opt-in sharing.
+
+As a significant bonus, interpreters are sufficiently isolated that
+they do not share the :term:`GIL`, which means combining threads with
+multiple interpreters enables full multi-core parallelism.
+(This has been the case since Python 3.12.)
+
+Communication Between Interpreters
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In practice, multiple interpreters are useful only if we have a way
+to communicate between them. This usually involves some form of
+message passing, but can even mean sharing data in some carefully
+managed way.
+
+With this in mind, the :mod:`!concurrent.interpreters` module provides
+a :class:`queue.Queue` implementation, available through
+:func:`create_queue`.
+
+.. _interp-object-sharing:
+
+"Sharing" Objects
+^^^^^^^^^^^^^^^^^
+
+Any data actually shared between interpreters loses the thread-safety
+provided by the :term:`GIL`. There are various options for dealing with
+this in extension modules. However, from Python code the lack of
+thread-safety means objects can't actually be shared, with a few
+exceptions. Instead, a copy must be created, which means mutable
+objects won't stay in sync.
+
+By default, most objects are copied with :mod:`pickle` when they are
+passed to another interpreter. Nearly all of the immutable builtin
+objects are either directly shared or copied efficiently. For example:
+
+* :const:`None`
+* :class:`bool` (:const:`True` and :const:`False`)
+* :class:`bytes`
+* :class:`str`
+* :class:`int`
+* :class:`float`
+* :class:`tuple` (of similarly supported objects)
+
+There is a small number of Python types that actually share mutable
+data between interpreters:
+
+* :class:`memoryview`
+* :class:`Queue`
+
+
+Reference
+---------
+
+This module defines the following functions:
+
+.. function:: list_all()
+
+ Return a :class:`list` of :class:`Interpreter` objects,
+ one for each existing interpreter.
+
+.. function:: get_current()
+
+ Return an :class:`Interpreter` object for the currently running
+ interpreter.
+
+.. function:: get_main()
+
+ Return an :class:`Interpreter` object for the main interpreter.
+ This is the interpreter the runtime created to run the :term:`REPL`
+ or the script given at the command-line. It is usually the only one.
+
+.. function:: create()
+
+ Initialize a new (idle) Python interpreter
+ and return a :class:`Interpreter` object for it.
+
+.. function:: create_queue()
+
+ Initialize a new cross-interpreter queue and return a :class:`Queue`
+ object for it.
+
+
+Interpreter objects
+^^^^^^^^^^^^^^^^^^^
+
+.. class:: Interpreter(id)
+
+ A single interpreter in the current process.
+
+ Generally, :class:`Interpreter` shouldn't be called directly.
+ Instead, use :func:`create` or one of the other module functions.
+
+ .. attribute:: id
+
+ (read-only)
+
+ The underlying interpreter's ID.
+
+ .. attribute:: whence
+
+ (read-only)
+
+ A string describing where the interpreter came from.
+
+ .. method:: is_running()
+
+ Return ``True`` if the interpreter is currently executing code
+ in its :mod:`!__main__` module and ``False`` otherwise.
+
+ .. method:: close()
+
+ Finalize and destroy the interpreter.
+
+ .. method:: prepare_main(ns=None, **kwargs)
+
+ Bind objects in the interpreter's :mod:`!__main__` module.
+
+ Some objects are actually shared and some are copied efficiently,
+ but most are copied via :mod:`pickle`. See :ref:`interp-object-sharing`.
+
+ .. method:: exec(code, /, dedent=True)
+
+ Run the given source code in the interpreter (in the current thread).
+
+ .. method:: call(callable, /, *args, **kwargs)
+
+ Return the result of calling running the given function in the
+ interpreter (in the current thread).
+
+ .. _interp-call-in-thread:
+
+ .. method:: call_in_thread(callable, /, *args, **kwargs)
+
+ Run the given function in the interpreter (in a new thread).
+
+Exceptions
+^^^^^^^^^^
+
+.. exception:: InterpreterError
+
+ This exception, a subclass of :exc:`Exception`, is raised when
+ an interpreter-related error happens.
+
+.. exception:: InterpreterNotFoundError
+
+ This exception, a subclass of :exc:`InterpreterError`, is raised when
+ the targeted interpreter no longer exists.
+
+.. exception:: ExecutionFailed
+
+ This exception, a subclass of :exc:`InterpreterError`, is raised when
+ the running code raised an uncaught exception.
+
+ .. attribute:: excinfo
+
+ A basic snapshot of the exception raised in the other interpreter.
+
+.. XXX Document the excinfoattrs?
+
+.. exception:: NotShareableError
+
+ This exception, a subclass of :exc:`TypeError`, is raised when
+ an object cannot be sent to another interpreter.
+
+
+Communicating Between Interpreters
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. class:: Queue(id)
+
+ A wrapper around a low-level, cross-interpreter queue, which
+ implements the :class:`queue.Queue` interface. The underlying queue
+ can only be created through :func:`create_queue`.
+
+ Some objects are actually shared and some are copied efficiently,
+ but most are copied via :mod:`pickle`. See :ref:`interp-object-sharing`.
+
+ .. attribute:: id
+
+ (read-only)
+
+ The queue's ID.
+
+
+.. exception:: QueueEmptyError
+
+ This exception, a subclass of :exc:`queue.Empty`, is raised from
+ :meth:`!Queue.get` and :meth:`!Queue.get_nowait` when the queue
+ is empty.
+
+.. exception:: QueueFullError
+
+ This exception, a subclass of :exc:`queue.Full`, is raised from
+ :meth:`!Queue.put` and :meth:`!Queue.put_nowait` when the queue
+ is full.
+
+
+Basic usage
+-----------
+
+Creating an interpreter and running code in it::
+
+ from concurrent import interpreters
+
+ interp = interpreters.create()
+
+ # Run in the current OS thread.
+
+ interp.exec('print("spam!")')
+
+ interp.exec("""if True:
+ print('spam!')
+ """)
+
+ from textwrap import dedent
+ interp.exec(dedent("""
+ print('spam!')
+ """))
+
+ def run(arg):
+ return arg
+
+ res = interp.call(run, 'spam!')
+ print(res)
+
+ def run():
+ print('spam!')
+
+ interp.call(run)
+
+ # Run in new OS thread.
+
+ t = interp.call_in_thread(run)
+ t.join()
diff --git a/Doc/library/concurrent.rst b/Doc/library/concurrent.rst
index 8caea78bbb5..748c72c733b 100644
--- a/Doc/library/concurrent.rst
+++ b/Doc/library/concurrent.rst
@@ -1,6 +1,7 @@
The :mod:`!concurrent` package
==============================
-Currently, there is only one module in this package:
+This package contains the following modules:
* :mod:`concurrent.futures` -- Launching parallel tasks
+* :mod:`concurrent.interpreters` -- Multiple interpreters in the same process
diff --git a/Doc/library/copy.rst b/Doc/library/copy.rst
index 95b41f988a0..210ad718800 100644
--- a/Doc/library/copy.rst
+++ b/Doc/library/copy.rst
@@ -122,6 +122,8 @@ and only supports named tuples created by :func:`~collections.namedtuple`,
This method should create a new object of the same type,
replacing fields with values from *changes*.
+ .. versionadded:: 3.13
+
.. seealso::
diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst
index 533cdf13974..d39c4ca4a58 100644
--- a/Doc/library/csv.rst
+++ b/Doc/library/csv.rst
@@ -53,7 +53,7 @@ The :mod:`csv` module defines the following functions:
.. index::
single: universal newlines; csv.reader function
-.. function:: reader(csvfile, dialect='excel', **fmtparams)
+.. function:: reader(csvfile, /, dialect='excel', **fmtparams)
Return a :ref:`reader object <reader-objects>` that will process
lines from the given *csvfile*. A csvfile must be an iterable of
@@ -70,7 +70,7 @@ The :mod:`csv` module defines the following functions:
section :ref:`csv-fmt-params`.
Each row read from the csv file is returned as a list of strings. No
- automatic data type conversion is performed unless the ``QUOTE_NONNUMERIC`` format
+ automatic data type conversion is performed unless the :data:`QUOTE_NONNUMERIC` format
option is specified (in which case unquoted fields are transformed into floats).
A short usage example::
@@ -84,7 +84,7 @@ The :mod:`csv` module defines the following functions:
Spam, Lovely Spam, Wonderful Spam
-.. function:: writer(csvfile, dialect='excel', **fmtparams)
+.. function:: writer(csvfile, /, dialect='excel', **fmtparams)
Return a writer object responsible for converting the user's data into delimited
strings on the given file-like object. *csvfile* can be any object with a
@@ -323,23 +323,32 @@ The :mod:`csv` module defines the following constants:
.. data:: QUOTE_MINIMAL
Instructs :class:`writer` objects to only quote those fields which contain
- special characters such as *delimiter*, *quotechar* or any of the characters in
- *lineterminator*.
+ special characters such as *delimiter*, *quotechar*, ``'\r'``, ``'\n'``
+ or any of the characters in *lineterminator*.
.. data:: QUOTE_NONNUMERIC
Instructs :class:`writer` objects to quote all non-numeric fields.
- Instructs :class:`reader` objects to convert all non-quoted fields to type *float*.
+ Instructs :class:`reader` objects to convert all non-quoted fields to type :class:`float`.
+ .. note::
+ Some numeric types, such as :class:`bool`, :class:`~fractions.Fraction`,
+ or :class:`~enum.IntEnum`, have a string representation that cannot be
+ converted to :class:`float`.
+ They cannot be read in the :data:`QUOTE_NONNUMERIC` and
+ :data:`QUOTE_STRINGS` modes.
.. data:: QUOTE_NONE
- Instructs :class:`writer` objects to never quote fields. When the current
- *delimiter* occurs in output data it is preceded by the current *escapechar*
- character. If *escapechar* is not set, the writer will raise :exc:`Error` if
+ Instructs :class:`writer` objects to never quote fields.
+ When the current *delimiter*, *quotechar*, *escapechar*, ``'\r'``, ``'\n'``
+ or any of the characters in *lineterminator* occurs in output data
+ it is preceded by the current *escapechar* character.
+ If *escapechar* is not set, the writer will raise :exc:`Error` if
any characters that require escaping are encountered.
+ Set *quotechar* to ``None`` to prevent its escaping.
Instructs :class:`reader` objects to perform no special processing of quote characters.
@@ -408,9 +417,16 @@ Dialects support the following attributes:
.. attribute:: Dialect.escapechar
- A one-character string used by the writer to escape the *delimiter* if *quoting*
- is set to :const:`QUOTE_NONE` and the *quotechar* if *doublequote* is
- :const:`False`. On reading, the *escapechar* removes any special meaning from
+ A one-character string used by the writer to escape characters that
+ require escaping:
+
+ * the *delimiter*, the *quotechar*, ``'\r'``, ``'\n'`` and any of the
+ characters in *lineterminator* are escaped if *quoting* is set to
+ :const:`QUOTE_NONE`;
+ * the *quotechar* is escaped if *doublequote* is :const:`False`;
+ * the *escapechar* itself.
+
+ On reading, the *escapechar* removes any special meaning from
the following character. It defaults to :const:`None`, which disables escaping.
.. versionchanged:: 3.11
@@ -430,9 +446,12 @@ Dialects support the following attributes:
.. attribute:: Dialect.quotechar
- A one-character string used to quote fields containing special characters, such
- as the *delimiter* or *quotechar*, or which contain new-line characters. It
- defaults to ``'"'``.
+ A one-character string used to quote fields containing special characters,
+ such as the *delimiter* or the *quotechar*, or which contain new-line
+ characters (``'\r'``, ``'\n'`` or any of the characters in *lineterminator*).
+ It defaults to ``'"'``.
+ Can be set to ``None`` to prevent escaping ``'"'`` if *quoting* is set
+ to :const:`QUOTE_NONE`.
.. versionchanged:: 3.11
An empty *quotechar* is not allowed.
@@ -441,7 +460,8 @@ Dialects support the following attributes:
Controls when quotes should be generated by the writer and recognised by the
reader. It can take on any of the :ref:`QUOTE_\* constants <csv-constants>`
- and defaults to :const:`QUOTE_MINIMAL`.
+ and defaults to :const:`QUOTE_MINIMAL` if *quotechar* is not ``None``,
+ and :const:`QUOTE_NONE` otherwise.
.. attribute:: Dialect.skipinitialspace
@@ -603,7 +623,7 @@ A slightly more advanced use of the reader --- catching and reporting errors::
for row in reader:
print(row)
except csv.Error as e:
- sys.exit('file {}, line {}: {}'.format(filename, reader.line_num, e))
+ sys.exit(f'file {filename}, line {reader.line_num}: {e}')
And while the module doesn't directly support parsing strings, it can easily be
done::
diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index 2825590400c..846cece3761 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -714,10 +714,16 @@ item in the :attr:`~Structure._fields_` tuples::
... ("second_16", c_int, 16)]
...
>>> print(Int.first_16)
- <Field type=c_long, ofs=0:0, bits=16>
+ <ctypes.CField 'first_16' type=c_int, ofs=0, bit_size=16, bit_offset=0>
>>> print(Int.second_16)
- <Field type=c_long, ofs=0:16, bits=16>
- >>>
+ <ctypes.CField 'second_16' type=c_int, ofs=0, bit_size=16, bit_offset=16>
+
+It is important to note that bit field allocation and layout in memory are not
+defined as a C standard; their implementation is compiler-specific.
+By default, Python will attempt to match the behavior of a "native" compiler
+for the current platform.
+See the :attr:`~Structure._layout_` attribute for details on the default
+behavior and how to change it.
.. _ctypes-arrays:
@@ -876,7 +882,7 @@ invalid non-\ ``NULL`` pointers would crash Python)::
Thread safety without the GIL
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-In Python 3.13, the :term:`GIL` may be disabled on :term:`experimental free threaded <free threading>` builds.
+From Python 3.13 onward, the :term:`GIL` can be disabled on :term:`free threaded <free threading>` builds.
In ctypes, reads and writes to a single object concurrently is safe, but not across multiple objects:
.. code-block:: pycon
@@ -2031,35 +2037,55 @@ Utility functions
pointer.
-.. function:: create_string_buffer(init_or_size, size=None)
+.. function:: create_string_buffer(init, size=None)
+ create_string_buffer(size)
This function creates a mutable character buffer. The returned object is a
ctypes array of :class:`c_char`.
- *init_or_size* must be an integer which specifies the size of the array, or a
- bytes object which will be used to initialize the array items.
+ If *size* is given (and not ``None``), it must be an :class:`int`.
+ It specifies the size of the returned array.
+
+ If the *init* argument is given, it must be :class:`bytes`. It is used
+ to initialize the array items. Bytes not initialized this way are
+ set to zero (NUL).
+
+ If *size* is not given (or if it is ``None``), the buffer is made one element
+ larger than *init*, effectively adding a NUL terminator.
- If a bytes object is specified as first argument, the buffer is made one item
- larger than its length so that the last element in the array is a NUL
- termination character. An integer can be passed as second argument which allows
- specifying the size of the array if the length of the bytes should not be used.
+ If both arguments are given, *size* must not be less than ``len(init)``.
+
+ .. warning::
+
+ If *size* is equal to ``len(init)``, a NUL terminator is
+ not added. Do not treat such a buffer as a C string.
+
+ For example::
+
+ >>> bytes(create_string_buffer(2))
+ b'\x00\x00'
+ >>> bytes(create_string_buffer(b'ab'))
+ b'ab\x00'
+ >>> bytes(create_string_buffer(b'ab', 2))
+ b'ab'
+ >>> bytes(create_string_buffer(b'ab', 4))
+ b'ab\x00\x00'
+ >>> bytes(create_string_buffer(b'abcdef', 2))
+ Traceback (most recent call last):
+ ...
+ ValueError: byte string too long
.. audit-event:: ctypes.create_string_buffer init,size ctypes.create_string_buffer
-.. function:: create_unicode_buffer(init_or_size, size=None)
+.. function:: create_unicode_buffer(init, size=None)
+ create_unicode_buffer(size)
This function creates a mutable unicode character buffer. The returned object is
a ctypes array of :class:`c_wchar`.
- *init_or_size* must be an integer which specifies the size of the array, or a
- string which will be used to initialize the array items.
-
- If a string is specified as first argument, the buffer is made one item
- larger than the length of the string so that the last element in the array is a
- NUL termination character. An integer can be passed as second argument which
- allows specifying the size of the array if the length of the string should not
- be used.
+ The function takes the same arguments as :func:`~create_string_buffer` except
+ *init* must be a string and *size* counts :class:`c_wchar`.
.. audit-event:: ctypes.create_unicode_buffer init,size ctypes.create_unicode_buffer
@@ -2358,7 +2384,7 @@ Data types
:func:`POINTER` for corresponding ctypes data type. If a pointer type
was not yet created, the attribute is missing.
- .. versionadded:: next
+ .. versionadded:: 3.14
Common instance variables of ctypes data types:
@@ -2754,6 +2780,16 @@ fields, or any other data types containing pointer type fields.
when :attr:`_fields_` is assigned, otherwise it will have no effect.
Setting this attribute to 0 is the same as not setting it at all.
+ This is only implemented for the MSVC-compatible memory layout.
+
+ .. deprecated-removed:: 3.14 3.19
+
+ For historical reasons, if :attr:`!_pack_` is non-zero,
+ the MSVC-compatible layout will be used by default.
+ On non-Windows platforms, this default is deprecated and is slated to
+ become an error in Python 3.19.
+ If it is intended, set :attr:`~Structure._layout_` to ``'ms'``
+ explicitly.
.. attribute:: _align_
@@ -2782,12 +2818,15 @@ fields, or any other data types containing pointer type fields.
Currently the default will be:
- On Windows: ``"ms"``
- - When :attr:`~Structure._pack_` is specified: ``"ms"``
+ - When :attr:`~Structure._pack_` is specified: ``"ms"``.
+ (This is deprecated; see :attr:`~Structure._pack_` documentation.)
- Otherwise: ``"gcc-sysv"``
:attr:`!_layout_` must already be defined when
:attr:`~Structure._fields_` is assigned, otherwise it will have no effect.
+ .. versionadded:: 3.14
+
.. attribute:: _anonymous_
An optional sequence that lists the names of unnamed (anonymous) fields.
@@ -2926,7 +2965,7 @@ fields, or any other data types containing pointer type fields.
.. attribute:: is_anonymous
True if this field is anonymous, that is, it contains nested sub-fields
- that should be be merged into a containing structure or union.
+ that should be merged into a containing structure or union.
.. _ctypes-arrays-pointers:
diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst
index 6c7fc721a3e..0b13c559295 100644
--- a/Doc/library/curses.rst
+++ b/Doc/library/curses.rst
@@ -68,6 +68,21 @@ The module :mod:`curses` defines the following exception:
The module :mod:`curses` defines the following functions:
+.. function:: assume_default_colors(fg, bg, /)
+
+ Allow use of default values for colors on terminals supporting this feature.
+ Use this to support transparency in your application.
+
+ * Assign terminal default foreground/background colors to color number ``-1``.
+ So ``init_pair(x, COLOR_RED, -1)`` will initialize pair *x* as red
+ on default background and ``init_pair(x, -1, COLOR_BLUE)`` will
+ initialize pair *x* as default foreground on blue.
+
+ * Change the definition of the color-pair ``0`` to ``(fg, bg)``.
+
+ .. versionadded:: 3.14
+
+
.. function:: baudrate()
Return the output speed of the terminal in bits per second. On software
@@ -290,9 +305,11 @@ The module :mod:`curses` defines the following functions:
Change the definition of a color-pair. It takes three arguments: the number of
the color-pair to be changed, the foreground color number, and the background
color number. The value of *pair_number* must be between ``1`` and
- ``COLOR_PAIRS - 1`` (the ``0`` color pair is wired to white on black and cannot
- be changed). The value of *fg* and *bg* arguments must be between ``0`` and
- ``COLORS - 1``, or, after calling :func:`use_default_colors`, ``-1``.
+ ``COLOR_PAIRS - 1`` (the ``0`` color pair can only be changed by
+ :func:`use_default_colors` and :func:`assume_default_colors`).
+ The value of *fg* and *bg* arguments must be between ``0`` and
+ ``COLORS - 1``, or, after calling :func:`!use_default_colors` or
+ :func:`!assume_default_colors`, ``-1``.
If the color-pair was previously initialized, the screen is
refreshed and all occurrences of that color-pair are changed to the new
definition.
@@ -678,11 +695,7 @@ The module :mod:`curses` defines the following functions:
.. function:: use_default_colors()
- Allow use of default values for colors on terminals supporting this feature. Use
- this to support transparency in your application. The default color is assigned
- to the color number ``-1``. After calling this function, ``init_pair(x,
- curses.COLOR_RED, -1)`` initializes, for instance, color pair *x* to a red
- foreground color on the default background.
+ Equivalent to ``assume_default_colors(-1, -1)``.
.. function:: wrapper(func, /, *args, **kwargs)
@@ -975,6 +988,10 @@ the following methods and attributes:
window.getstr(y, x, n)
Read a bytes object from the user, with primitive line editing capacity.
+ The maximum value for *n* is 2047.
+
+ .. versionchanged:: 3.14
+ The maximum value for *n* was increased from 1023 to 2047.
.. method:: window.getyx()
@@ -1066,6 +1083,10 @@ the following methods and attributes:
current cursor position, or at *y*, *x* if specified. Attributes are stripped
from the characters. If *n* is specified, :meth:`instr` returns a string
at most *n* characters long (exclusive of the trailing NUL).
+ The maximum value for *n* is 2047.
+
+ .. versionchanged:: 3.14
+ The maximum value for *n* was increased from 1023 to 2047.
.. method:: window.is_linetouched(line)
diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst
index 72612211b43..299c8aa399c 100644
--- a/Doc/library/dataclasses.rst
+++ b/Doc/library/dataclasses.rst
@@ -121,8 +121,11 @@ Module contents
:meth:`!__le__`, :meth:`!__gt__`, or :meth:`!__ge__`, then
:exc:`TypeError` is raised.
- - *unsafe_hash*: If ``False`` (the default), a :meth:`~object.__hash__` method
- is generated according to how *eq* and *frozen* are set.
+ - *unsafe_hash*: If true, force ``dataclasses`` to create a
+ :meth:`~object.__hash__` method, even though it may not be safe to do so.
+ Otherwise, generate a :meth:`~object.__hash__` method according to how
+ *eq* and *frozen* are set.
+ The default value is ``False``.
:meth:`!__hash__` is used by built-in :meth:`hash`, and when objects are
added to hashed collections such as dictionaries and sets. Having a
@@ -304,9 +307,9 @@ Module contents
.. versionadded:: 3.10
- - ``doc``: optional docstring for this field.
+ - *doc*: optional docstring for this field.
- .. versionadded:: 3.13
+ .. versionadded:: 3.14
If the default value of a field is specified by a call to
:func:`!field`, then the class attribute for this field will be
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
index 1ce2013f05d..16ed3215bc2 100644
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -261,6 +261,22 @@ A :class:`timedelta` object represents a duration, the difference between two
>>> (d.days, d.seconds, d.microseconds)
(-1, 86399, 999999)
+ Since the string representation of :class:`!timedelta` objects can be confusing,
+ use the following recipe to produce a more readable format:
+
+ .. code-block:: pycon
+
+ >>> def pretty_timedelta(td):
+ ... if td.days >= 0:
+ ... return str(td)
+ ... return f'-({-td!s})'
+ ...
+ >>> d = timedelta(hours=-1)
+ >>> str(d) # not human-friendly
+ '-1 day, 23:00:00'
+ >>> pretty_timedelta(d)
+ '-(1:00:00)'
+
Class attributes:
@@ -1486,11 +1502,11 @@ Instance methods:
returned by :func:`time.time`.
Naive :class:`.datetime` instances are assumed to represent local
- time and this method relies on the platform C :c:func:`mktime`
- function to perform the conversion. Since :class:`.datetime`
- supports wider range of values than :c:func:`mktime` on many
- platforms, this method may raise :exc:`OverflowError` or :exc:`OSError`
- for times far in the past or far in the future.
+ time and this method relies on platform C functions to perform
+ the conversion. Since :class:`.datetime` supports a wider range of
+ values than the platform C functions on many platforms, this
+ method may raise :exc:`OverflowError` or :exc:`OSError` for times
+ far in the past or far in the future.
For aware :class:`.datetime` instances, the return value is computed
as::
@@ -1503,6 +1519,10 @@ Instance methods:
The :meth:`timestamp` method uses the :attr:`.fold` attribute to
disambiguate the times during a repeated interval.
+ .. versionchanged:: 3.6
+ This method no longer relies on the platform C :c:func:`mktime`
+ function to perform conversions.
+
.. note::
There is no method to obtain the POSIX timestamp directly from a
diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst
index 36221c026d6..39e287b1521 100644
--- a/Doc/library/dbm.rst
+++ b/Doc/library/dbm.rst
@@ -15,10 +15,16 @@
* :mod:`dbm.ndbm`
If none of these modules are installed, the
-slow-but-simple implementation in module :mod:`dbm.dumb` will be used. There
+slow-but-simple implementation in module :mod:`dbm.dumb` will be used. There
is a `third party interface <https://www.jcea.es/programacion/pybsddb.htm>`_ to
the Oracle Berkeley DB.
+.. note::
+ None of the underlying modules will automatically shrink the disk space used by
+ the database file. However, :mod:`dbm.sqlite3`, :mod:`dbm.gnu` and :mod:`dbm.dumb`
+ provide a :meth:`!reorganize` method that can be used for this purpose.
+
+
.. exception:: error
A tuple containing the exceptions that can be raised by each of the supported
@@ -186,6 +192,17 @@ or any other SQLite browser, including the SQLite CLI.
The Unix file access mode of the file (default: octal ``0o666``),
used only when the database has to be created.
+ .. method:: sqlite3.reorganize()
+
+ If you have carried out a lot of deletions and would like to shrink the space
+ used on disk, this method will reorganize the database; otherwise, deleted file
+ space will be kept and reused as new (key, value) pairs are added.
+
+ .. note::
+ While reorganizing, as much as two times the size of the original database is required
+ in free disk space. However, be aware that this factor changes for each :mod:`dbm` submodule.
+
+ .. versionadded:: next
:mod:`dbm.gnu` --- GNU database manager
---------------------------------------
@@ -237,6 +254,9 @@ functionality like crash tolerance.
* ``'s'``: Synchronized mode.
Changes to the database will be written immediately to the file.
* ``'u'``: Do not lock database.
+ * ``'m'``: Do not use :manpage:`mmap(2)`.
+ This may harm performance, but improve crash tolerance.
+ .. versionadded:: next
Not all flags are valid for all versions of GDBM.
See the :data:`open_flags` member for a list of supported flag characters.
@@ -284,6 +304,10 @@ functionality like crash tolerance.
reorganization; otherwise, deleted file space will be kept and reused as new
(key, value) pairs are added.
+ .. note::
+ While reorganizing, as much as one time the size of the original database is required
+ in free disk space. However, be aware that this factor changes for each :mod:`dbm` submodule.
+
.. method:: gdbm.sync()
When the database has been opened in fast mode, this method forces any
@@ -438,6 +462,11 @@ The :mod:`!dbm.dumb` module defines the following:
with a sufficiently large/complex entry due to stack depth limitations in
Python's AST compiler.
+ .. warning::
+ :mod:`dbm.dumb` does not support concurrent read/write access. (Multiple
+ simultaneous read accesses are safe.) When a program has the database open
+ for writing, no other program should have it open for reading or writing.
+
.. versionchanged:: 3.5
:func:`~dbm.dumb.open` always creates a new database when *flag* is ``'n'``.
@@ -460,3 +489,15 @@ The :mod:`!dbm.dumb` module defines the following:
.. method:: dumbdbm.close()
Close the database.
+
+ .. method:: dumbdbm.reorganize()
+
+ If you have carried out a lot of deletions and would like to shrink the space
+ used on disk, this method will reorganize the database; otherwise, deleted file
+ space will not be reused.
+
+ .. note::
+ While reorganizing, no additional free disk space is required. However, be aware
+ that this factor changes for each :mod:`dbm` submodule.
+
+ .. versionadded:: next
diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst
index deaad059ef8..10ddfa02b43 100644
--- a/Doc/library/decimal.rst
+++ b/Doc/library/decimal.rst
@@ -2,7 +2,7 @@
=====================================================================
.. module:: decimal
- :synopsis: Implementation of the General Decimal Arithmetic Specification.
+ :synopsis: Implementation of the General Decimal Arithmetic Specification.
.. moduleauthor:: Eric Price <eprice at tjhsst.edu>
.. moduleauthor:: Facundo Batista <facundo at taniquetil.com.ar>
@@ -121,7 +121,7 @@ reset them before monitoring a calculation.
.. _decimal-tutorial:
-Quick-start Tutorial
+Quick-start tutorial
--------------------
The usual start to using decimals is importing the module, viewing the current
@@ -1037,7 +1037,7 @@ function to temporarily change the active context.
IEEE interchange formats. The argument must be a multiple of 32 and less
than :const:`IEEE_CONTEXT_MAX_BITS`.
- .. versionadded:: next
+ .. versionadded:: 3.14
New contexts can also be created using the :class:`Context` constructor
described below. In addition, the module provides three pre-made contexts:
@@ -1096,40 +1096,52 @@ In addition to the three supplied contexts, new contexts can be created with the
default values are copied from the :const:`DefaultContext`. If the *flags*
field is not specified or is :const:`None`, all flags are cleared.
- *prec* is an integer in the range [``1``, :const:`MAX_PREC`] that sets
- the precision for arithmetic operations in the context.
+ .. attribute:: prec
- The *rounding* option is one of the constants listed in the section
- `Rounding Modes`_.
+ An integer in the range [``1``, :const:`MAX_PREC`] that sets
+ the precision for arithmetic operations in the context.
- The *traps* and *flags* fields list any signals to be set. Generally, new
- contexts should only set traps and leave the flags clear.
+ .. attribute:: rounding
- The *Emin* and *Emax* fields are integers specifying the outer limits allowable
- for exponents. *Emin* must be in the range [:const:`MIN_EMIN`, ``0``],
- *Emax* in the range [``0``, :const:`MAX_EMAX`].
+ One of the constants listed in the section `Rounding Modes`_.
- The *capitals* field is either ``0`` or ``1`` (the default). If set to
- ``1``, exponents are printed with a capital ``E``; otherwise, a
- lowercase ``e`` is used: ``Decimal('6.02e+23')``.
+ .. attribute:: traps
+ flags
- The *clamp* field is either ``0`` (the default) or ``1``.
- If set to ``1``, the exponent ``e`` of a :class:`Decimal`
- instance representable in this context is strictly limited to the
- range ``Emin - prec + 1 <= e <= Emax - prec + 1``. If *clamp* is
- ``0`` then a weaker condition holds: the adjusted exponent of
- the :class:`Decimal` instance is at most :attr:`~Context.Emax`. When *clamp* is
- ``1``, a large normal number will, where possible, have its
- exponent reduced and a corresponding number of zeros added to its
- coefficient, in order to fit the exponent constraints; this
- preserves the value of the number but loses information about
- significant trailing zeros. For example::
+ Lists of any signals to be set. Generally, new contexts should only set
+ traps and leave the flags clear.
- >>> Context(prec=6, Emax=999, clamp=1).create_decimal('1.23e999')
- Decimal('1.23000E+999')
+ .. attribute:: Emin
+ Emax
- A *clamp* value of ``1`` allows compatibility with the
- fixed-width decimal interchange formats specified in IEEE 754.
+ Integers specifying the outer limits allowable for exponents. *Emin* must
+ be in the range [:const:`MIN_EMIN`, ``0``], *Emax* in the range
+ [``0``, :const:`MAX_EMAX`].
+
+ .. attribute:: capitals
+
+ Either ``0`` or ``1`` (the default). If set to
+ ``1``, exponents are printed with a capital ``E``; otherwise, a
+ lowercase ``e`` is used: ``Decimal('6.02e+23')``.
+
+ .. attribute:: clamp
+
+ Either ``0`` (the default) or ``1``. If set to ``1``, the exponent ``e``
+ of a :class:`Decimal` instance representable in this context is strictly
+ limited to the range ``Emin - prec + 1 <= e <= Emax - prec + 1``.
+ If *clamp* is ``0`` then a weaker condition holds: the adjusted exponent of
+ the :class:`Decimal` instance is at most :attr:`~Context.Emax`. When *clamp* is
+ ``1``, a large normal number will, where possible, have its
+ exponent reduced and a corresponding number of zeros added to its
+ coefficient, in order to fit the exponent constraints; this
+ preserves the value of the number but loses information about
+ significant trailing zeros. For example::
+
+ >>> Context(prec=6, Emax=999, clamp=1).create_decimal('1.23e999')
+ Decimal('1.23000E+999')
+
+ A *clamp* value of ``1`` allows compatibility with the
+ fixed-width decimal interchange formats specified in IEEE 754.
The :class:`Context` class defines several general purpose methods as well as
a large number of methods for doing arithmetic directly in a given context.
@@ -1769,7 +1781,7 @@ The following table summarizes the hierarchy of signals::
.. _decimal-notes:
-Floating-Point Notes
+Floating-point notes
--------------------
diff --git a/Doc/library/dialog.rst b/Doc/library/dialog.rst
index 191e0da1210..e0693e8eb6e 100644
--- a/Doc/library/dialog.rst
+++ b/Doc/library/dialog.rst
@@ -220,7 +220,7 @@ is the base class for dialogs defined in other supporting modules.
.. class:: Dialog(master=None, **options)
- .. method:: show(color=None, **options)
+ .. method:: show(**options)
Render the Dialog window.
diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst
index 44767b5dd2d..11685a32f48 100644
--- a/Doc/library/dis.rst
+++ b/Doc/library/dis.rst
@@ -1094,14 +1094,6 @@ iterations of the loop.
.. versionadded:: 3.14
-.. opcode:: LOAD_CONST_IMMORTAL (consti)
-
- Pushes ``co_consts[consti]`` onto the stack.
- Can be used when the constant value is known to be immortal.
-
- .. versionadded:: 3.14
-
-
.. opcode:: LOAD_NAME (namei)
Pushes the value associated with ``co_names[namei]`` onto the stack.
diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst
index b86fef9fd6f..fb43cf918b8 100644
--- a/Doc/library/doctest.rst
+++ b/Doc/library/doctest.rst
@@ -174,7 +174,7 @@ with assorted summaries at the end.
You can force verbose mode by passing ``verbose=True`` to :func:`testmod`, or
prohibit it by passing ``verbose=False``. In either of those cases,
-``sys.argv`` is not examined by :func:`testmod` (so passing ``-v`` or not
+:data:`sys.argv` is not examined by :func:`testmod` (so passing ``-v`` or not
has no effect).
There is also a command line shortcut for running :func:`testmod`, see section
@@ -231,7 +231,7 @@ documentation::
As with :func:`testmod`, :func:`testfile` won't display anything unless an
example fails. If an example does fail, then the failing example(s) and the
cause(s) of the failure(s) are printed to stdout, using the same format as
-:func:`testmod`.
+:func:`!testmod`.
By default, :func:`testfile` looks for files in the calling module's directory.
See section :ref:`doctest-basic-api` for a description of the optional arguments
@@ -311,6 +311,9 @@ Which Docstrings Are Examined?
The module docstring, and all function, class and method docstrings are
searched. Objects imported into the module are not searched.
+.. attribute:: module.__test__
+ :no-typesetting:
+
In addition, there are cases when you want tests to be part of a module but not part
of the help text, which requires that the tests not be included in the docstring.
Doctest looks for a module-level variable called ``__test__`` and uses it to locate other
@@ -533,7 +536,7 @@ Some details you should read once, but won't need to remember:
* The interactive shell omits the traceback header line for some
:exc:`SyntaxError`\ s. But doctest uses the traceback header line to
distinguish exceptions from non-exceptions. So in the rare case where you need
- to test a :exc:`SyntaxError` that omits the traceback header, you will need to
+ to test a :exc:`!SyntaxError` that omits the traceback header, you will need to
manually add the traceback header line to your test example.
.. index:: single: ^ (caret); marker
@@ -860,15 +863,15 @@ The :const:`ELLIPSIS` directive gives a nice approach for the last example:
<C object at 0x...>
Floating-point numbers are also subject to small output variations across
-platforms, because Python defers to the platform C library for float formatting,
-and C libraries vary widely in quality here. ::
+platforms, because Python defers to the platform C library for some
+floating-point calculations, and C libraries vary widely in quality here. ::
- >>> 1./7 # risky
- 0.14285714285714285
- >>> print(1./7) # safer
- 0.142857142857
- >>> print(round(1./7, 6)) # much safer
- 0.142857
+ >>> 1000**0.1 # risky
+ 1.9952623149688797
+ >>> round(1000**0.1, 9) # safer
+ 1.995262315
+ >>> print(f'{1000**0.1:.4f}') # much safer
+ 1.9953
Numbers of the form ``I/2.**J`` are safe across all platforms, and I often
contrive doctest examples to produce numbers of that form::
@@ -938,13 +941,13 @@ and :ref:`doctest-simple-testfile`.
Optional argument *verbose* prints lots of stuff if true, and prints only
failures if false; by default, or if ``None``, it's true if and only if ``'-v'``
- is in ``sys.argv``.
+ is in :data:`sys.argv`.
Optional argument *report* prints a summary at the end when true, else prints
nothing at the end. In verbose mode, the summary is detailed, else the summary
is very brief (in fact, empty if all tests passed).
- Optional argument *optionflags* (default value 0) takes the
+ Optional argument *optionflags* (default value ``0``) takes the
:ref:`bitwise OR <bitwise>` of option flags.
See section :ref:`doctest-options`.
@@ -1043,12 +1046,15 @@ from text files and modules with doctests:
Convert doctest tests from one or more text files to a
:class:`unittest.TestSuite`.
- The returned :class:`unittest.TestSuite` is to be run by the unittest framework
- and runs the interactive examples in each file. If an example in any file
- fails, then the synthesized unit test fails, and a :exc:`failureException`
- exception is raised showing the name of the file containing the test and a
- (sometimes approximate) line number. If all the examples in a file are
- skipped, then the synthesized unit test is also marked as skipped.
+ The returned :class:`unittest.TestSuite` is to be run by the unittest
+ framework and runs the interactive examples in each file.
+ Each file is run as a separate unit test, and each example in a file
+ is run as a :ref:`subtest <subtests>`.
+ If any example in a file fails, then the synthesized unit test fails.
+ The traceback for failure or error contains the name of the file
+ containing the test and a (sometimes approximate) line number.
+ If all the examples in a file are skipped, then the synthesized unit
+ test is also marked as skipped.
Pass one or more paths (as strings) to text files to be examined.
@@ -1078,13 +1084,14 @@ from text files and modules with doctests:
Optional argument *setUp* specifies a set-up function for the test suite.
This is called before running the tests in each file. The *setUp* function
- will be passed a :class:`DocTest` object. The setUp function can access the
- test globals as the *globs* attribute of the test passed.
+ will be passed a :class:`DocTest` object. The *setUp* function can access the
+ test globals as the :attr:`~DocTest.globs` attribute of the test passed.
Optional argument *tearDown* specifies a tear-down function for the test
suite. This is called after running the tests in each file. The *tearDown*
- function will be passed a :class:`DocTest` object. The setUp function can
- access the test globals as the *globs* attribute of the test passed.
+ function will be passed a :class:`DocTest` object. The *tearDown* function can
+ access the test globals as the :attr:`~DocTest.globs` attribute of the test
+ passed.
Optional argument *globs* is a dictionary containing the initial global
variables for the tests. A new copy of this dictionary is created for each
@@ -1105,16 +1112,22 @@ from text files and modules with doctests:
The global ``__file__`` is added to the globals provided to doctests loaded
from a text file using :func:`DocFileSuite`.
+ .. versionchanged:: next
+ Run each example as a :ref:`subtest <subtests>`.
+
.. function:: DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, optionflags=0, checker=None)
Convert doctest tests for a module to a :class:`unittest.TestSuite`.
- The returned :class:`unittest.TestSuite` is to be run by the unittest framework
- and runs each doctest in the module. If any of the doctests fail, then the
- synthesized unit test fails, and a :exc:`failureException` exception is raised
- showing the name of the file containing the test and a (sometimes approximate)
- line number. If all the examples in a docstring are skipped, then the
+ The returned :class:`unittest.TestSuite` is to be run by the unittest
+ framework and runs each doctest in the module.
+ Each docstring is run as a separate unit test, and each example in
+ a docstring is run as a :ref:`subtest <subtests>`.
+ If any of the doctests fail, then the synthesized unit test fails.
+ The traceback for failure or error contains the name of the file
+ containing the test and a (sometimes approximate) line number.
+ If all the examples in a docstring are skipped, then the
synthesized unit test is also marked as skipped.
Optional argument *module* provides the module to be tested. It can be a module
@@ -1123,7 +1136,7 @@ from text files and modules with doctests:
Optional argument *globs* is a dictionary containing the initial global
variables for the tests. A new copy of this dictionary is created for each
- test. By default, *globs* is a new empty dictionary.
+ test. By default, *globs* is the module's :attr:`~module.__dict__`.
Optional argument *extraglobs* specifies an extra set of global variables, which
is merged into *globs*. By default, no extra globals are used.
@@ -1132,7 +1145,7 @@ from text files and modules with doctests:
drop-in replacement) that is used to extract doctests from the module.
Optional arguments *setUp*, *tearDown*, and *optionflags* are the same as for
- function :func:`DocFileSuite` above.
+ function :func:`DocFileSuite` above, but they are called for each docstring.
This function uses the same search technique as :func:`testmod`.
@@ -1140,11 +1153,8 @@ from text files and modules with doctests:
:func:`DocTestSuite` returns an empty :class:`unittest.TestSuite` if *module*
contains no docstrings instead of raising :exc:`ValueError`.
-.. exception:: failureException
-
- When doctests which have been converted to unit tests by :func:`DocFileSuite`
- or :func:`DocTestSuite` fail, this exception is raised showing the name of
- the file containing the test and a (sometimes approximate) line number.
+ .. versionchanged:: next
+ Run each example as a :ref:`subtest <subtests>`.
Under the covers, :func:`DocTestSuite` creates a :class:`unittest.TestSuite` out
of :class:`!doctest.DocTestCase` instances, and :class:`!DocTestCase` is a
@@ -1158,15 +1168,15 @@ of :class:`!DocTestCase`.
So both ways of creating a :class:`unittest.TestSuite` run instances of
:class:`!DocTestCase`. This is important for a subtle reason: when you run
-:mod:`doctest` functions yourself, you can control the :mod:`doctest` options in
-use directly, by passing option flags to :mod:`doctest` functions. However, if
-you're writing a :mod:`unittest` framework, :mod:`unittest` ultimately controls
+:mod:`doctest` functions yourself, you can control the :mod:`!doctest` options in
+use directly, by passing option flags to :mod:`!doctest` functions. However, if
+you're writing a :mod:`unittest` framework, :mod:`!unittest` ultimately controls
when and how tests get run. The framework author typically wants to control
-:mod:`doctest` reporting options (perhaps, e.g., specified by command line
-options), but there's no way to pass options through :mod:`unittest` to
-:mod:`doctest` test runners.
+:mod:`!doctest` reporting options (perhaps, e.g., specified by command line
+options), but there's no way to pass options through :mod:`!unittest` to
+:mod:`!doctest` test runners.
-For this reason, :mod:`doctest` also supports a notion of :mod:`doctest`
+For this reason, :mod:`doctest` also supports a notion of :mod:`!doctest`
reporting flags specific to :mod:`unittest` support, via this function:
@@ -1181,12 +1191,12 @@ reporting flags specific to :mod:`unittest` support, via this function:
:mod:`unittest`: the :meth:`!runTest` method of :class:`!DocTestCase` looks at
the option flags specified for the test case when the :class:`!DocTestCase`
instance was constructed. If no reporting flags were specified (which is the
- typical and expected case), :mod:`!doctest`'s :mod:`unittest` reporting flags are
+ typical and expected case), :mod:`!doctest`'s :mod:`!unittest` reporting flags are
:ref:`bitwise ORed <bitwise>` into the option flags, and the option flags
so augmented are passed to the :class:`DocTestRunner` instance created to
run the doctest. If any reporting flags were specified when the
:class:`!DocTestCase` instance was constructed, :mod:`!doctest`'s
- :mod:`unittest` reporting flags are ignored.
+ :mod:`!unittest` reporting flags are ignored.
The value of the :mod:`unittest` reporting flags in effect before the function
was called is returned by the function.
@@ -1279,7 +1289,7 @@ DocTest Objects
.. attribute:: filename
The name of the file that this :class:`DocTest` was extracted from; or
- ``None`` if the filename is unknown, or if the :class:`DocTest` was not
+ ``None`` if the filename is unknown, or if the :class:`!DocTest` was not
extracted from a file.
@@ -1419,10 +1429,10 @@ DocTestFinder objects
The globals for each :class:`DocTest` is formed by combining *globs* and
*extraglobs* (bindings in *extraglobs* override bindings in *globs*). A new
- shallow copy of the globals dictionary is created for each :class:`DocTest`.
- If *globs* is not specified, then it defaults to the module's *__dict__*, if
- specified, or ``{}`` otherwise. If *extraglobs* is not specified, then it
- defaults to ``{}``.
+ shallow copy of the globals dictionary is created for each :class:`!DocTest`.
+ If *globs* is not specified, then it defaults to the module's
+ :attr:`~module.__dict__`, if specified, or ``{}`` otherwise.
+ If *extraglobs* is not specified, then it defaults to ``{}``.
.. _doctest-doctestparser:
@@ -1446,7 +1456,7 @@ DocTestParser objects
:class:`DocTest` object.
*globs*, *name*, *filename*, and *lineno* are attributes for the new
- :class:`DocTest` object. See the documentation for :class:`DocTest` for more
+ :class:`!DocTest` object. See the documentation for :class:`DocTest` for more
information.
@@ -1461,7 +1471,7 @@ DocTestParser objects
Divide the given string into examples and intervening text, and return them as
a list of alternating :class:`Example`\ s and strings. Line numbers for the
- :class:`Example`\ s are 0-based. The optional argument *name* is a name
+ :class:`!Example`\ s are 0-based. The optional argument *name* is a name
identifying this string, and is only used for error messages.
@@ -1501,14 +1511,14 @@ DocTestRunner objects
:class:`OutputChecker`. This comparison may be customized with a number of
option flags; see section :ref:`doctest-options` for more information. If the
option flags are insufficient, then the comparison may also be customized by
- passing a subclass of :class:`OutputChecker` to the constructor.
+ passing a subclass of :class:`!OutputChecker` to the constructor.
The test runner's display output can be controlled in two ways. First, an output
function can be passed to :meth:`run`; this function will be called
with strings that should be displayed. It defaults to ``sys.stdout.write``. If
capturing the output is not sufficient, then the display output can be also
customized by subclassing DocTestRunner, and overriding the methods
- :meth:`report_start`, :meth:`report_success`,
+ :meth:`report_skip`, :meth:`report_start`, :meth:`report_success`,
:meth:`report_unexpected_exception`, and :meth:`report_failure`.
The optional keyword argument *checker* specifies the :class:`OutputChecker`
@@ -1533,6 +1543,19 @@ DocTestRunner objects
:class:`DocTestRunner` defines the following methods:
+ .. method:: report_skip(out, test, example)
+
+ Report that the given example was skipped. This method is provided to
+ allow subclasses of :class:`DocTestRunner` to customize their output; it
+ should not be called directly.
+
+ *example* is the example about to be processed. *test* is the test
+ containing *example*. *out* is the output function that was passed to
+ :meth:`DocTestRunner.run`.
+
+ .. versionadded:: next
+
+
.. method:: report_start(out, test, example)
Report that the test runner is about to process the given example. This method
@@ -1540,7 +1563,7 @@ DocTestRunner objects
output; it should not be called directly.
*example* is the example about to be processed. *test* is the test
- *containing example*. *out* is the output function that was passed to
+ containing *example*. *out* is the output function that was passed to
:meth:`DocTestRunner.run`.
@@ -1940,7 +1963,7 @@ several options for organizing tests:
containing test cases for the named topics. These functions can be included in
the same file as the module, or separated out into a separate test file.
-* Define a ``__test__`` dictionary mapping from regression test topics to
+* Define a :attr:`~module.__test__` dictionary mapping from regression test topics to
docstrings containing test cases.
When you have placed your tests in a module, the module can itself be the test
diff --git a/Doc/library/email.header.rst b/Doc/library/email.header.rst
index 219fad0d2f6..f49885b8785 100644
--- a/Doc/library/email.header.rst
+++ b/Doc/library/email.header.rst
@@ -178,16 +178,36 @@ The :mod:`email.header` module also provides the following convenient functions.
Decode a message header value without converting the character set. The header
value is in *header*.
- This function returns a list of ``(decoded_string, charset)`` pairs containing
- each of the decoded parts of the header. *charset* is ``None`` for non-encoded
- parts of the header, otherwise a lower case string containing the name of the
- character set specified in the encoded string.
+ For historical reasons, this function may return either:
- Here's an example::
+ 1. A list of pairs containing each of the decoded parts of the header,
+ ``(decoded_bytes, charset)``, where *decoded_bytes* is always an instance of
+ :class:`bytes`, and *charset* is either:
+
+ - A lower case string containing the name of the character set specified.
+
+ - ``None`` for non-encoded parts of the header.
+
+ 2. A list of length 1 containing a pair ``(string, None)``, where
+ *string* is always an instance of :class:`str`.
+
+ An :exc:`email.errors.HeaderParseError` may be raised when certain decoding
+ errors occur (e.g. a base64 decoding exception).
+
+ Here are examples:
>>> from email.header import decode_header
>>> decode_header('=?iso-8859-1?q?p=F6stal?=')
[(b'p\xf6stal', 'iso-8859-1')]
+ >>> decode_header('unencoded_string')
+ [('unencoded_string', None)]
+ >>> decode_header('bar =?utf-8?B?ZsOzbw==?=')
+ [(b'bar ', None), (b'f\xc3\xb3o', 'utf-8')]
+
+ .. note::
+
+ This function exists for backwards compatibility only. For
+ new code, we recommend using :class:`email.headerregistry.HeaderRegistry`.
.. function:: make_header(decoded_seq, maxlinelen=None, header_name=None, continuation_ws=' ')
@@ -203,3 +223,7 @@ The :mod:`email.header` module also provides the following convenient functions.
:class:`Header` instance. Optional *maxlinelen*, *header_name*, and
*continuation_ws* are as in the :class:`Header` constructor.
+ .. note::
+
+ This function exists for backwards compatibility only, and is
+ not recommended for use in new code.
diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst
index 4b391ba020a..c09e1615a5b 100644
--- a/Doc/library/exceptions.rst
+++ b/Doc/library/exceptions.rst
@@ -429,17 +429,24 @@ The following exceptions are the exceptions that are usually raised.
* Creating a new Python thread.
* :meth:`Joining <threading.Thread.join>` a running daemon thread.
- * :func:`os.fork`.
+ * :func:`os.fork`,
+ * acquiring a lock such as :class:`threading.Lock`, when it is known that
+ the operation would otherwise deadlock.
See also the :func:`sys.is_finalizing` function.
.. versionadded:: 3.13
Previously, a plain :exc:`RuntimeError` was raised.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
:meth:`threading.Thread.join` can now raise this exception.
+ .. versionchanged:: next
+
+ This exception may be raised when acquiring :meth:`threading.Lock`
+ or :meth:`threading.RLock`.
+
.. exception:: RecursionError
This exception is derived from :exc:`RuntimeError`. It is raised when the
@@ -1048,7 +1055,7 @@ their subgroups based on the types of the contained exceptions.
subclasses that need a different constructor signature need to
override that rather than :meth:`~object.__init__`. For example, the following
defines an exception group subclass which accepts an exit_code and
- and constructs the group's message from it. ::
+ constructs the group's message from it. ::
class Errors(ExceptionGroup):
def __new__(cls, errors, exit_code):
diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst
index e34b067aea5..677966a8b2e 100644
--- a/Doc/library/faulthandler.rst
+++ b/Doc/library/faulthandler.rst
@@ -69,7 +69,7 @@ Dumping the traceback
Dumping the C stack
-------------------
-.. versionadded:: next
+.. versionadded:: 3.14
.. function:: dump_c_stack(file=sys.stderr)
@@ -90,7 +90,7 @@ An error will be printed instead of the stack.
Additionally, some compilers do not support :term:`CPython's <CPython>`
implementation of C stack dumps. As a result, a different error may be printed
-instead of the stack, even if the the operating system supports dumping stacks.
+instead of the stack, even if the operating system supports dumping stacks.
.. note::
@@ -130,7 +130,7 @@ Fault handler state
Only the current thread is dumped if the :term:`GIL` is disabled to
prevent the risk of data races.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
The dump now displays the C stack trace if *c_stack* is true.
.. function:: disable()
@@ -228,6 +228,41 @@ handler:
Fatal Python error: Segmentation fault
Current thread 0x00007fb899f39700 (most recent call first):
- File "/home/python/cpython/Lib/ctypes/__init__.py", line 486 in string_at
+ File "/opt/python/Lib/ctypes/__init__.py", line 486 in string_at
File "<stdin>", line 1 in <module>
+
+ Current thread's C stack trace (most recent call first):
+ Binary file "/opt/python/python", at _Py_DumpStack+0x42 [0x5b27f7d7147e]
+ Binary file "/opt/python/python", at +0x32dcbd [0x5b27f7d85cbd]
+ Binary file "/opt/python/python", at +0x32df8a [0x5b27f7d85f8a]
+ Binary file "/usr/lib/libc.so.6", at +0x3def0 [0x77b73226bef0]
+ Binary file "/usr/lib/libc.so.6", at +0x17ef9c [0x77b7323acf9c]
+ Binary file "/opt/python/build/lib.linux-x86_64-3.15/_ctypes.cpython-315d-x86_64-linux-gnu.so", at +0xcdf6 [0x77b7315dddf6]
+ Binary file "/usr/lib/libffi.so.8", at +0x7976 [0x77b73158f976]
+ Binary file "/usr/lib/libffi.so.8", at +0x413c [0x77b73158c13c]
+ Binary file "/usr/lib/libffi.so.8", at ffi_call+0x12e [0x77b73158ef0e]
+ Binary file "/opt/python/build/lib.linux-x86_64-3.15/_ctypes.cpython-315d-x86_64-linux-gnu.so", at +0x15a33 [0x77b7315e6a33]
+ Binary file "/opt/python/build/lib.linux-x86_64-3.15/_ctypes.cpython-315d-x86_64-linux-gnu.so", at +0x164fa [0x77b7315e74fa]
+ Binary file "/opt/python/build/lib.linux-x86_64-3.15/_ctypes.cpython-315d-x86_64-linux-gnu.so", at +0xc624 [0x77b7315dd624]
+ Binary file "/opt/python/python", at _PyObject_MakeTpCall+0xce [0x5b27f7b73883]
+ Binary file "/opt/python/python", at +0x11bab6 [0x5b27f7b73ab6]
+ Binary file "/opt/python/python", at PyObject_Vectorcall+0x23 [0x5b27f7b73b04]
+ Binary file "/opt/python/python", at _PyEval_EvalFrameDefault+0x490c [0x5b27f7cbb302]
+ Binary file "/opt/python/python", at +0x2818e6 [0x5b27f7cd98e6]
+ Binary file "/opt/python/python", at +0x281aab [0x5b27f7cd9aab]
+ Binary file "/opt/python/python", at PyEval_EvalCode+0xc5 [0x5b27f7cd9ba3]
+ Binary file "/opt/python/python", at +0x255957 [0x5b27f7cad957]
+ Binary file "/opt/python/python", at +0x255ab4 [0x5b27f7cadab4]
+ Binary file "/opt/python/python", at _PyEval_EvalFrameDefault+0x6c3e [0x5b27f7cbd634]
+ Binary file "/opt/python/python", at +0x2818e6 [0x5b27f7cd98e6]
+ Binary file "/opt/python/python", at +0x281aab [0x5b27f7cd9aab]
+ Binary file "/opt/python/python", at +0x11b6e1 [0x5b27f7b736e1]
+ Binary file "/opt/python/python", at +0x11d348 [0x5b27f7b75348]
+ Binary file "/opt/python/python", at +0x11d626 [0x5b27f7b75626]
+ Binary file "/opt/python/python", at PyObject_Call+0x20 [0x5b27f7b7565e]
+ Binary file "/opt/python/python", at +0x32a67a [0x5b27f7d8267a]
+ Binary file "/opt/python/python", at +0x32a7f8 [0x5b27f7d827f8]
+ Binary file "/opt/python/python", at +0x32ac1b [0x5b27f7d82c1b]
+ Binary file "/opt/python/python", at Py_RunMain+0x31 [0x5b27f7d82ebe]
+ <truncated rest of calls>
Segmentation fault
diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst
index b4ea3e7e31b..5c078df44ff 100644
--- a/Doc/library/fcntl.rst
+++ b/Doc/library/fcntl.rst
@@ -79,7 +79,7 @@ descriptor.
On macOS and NetBSD, the :mod:`!fcntl` module exposes the ``F_GETNOSIGPIPE``
and ``F_SETNOSIGPIPE`` constant.
-.. versionchanged:: next
+.. versionchanged:: 3.14
On Linux >= 6.1, the :mod:`!fcntl` module exposes the ``F_DUPFD_QUERY``
to query a file descriptor pointing to the same file.
@@ -107,24 +107,27 @@ The module defines the following functions:
passed to the C :c:func:`fcntl` call. The return value after a successful
call is the contents of the buffer, converted to a :class:`bytes` object.
The length of the returned object will be the same as the length of the
- *arg* argument. This is limited to 1024 bytes.
+ *arg* argument.
If the :c:func:`fcntl` call fails, an :exc:`OSError` is raised.
.. note::
- If the type or the size of *arg* does not match the type or size
- of the argument of the operation (for example, if an integer is
+ If the type or size of *arg* does not match the type or size
+ of the operation's argument (for example, if an integer is
passed when a pointer is expected, or the information returned in
- the buffer by the operating system is larger than 1024 bytes),
+ the buffer by the operating system is larger than the size of *arg*),
this is most likely to result in a segmentation violation or
a more subtle data corruption.
.. audit-event:: fcntl.fcntl fd,cmd,arg fcntl.fcntl
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Add support of arbitrary :term:`bytes-like objects <bytes-like object>`,
not only :class:`bytes`.
+ .. versionchanged:: next
+ The size of bytes-like objects is no longer limited to 1024 bytes.
+
.. function:: ioctl(fd, request, arg=0, mutate_flag=True, /)
@@ -161,8 +164,7 @@ The module defines the following functions:
If the type or size of *arg* does not match the type or size
of the operation's argument (for example, if an integer is
passed when a pointer is expected, or the information returned in
- the buffer by the operating system is larger than 1024 bytes,
- or the size of the mutable bytes-like object is too small),
+ the buffer by the operating system is larger than the size of *arg*),
this is most likely to result in a segmentation violation or
a more subtle data corruption.
@@ -181,10 +183,14 @@ The module defines the following functions:
.. audit-event:: fcntl.ioctl fd,request,arg fcntl.ioctl
- .. versionchanged:: next
+ .. versionchanged:: 3.14
The GIL is always released during a system call.
System calls failing with EINTR are automatically retried.
+ .. versionchanged:: next
+ The size of not mutated bytes-like objects is no longer
+ limited to 1024 bytes.
+
.. function:: flock(fd, operation, /)
Perform the lock operation *operation* on file descriptor *fd* (file objects providing
diff --git a/Doc/library/fractions.rst b/Doc/library/fractions.rst
index fc7f9a6301a..392b6d40e86 100644
--- a/Doc/library/fractions.rst
+++ b/Doc/library/fractions.rst
@@ -142,7 +142,7 @@ another rational number, or from a string.
.. versionadded:: 3.12
- .. classmethod:: from_float(flt)
+ .. classmethod:: from_float(f)
Alternative constructor which only accepts instances of
:class:`float` or :class:`numbers.Integral`. Beware that
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index 7e367a0f2b6..80bd1275973 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -1154,44 +1154,44 @@ are always available. They are listed here in alphabetical order.
.. function:: locals()
- Return a mapping object representing the current local symbol table, with
- variable names as the keys, and their currently bound references as the
- values.
-
- At module scope, as well as when using :func:`exec` or :func:`eval` with
- a single namespace, this function returns the same namespace as
- :func:`globals`.
-
- At class scope, it returns the namespace that will be passed to the
- metaclass constructor.
-
- When using ``exec()`` or ``eval()`` with separate local and global
- arguments, it returns the local namespace passed in to the function call.
-
- In all of the above cases, each call to ``locals()`` in a given frame of
- execution will return the *same* mapping object. Changes made through
- the mapping object returned from ``locals()`` will be visible as assigned,
- reassigned, or deleted local variables, and assigning, reassigning, or
- deleting local variables will immediately affect the contents of the
- returned mapping object.
-
- In an :term:`optimized scope` (including functions, generators, and
- coroutines), each call to ``locals()`` instead returns a fresh dictionary
- containing the current bindings of the function's local variables and any
- nonlocal cell references. In this case, name binding changes made via the
- returned dict are *not* written back to the corresponding local variables
- or nonlocal cell references, and assigning, reassigning, or deleting local
- variables and nonlocal cell references does *not* affect the contents
- of previously returned dictionaries.
-
- Calling ``locals()`` as part of a comprehension in a function, generator, or
- coroutine is equivalent to calling it in the containing scope, except that
- the comprehension's initialised iteration variables will be included. In
- other scopes, it behaves as if the comprehension were running as a nested
- function.
-
- Calling ``locals()`` as part of a generator expression is equivalent to
- calling it in a nested generator function.
+ Return a mapping object representing the current local symbol table, with
+ variable names as the keys, and their currently bound references as the
+ values.
+
+ At module scope, as well as when using :func:`exec` or :func:`eval` with
+ a single namespace, this function returns the same namespace as
+ :func:`globals`.
+
+ At class scope, it returns the namespace that will be passed to the
+ metaclass constructor.
+
+ When using ``exec()`` or ``eval()`` with separate local and global
+ arguments, it returns the local namespace passed in to the function call.
+
+ In all of the above cases, each call to ``locals()`` in a given frame of
+ execution will return the *same* mapping object. Changes made through
+ the mapping object returned from ``locals()`` will be visible as assigned,
+ reassigned, or deleted local variables, and assigning, reassigning, or
+ deleting local variables will immediately affect the contents of the
+ returned mapping object.
+
+ In an :term:`optimized scope` (including functions, generators, and
+ coroutines), each call to ``locals()`` instead returns a fresh dictionary
+ containing the current bindings of the function's local variables and any
+ nonlocal cell references. In this case, name binding changes made via the
+ returned dict are *not* written back to the corresponding local variables
+ or nonlocal cell references, and assigning, reassigning, or deleting local
+ variables and nonlocal cell references does *not* affect the contents
+ of previously returned dictionaries.
+
+ Calling ``locals()`` as part of a comprehension in a function, generator, or
+ coroutine is equivalent to calling it in the containing scope, except that
+ the comprehension's initialised iteration variables will be included. In
+ other scopes, it behaves as if the comprehension were running as a nested
+ function.
+
+ Calling ``locals()`` as part of a generator expression is equivalent to
+ calling it in a nested generator function.
.. versionchanged:: 3.12
The behaviour of ``locals()`` in a comprehension has been updated as
@@ -1839,15 +1839,15 @@ are always available. They are listed here in alphabetical order.
``range(start, stop, step)``. The *start* and *step* arguments default to
``None``.
+ Slice objects have read-only data attributes :attr:`!start`,
+ :attr:`!stop`, and :attr:`!step` which merely return the argument
+ values (or their default). They have no other explicit functionality;
+ however, they are used by NumPy and other third-party packages.
+
.. attribute:: slice.start
.. attribute:: slice.stop
.. attribute:: slice.step
- Slice objects have read-only data attributes :attr:`!start`,
- :attr:`!stop`, and :attr:`!step` which merely return the argument
- values (or their default). They have no other explicit functionality;
- however, they are used by NumPy and other third-party packages.
-
Slice objects are also generated when extended indexing syntax is used. For
example: ``a[start:stop:step]`` or ``a[start:stop, i]``. See
:func:`itertools.islice` for an alternate version that returns an
diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst
index 3a933dff057..3e75621be6d 100644
--- a/Doc/library/functools.rst
+++ b/Doc/library/functools.rst
@@ -403,8 +403,7 @@ The :mod:`functools` module defines the following functions:
>>> remove_first_dear(message)
'Hello, dear world!'
- :data:`!Placeholder` has no special treatment when used in a keyword
- argument to :func:`!partial`.
+ :data:`!Placeholder` cannot be passed to :func:`!partial` as a keyword argument.
.. versionchanged:: 3.14
Added support for :data:`Placeholder` in positional arguments.
diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst
index 480a9dec7f1..7ccb0e6bdf9 100644
--- a/Doc/library/gc.rst
+++ b/Doc/library/gc.rst
@@ -128,6 +128,11 @@ The :mod:`gc` module provides the following functions:
starts. For each collection, all the objects in the young generation and some
fraction of the old generation is collected.
+ In the free-threaded build, the increase in process memory usage is also
+ checked before running the collector. If the memory usage has not increased
+ by 10% since the last collection and the net number of object allocations
+ has not exceeded 40 times *threshold0*, the collection is not run.
+
The fraction of the old generation that is collected is **inversely** proportional
to *threshold1*. The larger *threshold1* is, the slower objects in the old generation
are collected.
diff --git a/Doc/library/getpass.rst b/Doc/library/getpass.rst
index 3b5296f9ec6..0fb0fc88683 100644
--- a/Doc/library/getpass.rst
+++ b/Doc/library/getpass.rst
@@ -16,7 +16,7 @@
The :mod:`getpass` module provides two functions:
-.. function:: getpass(prompt='Password: ', stream=None)
+.. function:: getpass(prompt='Password: ', stream=None, *, echo_char=None)
Prompt the user for a password without echoing. The user is prompted using
the string *prompt*, which defaults to ``'Password: '``. On Unix, the
@@ -25,6 +25,12 @@ The :mod:`getpass` module provides two functions:
(:file:`/dev/tty`) or if that is unavailable to ``sys.stderr`` (this
argument is ignored on Windows).
+ The *echo_char* argument controls how user input is displayed while typing.
+ If *echo_char* is ``None`` (default), input remains hidden. Otherwise,
+ *echo_char* must be a printable ASCII string and each typed character
+ is replaced by it. For example, ``echo_char='*'`` will display
+ asterisks instead of the actual input.
+
If echo free input is unavailable getpass() falls back to printing
a warning message to *stream* and reading from ``sys.stdin`` and
issuing a :exc:`GetPassWarning`.
@@ -33,6 +39,9 @@ The :mod:`getpass` module provides two functions:
If you call getpass from within IDLE, the input may be done in the
terminal you launched IDLE from rather than the idle window itself.
+ .. versionchanged:: 3.14
+ Added the *echo_char* parameter for keyboard feedback.
+
.. exception:: GetPassWarning
A :exc:`UserWarning` subclass issued when password input may be echoed.
diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst
index ff15a08a792..8bba6700930 100644
--- a/Doc/library/hashlib.rst
+++ b/Doc/library/hashlib.rst
@@ -94,6 +94,13 @@ accessible by name via :func:`new`. See :data:`algorithms_available`.
OpenSSL does not provide we fall back to a verified implementation from
the `HACL\* project`_.
+.. deprecated-removed:: 3.15 3.19
+ The undocumented ``string`` keyword parameter in :func:`!_hashlib.new`
+ and hash-named constructors such as :func:`!_md5.md5` is deprecated.
+ Prefer passing the initial data as a positional argument for maximum
+ backwards compatibility.
+
+
Usage
-----
@@ -284,7 +291,7 @@ a file or file-like object.
Example:
>>> import io, hashlib, hmac
- >>> with open(hashlib.__file__, "rb") as f:
+ >>> with open("library/hashlib.rst", "rb") as f:
... digest = hashlib.file_digest(f, "sha256")
...
>>> digest.hexdigest() # doctest: +ELLIPSIS
@@ -302,7 +309,7 @@ a file or file-like object.
.. versionadded:: 3.11
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Now raises a :exc:`BlockingIOError` if the file is opened in blocking
mode. Previously, spurious null bytes were added to the digest.
diff --git a/Doc/library/heapq-binary-tree.svg b/Doc/library/heapq-binary-tree.svg
new file mode 100644
index 00000000000..074a9a44275
--- /dev/null
+++ b/Doc/library/heapq-binary-tree.svg
@@ -0,0 +1,211 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="89.9 70 450.78 193.82">
+<defs>
+<g>
+<g id="glyph-0-0">
+<path d="M 4.578125 -3.1875 C 4.578125 -3.984375 4.53125 -4.78125 4.1875 -5.515625 C 3.734375 -6.484375 2.90625 -6.640625 2.5 -6.640625 C 1.890625 -6.640625 1.171875 -6.375 0.75 -5.453125 C 0.4375 -4.765625 0.390625 -3.984375 0.390625 -3.1875 C 0.390625 -2.4375 0.421875 -1.546875 0.84375 -0.78125 C 1.265625 0.015625 2 0.21875 2.484375 0.21875 C 3.015625 0.21875 3.78125 0.015625 4.21875 -0.9375 C 4.53125 -1.625 4.578125 -2.40625 4.578125 -3.1875 Z M 2.484375 0 C 2.09375 0 1.5 -0.25 1.328125 -1.203125 C 1.21875 -1.796875 1.21875 -2.71875 1.21875 -3.3125 C 1.21875 -3.953125 1.21875 -4.609375 1.296875 -5.140625 C 1.484375 -6.328125 2.234375 -6.421875 2.484375 -6.421875 C 2.8125 -6.421875 3.46875 -6.234375 3.65625 -5.25 C 3.765625 -4.6875 3.765625 -3.9375 3.765625 -3.3125 C 3.765625 -2.5625 3.765625 -1.890625 3.65625 -1.25 C 3.5 -0.296875 2.9375 0 2.484375 0 Z M 2.484375 0 "/>
+</g>
+<g id="glyph-0-1">
+<path d="M 2.9375 -6.375 C 2.9375 -6.625 2.9375 -6.640625 2.703125 -6.640625 C 2.078125 -6 1.203125 -6 0.890625 -6 L 0.890625 -5.6875 C 1.09375 -5.6875 1.671875 -5.6875 2.1875 -5.953125 L 2.1875 -0.78125 C 2.1875 -0.421875 2.15625 -0.3125 1.265625 -0.3125 L 0.953125 -0.3125 L 0.953125 0 C 1.296875 -0.03125 2.15625 -0.03125 2.5625 -0.03125 C 2.953125 -0.03125 3.828125 -0.03125 4.171875 0 L 4.171875 -0.3125 L 3.859375 -0.3125 C 2.953125 -0.3125 2.9375 -0.421875 2.9375 -0.78125 Z M 2.9375 -6.375 "/>
+</g>
+<g id="glyph-0-2">
+<path d="M 2.890625 -3.515625 C 3.703125 -3.78125 4.28125 -4.46875 4.28125 -5.265625 C 4.28125 -6.078125 3.40625 -6.640625 2.453125 -6.640625 C 1.453125 -6.640625 0.6875 -6.046875 0.6875 -5.28125 C 0.6875 -4.953125 0.90625 -4.765625 1.203125 -4.765625 C 1.5 -4.765625 1.703125 -4.984375 1.703125 -5.28125 C 1.703125 -5.765625 1.234375 -5.765625 1.09375 -5.765625 C 1.390625 -6.265625 2.046875 -6.390625 2.40625 -6.390625 C 2.828125 -6.390625 3.375 -6.171875 3.375 -5.28125 C 3.375 -5.15625 3.34375 -4.578125 3.09375 -4.140625 C 2.796875 -3.65625 2.453125 -3.625 2.203125 -3.625 C 2.125 -3.609375 1.890625 -3.59375 1.8125 -3.59375 C 1.734375 -3.578125 1.671875 -3.5625 1.671875 -3.46875 C 1.671875 -3.359375 1.734375 -3.359375 1.90625 -3.359375 L 2.34375 -3.359375 C 3.15625 -3.359375 3.53125 -2.6875 3.53125 -1.703125 C 3.53125 -0.34375 2.84375 -0.0625 2.40625 -0.0625 C 1.96875 -0.0625 1.21875 -0.234375 0.875 -0.8125 C 1.21875 -0.765625 1.53125 -0.984375 1.53125 -1.359375 C 1.53125 -1.71875 1.265625 -1.921875 0.984375 -1.921875 C 0.734375 -1.921875 0.421875 -1.78125 0.421875 -1.34375 C 0.421875 -0.4375 1.34375 0.21875 2.4375 0.21875 C 3.65625 0.21875 4.5625 -0.6875 4.5625 -1.703125 C 4.5625 -2.515625 3.921875 -3.296875 2.890625 -3.515625 Z M 2.890625 -3.515625 "/>
+</g>
+<g id="glyph-0-3">
+<path d="M 4.75 -6.078125 C 4.828125 -6.1875 4.828125 -6.203125 4.828125 -6.421875 L 2.40625 -6.421875 C 1.203125 -6.421875 1.171875 -6.546875 1.140625 -6.734375 L 0.890625 -6.734375 L 0.5625 -4.6875 L 0.8125 -4.6875 C 0.84375 -4.84375 0.921875 -5.46875 1.0625 -5.59375 C 1.125 -5.65625 1.90625 -5.65625 2.03125 -5.65625 L 4.09375 -5.65625 C 3.984375 -5.5 3.203125 -4.40625 2.984375 -4.078125 C 2.078125 -2.734375 1.75 -1.34375 1.75 -0.328125 C 1.75 -0.234375 1.75 0.21875 2.21875 0.21875 C 2.671875 0.21875 2.671875 -0.234375 2.671875 -0.328125 L 2.671875 -0.84375 C 2.671875 -1.390625 2.703125 -1.9375 2.78125 -2.46875 C 2.828125 -2.703125 2.953125 -3.5625 3.40625 -4.171875 Z M 4.75 -6.078125 "/>
+</g>
+<g id="glyph-0-4">
+<path d="M 4.46875 -2 C 4.46875 -3.1875 3.65625 -4.1875 2.578125 -4.1875 C 2.109375 -4.1875 1.671875 -4.03125 1.3125 -3.671875 L 1.3125 -5.625 C 1.515625 -5.5625 1.84375 -5.5 2.15625 -5.5 C 3.390625 -5.5 4.09375 -6.40625 4.09375 -6.53125 C 4.09375 -6.59375 4.0625 -6.640625 3.984375 -6.640625 C 3.984375 -6.640625 3.953125 -6.640625 3.90625 -6.609375 C 3.703125 -6.515625 3.21875 -6.3125 2.546875 -6.3125 C 2.15625 -6.3125 1.6875 -6.390625 1.21875 -6.59375 C 1.140625 -6.625 1.125 -6.625 1.109375 -6.625 C 1 -6.625 1 -6.546875 1 -6.390625 L 1 -3.4375 C 1 -3.265625 1 -3.1875 1.140625 -3.1875 C 1.21875 -3.1875 1.234375 -3.203125 1.28125 -3.265625 C 1.390625 -3.421875 1.75 -3.96875 2.5625 -3.96875 C 3.078125 -3.96875 3.328125 -3.515625 3.40625 -3.328125 C 3.5625 -2.953125 3.59375 -2.578125 3.59375 -2.078125 C 3.59375 -1.71875 3.59375 -1.125 3.34375 -0.703125 C 3.109375 -0.3125 2.734375 -0.0625 2.28125 -0.0625 C 1.5625 -0.0625 0.984375 -0.59375 0.8125 -1.171875 C 0.84375 -1.171875 0.875 -1.15625 0.984375 -1.15625 C 1.3125 -1.15625 1.484375 -1.40625 1.484375 -1.640625 C 1.484375 -1.890625 1.3125 -2.140625 0.984375 -2.140625 C 0.84375 -2.140625 0.5 -2.0625 0.5 -1.609375 C 0.5 -0.75 1.1875 0.21875 2.296875 0.21875 C 3.453125 0.21875 4.46875 -0.734375 4.46875 -2 Z M 4.46875 -2 "/>
+</g>
+<g id="glyph-0-5">
+<path d="M 1.3125 -3.265625 L 1.3125 -3.515625 C 1.3125 -6.03125 2.546875 -6.390625 3.0625 -6.390625 C 3.296875 -6.390625 3.71875 -6.328125 3.9375 -5.984375 C 3.78125 -5.984375 3.390625 -5.984375 3.390625 -5.546875 C 3.390625 -5.234375 3.625 -5.078125 3.84375 -5.078125 C 4 -5.078125 4.3125 -5.171875 4.3125 -5.5625 C 4.3125 -6.15625 3.875 -6.640625 3.046875 -6.640625 C 1.765625 -6.640625 0.421875 -5.359375 0.421875 -3.15625 C 0.421875 -0.484375 1.578125 0.21875 2.5 0.21875 C 3.609375 0.21875 4.5625 -0.71875 4.5625 -2.03125 C 4.5625 -3.296875 3.671875 -4.25 2.5625 -4.25 C 1.890625 -4.25 1.515625 -3.75 1.3125 -3.265625 Z M 2.5 -0.0625 C 1.875 -0.0625 1.578125 -0.65625 1.515625 -0.8125 C 1.328125 -1.28125 1.328125 -2.078125 1.328125 -2.25 C 1.328125 -3.03125 1.65625 -4.03125 2.546875 -4.03125 C 2.71875 -4.03125 3.171875 -4.03125 3.484375 -3.40625 C 3.65625 -3.046875 3.65625 -2.53125 3.65625 -2.046875 C 3.65625 -1.5625 3.65625 -1.0625 3.484375 -0.703125 C 3.1875 -0.109375 2.734375 -0.0625 2.5 -0.0625 Z M 2.5 -0.0625 "/>
+</g>
+<g id="glyph-0-6">
+<path d="M 1.625 -4.5625 C 1.171875 -4.859375 1.125 -5.1875 1.125 -5.359375 C 1.125 -5.96875 1.78125 -6.390625 2.484375 -6.390625 C 3.203125 -6.390625 3.84375 -5.875 3.84375 -5.15625 C 3.84375 -4.578125 3.453125 -4.109375 2.859375 -3.765625 Z M 3.078125 -3.609375 C 3.796875 -3.984375 4.28125 -4.5 4.28125 -5.15625 C 4.28125 -6.078125 3.40625 -6.640625 2.5 -6.640625 C 1.5 -6.640625 0.6875 -5.90625 0.6875 -4.96875 C 0.6875 -4.796875 0.703125 -4.34375 1.125 -3.875 C 1.234375 -3.765625 1.609375 -3.515625 1.859375 -3.34375 C 1.28125 -3.046875 0.421875 -2.5 0.421875 -1.5 C 0.421875 -0.453125 1.4375 0.21875 2.484375 0.21875 C 3.609375 0.21875 4.5625 -0.609375 4.5625 -1.671875 C 4.5625 -2.03125 4.453125 -2.484375 4.0625 -2.90625 C 3.875 -3.109375 3.71875 -3.203125 3.078125 -3.609375 Z M 2.078125 -3.1875 L 3.3125 -2.40625 C 3.59375 -2.21875 4.0625 -1.921875 4.0625 -1.3125 C 4.0625 -0.578125 3.3125 -0.0625 2.5 -0.0625 C 1.640625 -0.0625 0.921875 -0.671875 0.921875 -1.5 C 0.921875 -2.078125 1.234375 -2.71875 2.078125 -3.1875 Z M 2.078125 -3.1875 "/>
+</g>
+<g id="glyph-0-7">
+<path d="M 2.9375 -1.640625 L 2.9375 -0.78125 C 2.9375 -0.421875 2.90625 -0.3125 2.171875 -0.3125 L 1.96875 -0.3125 L 1.96875 0 C 2.375 -0.03125 2.890625 -0.03125 3.3125 -0.03125 C 3.734375 -0.03125 4.25 -0.03125 4.671875 0 L 4.671875 -0.3125 L 4.453125 -0.3125 C 3.71875 -0.3125 3.703125 -0.421875 3.703125 -0.78125 L 3.703125 -1.640625 L 4.6875 -1.640625 L 4.6875 -1.953125 L 3.703125 -1.953125 L 3.703125 -6.484375 C 3.703125 -6.6875 3.703125 -6.75 3.53125 -6.75 C 3.453125 -6.75 3.421875 -6.75 3.34375 -6.625 L 0.28125 -1.953125 L 0.28125 -1.640625 Z M 2.984375 -1.953125 L 0.5625 -1.953125 L 2.984375 -5.671875 Z M 2.984375 -1.953125 "/>
+</g>
+<g id="glyph-0-8">
+<path d="M 3.65625 -3.171875 L 3.65625 -2.84375 C 3.65625 -0.515625 2.625 -0.0625 2.046875 -0.0625 C 1.875 -0.0625 1.328125 -0.078125 1.0625 -0.421875 C 1.5 -0.421875 1.578125 -0.703125 1.578125 -0.875 C 1.578125 -1.1875 1.34375 -1.328125 1.125 -1.328125 C 0.96875 -1.328125 0.671875 -1.25 0.671875 -0.859375 C 0.671875 -0.1875 1.203125 0.21875 2.046875 0.21875 C 3.34375 0.21875 4.5625 -1.140625 4.5625 -3.28125 C 4.5625 -5.96875 3.40625 -6.640625 2.515625 -6.640625 C 1.96875 -6.640625 1.484375 -6.453125 1.0625 -6.015625 C 0.640625 -5.5625 0.421875 -5.140625 0.421875 -4.390625 C 0.421875 -3.15625 1.296875 -2.171875 2.40625 -2.171875 C 3.015625 -2.171875 3.421875 -2.59375 3.65625 -3.171875 Z M 2.421875 -2.40625 C 2.265625 -2.40625 1.796875 -2.40625 1.5 -3.03125 C 1.3125 -3.40625 1.3125 -3.890625 1.3125 -4.390625 C 1.3125 -4.921875 1.3125 -5.390625 1.53125 -5.765625 C 1.796875 -6.265625 2.171875 -6.390625 2.515625 -6.390625 C 2.984375 -6.390625 3.3125 -6.046875 3.484375 -5.609375 C 3.59375 -5.28125 3.640625 -4.65625 3.640625 -4.203125 C 3.640625 -3.375 3.296875 -2.40625 2.421875 -2.40625 Z M 2.421875 -2.40625 "/>
+</g>
+<g id="glyph-0-9">
+<path d="M 1.265625 -0.765625 L 2.328125 -1.796875 C 3.875 -3.171875 4.46875 -3.703125 4.46875 -4.703125 C 4.46875 -5.84375 3.578125 -6.640625 2.359375 -6.640625 C 1.234375 -6.640625 0.5 -5.71875 0.5 -4.828125 C 0.5 -4.28125 1 -4.28125 1.03125 -4.28125 C 1.203125 -4.28125 1.546875 -4.390625 1.546875 -4.8125 C 1.546875 -5.0625 1.359375 -5.328125 1.015625 -5.328125 C 0.9375 -5.328125 0.921875 -5.328125 0.890625 -5.3125 C 1.109375 -5.96875 1.65625 -6.328125 2.234375 -6.328125 C 3.140625 -6.328125 3.5625 -5.515625 3.5625 -4.703125 C 3.5625 -3.90625 3.078125 -3.125 2.515625 -2.5 L 0.609375 -0.375 C 0.5 -0.265625 0.5 -0.234375 0.5 0 L 4.203125 0 L 4.46875 -1.734375 L 4.234375 -1.734375 C 4.171875 -1.4375 4.109375 -1 4 -0.84375 C 3.9375 -0.765625 3.28125 -0.765625 3.0625 -0.765625 Z M 1.265625 -0.765625 "/>
+</g>
+</g>
+</defs>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 8.754781 -0.000125 C 8.754781 4.835813 4.832906 8.753781 0.000875 8.753781 C -4.835062 8.753781 -8.753031 4.835813 -8.753031 -0.000125 C -8.753031 -4.836062 -4.835062 -8.754031 0.000875 -8.754031 C 4.832906 -8.754031 8.754781 -4.836062 8.754781 -0.000125 Z M 8.754781 -0.000125 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-0" x="312.806" y="84.163"/>
+</g>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -104.635844 -42.519656 C -104.635844 -37.687625 -108.553812 -33.76575 -113.385844 -33.76575 C -118.221781 -33.76575 -122.13975 -37.687625 -122.13975 -42.519656 C -122.13975 -47.355594 -118.221781 -51.273562 -113.385844 -51.273562 C -108.553812 -51.273562 -104.635844 -47.355594 -104.635844 -42.519656 Z M -104.635844 -42.519656 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="199.42" y="126.682"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -8.381937 -3.144656 L -105.006937 -39.375125 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -161.32725 -85.039187 C -161.32725 -80.207156 -165.245219 -76.289187 -170.081156 -76.289187 C -174.917094 -76.289187 -178.835062 -80.207156 -178.835062 -85.039187 C -178.835062 -89.875125 -174.917094 -93.793094 -170.081156 -93.793094 C -165.245219 -93.793094 -161.32725 -89.875125 -161.32725 -85.039187 Z M -161.32725 -85.039187 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-2" x="142.727" y="169.202"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -120.549906 -47.89075 L -162.921 -79.668094 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -189.674906 -127.558719 C -189.674906 -122.726687 -193.592875 -118.808719 -198.428812 -118.808719 C -203.260844 -118.808719 -207.182719 -122.726687 -207.182719 -127.558719 C -207.182719 -132.394656 -203.260844 -136.312625 -198.428812 -136.312625 C -193.592875 -136.312625 -189.674906 -132.394656 -189.674906 -127.558719 Z M -189.674906 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-3" x="114.38" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -175.046 -92.488406 L -193.463969 -120.113406 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -202.026469 -170.082156 C -202.026469 -164.242312 -206.760844 -159.507937 -212.600687 -159.507937 C -218.440531 -159.507937 -223.174906 -164.242312 -223.174906 -170.082156 C -223.174906 -175.922 -218.440531 -180.652469 -212.600687 -180.652469 C -206.760844 -180.652469 -202.026469 -175.922 -202.026469 -170.082156 Z M -202.026469 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="97.717" y="254.241"/>
+<use xlink:href="#glyph-0-4" x="102.6983" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -201.256937 -136.054812 L -209.194437 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -173.678812 -170.082156 C -173.678812 -164.242312 -178.413187 -159.507937 -184.253031 -159.507937 C -190.092875 -159.507937 -194.82725 -164.242312 -194.82725 -170.082156 C -194.82725 -175.922 -190.092875 -180.652469 -184.253031 -180.652469 C -178.413187 -180.652469 -173.678812 -175.922 -173.678812 -170.082156 Z M -173.678812 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="126.063" y="254.241"/>
+<use xlink:href="#glyph-0-5" x="131.0443" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -195.596781 -136.054812 L -187.659281 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -132.979594 -127.558719 C -132.979594 -122.726687 -136.901469 -118.808719 -141.7335 -118.808719 C -146.569437 -118.808719 -150.487406 -122.726687 -150.487406 -127.558719 C -150.487406 -132.394656 -146.569437 -136.312625 -141.7335 -136.312625 C -136.901469 -136.312625 -132.979594 -132.394656 -132.979594 -127.558719 Z M -132.979594 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-6" x="171.073" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -165.116312 -92.488406 L -146.698344 -120.113406 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -145.335062 -170.082156 C -145.335062 -164.242312 -150.069437 -159.507937 -155.909281 -159.507937 C -161.745219 -159.507937 -166.479594 -164.242312 -166.479594 -170.082156 C -166.479594 -175.922 -161.745219 -180.652469 -155.909281 -180.652469 C -150.069437 -180.652469 -145.335062 -175.922 -145.335062 -170.082156 Z M -145.335062 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="154.409" y="254.241"/>
+<use xlink:href="#glyph-0-3" x="159.3903" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -144.565531 -136.054812 L -152.499125 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -116.987406 -170.082156 C -116.987406 -164.242312 -121.721781 -159.507937 -127.561625 -159.507937 C -133.401469 -159.507937 -138.135844 -164.242312 -138.135844 -170.082156 C -138.135844 -175.922 -133.401469 -180.652469 -127.561625 -180.652469 C -121.721781 -180.652469 -116.987406 -175.922 -116.987406 -170.082156 Z M -116.987406 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="182.756" y="254.241"/>
+<use xlink:href="#glyph-0-6" x="187.7373" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -138.901469 -136.054812 L -130.967875 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -47.940531 -85.039187 C -47.940531 -80.207156 -51.8585 -76.289187 -56.694437 -76.289187 C -61.526469 -76.289187 -65.448344 -80.207156 -65.448344 -85.039187 C -65.448344 -89.875125 -61.526469 -93.793094 -56.694437 -93.793094 C -51.8585 -93.793094 -47.940531 -89.875125 -47.940531 -85.039187 Z M -47.940531 -85.039187 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-7" x="256.113" y="169.202"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -106.225687 -47.89075 L -63.854594 -79.668094 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -76.288187 -127.558719 C -76.288187 -122.726687 -80.206156 -118.808719 -85.042094 -118.808719 C -89.874125 -118.808719 -93.792094 -122.726687 -93.792094 -127.558719 C -93.792094 -132.394656 -89.874125 -136.312625 -85.042094 -136.312625 C -80.206156 -136.312625 -76.288187 -132.394656 -76.288187 -127.558719 Z M -76.288187 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-8" x="227.766" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -61.659281 -92.488406 L -80.073344 -120.113406 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -88.63975 -170.082156 C -88.63975 -164.242312 -93.374125 -159.507937 -99.213969 -159.507937 C -105.053812 -159.507937 -109.788187 -164.242312 -109.788187 -170.082156 C -109.788187 -175.922 -105.053812 -180.652469 -99.213969 -180.652469 C -93.374125 -180.652469 -88.63975 -175.922 -88.63975 -170.082156 Z M -88.63975 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="211.102" y="254.241"/>
+<use xlink:href="#glyph-0-8" x="216.0833" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -87.870219 -136.054812 L -95.807719 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -60.292094 -170.082156 C -60.292094 -164.242312 -65.026469 -159.507937 -70.866312 -159.507937 C -76.706156 -159.507937 -81.440531 -164.242312 -81.440531 -170.082156 C -81.440531 -175.922 -76.706156 -180.652469 -70.866312 -180.652469 C -65.026469 -180.652469 -60.292094 -175.922 -60.292094 -170.082156 Z M -60.292094 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="239.449" y="254.241"/>
+<use xlink:href="#glyph-0-0" x="244.4303" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -82.210062 -136.054812 L -74.272562 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -17.772562 -127.558719 C -17.772562 -121.722781 -22.506937 -116.988406 -28.346781 -116.988406 C -34.186625 -116.988406 -38.921 -121.722781 -38.921 -127.558719 C -38.921 -133.398562 -34.186625 -138.132937 -28.346781 -138.132937 C -22.506937 -138.132937 -17.772562 -133.398562 -17.772562 -127.558719 Z M -17.772562 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="281.968" y="211.722"/>
+<use xlink:href="#glyph-0-0" x="286.9493" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -51.729594 -92.488406 L -34.323344 -118.597781 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -31.948344 -170.082156 C -31.948344 -164.242312 -36.678812 -159.507937 -42.518656 -159.507937 C -48.3585 -159.507937 -53.092875 -164.242312 -53.092875 -170.082156 C -53.092875 -175.922 -48.3585 -180.652469 -42.518656 -180.652469 C -36.678812 -180.652469 -31.948344 -175.922 -31.948344 -170.082156 Z M -31.948344 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="267.795" y="254.241"/>
+<use xlink:href="#glyph-0-1" x="272.7763" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -31.753031 -137.781375 L -39.112406 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M -3.600687 -170.082156 C -3.600687 -164.242312 -8.335062 -159.507937 -14.174906 -159.507937 C -20.01475 -159.507937 -24.745219 -164.242312 -24.745219 -170.082156 C -24.745219 -175.922 -20.01475 -180.652469 -14.174906 -180.652469 C -8.335062 -180.652469 -3.600687 -175.922 -3.600687 -170.082156 Z M -3.600687 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="296.142" y="254.241"/>
+<use xlink:href="#glyph-0-9" x="301.1233" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M -24.940531 -137.781375 L -17.581156 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 122.1415 -42.519656 C 122.1415 -37.687625 118.223531 -33.76575 113.387594 -33.76575 C 108.551656 -33.76575 104.633688 -37.687625 104.633688 -42.519656 C 104.633688 -47.355594 108.551656 -51.273562 113.387594 -51.273562 C 118.223531 -51.273562 122.1415 -47.355594 122.1415 -42.519656 Z M 122.1415 -42.519656 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="426.191" y="126.682"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 8.383688 -3.144656 L 105.004781 -39.375125 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 65.446188 -85.039187 C 65.446188 -80.207156 61.528219 -76.289187 56.692281 -76.289187 C 51.86025 -76.289187 47.942281 -80.207156 47.942281 -85.039187 C 47.942281 -89.875125 51.86025 -93.793094 56.692281 -93.793094 C 61.528219 -93.793094 65.446188 -89.875125 65.446188 -85.039187 Z M 65.446188 -85.039187 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-4" x="369.498" y="169.202"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 106.227438 -47.89075 L 63.856344 -79.668094 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 38.918844 -127.558719 C 38.918844 -121.722781 34.188375 -116.988406 28.348531 -116.988406 C 22.508688 -116.988406 17.774313 -121.722781 17.774313 -127.558719 C 17.774313 -133.398562 22.508688 -138.132937 28.348531 -138.132937 C 34.188375 -138.132937 38.918844 -133.398562 38.918844 -127.558719 Z M 38.918844 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="338.661" y="211.722"/>
+<use xlink:href="#glyph-0-1" x="343.6423" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 51.727438 -92.488406 L 34.321188 -118.597781 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 24.746969 -170.082156 C 24.746969 -164.242312 20.012594 -159.507937 14.17275 -159.507937 C 8.332906 -159.507937 3.598531 -164.242312 3.598531 -170.082156 C 3.598531 -175.922 8.332906 -180.652469 14.17275 -180.652469 C 20.012594 -180.652469 24.746969 -175.922 24.746969 -170.082156 Z M 24.746969 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="324.488" y="254.241"/>
+<use xlink:href="#glyph-0-2" x="329.4693" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 24.942281 -137.781375 L 17.579 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 53.094625 -170.082156 C 53.094625 -164.242312 48.36025 -159.507937 42.520406 -159.507937 C 36.680563 -159.507937 31.946188 -164.242312 31.946188 -170.082156 C 31.946188 -175.922 36.680563 -180.652469 42.520406 -180.652469 C 48.36025 -180.652469 53.094625 -175.922 53.094625 -170.082156 Z M 53.094625 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="352.835" y="254.241"/>
+<use xlink:href="#glyph-0-7" x="357.8163" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 31.754781 -137.781375 L 39.114156 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 95.614156 -127.558719 C 95.614156 -121.722781 90.879781 -116.988406 85.039938 -116.988406 C 79.200094 -116.988406 74.465719 -121.722781 74.465719 -127.558719 C 74.465719 -133.398562 79.200094 -138.132937 85.039938 -138.132937 C 90.879781 -138.132937 95.614156 -133.398562 95.614156 -127.558719 Z M 95.614156 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="395.354" y="211.722"/>
+<use xlink:href="#glyph-0-9" x="400.3353" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 61.661031 -92.488406 L 79.063375 -118.597781 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 81.442281 -170.082156 C 81.442281 -164.242312 76.707906 -159.507937 70.868063 -159.507937 C 65.028219 -159.507937 60.293844 -164.242312 60.293844 -170.082156 C 60.293844 -175.922 65.028219 -180.652469 70.868063 -180.652469 C 76.707906 -180.652469 81.442281 -175.922 81.442281 -170.082156 Z M 81.442281 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="381.181" y="254.241"/>
+<use xlink:href="#glyph-0-4" x="386.1623" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 81.633688 -137.781375 L 74.274313 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 109.786031 -170.082156 C 109.786031 -164.242312 105.051656 -159.507937 99.215719 -159.507937 C 93.375875 -159.507937 88.6415 -164.242312 88.6415 -170.082156 C 88.6415 -175.922 93.375875 -180.652469 99.215719 -180.652469 C 105.051656 -180.652469 109.786031 -175.922 109.786031 -170.082156 Z M 109.786031 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="409.527" y="254.241"/>
+<use xlink:href="#glyph-0-5" x="414.5083" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 88.446188 -137.781375 L 95.805563 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 178.832906 -85.039187 C 178.832906 -80.207156 174.914938 -76.289187 170.079 -76.289187 C 165.246969 -76.289187 161.329 -80.207156 161.329 -85.039187 C 161.329 -89.875125 165.246969 -93.793094 170.079 -93.793094 C 174.914938 -93.793094 178.832906 -89.875125 178.832906 -85.039187 Z M 178.832906 -85.039187 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-5" x="482.884" y="169.202"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 120.54775 -47.89075 L 162.918844 -79.668094 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 152.309469 -127.558719 C 152.309469 -121.722781 147.575094 -116.988406 141.73525 -116.988406 C 135.895406 -116.988406 131.161031 -121.722781 131.161031 -127.558719 C 131.161031 -133.398562 135.895406 -138.132937 141.73525 -138.132937 C 147.575094 -138.132937 152.309469 -133.398562 152.309469 -127.558719 Z M 152.309469 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="452.047" y="211.722"/>
+<use xlink:href="#glyph-0-2" x="457.0283" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 165.114156 -92.488406 L 147.707906 -118.597781 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 138.133688 -170.082156 C 138.133688 -164.242312 133.399313 -159.507937 127.559469 -159.507937 C 121.719625 -159.507937 116.989156 -164.242312 116.989156 -170.082156 C 116.989156 -175.922 121.719625 -180.652469 127.559469 -180.652469 C 133.399313 -180.652469 138.133688 -175.922 138.133688 -170.082156 Z M 138.133688 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="437.874" y="254.241"/>
+<use xlink:href="#glyph-0-3" x="442.8553" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 138.329 -137.781375 L 130.965719 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 166.481344 -170.082156 C 166.481344 -164.242312 161.746969 -159.507937 155.907125 -159.507937 C 150.067281 -159.507937 145.332906 -164.242312 145.332906 -170.082156 C 145.332906 -175.922 150.067281 -180.652469 155.907125 -180.652469 C 161.746969 -180.652469 166.481344 -175.922 166.481344 -170.082156 Z M 166.481344 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="466.22" y="254.241"/>
+<use xlink:href="#glyph-0-6" x="471.2013" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 145.1415 -137.781375 L 152.500875 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 209.000875 -127.558719 C 209.000875 -121.722781 204.2665 -116.988406 198.426656 -116.988406 C 192.586813 -116.988406 187.852438 -121.722781 187.852438 -127.558719 C 187.852438 -133.398562 192.586813 -138.132937 198.426656 -138.132937 C 204.2665 -138.132937 209.000875 -133.398562 209.000875 -127.558719 Z M 209.000875 -127.558719 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-1" x="508.74" y="211.722"/>
+<use xlink:href="#glyph-0-7" x="513.7213" y="211.722"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 175.04775 -92.488406 L 192.454 -118.597781 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 194.829 -170.082156 C 194.829 -164.242312 190.094625 -159.507937 184.254781 -159.507937 C 178.414938 -159.507937 173.680563 -164.242312 173.680563 -170.082156 C 173.680563 -175.922 178.414938 -180.652469 184.254781 -180.652469 C 190.094625 -180.652469 194.829 -175.922 194.829 -170.082156 Z M 194.829 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-9" x="494.567" y="254.241"/>
+<use xlink:href="#glyph-0-8" x="499.5483" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 195.020406 -137.781375 L 187.661031 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<path fill-rule="nonzero" fill="rgb(79.998779%, 79.998779%, 100%)" fill-opacity="1" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 100%)" stroke-opacity="1" stroke-miterlimit="10" d="M 223.176656 -170.082156 C 223.176656 -164.242312 218.442281 -159.507937 212.602438 -159.507937 C 206.762594 -159.507937 202.028219 -164.242312 202.028219 -170.082156 C 202.028219 -175.922 206.762594 -180.652469 212.602438 -180.652469 C 218.442281 -180.652469 223.176656 -175.922 223.176656 -170.082156 Z M 223.176656 -170.082156 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+<g fill="rgb(0%, 0%, 0%)" fill-opacity="1">
+<use xlink:href="#glyph-0-2" x="522.913" y="254.241"/>
+<use xlink:href="#glyph-0-0" x="527.8943" y="254.241"/>
+</g>
+<path fill="none" stroke-width="0.3985" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="10" d="M 201.832906 -137.781375 L 209.196188 -159.8595 " transform="matrix(1, 0, 0, -1, 315.296, 80.953)"/>
+</svg> \ No newline at end of file
diff --git a/Doc/library/heapq.rst b/Doc/library/heapq.rst
index d3c4b920ba5..462b65bc7af 100644
--- a/Doc/library/heapq.rst
+++ b/Doc/library/heapq.rst
@@ -16,40 +16,56 @@
This module provides an implementation of the heap queue algorithm, also known
as the priority queue algorithm.
-Heaps are binary trees for which every parent node has a value less than or
-equal to any of its children. We refer to this condition as the heap invariant.
+Min-heaps are binary trees for which every parent node has a value less than
+or equal to any of its children.
+We refer to this condition as the heap invariant.
-This implementation uses arrays for which
-``heap[k] <= heap[2*k+1]`` and ``heap[k] <= heap[2*k+2]`` for all *k*, counting
-elements from zero. For the sake of comparison, non-existing elements are
-considered to be infinite. The interesting property of a heap is that its
-smallest element is always the root, ``heap[0]``.
+For min-heaps, this implementation uses lists for which
+``heap[k] <= heap[2*k+1]`` and ``heap[k] <= heap[2*k+2]`` for all *k* for which
+the compared elements exist. Elements are counted from zero. The interesting
+property of a min-heap is that its smallest element is always the root,
+``heap[0]``.
-The API below differs from textbook heap algorithms in two aspects: (a) We use
-zero-based indexing. This makes the relationship between the index for a node
-and the indexes for its children slightly less obvious, but is more suitable
-since Python uses zero-based indexing. (b) Our pop method returns the smallest
-item, not the largest (called a "min heap" in textbooks; a "max heap" is more
-common in texts because of its suitability for in-place sorting).
+Max-heaps satisfy the reverse invariant: every parent node has a value
+*greater* than any of its children. These are implemented as lists for which
+``maxheap[2*k+1] <= maxheap[k]`` and ``maxheap[2*k+2] <= maxheap[k]`` for all
+*k* for which the compared elements exist.
+The root, ``maxheap[0]``, contains the *largest* element;
+``heap.sort(reverse=True)`` maintains the max-heap invariant.
-These two make it possible to view the heap as a regular Python list without
-surprises: ``heap[0]`` is the smallest item, and ``heap.sort()`` maintains the
-heap invariant!
+The :mod:`!heapq` API differs from textbook heap algorithms in two aspects: (a)
+We use zero-based indexing. This makes the relationship between the index for
+a node and the indexes for its children slightly less obvious, but is more
+suitable since Python uses zero-based indexing. (b) Textbooks often focus on
+max-heaps, due to their suitability for in-place sorting. Our implementation
+favors min-heaps as they better correspond to Python :class:`lists <list>`.
-To create a heap, use a list initialized to ``[]``, or you can transform a
-populated list into a heap via function :func:`heapify`.
+These two aspects make it possible to view the heap as a regular Python list
+without surprises: ``heap[0]`` is the smallest item, and ``heap.sort()``
+maintains the heap invariant!
-The following functions are provided:
+Like :meth:`list.sort`, this implementation uses only the ``<`` operator
+for comparisons, for both min-heaps and max-heaps.
+
+In the API below, and in this documentation, the unqualified term *heap*
+generally refers to a min-heap.
+The API for max-heaps is named using a ``_max`` suffix.
+
+To create a heap, use a list initialized as ``[]``, or transform an existing list
+into a min-heap or max-heap using the :func:`heapify` or :func:`heapify_max`
+functions, respectively.
+
+The following functions are provided for min-heaps:
.. function:: heappush(heap, item)
- Push the value *item* onto the *heap*, maintaining the heap invariant.
+ Push the value *item* onto the *heap*, maintaining the min-heap invariant.
.. function:: heappop(heap)
- Pop and return the smallest item from the *heap*, maintaining the heap
+ Pop and return the smallest item from the *heap*, maintaining the min-heap
invariant. If the heap is empty, :exc:`IndexError` is raised. To access the
smallest item without popping it, use ``heap[0]``.
@@ -63,7 +79,7 @@ The following functions are provided:
.. function:: heapify(x)
- Transform list *x* into a heap, in-place, in linear time.
+ Transform list *x* into a min-heap, in-place, in linear time.
.. function:: heapreplace(heap, item)
@@ -82,6 +98,56 @@ The following functions are provided:
on the heap.
+For max-heaps, the following functions are provided:
+
+
+.. function:: heapify_max(x)
+
+ Transform list *x* into a max-heap, in-place, in linear time.
+
+ .. versionadded:: 3.14
+
+
+.. function:: heappush_max(heap, item)
+
+ Push the value *item* onto the max-heap *heap*, maintaining the max-heap
+ invariant.
+
+ .. versionadded:: 3.14
+
+
+.. function:: heappop_max(heap)
+
+ Pop and return the largest item from the max-heap *heap*, maintaining the
+ max-heap invariant. If the max-heap is empty, :exc:`IndexError` is raised.
+ To access the largest item without popping it, use ``maxheap[0]``.
+
+ .. versionadded:: 3.14
+
+
+.. function:: heappushpop_max(heap, item)
+
+ Push *item* on the max-heap *heap*, then pop and return the largest item
+ from *heap*.
+ The combined action runs more efficiently than :func:`heappush_max`
+ followed by a separate call to :func:`heappop_max`.
+
+ .. versionadded:: 3.14
+
+
+.. function:: heapreplace_max(heap, item)
+
+ Pop and return the largest item from the max-heap *heap* and also push the
+ new *item*.
+ The max-heap size doesn't change. If the max-heap is empty,
+ :exc:`IndexError` is raised.
+
+ The value returned may be smaller than the *item* added. Refer to the
+ analogous function :func:`heapreplace` for detailed usage notes.
+
+ .. versionadded:: 3.14
+
+
The module also offers three general purpose functions based on heaps.
@@ -246,17 +312,12 @@ elements are considered to be infinite. The interesting property of a heap is
that ``a[0]`` is always its smallest element.
The strange invariant above is meant to be an efficient memory representation
-for a tournament. The numbers below are *k*, not ``a[k]``::
-
- 0
-
- 1 2
-
- 3 4 5 6
-
- 7 8 9 10 11 12 13 14
+for a tournament. The numbers below are *k*, not ``a[k]``:
- 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
+.. figure:: heapq-binary-tree.svg
+ :class: invert-in-dark-mode
+ :align: center
+ :alt: Example (min-heap) binary tree.
In the tree above, each cell *k* is topping ``2*k+1`` and ``2*k+2``. In a usual
binary tournament we see in sports, each cell is the winner over the two cells
diff --git a/Doc/library/html.parser.rst b/Doc/library/html.parser.rst
index 6d433b5a04f..dd67fc34e85 100644
--- a/Doc/library/html.parser.rst
+++ b/Doc/library/html.parser.rst
@@ -43,7 +43,9 @@ Example HTML Parser Application
As a basic example, below is a simple HTML parser that uses the
:class:`HTMLParser` class to print out start tags, end tags, and data
-as they are encountered::
+as they are encountered:
+
+.. testcode::
from html.parser import HTMLParser
@@ -63,7 +65,7 @@ as they are encountered::
The output will then be:
-.. code-block:: none
+.. testoutput::
Encountered a start tag: html
Encountered a start tag: head
@@ -230,7 +232,9 @@ Examples
--------
The following class implements a parser that will be used to illustrate more
-examples::
+examples:
+
+.. testcode::
from html.parser import HTMLParser
from html.entities import name2codepoint
@@ -266,13 +270,17 @@ examples::
parser = MyHTMLParser()
-Parsing a doctype::
+Parsing a doctype:
+
+.. doctest::
>>> parser.feed('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
... '"http://www.w3.org/TR/html4/strict.dtd">')
Decl : DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"
-Parsing an element with a few attributes and a title::
+Parsing an element with a few attributes and a title:
+
+.. doctest::
>>> parser.feed('<img src="python-logo.png" alt="The Python logo">')
Start tag: img
@@ -285,7 +293,9 @@ Parsing an element with a few attributes and a title::
End tag : h1
The content of ``script`` and ``style`` elements is returned as is, without
-further parsing::
+further parsing:
+
+.. doctest::
>>> parser.feed('<style type="text/css">#python { color: green }</style>')
Start tag: style
@@ -300,16 +310,25 @@ further parsing::
Data : alert("<strong>hello!</strong>");
End tag : script
-Parsing comments::
+Parsing comments:
+
+.. doctest::
- >>> parser.feed('<!-- a comment -->'
+ >>> parser.feed('<!--a comment-->'
... '<!--[if IE 9]>IE-specific content<![endif]-->')
- Comment : a comment
+ Comment : a comment
Comment : [if IE 9]>IE-specific content<![endif]
Parsing named and numeric character references and converting them to the
-correct char (note: these 3 references are all equivalent to ``'>'``)::
+correct char (note: these 3 references are all equivalent to ``'>'``):
+.. doctest::
+
+ >>> parser = MyHTMLParser()
+ >>> parser.feed('&gt;&#62;&#x3E;')
+ Data : >>>
+
+ >>> parser = MyHTMLParser(convert_charrefs=False)
>>> parser.feed('&gt;&#62;&#x3E;')
Named ent: >
Num ent : >
@@ -317,18 +336,22 @@ correct char (note: these 3 references are all equivalent to ``'>'``)::
Feeding incomplete chunks to :meth:`~HTMLParser.feed` works, but
:meth:`~HTMLParser.handle_data` might be called more than once
-(unless *convert_charrefs* is set to ``True``)::
+(unless *convert_charrefs* is set to ``True``):
- >>> for chunk in ['<sp', 'an>buff', 'ered ', 'text</s', 'pan>']:
+.. doctest::
+
+ >>> for chunk in ['<sp', 'an>buff', 'ered', ' text</s', 'pan>']:
... parser.feed(chunk)
...
Start tag: span
Data : buff
Data : ered
- Data : text
+ Data : text
End tag : span
-Parsing invalid HTML (e.g. unquoted attributes) also works::
+Parsing invalid HTML (e.g. unquoted attributes) also works:
+
+.. doctest::
>>> parser.feed('<p><a class=link href=#main>tag soup</p ></a>')
Start tag: p
diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst
index 54df4a7e804..063344e0284 100644
--- a/Doc/library/http.server.rst
+++ b/Doc/library/http.server.rst
@@ -429,8 +429,7 @@ instantiation, of which this module provides three different variants:
``'Last-Modified:'`` header with the file's modification time.
Then follows a blank line signifying the end of the headers, and then the
- contents of the file are output. If the file's MIME type starts with
- ``text/`` the file is opened in text mode; otherwise binary mode is used.
+ contents of the file are output.
For example usage, see the implementation of the ``test`` function
in :source:`Lib/http/server.py`.
@@ -459,55 +458,6 @@ such as using different index file names by overriding the class attribute
:attr:`index_pages`.
-.. class:: CGIHTTPRequestHandler(request, client_address, server)
-
- This class is used to serve either files or output of CGI scripts from the
- current directory and below. Note that mapping HTTP hierarchic structure to
- local directory structure is exactly as in :class:`SimpleHTTPRequestHandler`.
-
- .. note::
-
- CGI scripts run by the :class:`CGIHTTPRequestHandler` class cannot execute
- redirects (HTTP code 302), because code 200 (script output follows) is
- sent prior to execution of the CGI script. This pre-empts the status
- code.
-
- The class will however, run the CGI script, instead of serving it as a file,
- if it guesses it to be a CGI script. Only directory-based CGI are used ---
- the other common server configuration is to treat special extensions as
- denoting CGI scripts.
-
- The :func:`do_GET` and :func:`do_HEAD` functions are modified to run CGI scripts
- and serve the output, instead of serving files, if the request leads to
- somewhere below the ``cgi_directories`` path.
-
- The :class:`CGIHTTPRequestHandler` defines the following data member:
-
- .. attribute:: cgi_directories
-
- This defaults to ``['/cgi-bin', '/htbin']`` and describes directories to
- treat as containing CGI scripts.
-
- The :class:`CGIHTTPRequestHandler` defines the following method:
-
- .. method:: do_POST()
-
- This method serves the ``'POST'`` request type, only allowed for CGI
- scripts. Error 501, "Can only POST to CGI scripts", is output when trying
- to POST to a non-CGI url.
-
- Note that CGI scripts will be run with UID of user nobody, for security
- reasons. Problems with the CGI script will be translated to error 403.
-
- .. deprecated-removed:: 3.13 3.15
-
- :class:`CGIHTTPRequestHandler` is being removed in 3.15. CGI has not
- been considered a good way to do things for well over a decade. This code
- has been unmaintained for a while now and sees very little practical use.
- Retaining it could lead to further :ref:`security considerations
- <http.server-security>`.
-
-
.. _http-server-cli:
Command-line interface
@@ -564,24 +514,6 @@ The following options are accepted:
.. versionadded:: 3.11
-.. option:: --cgi
-
- :class:`CGIHTTPRequestHandler` can be enabled in the command line by passing
- the ``--cgi`` option::
-
- python -m http.server --cgi
-
- .. deprecated-removed:: 3.13 3.15
-
- :mod:`http.server` command line ``--cgi`` support is being removed
- because :class:`CGIHTTPRequestHandler` is being removed.
-
-.. warning::
-
- :class:`CGIHTTPRequestHandler` and the ``--cgi`` command-line option
- are not intended for use by untrusted clients and may be vulnerable
- to exploitation. Always use within a secure environment.
-
.. option:: --tls-cert
Specifies a TLS certificate chain for HTTPS connections::
diff --git a/Doc/library/importlib.resources.abc.rst b/Doc/library/importlib.resources.abc.rst
index 7a77466bcba..8253a33f591 100644
--- a/Doc/library/importlib.resources.abc.rst
+++ b/Doc/library/importlib.resources.abc.rst
@@ -49,44 +49,44 @@
.. method:: open_resource(resource)
:abstractmethod:
- Returns an opened, :term:`file-like object` for binary reading
- of the *resource*.
+ Returns an opened, :term:`file-like object` for binary reading
+ of the *resource*.
- If the resource cannot be found, :exc:`FileNotFoundError` is
- raised.
+ If the resource cannot be found, :exc:`FileNotFoundError` is
+ raised.
.. method:: resource_path(resource)
:abstractmethod:
- Returns the file system path to the *resource*.
+ Returns the file system path to the *resource*.
- If the resource does not concretely exist on the file system,
- raise :exc:`FileNotFoundError`.
+ If the resource does not concretely exist on the file system,
+ raise :exc:`FileNotFoundError`.
.. method:: is_resource(name)
:abstractmethod:
- Returns ``True`` if the named *name* is considered a resource.
- :exc:`FileNotFoundError` is raised if *name* does not exist.
+ Returns ``True`` if the named *name* is considered a resource.
+ :exc:`FileNotFoundError` is raised if *name* does not exist.
.. method:: contents()
:abstractmethod:
- Returns an :term:`iterable` of strings over the contents of
- the package. Do note that it is not required that all names
- returned by the iterator be actual resources, e.g. it is
- acceptable to return names for which :meth:`is_resource` would
- be false.
-
- Allowing non-resource names to be returned is to allow for
- situations where how a package and its resources are stored
- are known a priori and the non-resource names would be useful.
- For instance, returning subdirectory names is allowed so that
- when it is known that the package and resources are stored on
- the file system then those subdirectory names can be used
- directly.
-
- The abstract method returns an iterable of no items.
+ Returns an :term:`iterable` of strings over the contents of
+ the package. Do note that it is not required that all names
+ returned by the iterator be actual resources, e.g. it is
+ acceptable to return names for which :meth:`is_resource` would
+ be false.
+
+ Allowing non-resource names to be returned is to allow for
+ situations where how a package and its resources are stored
+ are known a priori and the non-resource names would be useful.
+ For instance, returning subdirectory names is allowed so that
+ when it is known that the package and resources are stored on
+ the file system then those subdirectory names can be used
+ directly.
+
+ The abstract method returns an iterable of no items.
.. class:: Traversable
diff --git a/Doc/library/io.rst b/Doc/library/io.rst
index 3aa2f35f05e..de5cab5aee6 100644
--- a/Doc/library/io.rst
+++ b/Doc/library/io.rst
@@ -528,14 +528,13 @@ I/O Base Classes
It inherits from :class:`IOBase`.
The main difference with :class:`RawIOBase` is that methods :meth:`read`,
- :meth:`readinto` and :meth:`write` will try (respectively) to read as much
- input as requested or to consume all given output, at the expense of
- making perhaps more than one system call.
+ :meth:`readinto` and :meth:`write` will try (respectively) to read
+ as much input as requested or to emit all provided data.
- In addition, those methods can raise :exc:`BlockingIOError` if the
- underlying raw stream is in non-blocking mode and cannot take or give
- enough data; unlike their :class:`RawIOBase` counterparts, they will
- never return ``None``.
+ In addition, if the underlying raw stream is in non-blocking mode, when the
+ system returns would block :meth:`write` will raise :exc:`BlockingIOError`
+ with :attr:`BlockingIOError.characters_written` and :meth:`read` will return
+ data read so far or ``None`` if no data is available.
Besides, the :meth:`read` method does not have a default
implementation that defers to :meth:`readinto`.
@@ -568,29 +567,40 @@ I/O Base Classes
.. method:: read(size=-1, /)
- Read and return up to *size* bytes. If the argument is omitted, ``None``,
- or negative, data is read and returned until EOF is reached. An empty
- :class:`bytes` object is returned if the stream is already at EOF.
+ Read and return up to *size* bytes. If the argument is omitted, ``None``,
+ or negative read as much as possible.
- If the argument is positive, and the underlying raw stream is not
- interactive, multiple raw reads may be issued to satisfy the byte count
- (unless EOF is reached first). But for interactive raw streams, at most
- one raw read will be issued, and a short result does not imply that EOF is
- imminent.
+ Fewer bytes may be returned than requested. An empty :class:`bytes` object
+ is returned if the stream is already at EOF. More than one read may be
+ made and calls may be retried if specific errors are encountered, see
+ :meth:`os.read` and :pep:`475` for more details. Less than size bytes
+ being returned does not imply that EOF is imminent.
- A :exc:`BlockingIOError` is raised if the underlying raw stream is in
- non blocking-mode, and has no data available at the moment.
+ When reading as much as possible the default implementation will use
+ ``raw.readall`` if available (which should implement
+ :meth:`RawIOBase.readall`), otherwise will read in a loop until read
+ returns ``None``, an empty :class:`bytes`, or a non-retryable error. For
+ most streams this is to EOF, but for non-blocking streams more data may
+ become available.
+
+ .. note::
+
+ When the underlying raw stream is non-blocking, implementations may
+ either raise :exc:`BlockingIOError` or return ``None`` if no data is
+ available. :mod:`io` implementations return ``None``.
.. method:: read1(size=-1, /)
- Read and return up to *size* bytes, with at most one call to the
- underlying raw stream's :meth:`~RawIOBase.read` (or
- :meth:`~RawIOBase.readinto`) method. This can be useful if you are
- implementing your own buffering on top of a :class:`BufferedIOBase`
- object.
+ Read and return up to *size* bytes, calling :meth:`~RawIOBase.readinto`
+ which may retry if :py:const:`~errno.EINTR` is encountered per
+ :pep:`475`. If *size* is ``-1`` or not provided, the implementation will
+ choose an arbitrary value for *size*.
- If *size* is ``-1`` (the default), an arbitrary number of bytes are
- returned (more than zero unless EOF is reached).
+ .. note::
+
+ When the underlying raw stream is non-blocking, implementations may
+ either raise :exc:`BlockingIOError` or return ``None`` if no data is
+ available. :mod:`io` implementations return ``None``.
.. method:: readinto(b, /)
@@ -767,34 +777,21 @@ than raw I/O does.
.. method:: peek(size=0, /)
- Return bytes from the stream without advancing the position. At most one
- single read on the raw stream is done to satisfy the call. The number of
- bytes returned may be less or more than requested.
+ Return bytes from the stream without advancing the position. The number of
+ bytes returned may be less or more than requested. If the underlying raw
+ stream is non-blocking and the operation would block, returns empty bytes.
.. method:: read(size=-1, /)
- Read and return *size* bytes, or if *size* is not given or negative, until
- EOF or if the read call would block in non-blocking mode.
-
- .. note::
-
- When the underlying raw stream is non-blocking, a :exc:`BlockingIOError`
- may be raised if a read operation cannot be completed immediately.
+ In :class:`BufferedReader` this is the same as :meth:`io.BufferedIOBase.read`
.. method:: read1(size=-1, /)
- Read and return up to *size* bytes with only one call on the raw stream.
- If at least one byte is buffered, only buffered bytes are returned.
- Otherwise, one raw stream read call is made.
+ In :class:`BufferedReader` this is the same as :meth:`io.BufferedIOBase.read1`
.. versionchanged:: 3.7
The *size* argument is now optional.
- .. note::
-
- When the underlying raw stream is non-blocking, a :exc:`BlockingIOError`
- may be raised if a read operation cannot be completed immediately.
-
.. class:: BufferedWriter(raw, buffer_size=DEFAULT_BUFFER_SIZE)
A buffered binary stream providing higher-level access to a writeable, non
@@ -826,8 +823,8 @@ than raw I/O does.
Write the :term:`bytes-like object`, *b*, and return the
number of bytes written. When in non-blocking mode, a
- :exc:`BlockingIOError` is raised if the buffer needs to be written out but
- the raw stream blocks.
+ :exc:`BlockingIOError` with :attr:`BlockingIOError.characters_written` set
+ is raised if the buffer needs to be written out but the raw stream blocks.
.. class:: BufferedRandom(raw, buffer_size=DEFAULT_BUFFER_SIZE)
@@ -894,9 +891,10 @@ Text I/O
.. attribute:: buffer
- The underlying binary buffer (a :class:`BufferedIOBase` instance) that
- :class:`TextIOBase` deals with. This is not part of the
- :class:`TextIOBase` API and may not exist in some implementations.
+ The underlying binary buffer (a :class:`BufferedIOBase`
+ or :class:`RawIOBase` instance) that :class:`TextIOBase` deals with.
+ This is not part of the :class:`TextIOBase` API and may not exist
+ in some implementations.
.. method:: detach()
diff --git a/Doc/library/json.rst b/Doc/library/json.rst
index 26579ec6328..12a5a96a3c5 100644
--- a/Doc/library/json.rst
+++ b/Doc/library/json.rst
@@ -18,12 +18,17 @@ is a lightweight data interchange format inspired by
`JavaScript <https://en.wikipedia.org/wiki/JavaScript>`_ object literal syntax
(although it is not a strict subset of JavaScript [#rfc-errata]_ ).
+.. note::
+ The term "object" in the context of JSON processing in Python can be
+ ambiguous. All values in Python are objects. In JSON, an object refers to
+ any data wrapped in curly braces, similar to a Python dictionary.
+
.. warning::
Be cautious when parsing JSON data from untrusted sources. A malicious
JSON string may cause the decoder to consume considerable CPU and memory
resources. Limiting the size of data to be parsed is recommended.
-:mod:`json` exposes an API familiar to users of the standard library
+This module exposes an API familiar to users of the standard library
:mod:`marshal` and :mod:`pickle` modules.
Encoding basic Python object hierarchies::
@@ -60,7 +65,7 @@ Pretty printing::
"6": 7
}
-Specializing JSON object encoding::
+Customizing JSON object encoding::
>>> import json
>>> def custom_json(obj):
@@ -83,7 +88,7 @@ Decoding JSON::
>>> json.load(io)
['streaming API']
-Specializing JSON object decoding::
+Customizing JSON object decoding::
>>> import json
>>> def as_complex(dct):
@@ -279,7 +284,7 @@ Basic Usage
:param object_hook:
If set, a function that is called with the result of
- any object literal decoded (a :class:`dict`).
+ any JSON object literal decoded (a :class:`dict`).
The return value of this function will be used
instead of the :class:`dict`.
This feature can be used to implement custom decoders,
@@ -289,7 +294,7 @@ Basic Usage
:param object_pairs_hook:
If set, a function that is called with the result of
- any object literal decoded with an ordered list of pairs.
+ any JSON object literal decoded with an ordered list of pairs.
The return value of this function will be used
instead of the :class:`dict`.
This feature can be used to implement custom decoders.
diff --git a/Doc/library/logging.config.rst b/Doc/library/logging.config.rst
index 0e9dc33ae21..96cca3073fe 100644
--- a/Doc/library/logging.config.rst
+++ b/Doc/library/logging.config.rst
@@ -548,7 +548,7 @@ mnemonic that the corresponding value is a callable.
The ``filters`` member of ``handlers`` and ``loggers`` can take
filter instances in addition to ids.
-You can also specify a special key ``'.'`` whose value is a dictionary is a
+You can also specify a special key ``'.'`` whose value is a
mapping of attribute names to values. If found, the specified attributes will
be set on the user-defined object before it is returned. Thus, with the
following configuration::
@@ -586,7 +586,7 @@ configuration dictionary for the handler named ``foo``, and later (once that
handler has been configured) it points to the configured handler instance.
Thus, ``cfg://handlers.foo`` could resolve to either a dictionary or a handler
instance. In general, it is wise to name handlers in a way such that dependent
-handlers are configured _after_ any handlers they depend on; that allows
+handlers are configured *after* any handlers they depend on; that allows
something like ``cfg://handlers.foo`` to be used in configuring a handler that
depends on handler ``foo``. If that dependent handler were named ``bar``,
problems would result, because the configuration of ``bar`` would be attempted
diff --git a/Doc/library/logging.handlers.rst b/Doc/library/logging.handlers.rst
index 72312b512a5..d74ef73ee28 100644
--- a/Doc/library/logging.handlers.rst
+++ b/Doc/library/logging.handlers.rst
@@ -352,6 +352,10 @@ module, supports rotation of disk log files.
Outputs the record to the file, catering for rollover as described
previously.
+ .. method:: shouldRollover(record)
+
+ See if the supplied record would cause the file to exceed the configured size limit.
+
.. _timed-rotating-file-handler:
TimedRotatingFileHandler
@@ -459,7 +463,11 @@ timed intervals.
.. method:: getFilesToDelete()
Returns a list of filenames which should be deleted as part of rollover. These
- are the absolute paths of the oldest backup log files written by the handler.
+
+ .. method:: shouldRollover(record)
+
+ See if enough time has passed for a rollover to occur and if it has, compute
+ the next rollover time.
.. _socket-handler:
@@ -1051,6 +1059,15 @@ possible, while any potentially slow operations (such as sending an email via
.. note:: If you are using :mod:`multiprocessing`, you should avoid using
:class:`~queue.SimpleQueue` and instead use :class:`multiprocessing.Queue`.
+ .. warning::
+
+ The :mod:`multiprocessing` module uses an internal logger created and
+ accessed via :meth:`~multiprocessing.get_logger`.
+ :class:`multiprocessing.Queue` will log ``DEBUG`` level messages upon
+ items being queued. If those log messages are processed by a
+ :class:`QueueHandler` using the same :class:`multiprocessing.Queue` instance,
+ it will cause a deadlock or infinite recursion.
+
.. method:: emit(record)
Enqueues the result of preparing the LogRecord. Should an exception
@@ -1148,7 +1165,7 @@ possible, while any potentially slow operations (such as sending an email via
.. versionchanged:: 3.5
The ``respect_handler_level`` argument was added.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
:class:`QueueListener` can now be used as a context manager via
:keyword:`with`. When entering the context, the listener is started. When
exiting the context, the listener is stopped.
@@ -1186,7 +1203,7 @@ possible, while any potentially slow operations (such as sending an email via
This starts up a background thread to monitor the queue for
LogRecords to process.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Raises :exc:`RuntimeError` if called and the listener is already
running.
diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst
index 72190e97240..4509da58916 100644
--- a/Doc/library/logging.rst
+++ b/Doc/library/logging.rst
@@ -1342,8 +1342,9 @@ functions.
.. function:: basicConfig(**kwargs)
- Does basic configuration for the logging system by creating a
- :class:`StreamHandler` with a default :class:`Formatter` and adding it to the
+ Does basic configuration for the logging system by either creating a
+ :class:`StreamHandler` with a default :class:`Formatter`
+ or using the given *formatter* instance, and adding it to the
root logger. The functions :func:`debug`, :func:`info`, :func:`warning`,
:func:`error` and :func:`critical` will call :func:`basicConfig` automatically
if no handlers are defined for the root logger.
@@ -1428,6 +1429,19 @@ functions.
| | which means that it will be treated the |
| | same as passing 'errors'. |
+--------------+---------------------------------------------+
+ | *formatter* | If specified, set this formatter instance |
+ | | (see :ref:`formatter-objects`) |
+ | | for all involved handlers. |
+ | | If not specified, the default is to create |
+ | | and use an instance of |
+ | | :class:`logging.Formatter` based on |
+ | | arguments *format*, *datefmt* and *style*. |
+ | | When *formatter* is specified together with |
+ | | any of the three arguments *format*, |
+ | | *datefmt* and *style*, a ``ValueError`` is |
+ | | raised to signal that these arguments would |
+ | | lose meaning otherwise. |
+ +--------------+---------------------------------------------+
.. versionchanged:: 3.2
The *style* argument was added.
@@ -1444,6 +1458,9 @@ functions.
.. versionchanged:: 3.9
The *encoding* and *errors* arguments were added.
+ .. versionchanged:: 3.15
+ The *formatter* argument was added.
+
.. function:: shutdown()
Informs the logging system to perform an orderly shutdown by flushing and
diff --git a/Doc/library/math.rst b/Doc/library/math.rst
index 0749367045d..bf7a00549fc 100644
--- a/Doc/library/math.rst
+++ b/Doc/library/math.rst
@@ -10,8 +10,8 @@
--------------
-This module provides access to the mathematical functions defined by the C
-standard.
+This module provides access to common mathematical functions and constants,
+including those defined by the C standard.
These functions cannot be used with complex numbers; use the functions of the
same name from the :mod:`cmath` module if you require support for complex
@@ -53,10 +53,13 @@ noted otherwise, all return values are floats.
:func:`frexp(x) <frexp>` Mantissa and exponent of *x*
:func:`isclose(a, b, rel_tol, abs_tol) <isclose>` Check if the values *a* and *b* are close to each other
:func:`isfinite(x) <isfinite>` Check if *x* is neither an infinity nor a NaN
+:func:`isnormal(x) <isnormal>` Check if *x* is a normal number
+:func:`issubnormal(x) <issubnormal>` Check if *x* is a subnormal number
:func:`isinf(x) <isinf>` Check if *x* is a positive or negative infinity
:func:`isnan(x) <isnan>` Check if *x* is a NaN (not a number)
:func:`ldexp(x, i) <ldexp>` ``x * (2**i)``, inverse of function :func:`frexp`
:func:`nextafter(x, y, steps) <nextafter>` Floating-point value *steps* steps after *x* towards *y*
+:func:`signbit(x) <signbit>` Check if *x* is a negative number
:func:`ulp(x) <ulp>` Value of the least significant bit of *x*
**Power, exponential and logarithmic functions**
@@ -144,8 +147,7 @@ Number-theoretic functions
.. function:: factorial(n)
- Return *n* factorial as an integer. Raises :exc:`ValueError` if *n* is not integral or
- is negative.
+ Return factorial of the nonnegative integer *n*.
.. versionchanged:: 3.10
Floats with integral values (like ``5.0``) are no longer accepted.
@@ -374,6 +376,24 @@ Floating point manipulation functions
.. versionadded:: 3.2
+.. function:: isnormal(x)
+
+ Return ``True`` if *x* is a normal number, that is a finite
+ nonzero number that is not a subnormal (see :func:`issubnormal`).
+ Return ``False`` otherwise.
+
+ .. versionadded:: next
+
+
+.. function:: issubnormal(x)
+
+ Return ``True`` if *x* is a subnormal number, that is a finite
+ nonzero number with a magnitude smaller than :data:`sys.float_info.min`.
+ Return ``False`` otherwise.
+
+ .. versionadded:: next
+
+
.. function:: isinf(x)
Return ``True`` if *x* is a positive or negative infinity, and
@@ -412,6 +432,15 @@ Floating point manipulation functions
Added the *steps* argument.
+.. function:: signbit(x)
+
+ Return ``True`` if the sign of *x* is negative and ``False`` otherwise.
+
+ This is useful to detect the sign bit of zeroes, infinities and NaNs.
+
+ .. versionadded:: next
+
+
.. function:: ulp(x)
Return the value of the least significant bit of the float *x*:
@@ -775,7 +804,7 @@ Constants
The mathematical constant *τ* = 6.283185..., to available precision.
Tau is a circle constant equal to 2\ *π*, the ratio of a circle's circumference to
its radius. To learn more about Tau, check out Vi Hart's video `Pi is (still)
- Wrong <https://www.youtube.com/watch?v=jG7vhMMXagQ>`_, and start celebrating
+ Wrong <https://vimeo.com/147792667>`_, and start celebrating
`Tau day <https://tauday.com/>`_ by eating twice as much pie!
.. versionadded:: 3.6
diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst
index 4e20c07331a..8fca79b23e4 100644
--- a/Doc/library/mmap.rst
+++ b/Doc/library/mmap.rst
@@ -269,7 +269,7 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
Resizing a map created with *access* of :const:`ACCESS_READ` or
:const:`ACCESS_COPY`, will raise a :exc:`TypeError` exception.
- Resizing a map created with with *trackfd* set to ``False``,
+ Resizing a map created with *trackfd* set to ``False``,
will raise a :exc:`ValueError` exception.
**On Windows**: Resizing the map will raise an :exc:`OSError` if there are other
diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
index e44142a8ed3..fc3c1134f97 100644
--- a/Doc/library/multiprocessing.rst
+++ b/Doc/library/multiprocessing.rst
@@ -687,7 +687,7 @@ The :mod:`multiprocessing` package mostly replicates the API of the
:attr:`exitcode` you may simply catch :exc:`KeyboardInterrupt` and call
``exit(your_code)``.
- .. versionadded:: next
+ .. versionadded:: 3.14
.. method:: terminate()
@@ -1081,7 +1081,7 @@ Miscellaneous
.. function:: freeze_support()
Add support for when a program which uses :mod:`multiprocessing` has been
- frozen to produce a Windows executable. (Has been tested with **py2exe**,
+ frozen to produce an executable. (Has been tested with **py2exe**,
**PyInstaller** and **cx_Freeze**.)
One needs to call this function straight after the ``if __name__ ==
@@ -1099,10 +1099,10 @@ Miscellaneous
If the ``freeze_support()`` line is omitted then trying to run the frozen
executable will raise :exc:`RuntimeError`.
- Calling ``freeze_support()`` has no effect when invoked on any operating
- system other than Windows. In addition, if the module is being run
- normally by the Python interpreter on Windows (the program has not been
- frozen), then ``freeze_support()`` has no effect.
+ Calling ``freeze_support()`` has no effect when the start method is not
+ *spawn*. In addition, if the module is being run normally by the Python
+ interpreter (the program has not been frozen), then ``freeze_support()``
+ has no effect.
.. function:: get_all_start_methods()
@@ -1369,6 +1369,12 @@ object -- see :ref:`multiprocessing-managers`.
A solitary difference from its close analog exists: its ``acquire`` method's
first argument is named *block*, as is consistent with :meth:`Lock.acquire`.
+ .. method:: locked()
+
+ Return a boolean indicating whether this object is locked right now.
+
+ .. versionadded:: 3.14
+
.. note::
On macOS, this is indistinguishable from :class:`Semaphore` because
``sem_getvalue()`` is not implemented on that platform.
@@ -1521,6 +1527,12 @@ object -- see :ref:`multiprocessing-managers`.
A solitary difference from its close analog exists: its ``acquire`` method's
first argument is named *block*, as is consistent with :meth:`Lock.acquire`.
+ .. method:: locked()
+
+ Return a boolean indicating whether this object is locked right now.
+
+ .. versionadded:: 3.14
+
.. note::
On macOS, ``sem_timedwait`` is unsupported, so calling ``acquire()`` with
diff --git a/Doc/library/netrc.rst b/Doc/library/netrc.rst
index f6260383b2b..74c97e8c9a9 100644
--- a/Doc/library/netrc.rst
+++ b/Doc/library/netrc.rst
@@ -24,12 +24,14 @@ the Unix :program:`ftp` program and other FTP clients.
a :exc:`FileNotFoundError` exception will be raised.
Parse errors will raise :exc:`NetrcParseError` with diagnostic
information including the file name, line number, and terminating token.
+
If no argument is specified on a POSIX system, the presence of passwords in
the :file:`.netrc` file will raise a :exc:`NetrcParseError` if the file
ownership or permissions are insecure (owned by a user other than the user
running the process, or accessible for read or write by any other user).
This implements security behavior equivalent to that of ftp and other
- programs that use :file:`.netrc`.
+ programs that use :file:`.netrc`. Such security checks are not available
+ on platforms that do not support :func:`os.getuid`.
.. versionchanged:: 3.4 Added the POSIX permission check.
diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst
index ecbbc1d7605..f72aee19d8f 100644
--- a/Doc/library/os.path.rst
+++ b/Doc/library/os.path.rst
@@ -408,9 +408,26 @@ the :mod:`glob` module.)
system). On Windows, this function will also resolve MS-DOS (also called 8.3)
style names such as ``C:\\PROGRA~1`` to ``C:\\Program Files``.
- If a path doesn't exist or a symlink loop is encountered, and *strict* is
- ``True``, :exc:`OSError` is raised. If *strict* is ``False`` these errors
- are ignored, and so the result might be missing or otherwise inaccessible.
+ By default, the path is evaluated up to the first component that does not
+ exist, is a symlink loop, or whose evaluation raises :exc:`OSError`.
+ All such components are appended unchanged to the existing part of the path.
+
+ Some errors that are handled this way include "access denied", "not a
+ directory", or "bad argument to internal function". Thus, the
+ resulting path may be missing or inaccessible, may still contain
+ links or loops, and may traverse non-directories.
+
+ This behavior can be modified by keyword arguments:
+
+ If *strict* is ``True``, the first error encountered when evaluating the path is
+ re-raised.
+ In particular, :exc:`FileNotFoundError` is raised if *path* does not exist,
+ or another :exc:`OSError` if it is otherwise inaccessible.
+
+ If *strict* is :py:data:`os.path.ALLOW_MISSING`, errors other than
+ :exc:`FileNotFoundError` are re-raised (as with ``strict=True``).
+ Thus, the returned path will not contain any symbolic links, but the named
+ file and some of its parent directories may be missing.
.. note::
This function emulates the operating system's procedure for making a path
@@ -429,6 +446,15 @@ the :mod:`glob` module.)
.. versionchanged:: 3.10
The *strict* parameter was added.
+ .. versionchanged:: next
+ The :py:data:`~os.path.ALLOW_MISSING` value for the *strict* parameter
+ was added.
+
+.. data:: ALLOW_MISSING
+
+ Special value used for the *strict* argument in :func:`realpath`.
+
+ .. versionadded:: next
.. function:: relpath(path, start=os.curdir)
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 54a5d3b98e8..1e54cfec609 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -2338,6 +2338,7 @@ features:
This function can support specifying *src_dir_fd* and/or *dst_dir_fd* to
supply :ref:`paths relative to directory descriptors <dir_fd>`, and :ref:`not
following symlinks <follow_symlinks>`.
+ The default value of *follow_symlinks* is ``False`` on Windows.
.. audit-event:: os.link src,dst,src_dir_fd,dst_dir_fd os.link
diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst
index b8298690286..47986a2d960 100644
--- a/Doc/library/pathlib.rst
+++ b/Doc/library/pathlib.rst
@@ -871,11 +871,11 @@ conforming to :rfc:`8089`.
.. versionadded:: 3.13
- .. versionchanged:: next
- If a URL authority (e.g. a hostname) is present and resolves to a local
- address, it is discarded. If an authority is present and *doesn't*
- resolve to a local address, then on Windows a UNC path is returned (as
- before), and on other platforms a :exc:`ValueError` is raised.
+ .. versionchanged:: 3.14
+ The URL authority is discarded if it matches the local hostname.
+ Otherwise, if the authority isn't empty or ``localhost``, then on
+ Windows a UNC path is returned (as before), and on other platforms a
+ :exc:`ValueError` is raised.
.. method:: Path.as_uri()
@@ -1781,9 +1781,12 @@ The following wildcards are supported in patterns for
``?``
Matches one non-separator character.
``[seq]``
- Matches one character in *seq*.
+ Matches one character in *seq*, where *seq* is a sequence of characters.
+ Range expressions are supported; for example, ``[a-z]`` matches any lowercase ASCII letter.
+ Multiple ranges can be combined: ``[a-zA-Z0-9_]`` matches any ASCII letter, digit, or underscore.
+
``[!seq]``
- Matches one character not in *seq*.
+ Matches one character not in *seq*, where *seq* follows the same rules as above.
For a literal match, wrap the meta-characters in brackets.
For example, ``"[?]"`` matches the character ``"?"``.
@@ -1982,7 +1985,7 @@ The :mod:`pathlib.types` module provides types for static type checking.
If *follow_symlinks* is ``False``, return ``True`` only if the path
is a file (without following symlinks); return ``False`` if the path
- is a directory or other other non-file, or if it doesn't exist.
+ is a directory or other non-file, or if it doesn't exist.
.. method:: is_symlink()
diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst
index 3c8c0707499..f4b51664545 100644
--- a/Doc/library/pdb.rst
+++ b/Doc/library/pdb.rst
@@ -80,7 +80,7 @@ The debugger's prompt is ``(Pdb)``, which is the indicator that you are in debug
You can also invoke :mod:`pdb` from the command line to debug other scripts. For
example::
- python -m pdb [-c command] (-m module | pyfile) [args ...]
+ python -m pdb [-c command] (-m module | -p pid | pyfile) [args ...]
When invoked as a module, pdb will automatically enter post-mortem debugging if
the program being debugged exits abnormally. After post-mortem debugging (or
@@ -104,6 +104,24 @@ useful than quitting the debugger upon program's exit.
.. versionchanged:: 3.7
Added the ``-m`` option.
+.. option:: -p, --pid <pid>
+
+ Attach to the process with the specified PID.
+
+ .. versionadded:: 3.14
+
+
+To attach to a running Python process for remote debugging, use the ``-p`` or
+``--pid`` option with the target process's PID::
+
+ python -m pdb -p 1234
+
+.. note::
+
+ Attaching to a process that is blocked in a system call or waiting for I/O
+ will only work once the next bytecode instruction is executed or when the
+ process receives a signal.
+
Typical usage to execute a statement under control of the debugger is::
>>> import pdb
@@ -243,7 +261,7 @@ The ``run*`` functions and :func:`set_trace` are aliases for instantiating the
access further features, you have to do this yourself:
.. class:: Pdb(completekey='tab', stdin=None, stdout=None, skip=None, \
- nosigint=False, readrc=True, mode=None, backend=None)
+ nosigint=False, readrc=True, mode=None, backend=None, colorize=False)
:class:`Pdb` is the debugger class.
@@ -273,6 +291,9 @@ access further features, you have to do this yourself:
is passed, the default backend will be used. See :func:`set_default_backend`.
Otherwise the supported backends are ``'settrace'`` and ``'monitoring'``.
+ The *colorize* argument, if set to ``True``, will enable colorized output in the
+ debugger, if color is supported. This will highlight source code displayed in pdb.
+
Example call to enable tracing with *skip*::
import pdb; pdb.Pdb(skip=['django.*']).set_trace()
@@ -295,6 +316,9 @@ access further features, you have to do this yourself:
.. versionadded:: 3.14
Added the *backend* argument.
+ .. versionadded:: 3.14
+ Added the *colorize* argument.
+
.. versionchanged:: 3.14
Inline breakpoints like :func:`breakpoint` or :func:`pdb.set_trace` will
always stop the program at calling frame, ignoring the *skip* pattern (if any).
diff --git a/Doc/library/pkgutil.rst b/Doc/library/pkgutil.rst
index 20b8f6bcf19..47d24b6f7d0 100644
--- a/Doc/library/pkgutil.rst
+++ b/Doc/library/pkgutil.rst
@@ -69,8 +69,8 @@ support.
Yield :term:`finder` objects for the given module name.
- If fullname contains a ``'.'``, the finders will be for the package
- containing fullname, otherwise they will be all registered top level
+ If *fullname* contains a ``'.'``, the finders will be for the package
+ containing *fullname*, otherwise they will be all registered top level
finders (i.e. those on both :data:`sys.meta_path` and :data:`sys.path_hooks`).
If the named module is in a package, that package is imported as a side
diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst
index 5c999054323..06de152a742 100644
--- a/Doc/library/platform.rst
+++ b/Doc/library/platform.rst
@@ -188,24 +188,6 @@ Cross platform
:attr:`processor` is resolved late instead of immediately.
-Java platform
--------------
-
-
-.. function:: java_ver(release='', vendor='', vminfo=('','',''), osinfo=('','',''))
-
- Version interface for Jython.
-
- Returns a tuple ``(release, vendor, vminfo, osinfo)`` with *vminfo* being a
- tuple ``(vm_name, vm_release, vm_vendor)`` and *osinfo* being a tuple
- ``(os_name, os_version, os_arch)``. Values which cannot be determined are set to
- the defaults given as parameters (which all default to ``''``).
-
- .. deprecated-removed:: 3.13 3.15
- It was largely untested, had a confusing API,
- and was only useful for Jython support.
-
-
Windows platform
----------------
diff --git a/Doc/library/python.rst b/Doc/library/python.rst
index c2c231af7c3..c5c762e11b9 100644
--- a/Doc/library/python.rst
+++ b/Doc/library/python.rst
@@ -27,3 +27,8 @@ overview:
inspect.rst
annotationlib.rst
site.rst
+
+.. seealso::
+
+ * See the :mod:`concurrent.interpreters` module, which similarly
+ exposes core runtime functionality.
diff --git a/Doc/library/re.rst b/Doc/library/re.rst
index 0ee2d68bcbe..75ebbf11c8e 100644
--- a/Doc/library/re.rst
+++ b/Doc/library/re.rst
@@ -667,7 +667,7 @@ character ``'$'``.
``\z``
Matches only at the end of the string.
- .. versionadded:: next
+ .. versionadded:: 3.14
``\Z``
The same as ``\z``. For compatibility with old Python versions.
@@ -991,8 +991,8 @@ Functions
That way, separator components are always found at the same relative
indices within the result list.
- Empty matches for the pattern split the string only when not adjacent
- to a previous empty match.
+ Adjacent empty matches are not possible, but an empty match can occur
+ immediately after a non-empty match.
.. code:: pycon
@@ -1095,9 +1095,12 @@ Functions
The optional argument *count* is the maximum number of pattern occurrences to be
replaced; *count* must be a non-negative integer. If omitted or zero, all
- occurrences will be replaced. Empty matches for the pattern are replaced only
- when not adjacent to a previous empty match, so ``sub('x*', '-', 'abxd')`` returns
- ``'-a-b--d-'``.
+ occurrences will be replaced.
+
+ Adjacent empty matches are not possible, but an empty match can occur
+ immediately after a non-empty match.
+ As a result, ``sub('x*', '-', 'abxd')`` returns ``'-a-b--d-'``
+ instead of ``'-a-b-d-'``.
.. index:: single: \g; in regular expressions
@@ -1128,8 +1131,7 @@ Functions
.. versionchanged:: 3.7
Unknown escapes in *repl* consisting of ``'\'`` and an ASCII letter
now are errors.
- Empty matches for the pattern are replaced when adjacent to a previous
- non-empty match.
+ An empty match can occur immediately after a non-empty match.
.. versionchanged:: 3.12
Group *id* can only contain ASCII digits.
diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst
index 29e560cbc7a..f649fce5efc 100644
--- a/Doc/library/readline.rst
+++ b/Doc/library/readline.rst
@@ -76,7 +76,7 @@ The following functions relate to the init file and user configuration:
if given, and :code:`"<readline_init_file>"` otherwise, regardless of
which file the library resolves.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
The auditing event was added.
@@ -119,7 +119,7 @@ The following functions operate on a history file:
and raises an :ref:`auditing event <auditing>` ``open`` with the file
name if given and :code:`"~/.history"` otherwise.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
The auditing event was added.
@@ -131,7 +131,7 @@ The following functions operate on a history file:
:ref:`auditing event <auditing>` ``open`` with the file name if given and
:code:`"~/.history"` otherwise.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
The auditing event was added.
@@ -146,7 +146,7 @@ The following functions operate on a history file:
.. versionadded:: 3.5
- .. versionchanged:: next
+ .. versionchanged:: 3.14
The auditing event was added.
diff --git a/Doc/library/shelve.rst b/Doc/library/shelve.rst
index 6e74a59b82b..23a2e0c3d0c 100644
--- a/Doc/library/shelve.rst
+++ b/Doc/library/shelve.rst
@@ -75,8 +75,15 @@ Two additional methods are supported:
Write back all entries in the cache if the shelf was opened with *writeback*
set to :const:`True`. Also empty the cache and synchronize the persistent
- dictionary on disk, if feasible. This is called automatically when the shelf
- is closed with :meth:`close`.
+ dictionary on disk, if feasible. This is called automatically when
+ :meth:`reorganize` is called or the shelf is closed with :meth:`close`.
+
+.. method:: Shelf.reorganize()
+
+ Calls :meth:`sync` and attempts to shrink space used on disk by removing empty
+ space resulting from deletions.
+
+ .. versionadded:: next
.. method:: Shelf.close()
@@ -116,6 +123,11 @@ Restrictions
* On macOS :mod:`dbm.ndbm` can silently corrupt the database file on updates,
which can cause hard crashes when trying to read from the database.
+* :meth:`Shelf.reorganize` may not be available for all database packages and
+ may temporarely increase resource usage (especially disk space) when called.
+ Additionally, it will never run automatically and instead needs to be called
+ explicitly.
+
.. class:: Shelf(dict, protocol=None, writeback=False, keyencoding='utf-8')
diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst
index 2cbf95bcf53..dde38498206 100644
--- a/Doc/library/shutil.rst
+++ b/Doc/library/shutil.rst
@@ -47,6 +47,13 @@ Directory and files operations
0, only the contents from the current file position to the end of the file will
be copied.
+ :func:`copyfileobj` will *not* guarantee that the destination stream has
+ been flushed on completion of the copy. If you want to read from the
+ destination at the completion of the copy operation (for example, reading
+ the contents of a temporary file that has been copied from a HTTP stream),
+ you must ensure that you have called :func:`~io.IOBase.flush` or
+ :func:`~io.IOBase.close` on the file-like object before attempting to read
+ the destination file.
.. function:: copyfile(src, dst, *, follow_symlinks=True)
@@ -327,6 +334,10 @@ Directory and files operations
The deprecated *onerror* is similar to *onexc*, except that the third
parameter it receives is the tuple returned from :func:`sys.exc_info`.
+ .. seealso::
+ :ref:`shutil-rmtree-example` for an example of handling the removal
+ of a directory tree that contains read-only files.
+
.. audit-event:: shutil.rmtree path,dir_fd shutil.rmtree
.. versionchanged:: 3.3
@@ -454,6 +465,10 @@ Directory and files operations
:envvar:`PATH` environment variable is read from :data:`os.environ`,
falling back to :data:`os.defpath` if it is not set.
+ If *cmd* contains a directory component, :func:`!which` only checks the
+ specified path directly and does not search the directories listed in
+ *path* or in the system's :envvar:`PATH` environment variable.
+
On Windows, the current directory is prepended to the *path* if *mode* does
not include ``os.X_OK``. When the *mode* does include ``os.X_OK``, the
Windows API ``NeedCurrentDirectoryForExePathW`` will be consulted to
@@ -603,7 +618,8 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules.
*format* is the archive format: one of
"zip" (if the :mod:`zlib` module is available), "tar", "gztar" (if the
:mod:`zlib` module is available), "bztar" (if the :mod:`bz2` module is
- available), or "xztar" (if the :mod:`lzma` module is available).
+ available), "xztar" (if the :mod:`lzma` module is available), or "zstdtar"
+ (if the :mod:`compression.zstd` module is available).
*root_dir* is a directory that will be the root directory of the
archive, all paths in the archive will be relative to it; for example,
@@ -658,6 +674,8 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules.
- *gztar*: gzip'ed tar-file (if the :mod:`zlib` module is available).
- *bztar*: bzip2'ed tar-file (if the :mod:`bz2` module is available).
- *xztar*: xz'ed tar-file (if the :mod:`lzma` module is available).
+ - *zstdtar*: Zstandard compressed tar-file (if the :mod:`compression.zstd`
+ module is available).
You can register new formats or provide your own archiver for any existing
formats, by using :func:`register_archive_format`.
@@ -701,8 +719,8 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules.
*extract_dir* is the name of the target directory where the archive is
unpacked. If not provided, the current working directory is used.
- *format* is the archive format: one of "zip", "tar", "gztar", "bztar", or
- "xztar". Or any other format registered with
+ *format* is the archive format: one of "zip", "tar", "gztar", "bztar",
+ "xztar", or "zstdtar". Or any other format registered with
:func:`register_unpack_format`. If not provided, :func:`unpack_archive`
will use the archive file name extension and see if an unpacker was
registered for that extension. In case none is found,
@@ -774,6 +792,8 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules.
- *gztar*: gzip'ed tar-file (if the :mod:`zlib` module is available).
- *bztar*: bzip2'ed tar-file (if the :mod:`bz2` module is available).
- *xztar*: xz'ed tar-file (if the :mod:`lzma` module is available).
+ - *zstdtar*: Zstandard compressed tar-file (if the :mod:`compression.zstd`
+ module is available).
You can register new formats or provide your own unpacker for any existing
formats, by using :func:`register_unpack_format`.
diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst
index c28841dbb8c..b0307d3dea1 100644
--- a/Doc/library/signal.rst
+++ b/Doc/library/signal.rst
@@ -211,8 +211,8 @@ The variables defined in the :mod:`signal` module are:
.. data:: SIGSTKFLT
- Stack fault on coprocessor. The Linux kernel does not raise this signal: it
- can only be raised in user space.
+ Stack fault on coprocessor. The Linux kernel does not raise this signal: it
+ can only be raised in user space.
.. availability:: Linux.
diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
index 3c8dc26138f..bc89a3228f0 100644
--- a/Doc/library/socket.rst
+++ b/Doc/library/socket.rst
@@ -170,7 +170,7 @@ created. Socket addresses are represented as follows:
.. versionchanged:: 3.13.3
FreeBSD support added.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Added ``channel`` field.
``device_id`` not packed in a tuple is now accepted.
@@ -178,7 +178,7 @@ created. Socket addresses are represented as follows:
the Bluetooth address as a string or a :class:`bytes` object.
(ex. ``'12:23:34:45:56:67'`` or ``b'12:23:34:45:56:67'``)
- .. versionchanged:: next
+ .. versionchanged:: 3.14
FreeBSD support added.
- :const:`AF_ALG` is a Linux-only socket based interface to Kernel
@@ -362,10 +362,10 @@ Exceptions
Constants
^^^^^^^^^
- The AF_* and SOCK_* constants are now :class:`AddressFamily` and
- :class:`SocketKind` :class:`.IntEnum` collections.
+The AF_* and SOCK_* constants are now :class:`AddressFamily` and
+:class:`SocketKind` :class:`.IntEnum` collections.
- .. versionadded:: 3.4
+.. versionadded:: 3.4
.. data:: AF_UNIX
AF_INET
@@ -498,7 +498,7 @@ Constants
.. versionchanged:: 3.11
NetBSD support was added.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Restored missing ``CAN_RAW_ERR_FILTER`` on Linux.
.. data:: CAN_BCM
@@ -709,7 +709,7 @@ Constants
:const:`!SO_BTH_*` are only available on Windows.
Other constants may be available on Linux and various BSD platforms.
- .. versionadded:: next
+ .. versionadded:: 3.14
.. data:: HCI_FILTER
HCI_TIME_STAMP
@@ -720,7 +720,7 @@ Constants
Option names for use with :const:`BTPROTO_HCI`.
Availability and format of the option values depend on platform.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Added :const:`!SO_HCI_EVT_FILTER` and :const:`!SO_HCI_PKT_FILTER`
on NetBSD and DragonFly BSD.
Added :const:`!HCI_DATA_DIR` on FreeBSD, NetBSD and DragonFly BSD.
@@ -732,7 +732,7 @@ Constants
.. availability:: Linux
- .. versionadded:: next
+ .. versionadded:: 3.14
.. data:: HCI_CHANNEL_RAW
HCI_CHANNEL_USER
@@ -744,7 +744,7 @@ Constants
.. availability:: Linux
- .. versionadded:: next
+ .. versionadded:: 3.14
.. data:: AF_QIPCRTR
@@ -773,9 +773,9 @@ Constants
Constant to optimize CPU locality, to be used in conjunction with
:data:`SO_REUSEPORT`.
- .. versionadded:: 3.11
+ .. versionadded:: 3.11
- .. availability:: Linux >= 3.9
+ .. availability:: Linux >= 3.9
.. data:: SO_REUSEPORT_LB
@@ -1492,7 +1492,7 @@ The :mod:`socket` module also offers various network-related services:
The *fds* parameter is a sequence of file descriptors.
Consult :meth:`~socket.sendmsg` for the documentation of these parameters.
- .. availability:: Unix, Windows, not WASI.
+ .. availability:: Unix, not WASI.
Unix platforms supporting :meth:`~socket.sendmsg`
and :const:`SCM_RIGHTS` mechanism.
@@ -1506,9 +1506,9 @@ The :mod:`socket` module also offers various network-related services:
Return ``(msg, list(fds), flags, addr)``.
Consult :meth:`~socket.recvmsg` for the documentation of these parameters.
- .. availability:: Unix, Windows, not WASI.
+ .. availability:: Unix, not WASI.
- Unix platforms supporting :meth:`~socket.sendmsg`
+ Unix platforms supporting :meth:`~socket.recvmsg`
and :const:`SCM_RIGHTS` mechanism.
.. versionadded:: 3.9
diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst
index 59cfa136a3b..7fb629f7d2f 100644
--- a/Doc/library/socketserver.rst
+++ b/Doc/library/socketserver.rst
@@ -24,6 +24,8 @@ There are four basic concrete server classes:
:meth:`~BaseServer.server_activate`. The other parameters are passed to
the :class:`BaseServer` base class.
+ .. versionchanged:: next
+ The default queue size is now ``socket.SOMAXCONN`` for :class:`socketserver.TCPServer`.
.. class:: UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
@@ -541,7 +543,7 @@ objects that simplify communication by providing the standard file interface)::
The difference is that the ``readline()`` call in the second handler will call
``recv()`` multiple times until it encounters a newline character, while the
-the first handler had to use a ``recv()`` loop to accumulate data until a
+first handler had to use a ``recv()`` loop to accumulate data until a
newline itself. If it had just used a single ``recv()`` without the loop it
would just have returned what has been received so far from the client.
TCP is stream based: data arrives in the order it was sent, but there no
diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst
index c615650b622..641e1f1de03 100644
--- a/Doc/library/sqlite3.rst
+++ b/Doc/library/sqlite3.rst
@@ -259,10 +259,10 @@ Reference
Module functions
^^^^^^^^^^^^^^^^
-.. function:: connect(database, timeout=5.0, detect_types=0, \
+.. function:: connect(database, *, timeout=5.0, detect_types=0, \
isolation_level="DEFERRED", check_same_thread=True, \
factory=sqlite3.Connection, cached_statements=128, \
- uri=False, *, \
+ uri=False, \
autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL)
Open a connection to an SQLite database.
@@ -355,11 +355,8 @@ Module functions
.. versionchanged:: 3.12
Added the *autocommit* parameter.
- .. versionchanged:: 3.13
- Positional use of the parameters *timeout*, *detect_types*,
- *isolation_level*, *check_same_thread*, *factory*, *cached_statements*,
- and *uri* is deprecated.
- They will become keyword-only parameters in Python 3.15.
+ .. versionchanged:: 3.15
+ All parameters except *database* are now keyword-only.
.. function:: complete_statement(statement)
@@ -510,6 +507,15 @@ Module constants
Version number of the runtime SQLite library as a :class:`tuple` of
:class:`integers <int>`.
+.. data:: SQLITE_KEYWORDS
+
+ A :class:`tuple` containing all sqlite3 keywords.
+
+ This constant is only available if Python was compiled with SQLite
+ 3.24.0 or greater.
+
+ .. versionadded:: next
+
.. data:: threadsafety
Integer constant required by the DB-API 2.0, stating the level of thread
@@ -693,7 +699,7 @@ Connection objects
:meth:`~Cursor.executescript` on it with the given *sql_script*.
Return the new cursor object.
- .. method:: create_function(name, narg, func, *, deterministic=False)
+ .. method:: create_function(name, narg, func, /, *, deterministic=False)
Create or remove a user-defined SQL function.
@@ -719,6 +725,9 @@ Connection objects
.. versionchanged:: 3.8
Added the *deterministic* parameter.
+ .. versionchanged:: 3.15
+ The first three parameters are now positional-only.
+
Example:
.. doctest::
@@ -733,13 +742,8 @@ Connection objects
('acbd18db4cc2f85cedef654fccc4a4d8',)
>>> con.close()
- .. versionchanged:: 3.13
-
- Passing *name*, *narg*, and *func* as keyword arguments is deprecated.
- These parameters will become positional-only in Python 3.15.
-
- .. method:: create_aggregate(name, n_arg, aggregate_class)
+ .. method:: create_aggregate(name, n_arg, aggregate_class, /)
Create or remove a user-defined SQL aggregate function.
@@ -763,6 +767,9 @@ Connection objects
Set to ``None`` to remove an existing SQL aggregate function.
:type aggregate_class: :term:`class` | None
+ .. versionchanged:: 3.15
+ All three parameters are now positional-only.
+
Example:
.. testcode::
@@ -792,11 +799,6 @@ Connection objects
3
- .. versionchanged:: 3.13
-
- Passing *name*, *n_arg*, and *aggregate_class* as keyword arguments is deprecated.
- These parameters will become positional-only in Python 3.15.
-
.. method:: create_window_function(name, num_params, aggregate_class, /)
@@ -937,7 +939,7 @@ Connection objects
Aborted queries will raise an :exc:`OperationalError`.
- .. method:: set_authorizer(authorizer_callback)
+ .. method:: set_authorizer(authorizer_callback, /)
Register :term:`callable` *authorizer_callback* to be invoked
for each attempt to access a column of a table in the database.
@@ -962,12 +964,11 @@ Connection objects
.. versionchanged:: 3.11
Added support for disabling the authorizer using ``None``.
- .. versionchanged:: 3.13
- Passing *authorizer_callback* as a keyword argument is deprecated.
- The parameter will become positional-only in Python 3.15.
+ .. versionchanged:: 3.15
+ The only parameter is now positional-only.
- .. method:: set_progress_handler(progress_handler, n)
+ .. method:: set_progress_handler(progress_handler, /, n)
Register :term:`callable` *progress_handler* to be invoked for every *n*
instructions of the SQLite virtual machine. This is useful if you want to
@@ -981,12 +982,11 @@ Connection objects
currently executing query and cause it to raise a :exc:`DatabaseError`
exception.
- .. versionchanged:: 3.13
- Passing *progress_handler* as a keyword argument is deprecated.
- The parameter will become positional-only in Python 3.15.
+ .. versionchanged:: 3.15
+ The first parameter is now positional-only.
- .. method:: set_trace_callback(trace_callback)
+ .. method:: set_trace_callback(trace_callback, /)
Register :term:`callable` *trace_callback* to be invoked
for each SQL statement that is actually executed by the SQLite backend.
@@ -1009,9 +1009,8 @@ Connection objects
.. versionadded:: 3.3
- .. versionchanged:: 3.13
- Passing *trace_callback* as a keyword argument is deprecated.
- The parameter will become positional-only in Python 3.15.
+ .. versionchanged:: 3.15
+ The first parameter is now positional-only.
.. method:: enable_load_extension(enabled, /)
@@ -1492,7 +1491,9 @@ Cursor objects
:type parameters: :class:`dict` | :term:`sequence`
:raises ProgrammingError:
- If *sql* contains more than one SQL statement.
+ When *sql* contains more than one SQL statement.
+ When :ref:`named placeholders <sqlite3-placeholders>` are used
+ and *parameters* is a sequence instead of a :class:`dict`.
If :attr:`~Connection.autocommit` is
:data:`LEGACY_TRANSACTION_CONTROL`,
@@ -1501,13 +1502,11 @@ Cursor objects
and there is no open transaction,
a transaction is implicitly opened before executing *sql*.
- .. deprecated-removed:: 3.12 3.14
+ .. versionchanged:: 3.14
- :exc:`DeprecationWarning` is emitted if
+ :exc:`ProgrammingError` is emitted if
:ref:`named placeholders <sqlite3-placeholders>` are used
and *parameters* is a sequence instead of a :class:`dict`.
- Starting with Python 3.14, :exc:`ProgrammingError` will
- be raised instead.
Use :meth:`executescript` to execute multiple SQL statements.
@@ -1529,8 +1528,10 @@ Cursor objects
:type parameters: :term:`iterable`
:raises ProgrammingError:
- If *sql* contains more than one SQL statement,
- or is not a DML statement.
+ When *sql* contains more than one SQL statement
+ or is not a DML statement,
+ When :ref:`named placeholders <sqlite3-placeholders>` are used
+ and the items in *parameters* are sequences instead of :class:`dict`\s.
Example:
@@ -1554,14 +1555,12 @@ Cursor objects
.. _RETURNING clauses: https://www.sqlite.org/lang_returning.html
- .. deprecated-removed:: 3.12 3.14
+ .. versionchanged:: 3.14
- :exc:`DeprecationWarning` is emitted if
+ :exc:`ProgrammingError` is emitted if
:ref:`named placeholders <sqlite3-placeholders>` are used
and the items in *parameters* are sequences
instead of :class:`dict`\s.
- Starting with Python 3.14, :exc:`ProgrammingError` will
- be raised instead.
.. method:: executescript(sql_script, /)
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index c0dcecf737e..ae2e324d0ab 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -934,6 +934,13 @@ Constants
.. versionadded:: 3.13
+.. data:: HAS_PSK_TLS13
+
+ Whether the OpenSSL library has built-in support for External PSKs in TLS
+ 1.3 as described in :rfc:`9258`.
+
+ .. versionadded:: next
+
.. data:: HAS_PHA
Whether the OpenSSL library has built-in support for TLS-PHA.
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index 39aaa5da078..394c302fd35 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -1018,7 +1018,7 @@ operations have the same priority as the corresponding numeric operations. [3]_
| ``s * n`` or | equivalent to adding *s* to | (2)(7) |
| ``n * s`` | itself *n* times | |
+--------------------------+--------------------------------+----------+
-| ``s[i]`` | *i*\ th item of *s*, origin 0 | \(3) |
+| ``s[i]`` | *i*\ th item of *s*, origin 0 | (3)(9) |
+--------------------------+--------------------------------+----------+
| ``s[i:j]`` | slice of *s* from *i* to *j* | (3)(4) |
+--------------------------+--------------------------------+----------+
@@ -1150,6 +1150,9 @@ Notes:
without copying any data and with the returned index being relative to
the start of the sequence rather than the start of the slice.
+(9)
+ An :exc:`IndexError` is raised if *i* is outside the sequence range.
+
.. _typesseq-immutable:
@@ -1214,6 +1217,8 @@ accepts integers that meet the value restriction ``0 <= x <= 255``).
| ``s[i] = x`` | item *i* of *s* is replaced by | |
| | *x* | |
+------------------------------+--------------------------------+---------------------+
+| ``del s[i]`` | removes item *i* of *s* | |
++------------------------------+--------------------------------+---------------------+
| ``s[i:j] = t`` | slice of *s* from *i* to *j* | |
| | is replaced by the contents of | |
| | the iterable *t* | |
@@ -1788,8 +1793,14 @@ expression support in the :mod:`re` module).
Return centered in a string of length *width*. Padding is done using the
specified *fillchar* (default is an ASCII space). The original string is
- returned if *width* is less than or equal to ``len(s)``.
+ returned if *width* is less than or equal to ``len(s)``. For example::
+ >>> 'Python'.center(10)
+ ' Python '
+ >>> 'Python'.center(10, '-')
+ '--Python--'
+ >>> 'Python'.center(4)
+ 'Python'
.. method:: str.count(sub[, start[, end]])
@@ -1799,8 +1810,18 @@ expression support in the :mod:`re` module).
interpreted as in slice notation.
If *sub* is empty, returns the number of empty strings between characters
- which is the length of the string plus one.
-
+ which is the length of the string plus one. For example::
+
+ >>> 'spam, spam, spam'.count('spam')
+ 3
+ >>> 'spam, spam, spam'.count('spam', 5)
+ 2
+ >>> 'spam, spam, spam'.count('spam', 5, 10)
+ 1
+ >>> 'spam, spam, spam'.count('eggs')
+ 0
+ >>> 'spam, spam, spam'.count('')
+ 17
.. method:: str.encode(encoding="utf-8", errors="strict")
@@ -1820,6 +1841,14 @@ expression support in the :mod:`re` module).
unless an encoding error actually occurs,
:ref:`devmode` is enabled
or a :ref:`debug build <debug-build>` is used.
+ For example::
+
+ >>> encoded_str_to_bytes = 'Python'.encode()
+ >>> type(encoded_str_to_bytes)
+ <class 'bytes'>
+ >>> encoded_str_to_bytes
+ b'Python'
+
.. versionchanged:: 3.1
Added support for keyword arguments.
@@ -1834,7 +1863,19 @@ expression support in the :mod:`re` module).
Return ``True`` if the string ends with the specified *suffix*, otherwise return
``False``. *suffix* can also be a tuple of suffixes to look for. With optional
*start*, test beginning at that position. With optional *end*, stop comparing
- at that position.
+ at that position. Using *start* and *end* is equivalent to
+ ``str[start:end].endswith(suffix)``. For example::
+
+ >>> 'Python'.endswith('on')
+ True
+ >>> 'a tuple of suffixes'.endswith(('at', 'in'))
+ False
+ >>> 'a tuple of suffixes'.endswith(('at', 'es'))
+ True
+ >>> 'Python is amazing'.endswith('is', 0, 9)
+ True
+
+ See also :meth:`startswith` and :meth:`removesuffix`.
.. method:: str.expandtabs(tabsize=8)
@@ -1850,12 +1891,15 @@ expression support in the :mod:`re` module).
(``\n``) or return (``\r``), it is copied and the current column is reset to
zero. Any other character is copied unchanged and the current column is
incremented by one regardless of how the character is represented when
- printed.
+ printed. For example::
>>> '01\t012\t0123\t01234'.expandtabs()
'01 012 0123 01234'
>>> '01\t012\t0123\t01234'.expandtabs(4)
'01 012 0123 01234'
+ >>> print('01\t012\n0123\t01234'.expandtabs(4))
+ 01 012
+ 0123 01234
.. method:: str.find(sub[, start[, end]])
@@ -2012,7 +2056,7 @@ expression support in the :mod:`re` module).
.. method:: str.isprintable()
- Return true if all characters in the string are printable, false if it
+ Return ``True`` if all characters in the string are printable, ``False`` if it
contains at least one non-printable character.
Here "printable" means the character is suitable for :func:`repr` to use in
@@ -2269,6 +2313,18 @@ expression support in the :mod:`re` module).
>>> ' 1 2 3 '.split()
['1', '2', '3']
+ If *sep* is not specified or is ``None`` and *maxsplit* is ``0``, only
+ leading runs of consecutive whitespace are considered.
+
+ For example::
+
+ >>> "".split(None, 0)
+ []
+ >>> " ".split(None, 0)
+ []
+ >>> " foo ".split(maxsplit=0)
+ ['foo ']
+
.. index::
single: universal newlines; str.splitlines method
@@ -4823,7 +4879,13 @@ can be used interchangeably to index the same dictionary entry.
being added is already present, the value from the keyword argument
replaces the value from the positional argument.
- To illustrate, the following examples all return a dictionary equal to
+ Providing keyword arguments as in the first example only works for keys that
+ are valid Python identifiers. Otherwise, any valid keys can be used.
+
+ Dictionaries compare equal if and only if they have the same ``(key,
+ value)`` pairs (regardless of ordering). Order comparisons ('<', '<=', '>=', '>') raise
+ :exc:`TypeError`. To illustrate dictionary creation and equality,
+ the following examples all return a dictionary equal to
``{"one": 1, "two": 2, "three": 3}``::
>>> a = dict(one=1, two=2, three=3)
@@ -4838,6 +4900,27 @@ can be used interchangeably to index the same dictionary entry.
Providing keyword arguments as in the first example only works for keys that
are valid Python identifiers. Otherwise, any valid keys can be used.
+ Dictionaries preserve insertion order. Note that updating a key does not
+ affect the order. Keys added after deletion are inserted at the end. ::
+
+ >>> d = {"one": 1, "two": 2, "three": 3, "four": 4}
+ >>> d
+ {'one': 1, 'two': 2, 'three': 3, 'four': 4}
+ >>> list(d)
+ ['one', 'two', 'three', 'four']
+ >>> list(d.values())
+ [1, 2, 3, 4]
+ >>> d["one"] = 42
+ >>> d
+ {'one': 42, 'two': 2, 'three': 3, 'four': 4}
+ >>> del d["two"]
+ >>> d["two"] = None
+ >>> d
+ {'one': 42, 'three': 3, 'four': 4, 'two': None}
+
+ .. versionchanged:: 3.7
+ Dictionary order is guaranteed to be insertion order. This behavior was
+ an implementation detail of CPython from 3.6.
These are the operations that dictionaries support (and therefore, custom
mapping types should support too):
@@ -5008,32 +5091,6 @@ can be used interchangeably to index the same dictionary entry.
.. versionadded:: 3.9
- Dictionaries compare equal if and only if they have the same ``(key,
- value)`` pairs (regardless of ordering). Order comparisons ('<', '<=', '>=', '>') raise
- :exc:`TypeError`.
-
- Dictionaries preserve insertion order. Note that updating a key does not
- affect the order. Keys added after deletion are inserted at the end. ::
-
- >>> d = {"one": 1, "two": 2, "three": 3, "four": 4}
- >>> d
- {'one': 1, 'two': 2, 'three': 3, 'four': 4}
- >>> list(d)
- ['one', 'two', 'three', 'four']
- >>> list(d.values())
- [1, 2, 3, 4]
- >>> d["one"] = 42
- >>> d
- {'one': 42, 'two': 2, 'three': 3, 'four': 4}
- >>> del d["two"]
- >>> d["two"] = None
- >>> d
- {'one': 42, 'three': 3, 'four': 4, 'two': None}
-
- .. versionchanged:: 3.7
- Dictionary order is guaranteed to be insertion order. This behavior was
- an implementation detail of CPython from 3.6.
-
Dictionaries and dictionary views are reversible. ::
>>> d = {"one": 1, "two": 2, "three": 3, "four": 4}
diff --git a/Doc/library/string.rst b/Doc/library/string.rst
index b44d98819b6..23e15780075 100644
--- a/Doc/library/string.rst
+++ b/Doc/library/string.rst
@@ -328,7 +328,7 @@ The general form of a *standard format specifier* is:
sign: "+" | "-" | " "
width_and_precision: [`width_with_grouping`][`precision_with_grouping`]
width_with_grouping: [`width`][`grouping`]
- precision_with_grouping: "." [`precision`][`grouping`]
+ precision_with_grouping: "." [`precision`][`grouping`] | "." `grouping`
width: `~python-grammar:digit`+
precision: `~python-grammar:digit`+
grouping: "," | "_"
@@ -858,7 +858,7 @@ these rules. The methods of :class:`Template` are:
.. method:: is_valid()
- Returns false if the template has invalid placeholders that will cause
+ Returns ``False`` if the template has invalid placeholders that will cause
:meth:`substitute` to raise :exc:`ValueError`.
.. versionadded:: 3.11
diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
index 05d09e304b3..028a7861f36 100644
--- a/Doc/library/subprocess.rst
+++ b/Doc/library/subprocess.rst
@@ -1525,6 +1525,24 @@ handling consistency are valid for these functions.
Notes
-----
+.. _subprocess-timeout-behavior:
+
+Timeout Behavior
+^^^^^^^^^^^^^^^^
+
+When using the ``timeout`` parameter in functions like :func:`run`,
+:meth:`Popen.wait`, or :meth:`Popen.communicate`,
+users should be aware of the following behaviors:
+
+1. **Process Creation Delay**: The initial process creation itself cannot be interrupted
+ on many platform APIs. This means that even when specifying a timeout, you are not
+ guaranteed to see a timeout exception until at least after however long process
+ creation takes.
+
+2. **Extremely Small Timeout Values**: Setting very small timeout values (such as a few
+ milliseconds) may result in almost immediate :exc:`TimeoutExpired` exceptions because
+ process creation and system scheduling inherently require time.
+
.. _converting-argument-sequence:
Converting an argument sequence to a string on Windows
diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst
index 0674074b8c0..f62a4011e41 100644
--- a/Doc/library/sys.monitoring.rst
+++ b/Doc/library/sys.monitoring.rst
@@ -137,7 +137,8 @@ The following events are supported:
.. monitoring-event:: PY_UNWIND
- Exit from a Python function during exception unwinding.
+ Exit from a Python function during exception unwinding. This includes exceptions raised directly within the
+ function and that are allowed to continue to propagate.
.. monitoring-event:: PY_YIELD
@@ -171,7 +172,7 @@ events, use the expression ``PY_RETURN | PY_START``.
if get_events(DEBUGGER_ID) == NO_EVENTS:
...
-Events are divided into three groups:
+ Setting this event deactivates all events.
.. _monitoring-event-local:
@@ -243,20 +244,23 @@ raise an exception unless it would be visible to other code.
To allow tools to monitor for real exceptions without slowing down generators
and coroutines, the :monitoring-event:`STOP_ITERATION` event is provided.
-:monitoring-event:`STOP_ITERATION` can be locally disabled, unlike :monitoring-event:`RAISE`.
+:monitoring-event:`STOP_ITERATION` can be locally disabled, unlike
+:monitoring-event:`RAISE`.
-Note that the :monitoring-event:`STOP_ITERATION` event and the :monitoring-event:`RAISE`
-event for a :exc:`StopIteration` exception are equivalent, and are treated as interchangeable
-when generating events. Implementations will favor :monitoring-event:`STOP_ITERATION` for
-performance reasons, but may generate a :monitoring-event:`RAISE` event with a :exc:`StopIteration`.
+Note that the :monitoring-event:`STOP_ITERATION` event and the
+:monitoring-event:`RAISE` event for a :exc:`StopIteration` exception are
+equivalent, and are treated as interchangeable when generating events.
+Implementations will favor :monitoring-event:`STOP_ITERATION` for performance
+reasons, but may generate a :monitoring-event:`RAISE` event with a
+:exc:`StopIteration`.
Turning events on and off
-------------------------
In order to monitor an event, it must be turned on and a corresponding callback
-must be registered.
-Events can be turned on or off by setting the events either globally or
-for a particular code object.
+must be registered. Events can be turned on or off by setting the events either
+globally and/or for a particular code object. An event will trigger only once,
+even if it is turned on both globally and locally.
Setting events globally
@@ -292,10 +296,6 @@ in Python (see :ref:`c-api-monitoring`).
Activates all the local events for *code* which are set in *event_set*.
Raises a :exc:`ValueError` if *tool_id* is not in use.
-Local events add to global events, but do not mask them.
-In other words, all global events will trigger for a code object,
-regardless of the local events.
-
Disabling events
''''''''''''''''
@@ -325,8 +325,6 @@ except for a few breakpoints.
Registering callback functions
------------------------------
-To register a callable for events call
-
.. function:: register_callback(tool_id: int, event: int, func: Callable | None, /) -> Callable | None
Registers the callable *func* for the *event* with the given *tool_id*
@@ -335,12 +333,16 @@ To register a callable for events call
it is unregistered and returned.
Otherwise :func:`register_callback` returns ``None``.
-
Functions can be unregistered by calling
``sys.monitoring.register_callback(tool_id, event, None)``.
Callback functions can be registered and unregistered at any time.
+Callbacks are called only once regardless if the event is turned on both
+globally and locally. As such, if an event could be turned on for both global
+and local events by your code then the callback needs to be written to handle
+either trigger.
+
Registering or unregistering a callback function will generate a :func:`sys.audit` event.
@@ -353,37 +355,46 @@ Callback function arguments
that there are no arguments to the call.
When an active event occurs, the registered callback function is called.
+Callback functions returning an object other than :data:`DISABLE` will have no effect.
Different events will provide the callback function with different arguments, as follows:
* :monitoring-event:`PY_START` and :monitoring-event:`PY_RESUME`::
- func(code: CodeType, instruction_offset: int) -> DISABLE | Any
+ func(code: CodeType, instruction_offset: int) -> object
* :monitoring-event:`PY_RETURN` and :monitoring-event:`PY_YIELD`::
- func(code: CodeType, instruction_offset: int, retval: object) -> DISABLE | Any
+ func(code: CodeType, instruction_offset: int, retval: object) -> object
-* :monitoring-event:`CALL`, :monitoring-event:`C_RAISE` and :monitoring-event:`C_RETURN`::
+* :monitoring-event:`CALL`, :monitoring-event:`C_RAISE` and :monitoring-event:`C_RETURN`
+ (*arg0* can be :data:`MISSING` specifically)::
- func(code: CodeType, instruction_offset: int, callable: object, arg0: object | MISSING) -> DISABLE | Any
+ func(code: CodeType, instruction_offset: int, callable: object, arg0: object) -> object
+ *code* represents the code object where the call is being made, while
+ *callable* is the object that is about to be called (and thus
+ triggered the event).
If there are no arguments, *arg0* is set to :data:`sys.monitoring.MISSING`.
+ For instance methods, *callable* will be the function object as found on the
+ class with *arg0* set to the instance (i.e. the ``self`` argument to the
+ method).
+
* :monitoring-event:`RAISE`, :monitoring-event:`RERAISE`, :monitoring-event:`EXCEPTION_HANDLED`,
:monitoring-event:`PY_UNWIND`, :monitoring-event:`PY_THROW` and :monitoring-event:`STOP_ITERATION`::
- func(code: CodeType, instruction_offset: int, exception: BaseException) -> DISABLE | Any
+ func(code: CodeType, instruction_offset: int, exception: BaseException) -> object
* :monitoring-event:`LINE`::
- func(code: CodeType, line_number: int) -> DISABLE | Any
+ func(code: CodeType, line_number: int) -> object
* :monitoring-event:`BRANCH_LEFT`, :monitoring-event:`BRANCH_RIGHT` and :monitoring-event:`JUMP`::
- func(code: CodeType, instruction_offset: int, destination_offset: int) -> DISABLE | Any
+ func(code: CodeType, instruction_offset: int, destination_offset: int) -> object
Note that the *destination_offset* is where the code will next execute.
* :monitoring-event:`INSTRUCTION`::
- func(code: CodeType, instruction_offset: int) -> DISABLE | Any
+ func(code: CodeType, instruction_offset: int) -> object
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index fbfd5e1e75b..1626a89a073 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -1185,6 +1185,15 @@ always available. Unless explicitly noted otherwise, all variables are read-only
``cache_tag`` is set to ``None``, it indicates that module caching should
be disabled.
+ *supports_isolated_interpreters* is a boolean value, whether
+ this implementation supports multiple isolated interpreters.
+ It is ``True`` for CPython on most platforms. Platforms with
+ this support implement the low-level :mod:`!_interpreters` module.
+
+ .. seealso::
+
+ :pep:`684`, :pep:`734`, and :mod:`concurrent.interpreters`.
+
:data:`sys.implementation` may contain additional attributes specific to
the Python implementation. These non-standard attributes must start with
an underscore, and are not described here. Regardless of its contents,
@@ -1194,6 +1203,9 @@ always available. Unless explicitly noted otherwise, all variables are read-only
.. versionadded:: 3.3
+ .. versionchanged:: 3.14
+ Added ``supports_isolated_interpreters`` field.
+
.. note::
The addition of new required attributes must go through the normal PEP
@@ -1282,6 +1294,64 @@ always available. Unless explicitly noted otherwise, all variables are read-only
.. versionadded:: 3.5
+.. data:: _jit
+
+ Utilities for observing just-in-time compilation.
+
+ .. impl-detail::
+
+ JIT compilation is an *experimental implementation detail* of CPython.
+ ``sys._jit`` is not guaranteed to exist or behave the same way in all
+ Python implementations, versions, or build configurations.
+
+ .. versionadded:: 3.14
+
+ .. function:: _jit.is_available()
+
+ Return ``True`` if the current Python executable supports JIT compilation,
+ and ``False`` otherwise. This can be controlled by building CPython with
+ the ``--experimental-jit`` option on Windows, and the
+ :option:`--enable-experimental-jit` option on all other platforms.
+
+ .. function:: _jit.is_enabled()
+
+ Return ``True`` if JIT compilation is enabled for the current Python
+ process (implies :func:`sys._jit.is_available`), and ``False`` otherwise.
+ If JIT compilation is available, this can be controlled by setting the
+ :envvar:`PYTHON_JIT` environment variable to ``0`` (disabled) or ``1``
+ (enabled) at interpreter startup.
+
+ .. function:: _jit.is_active()
+
+ Return ``True`` if the topmost Python frame is currently executing JIT
+ code (implies :func:`sys._jit.is_enabled`), and ``False`` otherwise.
+
+ .. note::
+
+ This function is intended for testing and debugging the JIT itself.
+ It should be avoided for any other purpose.
+
+ .. note::
+
+ Due to the nature of tracing JIT compilers, repeated calls to this
+ function may give surprising results. For example, branching on its
+ return value will likely lead to unexpected behavior (if doing so
+ causes JIT code to be entered or exited):
+
+ .. code-block:: pycon
+
+ >>> for warmup in range(BIG_NUMBER):
+ ... # This line is "hot", and is eventually JIT-compiled:
+ ... if sys._jit.is_active():
+ ... # This line is "cold", and is run in the interpreter:
+ ... assert sys._jit.is_active()
+ ...
+ Traceback (most recent call last):
+ File "<stdin>", line 5, in <module>
+ assert sys._jit.is_active()
+ ~~~~~~~~~~~~~~~~~~^^
+ AssertionError
+
.. data:: last_exc
This variable is not always defined; it is set to the exception instance
@@ -1875,6 +1945,22 @@ always available. Unless explicitly noted otherwise, all variables are read-only
interpreter is pre-release (alpha, beta, or release candidate) then the
local and remote interpreters must be the same exact version.
+ .. audit-event:: sys.remote_exec pid script_path
+
+ When the code is executed in the remote process, an
+ :ref:`auditing event <auditing>` ``sys.remote_exec`` is raised with
+ the *pid* and the path to the script file.
+ This event is raised in the process that called :func:`sys.remote_exec`.
+
+ .. audit-event:: cpython.remote_debugger_script script_path
+
+ When the script is executed in the remote process, an
+ :ref:`auditing event <auditing>`
+ ``cpython.remote_debugger_script`` is raised
+ with the path in the remote process.
+ This event is raised in the remote process, not the one
+ that called :func:`sys.remote_exec`.
+
.. availability:: Unix, Windows.
.. versionadded:: 3.14
diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
index 8e9775ddbc6..70466fbbc4d 100644
--- a/Doc/library/tarfile.rst
+++ b/Doc/library/tarfile.rst
@@ -18,8 +18,8 @@ higher-level functions in :ref:`shutil <archiving-operations>`.
Some facts and figures:
-* reads and writes :mod:`gzip`, :mod:`bz2` and :mod:`lzma` compressed archives
- if the respective modules are available.
+* reads and writes :mod:`gzip`, :mod:`bz2`, :mod:`compression.zstd`, and
+ :mod:`lzma` compressed archives if the respective modules are available.
* read/write support for the POSIX.1-1988 (ustar) format.
@@ -47,6 +47,10 @@ Some facts and figures:
or paths outside of the destination. Previously, the filter strategy
was equivalent to :func:`fully_trusted <fully_trusted_filter>`.
+.. versionchanged:: 3.14
+
+ Added support for Zstandard compression using :mod:`compression.zstd`.
+
.. function:: open(name=None, mode='r', fileobj=None, bufsize=10240, **kwargs)
Return a :class:`TarFile` object for the pathname *name*. For detailed
@@ -71,6 +75,8 @@ Some facts and figures:
+------------------+---------------------------------------------+
| ``'r:xz'`` | Open for reading with lzma compression. |
+------------------+---------------------------------------------+
+ | ``'r:zst'`` | Open for reading with Zstandard compression.|
+ +------------------+---------------------------------------------+
| ``'x'`` or | Create a tarfile exclusively without |
| ``'x:'`` | compression. |
| | Raise a :exc:`FileExistsError` exception |
@@ -88,6 +94,10 @@ Some facts and figures:
| | Raise a :exc:`FileExistsError` exception |
| | if it already exists. |
+------------------+---------------------------------------------+
+ | ``'x:zst'`` | Create a tarfile with Zstandard compression.|
+ | | Raise a :exc:`FileExistsError` exception |
+ | | if it already exists. |
+ +------------------+---------------------------------------------+
| ``'a' or 'a:'`` | Open for appending with no compression. The |
| | file is created if it does not exist. |
+------------------+---------------------------------------------+
@@ -99,6 +109,8 @@ Some facts and figures:
+------------------+---------------------------------------------+
| ``'w:xz'`` | Open for lzma compressed writing. |
+------------------+---------------------------------------------+
+ | ``'w:zst'`` | Open for Zstandard compressed writing. |
+ +------------------+---------------------------------------------+
Note that ``'a:gz'``, ``'a:bz2'`` or ``'a:xz'`` is not possible. If *mode*
is not suitable to open a certain (compressed) file for reading,
@@ -115,6 +127,15 @@ Some facts and figures:
For modes ``'w:xz'``, ``'x:xz'`` and ``'w|xz'``, :func:`tarfile.open` accepts the
keyword argument *preset* to specify the compression level of the file.
+ For modes ``'w:zst'``, ``'x:zst'`` and ``'w|zst'``, :func:`tarfile.open`
+ accepts the keyword argument *level* to specify the compression level of
+ the file. The keyword argument *options* may also be passed, providing
+ advanced Zstandard compression parameters described by
+ :class:`~compression.zstd.CompressionParameter`. The keyword argument
+ *zstd_dict* can be passed to provide a :class:`~compression.zstd.ZstdDict`,
+ a Zstandard dictionary used to improve compression of smaller amounts of
+ data.
+
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
@@ -146,6 +167,9 @@ Some facts and figures:
| ``'r|xz'`` | Open an lzma compressed *stream* for |
| | reading. |
+-------------+--------------------------------------------+
+ | ``'r|zst'`` | Open a Zstandard compressed *stream* for |
+ | | reading. |
+ +-------------+--------------------------------------------+
| ``'w|'`` | Open an uncompressed *stream* for writing. |
+-------------+--------------------------------------------+
| ``'w|gz'`` | Open a gzip compressed *stream* for |
@@ -157,6 +181,9 @@ Some facts and figures:
| ``'w|xz'`` | Open an lzma compressed *stream* for |
| | writing. |
+-------------+--------------------------------------------+
+ | ``'w|zst'`` | Open a Zstandard compressed *stream* for |
+ | | writing. |
+ +-------------+--------------------------------------------+
.. versionchanged:: 3.5
The ``'x'`` (exclusive creation) mode was added.
@@ -167,7 +194,7 @@ Some facts and figures:
.. versionchanged:: 3.12
The *compresslevel* keyword argument also works for streams.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
The *preset* keyword argument also works for streams.
@@ -255,6 +282,15 @@ The :mod:`tarfile` module defines the following exceptions:
Raised to refuse extracting a symbolic link pointing outside the destination
directory.
+.. exception:: LinkFallbackError
+
+ Raised to refuse emulating a link (hard or symbolic) by extracting another
+ archive member, when that member would be rejected by the filter location.
+ The exception that was raised to reject the replacement member is available
+ as :attr:`!BaseException.__context__`.
+
+ .. versionadded:: next
+
The following constants are available at the module level:
@@ -1068,6 +1104,12 @@ reused in custom filters:
Implements the ``'data'`` filter.
In addition to what ``tar_filter`` does:
+ - Normalize link targets (:attr:`TarInfo.linkname`) using
+ :func:`os.path.normpath`.
+ Note that this removes internal ``..`` components, which may change the
+ meaning of the link if the path in :attr:`!TarInfo.linkname` traverses
+ symbolic links.
+
- :ref:`Refuse <tarfile-extraction-refuse>` to extract links (hard or soft)
that link to absolute paths, or ones that link outside the destination.
@@ -1099,6 +1141,10 @@ reused in custom filters:
Note that this filter does not block *all* dangerous archive features.
See :ref:`tarfile-further-verification` for details.
+ .. versionchanged:: next
+
+ Link targets are now normalized.
+
.. _tarfile-extraction-refuse:
@@ -1127,6 +1173,7 @@ Here is an incomplete list of things to consider:
* Extract to a :func:`new temporary directory <tempfile.mkdtemp>`
to prevent e.g. exploiting pre-existing links, and to make it easier to
clean up after a failed extraction.
+* Disallow symbolic links if you do not need the functionality.
* When working with untrusted data, use external (e.g. OS-level) limits on
disk, memory and CPU usage.
* Check filenames against an allow-list of characters
diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst
index d948493c210..cabb41442f8 100644
--- a/Doc/library/threading.rst
+++ b/Doc/library/threading.rst
@@ -11,6 +11,52 @@
This module constructs higher-level threading interfaces on top of the lower
level :mod:`_thread` module.
+.. include:: ../includes/wasm-notavail.rst
+
+Introduction
+------------
+
+The :mod:`!threading` module provides a way to run multiple `threads
+<https://en.wikipedia.org/wiki/Thread_(computing)>`_ (smaller
+units of a process) concurrently within a single process. It allows for the
+creation and management of threads, making it possible to execute tasks in
+parallel, sharing memory space. Threads are particularly useful when tasks are
+I/O bound, such as file operations or making network requests,
+where much of the time is spent waiting for external resources.
+
+A typical use case for :mod:`!threading` includes managing a pool of worker
+threads that can process multiple tasks concurrently. Here's a basic example of
+creating and starting threads using :class:`~threading.Thread`::
+
+ import threading
+ import time
+
+ def crawl(link, delay=3):
+ print(f"crawl started for {link}")
+ time.sleep(delay) # Blocking I/O (simulating a network request)
+ print(f"crawl ended for {link}")
+
+ links = [
+ "https://python.org",
+ "https://docs.python.org",
+ "https://peps.python.org",
+ ]
+
+ # Start threads for each link
+ threads = []
+ for link in links:
+ # Using `args` to pass positional arguments and `kwargs` for keyword arguments
+ t = threading.Thread(target=crawl, args=(link,), kwargs={"delay": 2})
+ threads.append(t)
+
+ # Start each thread
+ for t in threads:
+ t.start()
+
+ # Wait for all threads to finish
+ for t in threads:
+ t.join()
+
.. versionchanged:: 3.7
This module used to be optional, it is now always available.
@@ -45,7 +91,25 @@ level :mod:`_thread` module.
However, threading is still an appropriate model if you want to run
multiple I/O-bound tasks simultaneously.
-.. include:: ../includes/wasm-notavail.rst
+GIL and performance considerations
+----------------------------------
+
+Unlike the :mod:`multiprocessing` module, which uses separate processes to
+bypass the :term:`global interpreter lock` (GIL), the threading module operates
+within a single process, meaning that all threads share the same memory space.
+However, the GIL limits the performance gains of threading when it comes to
+CPU-bound tasks, as only one thread can execute Python bytecode at a time.
+Despite this, threads remain a useful tool for achieving concurrency in many
+scenarios.
+
+As of Python 3.13, :term:`free-threaded <free threading>` builds
+can disable the GIL, enabling true parallel execution of threads, but this
+feature is not available by default (see :pep:`703`).
+
+.. TODO: At some point this feature will become available by default.
+
+Reference
+---------
This module defines the following functions:
@@ -62,7 +126,7 @@ This module defines the following functions:
Return the current :class:`Thread` object, corresponding to the caller's thread
of control. If the caller's thread of control was not created through the
- :mod:`threading` module, a dummy thread object with limited functionality is
+ :mod:`!threading` module, a dummy thread object with limited functionality is
returned.
The function ``currentThread`` is a deprecated alias for this function.
@@ -157,13 +221,13 @@ This module defines the following functions:
.. index:: single: trace function
- Set a trace function for all threads started from the :mod:`threading` module.
+ Set a trace function for all threads started from the :mod:`!threading` module.
The *func* will be passed to :func:`sys.settrace` for each thread, before its
:meth:`~Thread.run` method is called.
.. function:: settrace_all_threads(func)
- Set a trace function for all threads started from the :mod:`threading` module
+ Set a trace function for all threads started from the :mod:`!threading` module
and all Python threads that are currently executing.
The *func* will be passed to :func:`sys.settrace` for each thread, before its
@@ -186,13 +250,13 @@ This module defines the following functions:
.. index:: single: profile function
- Set a profile function for all threads started from the :mod:`threading` module.
+ Set a profile function for all threads started from the :mod:`!threading` module.
The *func* will be passed to :func:`sys.setprofile` for each thread, before its
:meth:`~Thread.run` method is called.
.. function:: setprofile_all_threads(func)
- Set a profile function for all threads started from the :mod:`threading` module
+ Set a profile function for all threads started from the :mod:`!threading` module
and all Python threads that are currently executing.
The *func* will be passed to :func:`sys.setprofile` for each thread, before its
@@ -257,31 +321,140 @@ when implemented, are mapped to module-level functions.
All of the methods described below are executed atomically.
-Thread-Local Data
------------------
+Thread-local data
+^^^^^^^^^^^^^^^^^
+
+Thread-local data is data whose values are thread specific. If you
+have data that you want to be local to a thread, create a
+:class:`local` object and use its attributes::
+
+ >>> mydata = local()
+ >>> mydata.number = 42
+ >>> mydata.number
+ 42
+
+You can also access the :class:`local`-object's dictionary::
+
+ >>> mydata.__dict__
+ {'number': 42}
+ >>> mydata.__dict__.setdefault('widgets', [])
+ []
+ >>> mydata.widgets
+ []
+
+If we access the data in a different thread::
+
+ >>> log = []
+ >>> def f():
+ ... items = sorted(mydata.__dict__.items())
+ ... log.append(items)
+ ... mydata.number = 11
+ ... log.append(mydata.number)
+
+ >>> import threading
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+ >>> log
+ [[], 11]
+
+we get different data. Furthermore, changes made in the other thread
+don't affect data seen in this thread::
+
+ >>> mydata.number
+ 42
+
+Of course, values you get from a :class:`local` object, including their
+:attr:`~object.__dict__` attribute, are for whatever thread was current
+at the time the attribute was read. For that reason, you generally
+don't want to save these values across threads, as they apply only to
+the thread they came from.
+
+You can create custom :class:`local` objects by subclassing the
+:class:`local` class::
+
+ >>> class MyLocal(local):
+ ... number = 2
+ ... def __init__(self, /, **kw):
+ ... self.__dict__.update(kw)
+ ... def squared(self):
+ ... return self.number ** 2
-Thread-local data is data whose values are thread specific. To manage
-thread-local data, just create an instance of :class:`local` (or a
-subclass) and store attributes on it::
+This can be useful to support default values, methods and
+initialization. Note that if you define an :py:meth:`~object.__init__`
+method, it will be called each time the :class:`local` object is used
+in a separate thread. This is necessary to initialize each thread's
+dictionary.
- mydata = threading.local()
- mydata.x = 1
+Now if we create a :class:`local` object::
-The instance's values will be different for separate threads.
+ >>> mydata = MyLocal(color='red')
+
+we have a default number::
+
+ >>> mydata.number
+ 2
+
+an initial color::
+
+ >>> mydata.color
+ 'red'
+ >>> del mydata.color
+
+And a method that operates on the data::
+
+ >>> mydata.squared()
+ 4
+
+As before, we can access the data in a separate thread::
+
+ >>> log = []
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+ >>> log
+ [[('color', 'red')], 11]
+
+without affecting this thread's data::
+
+ >>> mydata.number
+ 2
+ >>> mydata.color
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'MyLocal' object has no attribute 'color'
+
+Note that subclasses can define :term:`__slots__`, but they are not
+thread local. They are shared across threads::
+
+ >>> class MyLocal(local):
+ ... __slots__ = 'number'
+
+ >>> mydata = MyLocal()
+ >>> mydata.number = 42
+ >>> mydata.color = 'red'
+
+So, the separate thread::
+
+ >>> thread = threading.Thread(target=f)
+ >>> thread.start()
+ >>> thread.join()
+
+affects what we see::
+
+ >>> mydata.number
+ 11
.. class:: local()
A class that represents thread-local data.
- For more details and extensive examples, see the documentation string of the
- :mod:`!_threading_local` module: :source:`Lib/_threading_local.py`.
-
.. _thread-objects:
-Thread Objects
---------------
+Thread objects
+^^^^^^^^^^^^^^
The :class:`Thread` class represents an activity that is run in a separate
thread of control. There are two ways to specify the activity: by passing a
@@ -448,11 +621,11 @@ since it is impossible to detect the termination of alien threads.
an error to :meth:`~Thread.join` a thread before it has been started
and attempts to do so raise the same exception.
- If an attempt is made to join a running daemonic thread in in late stages
+ If an attempt is made to join a running daemonic thread in late stages
of :term:`Python finalization <interpreter shutdown>` :meth:`!join`
raises a :exc:`PythonFinalizationError`.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
May raise :exc:`PythonFinalizationError`.
@@ -536,8 +709,8 @@ since it is impossible to detect the termination of alien threads.
.. _lock-objects:
-Lock Objects
-------------
+Lock objects
+^^^^^^^^^^^^
A primitive lock is a synchronization primitive that is not owned by a
particular thread when locked. In Python, it is currently the lowest level
@@ -629,8 +802,8 @@ All methods are executed atomically.
.. _rlock-objects:
-RLock Objects
--------------
+RLock objects
+^^^^^^^^^^^^^
A reentrant lock is a synchronization primitive that may be acquired multiple
times by the same thread. Internally, it uses the concepts of "owning thread"
@@ -739,8 +912,8 @@ call release as many times the lock has been acquired can lead to deadlock.
.. _condition-objects:
-Condition Objects
------------------
+Condition objects
+^^^^^^^^^^^^^^^^^
A condition variable is always associated with some kind of lock; this can be
passed in or one will be created by default. Passing one in is useful when
@@ -917,8 +1090,8 @@ item to the buffer only needs to wake up one consumer thread.
.. _semaphore-objects:
-Semaphore Objects
------------------
+Semaphore objects
+^^^^^^^^^^^^^^^^^
This is one of the oldest synchronization primitives in the history of computer
science, invented by the early Dutch computer scientist Edsger W. Dijkstra (he
@@ -998,7 +1171,7 @@ Semaphores also support the :ref:`context management protocol <with-locks>`.
.. _semaphore-examples:
-:class:`Semaphore` Example
+:class:`Semaphore` example
^^^^^^^^^^^^^^^^^^^^^^^^^^
Semaphores are often used to guard resources with limited capacity, for example,
@@ -1026,8 +1199,8 @@ causes the semaphore to be released more than it's acquired will go undetected.
.. _event-objects:
-Event Objects
--------------
+Event objects
+^^^^^^^^^^^^^
This is one of the simplest mechanisms for communication between threads: one
thread signals an event and other threads wait for it.
@@ -1083,8 +1256,8 @@ method. The :meth:`~Event.wait` method blocks until the flag is true.
.. _timer-objects:
-Timer Objects
--------------
+Timer objects
+^^^^^^^^^^^^^
This class represents an action that should be run only after a certain amount
of time has passed --- a timer. :class:`Timer` is a subclass of :class:`Thread`
@@ -1121,8 +1294,8 @@ For example::
only work if the timer is still in its waiting stage.
-Barrier Objects
----------------
+Barrier objects
+^^^^^^^^^^^^^^^
.. versionadded:: 3.2
diff --git a/Doc/library/time.rst b/Doc/library/time.rst
index 542493a82af..29b695a9b19 100644
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -712,13 +712,18 @@ Functions
Clock:
- * On Windows, call ``GetSystemTimeAsFileTime()``.
+ * On Windows, call ``GetSystemTimePreciseAsFileTime()``.
* Call ``clock_gettime(CLOCK_REALTIME)`` if available.
* Otherwise, call ``gettimeofday()``.
Use :func:`time_ns` to avoid the precision loss caused by the :class:`float`
type.
+.. versionchanged:: 3.13
+
+ On Windows, calls ``GetSystemTimePreciseAsFileTime()`` instead of
+ ``GetSystemTimeAsFileTime()``.
+
.. function:: time_ns() -> int
diff --git a/Doc/library/token.rst b/Doc/library/token.rst
index 1529d173e17..c228006d4c1 100644
--- a/Doc/library/token.rst
+++ b/Doc/library/token.rst
@@ -51,7 +51,7 @@ The token constants are:
.. data:: NAME
Token value that indicates an :ref:`identifier <identifiers>`.
- Note that keywords are also initially tokenized an ``NAME`` tokens.
+ Note that keywords are also initially tokenized as ``NAME`` tokens.
.. data:: NUMBER
@@ -140,7 +140,7 @@ The token constants are:
The token string includes the prefix and the opening quote(s), but none
of the contents of the literal.
- .. versionadded:: next
+ .. versionadded:: 3.14
.. data:: TSTRING_MIDDLE
@@ -154,7 +154,7 @@ The token constants are:
:data:`LBRACE`, :data:`RBRACE`, :data:`EXCLAMATION` and :data:`COLON`
tokens.
- .. versionadded:: next
+ .. versionadded:: 3.14
.. data:: TSTRING_END
@@ -164,7 +164,7 @@ The token constants are:
The token string contains the closing quote(s).
- .. versionadded:: next
+ .. versionadded:: 3.14
.. data:: ENDMARKER
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 3afcba6e898..69df09c7795 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -1098,6 +1098,12 @@ These can be used as types in annotations. They all support subscription using
Union[Union[int, str], float] == Union[int, str, float]
+ However, this does not apply to unions referenced through a type
+ alias, to avoid forcing evaluation of the underlying :class:`TypeAliasType`::
+
+ type A = Union[int, str]
+ Union[A, float] != Union[int, str, float]
+
* Unions of a single argument vanish, e.g.::
Union[int] == int # The constructor actually returns int
@@ -1230,6 +1236,32 @@ These can be used as types in annotations. They all support subscription using
is allowed as type argument to ``Literal[...]``, but type checkers may
impose restrictions. See :pep:`586` for more details about literal types.
+ Additional details:
+
+ * The arguments must be literal values and there must be at least one.
+
+ * Nested ``Literal`` types are flattened, e.g.::
+
+ assert Literal[Literal[1, 2], 3] == Literal[1, 2, 3]
+
+ However, this does not apply to ``Literal`` types referenced through a type
+ alias, to avoid forcing evaluation of the underlying :class:`TypeAliasType`::
+
+ type A = Literal[1, 2]
+ assert Literal[A, 3] != Literal[1, 2, 3]
+
+ * Redundant arguments are skipped, e.g.::
+
+ assert Literal[1, 2, 1] == Literal[1, 2]
+
+ * When comparing literals, the argument order is ignored, e.g.::
+
+ assert Literal[1, 2] == Literal[2, 1]
+
+ * You cannot subclass or instantiate a ``Literal``.
+
+ * You cannot write ``Literal[X][Y]``.
+
.. versionadded:: 3.8
.. versionchanged:: 3.9.1
@@ -1400,6 +1432,14 @@ These can be used as types in annotations. They all support subscription using
int, ValueRange(3, 10), ctype("char")
]
+ However, this does not apply to ``Annotated`` types referenced through a type
+ alias, to avoid forcing evaluation of the underlying :class:`TypeAliasType`::
+
+ type From3To10[T] = Annotated[T, ValueRange(3, 10)]
+ assert Annotated[From3To10[int], ctype("char")] != Annotated[
+ int, ValueRange(3, 10), ctype("char")
+ ]
+
Duplicated metadata elements are not removed::
assert Annotated[int, ValueRange(3, 10)] != Annotated[
@@ -3460,20 +3500,11 @@ Introspection helpers
Evaluate an :class:`annotationlib.ForwardRef` as a :term:`type hint`.
This is similar to calling :meth:`annotationlib.ForwardRef.evaluate`,
- but unlike that method, :func:`!evaluate_forward_ref` also:
-
- * Recursively evaluates forward references nested within the type hint.
- * Raises :exc:`TypeError` when it encounters certain objects that are
- not valid type hints.
- * Replaces type hints that evaluate to :const:`!None` with
- :class:`types.NoneType`.
- * Supports the :attr:`~annotationlib.Format.FORWARDREF` and
- :attr:`~annotationlib.Format.STRING` formats.
+ but unlike that method, :func:`!evaluate_forward_ref` also
+ recursively evaluates forward references nested within the type hint.
See the documentation for :meth:`annotationlib.ForwardRef.evaluate` for
- the meaning of the *owner*, *globals*, *locals*, and *type_params* parameters.
- *format* specifies the format of the annotation and is a member of
- the :class:`annotationlib.Format` enum.
+ the meaning of the *owner*, *globals*, *locals*, *type_params*, and *format* parameters.
.. versionadded:: 3.14
@@ -3499,28 +3530,32 @@ Constant
.. data:: TYPE_CHECKING
A special constant that is assumed to be ``True`` by 3rd party static
- type checkers. It is ``False`` at runtime.
+ type checkers. It's ``False`` at runtime.
+
+ A module which is expensive to import, and which only contain types
+ used for typing annotations, can be safely imported inside an
+ ``if TYPE_CHECKING:`` block. This prevents the module from actually
+ being imported at runtime; annotations aren't eagerly evaluated
+ (see :pep:`649`) so using undefined symbols in annotations is
+ harmless--as long as you don't later examine them.
+ Your static type analysis tool will set ``TYPE_CHECKING`` to
+ ``True`` during static type analysis, which means the module will
+ be imported and the types will be checked properly during such analysis.
Usage::
if TYPE_CHECKING:
import expensive_mod
- def fun(arg: 'expensive_mod.SomeType') -> None:
+ def fun(arg: expensive_mod.SomeType) -> None:
local_var: expensive_mod.AnotherType = other_fun()
- The first type annotation must be enclosed in quotes, making it a
- "forward reference", to hide the ``expensive_mod`` reference from the
- interpreter runtime. Type annotations for local variables are not
- evaluated, so the second annotation does not need to be enclosed in quotes.
-
- .. note::
-
- If ``from __future__ import annotations`` is used,
- annotations are not evaluated at function definition time.
- Instead, they are stored as strings in ``__annotations__``.
- This makes it unnecessary to use quotes around the annotation
- (see :pep:`563`).
+ If you occasionally need to examine type annotations at runtime
+ which may contain undefined symbols, use
+ :meth:`annotationlib.get_annotations` with a ``format`` parameter
+ of :attr:`annotationlib.Format.STRING` or
+ :attr:`annotationlib.Format.FORWARDREF` to safely retrieve the
+ annotations without raising :exc:`NameError`.
.. versionadded:: 3.5.2
diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst
index 27c169dde72..091562cc9ae 100644
--- a/Doc/library/unittest.mock.rst
+++ b/Doc/library/unittest.mock.rst
@@ -2654,9 +2654,9 @@ with any methods on the mock:
.. code-block:: pycon
- >>> mock.has_data()
+ >>> mock.header_items()
<mock.Mock object at 0x...>
- >>> mock.has_data.assret_called_with() # Intentional typo!
+ >>> mock.header_items.assret_called_with() # Intentional typo!
Auto-speccing solves this problem. You can either pass ``autospec=True`` to
:func:`patch` / :func:`patch.object` or use the :func:`create_autospec` function to create a
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index 61022fe052c..d526e835caa 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -109,7 +109,7 @@ Here is a short script to test three string methods::
unittest.main()
-A testcase is created by subclassing :class:`unittest.TestCase`. The three
+A test case is created by subclassing :class:`unittest.TestCase`. The three
individual tests are defined with methods whose names start with the letters
``test``. This naming convention informs the test runner about which methods
represent tests.
@@ -1131,7 +1131,7 @@ Test cases
.. versionchanged:: 3.3
Added the *msg* keyword argument when used as a context manager.
- .. method:: assertLogs(logger=None, level=None)
+ .. method:: assertLogs(logger=None, level=None, formatter=None)
A context manager to test that at least one message is logged on
the *logger* or one of its children, with at least the given
@@ -1146,6 +1146,10 @@ Test cases
its string equivalent (for example either ``"ERROR"`` or
:const:`logging.ERROR`). The default is :const:`logging.INFO`.
+ If given, *formatter* should be a :class:`logging.Formatter` object.
+ The default is a formatter with format string
+ ``"%(levelname)s:%(name)s:%(message)s"``
+
The test passes if at least one message emitted inside the ``with``
block matches the *logger* and *level* conditions, otherwise it fails.
@@ -1173,6 +1177,9 @@ Test cases
.. versionadded:: 3.4
+ .. versionchanged:: next
+ Now accepts a *formatter* to control how messages are formatted.
+
.. method:: assertNoLogs(logger=None, level=None)
A context manager to test that no messages are logged on
diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst
index b7c0c7d5099..58bd111b5cc 100644
--- a/Doc/library/urllib.request.rst
+++ b/Doc/library/urllib.request.rst
@@ -171,11 +171,11 @@ The :mod:`urllib.request` module defines the following functions:
sections. For example, the path ``/etc/hosts`` is converted to
the URL ``///etc/hosts``.
- .. versionchanged:: next
- The *add_scheme* argument was added.
+ .. versionchanged:: 3.14
+ The *add_scheme* parameter was added.
-.. function:: url2pathname(url, *, require_scheme=False)
+.. function:: url2pathname(url, *, require_scheme=False, resolve_host=False)
Convert the given ``file:`` URL to a local path. This function uses
:func:`~urllib.parse.unquote` to decode the URL.
@@ -185,6 +185,13 @@ The :mod:`urllib.request` module defines the following functions:
value should include the prefix; a :exc:`~urllib.error.URLError` is raised
if it doesn't.
+ The URL authority is discarded if it is empty, ``localhost``, or the local
+ hostname. Otherwise, if *resolve_host* is set to true, the authority is
+ resolved using :func:`socket.gethostbyname` and discarded if it matches a
+ local IP address (as per :rfc:`RFC 8089 §3 <8089#section-3>`). If the
+ authority is still unhandled, then on Windows a UNC path is returned, and
+ on other platforms a :exc:`~urllib.error.URLError` is raised.
+
This example shows the function being used on Windows::
>>> from urllib.request import url2pathname
@@ -197,15 +204,14 @@ The :mod:`urllib.request` module defines the following functions:
characters not following a drive letter no longer cause an
:exc:`OSError` exception to be raised on Windows.
- .. versionchanged:: next
- This function calls :func:`socket.gethostbyname` if the URL authority
- isn't empty, ``localhost``, or the machine hostname. If the authority
- resolves to a local IP address then it is discarded; otherwise, on
+ .. versionchanged:: 3.14
+ The URL authority is discarded if it matches the local hostname.
+ Otherwise, if the authority isn't empty or ``localhost``, then on
Windows a UNC path is returned (as before), and on other platforms a
:exc:`~urllib.error.URLError` is raised.
- .. versionchanged:: next
- The *require_scheme* argument was added.
+ .. versionchanged:: 3.14
+ The *require_scheme* and *resolve_host* parameters were added.
.. function:: getproxies()
@@ -1115,7 +1121,7 @@ HTTPHandler Objects
.. method:: HTTPHandler.http_open(req)
Send an HTTP request, which can be either GET or POST, depending on
- ``req.has_data()``.
+ ``req.data``.
.. _https-handler-objects:
@@ -1127,7 +1133,7 @@ HTTPSHandler Objects
.. method:: HTTPSHandler.https_open(req)
Send an HTTPS request, which can be either GET or POST, depending on
- ``req.has_data()``.
+ ``req.data``.
.. _file-handler-objects:
diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst
index 8cce6b98cbc..6698e6d3f43 100644
--- a/Doc/library/uuid.rst
+++ b/Doc/library/uuid.rst
@@ -193,43 +193,52 @@ The :mod:`uuid` module defines the following functions:
.. function:: uuid1(node=None, clock_seq=None)
- Generate a UUID from a host ID, sequence number, and the current time. If *node*
- is not given, :func:`getnode` is used to obtain the hardware address. If
- *clock_seq* is given, it is used as the sequence number; otherwise a random
- 14-bit sequence number is chosen.
+ Generate a UUID from a host ID, sequence number, and the current time
+ according to :rfc:`RFC 9562, §5.1 <9562#section-5.1>`.
+
+ When *node* is not specified, :func:`getnode` is used to obtain the hardware
+ address as a 48-bit positive integer. When a sequence number *clock_seq* is
+ not specified, a pseudo-random 14-bit positive integer is generated.
+
+ If *node* or *clock_seq* exceed their expected bit count,
+ only their least significant bits are kept.
.. function:: uuid3(namespace, name)
Generate a UUID based on the MD5 hash of a namespace identifier (which is a
UUID) and a name (which is a :class:`bytes` object or a string
- that will be encoded using UTF-8).
+ that will be encoded using UTF-8)
+ according to :rfc:`RFC 9562, §5.3 <9562#section-5.3>`.
.. function:: uuid4()
- Generate a random UUID.
+ Generate a random UUID in a cryptographically-secure method
+ according to :rfc:`RFC 9562, §5.4 <9562#section-5.4>`.
.. function:: uuid5(namespace, name)
Generate a UUID based on the SHA-1 hash of a namespace identifier (which is a
UUID) and a name (which is a :class:`bytes` object or a string
- that will be encoded using UTF-8).
+ that will be encoded using UTF-8)
+ according to :rfc:`RFC 9562, §5.5 <9562#section-5.5>`.
.. function:: uuid6(node=None, clock_seq=None)
Generate a UUID from a sequence number and the current time according to
- :rfc:`9562`.
+ :rfc:`RFC 9562, §5.6 <9562#section-5.6>`.
+
This is an alternative to :func:`uuid1` to improve database locality.
When *node* is not specified, :func:`getnode` is used to obtain the hardware
address as a 48-bit positive integer. When a sequence number *clock_seq* is
not specified, a pseudo-random 14-bit positive integer is generated.
- If *node* or *clock_seq* exceed their expected bit count, only their least
- significant bits are kept.
+ If *node* or *clock_seq* exceed their expected bit count,
+ only their least significant bits are kept.
.. versionadded:: 3.14
@@ -257,6 +266,10 @@ The :mod:`uuid` module defines the following functions:
non-specified arguments are substituted for a pseudo-random integer of
appropriate size.
+ By default, *a*, *b* and *c* are not generated by a cryptographically
+ secure pseudo-random number generator (CSPRNG). Use :func:`uuid4` when
+ a UUID needs to be used in a security-sensitive context.
+
.. versionadded:: 3.14
diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
index bed799aedfd..f16e24eac08 100644
--- a/Doc/library/venv.rst
+++ b/Doc/library/venv.rst
@@ -105,36 +105,52 @@ The command, if run with ``-h``, will show the available options::
Creates virtual Python environments in one or more target directories.
- positional arguments:
- ENV_DIR A directory to create the environment in.
-
- options:
- -h, --help show this help message and exit
- --system-site-packages
- Give the virtual environment access to the system
- site-packages dir.
- --symlinks Try to use symlinks rather than copies, when
- symlinks are not the default for the platform.
- --copies Try to use copies rather than symlinks, even when
- symlinks are the default for the platform.
- --clear Delete the contents of the environment directory
- if it already exists, before environment creation.
- --upgrade Upgrade the environment directory to use this
- version of Python, assuming Python has been
- upgraded in-place.
- --without-pip Skips installing or upgrading pip in the virtual
- environment (pip is bootstrapped by default)
- --prompt PROMPT Provides an alternative prompt prefix for this
- environment.
- --upgrade-deps Upgrade core dependencies (pip) to the latest
- version in PyPI
- --without-scm-ignore-files
- Skips adding SCM ignore files to the environment
- directory (Git is supported by default).
-
Once an environment has been created, you may wish to activate it, e.g. by
sourcing an activate script in its bin directory.
+.. _venv-cli:
+.. program:: venv
+
+.. option:: ENV_DIR
+
+ A required argument specifying the directory to create the environment in.
+
+.. option:: --system-site-packages
+
+ Give the virtual environment access to the system site-packages directory.
+
+.. option:: --symlinks
+
+ Try to use symlinks rather than copies, when symlinks are not the default for the platform.
+
+.. option:: --copies
+
+ Try to use copies rather than symlinks, even when symlinks are the default for the platform.
+
+.. option:: --clear
+
+ Delete the contents of the environment directory if it already exists, before environment creation.
+
+.. option:: --upgrade
+
+ Upgrade the environment directory to use this version of Python, assuming Python has been upgraded in-place.
+
+.. option:: --without-pip
+
+ Skips installing or upgrading pip in the virtual environment (pip is bootstrapped by default).
+
+.. option:: --prompt <PROMPT>
+
+ Provides an alternative prompt prefix for this environment.
+
+.. option:: --upgrade-deps
+
+ Upgrade core dependencies (pip) to the latest version in PyPI.
+
+.. option:: --without-scm-ignore-files
+
+ Skips adding SCM ignore files to the environment directory (Git is supported by default).
+
.. versionchanged:: 3.4
Installs pip by default, added the ``--without-pip`` and ``--copies``
diff --git a/Doc/library/wave.rst b/Doc/library/wave.rst
index 36c2bde87fb..a3f5bfd5e2f 100644
--- a/Doc/library/wave.rst
+++ b/Doc/library/wave.rst
@@ -123,26 +123,6 @@ Wave_read Objects
Rewind the file pointer to the beginning of the audio stream.
- The following two methods are defined for compatibility with the old :mod:`!aifc`
- module, and don't do anything interesting.
-
-
- .. method:: getmarkers()
-
- Returns ``None``.
-
- .. deprecated-removed:: 3.13 3.15
- The method only existed for compatibility with the :mod:`!aifc` module
- which has been removed in Python 3.13.
-
-
- .. method:: getmark(id)
-
- Raise an error.
-
- .. deprecated-removed:: 3.13 3.15
- The method only existed for compatibility with the :mod:`!aifc` module
- which has been removed in Python 3.13.
The following two methods define a term "position" which is compatible between
them, and is otherwise implementation dependent.
diff --git a/Doc/library/webbrowser.rst b/Doc/library/webbrowser.rst
index cbb2b06c2a0..fd6abc70261 100644
--- a/Doc/library/webbrowser.rst
+++ b/Doc/library/webbrowser.rst
@@ -29,7 +29,7 @@ already registered browsers this browser is added to the front of the search lis
if the part does not contain ``%s``, it is simply interpreted as the name of the
browser to launch. [1]_
-.. versionchanged:: next
+.. versionchanged:: 3.14
The :envvar:`BROWSER` variable can now also be used to reorder the list of
platform defaults. This is particularly useful on macOS where the platform
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
index 6a4fa67332e..bf9136a2139 100644
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -129,14 +129,28 @@ The module defines the following items:
.. versionadded:: 3.3
+.. data:: ZIP_ZSTANDARD
+
+ The numeric constant for Zstandard compression. This requires the
+ :mod:`compression.zstd` module.
+
.. note::
- The ZIP file format specification has included support for bzip2 compression
- since 2001, and for LZMA compression since 2006. However, some tools
- (including older Python releases) do not support these compression
- methods, and may either refuse to process the ZIP file altogether,
- or fail to extract individual files.
+ In APPNOTE 6.3.7, the method ID ``20`` was assigned to Zstandard
+ compression. This was changed in APPNOTE 6.3.8 to method ID ``93`` to
+ avoid conflicts, with method ID ``20`` being deprecated. For
+ compatibility, the :mod:`!zipfile` module reads both method IDs but will
+ only write data with method ID ``93``.
+
+ .. versionadded:: 3.14
+
+.. note::
+ The ZIP file format specification has included support for bzip2 compression
+ since 2001, for LZMA compression since 2006, and Zstandard compression since
+ 2020. However, some tools (including older Python releases) do not support
+ these compression methods, and may either refuse to process the ZIP file
+ altogether, or fail to extract individual files.
.. seealso::
@@ -176,10 +190,11 @@ ZipFile Objects
*compression* is the ZIP compression method to use when writing the archive,
and should be :const:`ZIP_STORED`, :const:`ZIP_DEFLATED`,
- :const:`ZIP_BZIP2` or :const:`ZIP_LZMA`; unrecognized
- values will cause :exc:`NotImplementedError` to be raised. If
- :const:`ZIP_DEFLATED`, :const:`ZIP_BZIP2` or :const:`ZIP_LZMA` is specified
- but the corresponding module (:mod:`zlib`, :mod:`bz2` or :mod:`lzma`) is not
+ :const:`ZIP_BZIP2`, :const:`ZIP_LZMA`, or :const:`ZIP_ZSTANDARD`;
+ unrecognized values will cause :exc:`NotImplementedError` to be raised. If
+ :const:`ZIP_DEFLATED`, :const:`ZIP_BZIP2`, :const:`ZIP_LZMA`, or
+ :const:`ZIP_ZSTANDARD` is specified but the corresponding module
+ (:mod:`zlib`, :mod:`bz2`, :mod:`lzma`, or :mod:`compression.zstd`) is not
available, :exc:`RuntimeError` is raised. The default is :const:`ZIP_STORED`.
If *allowZip64* is ``True`` (the default) zipfile will create ZIP files that
@@ -194,6 +209,10 @@ ZipFile Objects
(see :class:`zlib <zlib.compressobj>` for more information).
When using :const:`ZIP_BZIP2` integers ``1`` through ``9`` are accepted
(see :class:`bz2 <bz2.BZ2File>` for more information).
+ When using :const:`ZIP_ZSTANDARD` integers ``-131072`` through ``22`` are
+ commonly accepted (see
+ :attr:`CompressionParameter.compression_level <compression.zstd.CompressionParameter.compression_level>`
+ for more on retrieving valid values and their meaning).
The *strict_timestamps* argument, when set to ``False``, allows to
zip files older than 1980-01-01 at the cost of setting the
@@ -415,9 +434,10 @@ ZipFile Objects
read or append. *pwd* is the password used for encrypted files as a :class:`bytes`
object and, if specified, overrides the default password set with :meth:`setpassword`.
Calling :meth:`read` on a ZipFile that uses a compression method other than
- :const:`ZIP_STORED`, :const:`ZIP_DEFLATED`, :const:`ZIP_BZIP2` or
- :const:`ZIP_LZMA` will raise a :exc:`NotImplementedError`. An error will also
- be raised if the corresponding compression module is not available.
+ :const:`ZIP_STORED`, :const:`ZIP_DEFLATED`, :const:`ZIP_BZIP2`,
+ :const:`ZIP_LZMA`, or :const:`ZIP_ZSTANDARD` will raise a
+ :exc:`NotImplementedError`. An error will also be raised if the
+ corresponding compression module is not available.
.. versionchanged:: 3.6
Calling :meth:`read` on a closed ZipFile will raise a :exc:`ValueError`.
diff --git a/Doc/library/zlib.rst b/Doc/library/zlib.rst
index 75ead3c4cb1..7c5e9b086e1 100644
--- a/Doc/library/zlib.rst
+++ b/Doc/library/zlib.rst
@@ -44,6 +44,20 @@ The available exception and functions in this module are:
.. versionchanged:: 3.0
The result is always unsigned.
+.. function:: adler32_combine(adler1, adler2, len2, /)
+
+ Combine two Adler-32 checksums into one.
+
+ Given the Adler-32 checksum *adler1* of a sequence ``A`` and the
+ Adler-32 checksum *adler2* of a sequence ``B`` of length *len2*,
+ return the Adler-32 checksum of ``A`` and ``B`` concatenated.
+
+ This function is typically useful to combine Adler-32 checksums
+ that were concurrently computed. To compute checksums sequentially, use
+ :func:`adler32` with the running checksum as the ``value`` argument.
+
+ .. versionadded:: next
+
.. function:: compress(data, /, level=-1, wbits=MAX_WBITS)
Compresses the bytes in *data*, returning a bytes object containing compressed data.
@@ -136,6 +150,20 @@ The available exception and functions in this module are:
.. versionchanged:: 3.0
The result is always unsigned.
+.. function:: crc32_combine(crc1, crc2, len2, /)
+
+ Combine two CRC-32 checksums into one.
+
+ Given the CRC-32 checksum *crc1* of a sequence ``A`` and the
+ CRC-32 checksum *crc2* of a sequence ``B`` of length *len2*,
+ return the CRC-32 checksum of ``A`` and ``B`` concatenated.
+
+ This function is typically useful to combine CRC-32 checksums
+ that were concurrently computed. To compute checksums sequentially, use
+ :func:`crc32` with the running checksum as the ``value`` argument.
+
+ .. versionadded:: next
+
.. function:: decompress(data, /, wbits=MAX_WBITS, bufsize=DEF_BUF_SIZE)
Decompresses the bytes in *data*, returning a bytes object containing the
diff --git a/Doc/library/zoneinfo.rst b/Doc/library/zoneinfo.rst
index a57f3b8b3e8..53d8e2598ec 100644
--- a/Doc/library/zoneinfo.rst
+++ b/Doc/library/zoneinfo.rst
@@ -195,7 +195,7 @@ The ``ZoneInfo`` class
The ``ZoneInfo`` class has two alternate constructors:
-.. classmethod:: ZoneInfo.from_file(fobj, /, key=None)
+.. classmethod:: ZoneInfo.from_file(file_obj, /, key=None)
Constructs a ``ZoneInfo`` object from a file-like object returning bytes
(e.g. a file opened in binary mode or an :class:`io.BytesIO` object).
@@ -325,7 +325,7 @@ The behavior of a ``ZoneInfo`` file depends on how it was constructed:
>>> a is b
False
-3. ``ZoneInfo.from_file(fobj, /, key=None)``: When constructed from a file, the
+3. ``ZoneInfo.from_file(file_obj, /, key=None)``: When constructed from a file, the
``ZoneInfo`` object raises an exception on pickling. If an end user wants to
pickle a ``ZoneInfo`` constructed from a file, it is recommended that they
use a wrapper type or a custom serialization function: either serializing by
diff --git a/Doc/license.rst b/Doc/license.rst
index 90783e3e31a..480414bb84c 100644
--- a/Doc/license.rst
+++ b/Doc/license.rst
@@ -1132,3 +1132,40 @@ The file is distributed under the 2-Clause BSD License::
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Zstandard bindings
+------------------
+
+Zstandard bindings in :file:`Modules/_zstd` and :file:`Lib/compression/zstd`
+are based on code from the
+`pyzstd library <https://github.com/Rogdham/pyzstd/>`_, copyright Ma Lin and
+contributors. The pyzstd code is distributed under the 3-Clause BSD License::
+
+ Copyright (c) 2020-present, Ma Lin and contributors.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index f36ed3e122f..e95fa3a6424 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -154,15 +154,15 @@ The :keyword:`for` statement is used to iterate over the elements of a sequence
(such as a string, tuple or list) or other iterable object:
.. productionlist:: python-grammar
- for_stmt: "for" `target_list` "in" `starred_list` ":" `suite`
+ for_stmt: "for" `target_list` "in" `starred_expression_list` ":" `suite`
: ["else" ":" `suite`]
-The ``starred_list`` expression is evaluated once; it should yield an
-:term:`iterable` object. An :term:`iterator` is created for that iterable.
-The first item provided
-by the iterator is then assigned to the target list using the standard
-rules for assignments (see :ref:`assignment`), and the suite is executed. This
-repeats for each item provided by the iterator. When the iterator is exhausted,
+The :token:`~python-grammar:starred_expression_list` expression is evaluated
+once; it should yield an :term:`iterable` object. An :term:`iterator` is
+created for that iterable. The first item provided by the iterator is then
+assigned to the target list using the standard rules for assignments
+(see :ref:`assignment`), and the suite is executed. This repeats for each
+item provided by the iterator. When the iterator is exhausted,
the suite in the :keyword:`!else` clause,
if present, is executed, and the loop terminates.
@@ -1885,7 +1885,7 @@ expressions. The presence of annotations does not change the runtime semantics o
the code, except if some mechanism is used that introspects and uses the annotations
(such as :mod:`dataclasses` or :func:`functools.singledispatch`).
-By default, annotations are lazily evaluated in a :ref:`annotation scope <annotation-scopes>`.
+By default, annotations are lazily evaluated in an :ref:`annotation scope <annotation-scopes>`.
This means that they are not evaluated when the code containing the annotation is evaluated.
Instead, the interpreter saves information that can be used to evaluate the annotation later
if requested. The :mod:`annotationlib` module provides tools for evaluating annotations.
@@ -1898,6 +1898,12 @@ all annotations are instead stored as strings::
>>> f.__annotations__
{'param': 'annotation'}
+This future statement will be deprecated and removed in a future version of Python,
+but not before Python 3.13 reaches its end of life (see :pep:`749`).
+When it is used, introspection tools like
+:func:`annotationlib.get_annotations` and :func:`typing.get_type_hints` are
+less likely to be able to resolve annotations at runtime.
+
.. rubric:: Footnotes
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index b3096a9f0c3..4a099e81dac 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -262,6 +262,8 @@ Booleans (:class:`bool`)
a string, the strings ``"False"`` or ``"True"`` are returned, respectively.
+.. _datamodel-float:
+
:class:`numbers.Real` (:class:`float`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1228,15 +1230,21 @@ Special attributes
:attr:`__annotations__ attributes <object.__annotations__>`.
For best practices on working with :attr:`~object.__annotations__`,
- please see :mod:`annotationlib`.
+ please see :mod:`annotationlib`. Use
+ :func:`annotationlib.get_annotations` instead of accessing this
+ attribute directly.
+
+ .. warning::
- .. caution::
+ Accessing the :attr:`!__annotations__` attribute directly
+ on a class object may return annotations for the wrong class, specifically
+ in certain cases where the class, its base class, or a metaclass
+ is defined under ``from __future__ import annotations``.
+ See :pep:`749 <749#pep749-metaclasses>` for details.
- Accessing the :attr:`!__annotations__` attribute of a class
- object directly may yield incorrect results in the presence of
- metaclasses. In addition, the attribute may not exist for
- some classes. Use :func:`annotationlib.get_annotations` to
- retrieve class annotations safely.
+ This attribute does not exist on certain builtin classes. On
+ user-defined classes without ``__annotations__``, it is an
+ empty dictionary.
.. versionchanged:: 3.14
Annotations are now :ref:`lazily evaluated <lazy-evaluation>`.
@@ -1247,13 +1255,6 @@ Special attributes
if the class has no annotations.
See also: :attr:`__annotate__ attributes <object.__annotate__>`.
- .. caution::
-
- Accessing the :attr:`!__annotate__` attribute of a class
- object directly may yield incorrect results in the presence of
- metaclasses. Use :func:`annotationlib.get_annotate_function` to
- retrieve the annotate function safely.
-
.. versionadded:: 3.14
* - .. attribute:: type.__type_params__
@@ -3359,7 +3360,7 @@ left undefined.
argument if the three-argument version of the built-in :func:`pow` function
is to be supported.
- .. versionchanged:: next
+ .. versionchanged:: 3.14
Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
Previously it was only called in two-argument :func:`!pow` and the binary
diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst
index 8837344e5dd..24544a055c3 100644
--- a/Doc/reference/expressions.rst
+++ b/Doc/reference/expressions.rst
@@ -134,8 +134,7 @@ Literals
Python supports string and bytes literals and various numeric literals:
.. productionlist:: python-grammar
- literal: `stringliteral` | `bytesliteral`
- : | `integer` | `floatnumber` | `imagnumber`
+ literal: `stringliteral` | `bytesliteral` | `NUMBER`
Evaluation of a literal yields an object of the given type (string, bytes,
integer, floating-point number, complex number) with the given value. The value
@@ -406,8 +405,9 @@ brackets or curly braces.
Variables used in the generator expression are evaluated lazily when the
:meth:`~generator.__next__` method is called for the generator object (in the same
fashion as normal generators). However, the iterable expression in the
-leftmost :keyword:`!for` clause is immediately evaluated, so that an error
-produced by it will be emitted at the point where the generator expression
+leftmost :keyword:`!for` clause is immediately evaluated, and the
+:term:`iterator` is immediately created for that iterable, so that an error
+produced while creating the iterator will be emitted at the point where the generator expression
is defined, rather than at the point where the first value is retrieved.
Subsequent :keyword:`!for` clauses and any filter condition in the leftmost
:keyword:`!for` clause cannot be evaluated in the enclosing scope as they may
@@ -625,8 +625,10 @@ is already executing raises a :exc:`ValueError` exception.
.. method:: generator.close()
- Raises a :exc:`GeneratorExit` at the point where the generator function was
- paused. If the generator function catches the exception and returns a
+ Raises a :exc:`GeneratorExit` exception at the point where the generator
+ function was paused (equivalent to calling ``throw(GeneratorExit)``).
+ The exception is raised by the yield expression where the generator was paused.
+ If the generator function catches the exception and returns a
value, this value is returned from :meth:`close`. If the generator function
is already closed, or raises :exc:`GeneratorExit` (by not catching the
exception), :meth:`close` returns :const:`None`. If the generator yields a
@@ -1023,7 +1025,7 @@ series of :term:`arguments <argument>`:
: ["," `keywords_arguments`]
: | `starred_and_keywords` ["," `keywords_arguments`]
: | `keywords_arguments`
- positional_arguments: positional_item ("," positional_item)*
+ positional_arguments: `positional_item` ("," `positional_item`)*
positional_item: `assignment_expression` | "*" `expression`
starred_and_keywords: ("*" `expression` | `keyword_item`)
: ("," "*" `expression` | "," `keyword_item`)*
@@ -1928,7 +1930,7 @@ Expression lists
single: , (comma); expression list
.. productionlist:: python-grammar
- starred_expression: ["*"] `or_expr`
+ starred_expression: "*" `or_expr` | `expression`
flexible_expression: `assignment_expression` | `starred_expression`
flexible_expression_list: `flexible_expression` ("," `flexible_expression`)* [","]
starred_expression_list: `starred_expression` ("," `starred_expression`)* [","]
diff --git a/Doc/reference/grammar.rst b/Doc/reference/grammar.rst
index b9cca4444c9..55c148801d8 100644
--- a/Doc/reference/grammar.rst
+++ b/Doc/reference/grammar.rst
@@ -8,15 +8,15 @@ used to generate the CPython parser (see :source:`Grammar/python.gram`).
The version here omits details related to code generation and
error recovery.
-The notation is a mixture of `EBNF
-<https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form>`_
-and `PEG <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`_.
-In particular, ``&`` followed by a symbol, token or parenthesized
-group indicates a positive lookahead (i.e., is required to match but
-not consumed), while ``!`` indicates a negative lookahead (i.e., is
-required *not* to match). We use the ``|`` separator to mean PEG's
-"ordered choice" (written as ``/`` in traditional PEG grammars). See
-:pep:`617` for more details on the grammar's syntax.
+The notation used here is the same as in the preceding docs,
+and is described in the :ref:`notation <notation>` section,
+except for a few extra complications:
+
+* ``&e``: a positive lookahead (that is, ``e`` is required to match but
+ not consumed)
+* ``!e``: a negative lookahead (that is, ``e`` is required *not* to match)
+* ``~`` ("cut"): commit to the current alternative and fail the rule
+ even if this fails to parse
.. literalinclude:: ../../Grammar/python.gram
:language: peg
diff --git a/Doc/reference/introduction.rst b/Doc/reference/introduction.rst
index b7b70e6be5a..444acac374a 100644
--- a/Doc/reference/introduction.rst
+++ b/Doc/reference/introduction.rst
@@ -90,44 +90,122 @@ Notation
.. index:: BNF, grammar, syntax, notation
-The descriptions of lexical analysis and syntax use a modified
-`Backus–Naur form (BNF) <https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form>`_ grammar
-notation. This uses the following style of definition:
-
-.. productionlist:: notation
- name: `lc_letter` (`lc_letter` | "_")*
- lc_letter: "a"..."z"
-
-The first line says that a ``name`` is an ``lc_letter`` followed by a sequence
-of zero or more ``lc_letter``\ s and underscores. An ``lc_letter`` in turn is
-any of the single characters ``'a'`` through ``'z'``. (This rule is actually
-adhered to for the names defined in lexical and grammar rules in this document.)
-
-Each rule begins with a name (which is the name defined by the rule) and
-``::=``. A vertical bar (``|``) is used to separate alternatives; it is the
-least binding operator in this notation. A star (``*``) means zero or more
-repetitions of the preceding item; likewise, a plus (``+``) means one or more
-repetitions, and a phrase enclosed in square brackets (``[ ]``) means zero or
-one occurrences (in other words, the enclosed phrase is optional). The ``*``
-and ``+`` operators bind as tightly as possible; parentheses are used for
-grouping. Literal strings are enclosed in quotes. White space is only
-meaningful to separate tokens. Rules are normally contained on a single line;
-rules with many alternatives may be formatted alternatively with each line after
-the first beginning with a vertical bar.
-
-.. index:: lexical definitions, ASCII
-
-In lexical definitions (as the example above), two more conventions are used:
-Two literal characters separated by three dots mean a choice of any single
-character in the given (inclusive) range of ASCII characters. A phrase between
-angular brackets (``<...>``) gives an informal description of the symbol
-defined; e.g., this could be used to describe the notion of 'control character'
-if needed.
-
-Even though the notation used is almost the same, there is a big difference
-between the meaning of lexical and syntactic definitions: a lexical definition
-operates on the individual characters of the input source, while a syntax
-definition operates on the stream of tokens generated by the lexical analysis.
-All uses of BNF in the next chapter ("Lexical Analysis") are lexical
-definitions; uses in subsequent chapters are syntactic definitions.
-
+The descriptions of lexical analysis and syntax use a grammar notation that
+is a mixture of
+`EBNF <https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form>`_
+and `PEG <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`_.
+For example:
+
+.. grammar-snippet::
+ :group: notation
+
+ name: `letter` (`letter` | `digit` | "_")*
+ letter: "a"..."z" | "A"..."Z"
+ digit: "0"..."9"
+
+In this example, the first line says that a ``name`` is a ``letter`` followed
+by a sequence of zero or more ``letter``\ s, ``digit``\ s, and underscores.
+A ``letter`` in turn is any of the single characters ``'a'`` through
+``'z'`` and ``A`` through ``Z``; a ``digit`` is a single character from ``0``
+to ``9``.
+
+Each rule begins with a name (which identifies the rule that's being defined)
+followed by a colon, ``:``.
+The definition to the right of the colon uses the following syntax elements:
+
+* ``name``: A name refers to another rule.
+ Where possible, it is a link to the rule's definition.
+
+ * ``TOKEN``: An uppercase name refers to a :term:`token`.
+ For the purposes of grammar definitions, tokens are the same as rules.
+
+* ``"text"``, ``'text'``: Text in single or double quotes must match literally
+ (without the quotes). The type of quote is chosen according to the meaning
+ of ``text``:
+
+ * ``'if'``: A name in single quotes denotes a :ref:`keyword <keywords>`.
+ * ``"case"``: A name in double quotes denotes a
+ :ref:`soft-keyword <soft-keywords>`.
+ * ``'@'``: A non-letter symbol in single quotes denotes an
+ :py:data:`~token.OP` token, that is, a :ref:`delimiter <delimiters>` or
+ :ref:`operator <operators>`.
+
+* ``e1 e2``: Items separated only by whitespace denote a sequence.
+ Here, ``e1`` must be followed by ``e2``.
+* ``e1 | e2``: A vertical bar is used to separate alternatives.
+ It denotes PEG's "ordered choice": if ``e1`` matches, ``e2`` is
+ not considered.
+ In traditional PEG grammars, this is written as a slash, ``/``, rather than
+ a vertical bar.
+ See :pep:`617` for more background and details.
+* ``e*``: A star means zero or more repetitions of the preceding item.
+* ``e+``: Likewise, a plus means one or more repetitions.
+* ``[e]``: A phrase enclosed in square brackets means zero or
+ one occurrences. In other words, the enclosed phrase is optional.
+* ``e?``: A question mark has exactly the same meaning as square brackets:
+ the preceding item is optional.
+* ``(e)``: Parentheses are used for grouping.
+* ``"a"..."z"``: Two literal characters separated by three dots mean a choice
+ of any single character in the given (inclusive) range of ASCII characters.
+ This notation is only used in
+ :ref:`lexical definitions <notation-lexical-vs-syntactic>`.
+* ``<...>``: A phrase between angular brackets gives an informal description
+ of the matched symbol (for example, ``<any ASCII character except "\">``),
+ or an abbreviation that is defined in nearby text (for example, ``<Lu>``).
+ This notation is only used in
+ :ref:`lexical definitions <notation-lexical-vs-syntactic>`.
+
+The unary operators (``*``, ``+``, ``?``) bind as tightly as possible;
+the vertical bar (``|``) binds most loosely.
+
+White space is only meaningful to separate tokens.
+
+Rules are normally contained on a single line, but rules that are too long
+may be wrapped:
+
+.. grammar-snippet::
+ :group: notation
+
+ literal: stringliteral | bytesliteral
+ | integer | floatnumber | imagnumber
+
+Alternatively, rules may be formatted with the first line ending at the colon,
+and each alternative beginning with a vertical bar on a new line.
+For example:
+
+
+.. grammar-snippet::
+ :group: notation-alt
+
+ literal:
+ | stringliteral
+ | bytesliteral
+ | integer
+ | floatnumber
+ | imagnumber
+
+This does *not* mean that there is an empty first alternative.
+
+.. index:: lexical definitions
+
+.. _notation-lexical-vs-syntactic:
+
+Lexical and Syntactic definitions
+---------------------------------
+
+There is some difference between *lexical* and *syntactic* analysis:
+the :term:`lexical analyzer` operates on the individual characters of the
+input source, while the *parser* (syntactic analyzer) operates on the stream
+of :term:`tokens <token>` generated by the lexical analysis.
+However, in some cases the exact boundary between the two phases is a
+CPython implementation detail.
+
+The practical difference between the two is that in *lexical* definitions,
+all whitespace is significant.
+The lexical analyzer :ref:`discards <whitespace>` all whitespace that is not
+converted to tokens like :data:`token.INDENT` or :data:`~token.NEWLINE`.
+*Syntactic* definitions then use these tokens, rather than source characters.
+
+This documentation uses the same BNF grammar for both styles of definitions.
+All uses of BNF in the next chapter (:ref:`lexical`) are lexical definitions;
+uses in subsequent chapters are syntactic definitions.
diff --git a/Doc/reference/lexical_analysis.rst b/Doc/reference/lexical_analysis.rst
index ff801a7d4fc..567c70111c2 100644
--- a/Doc/reference/lexical_analysis.rst
+++ b/Doc/reference/lexical_analysis.rst
@@ -35,11 +35,11 @@ Logical lines
.. index:: logical line, physical line, line joining, NEWLINE token
-The end of a logical line is represented by the token NEWLINE. Statements
-cannot cross logical line boundaries except where NEWLINE is allowed by the
-syntax (e.g., between statements in compound statements). A logical line is
-constructed from one or more *physical lines* by following the explicit or
-implicit *line joining* rules.
+The end of a logical line is represented by the token :data:`~token.NEWLINE`.
+Statements cannot cross logical line boundaries except where :data:`!NEWLINE`
+is allowed by the syntax (e.g., between statements in compound statements).
+A logical line is constructed from one or more *physical lines* by following
+the explicit or implicit *line joining* rules.
.. _physical-lines:
@@ -99,7 +99,7 @@ which is recognized by Bram Moolenaar's VIM.
If no encoding declaration is found, the default encoding is UTF-8. If the
implicit or explicit encoding of a file is UTF-8, an initial UTF-8 byte-order
-mark (b'\xef\xbb\xbf') is ignored rather than being a syntax error.
+mark (``b'\xef\xbb\xbf'``) is ignored rather than being a syntax error.
If an encoding is declared, the encoding name must be recognized by Python
(see :ref:`standard-encodings`). The
@@ -160,11 +160,12 @@ Blank lines
.. index:: single: blank line
A logical line that contains only spaces, tabs, formfeeds and possibly a
-comment, is ignored (i.e., no NEWLINE token is generated). During interactive
-input of statements, handling of a blank line may differ depending on the
-implementation of the read-eval-print loop. In the standard interactive
-interpreter, an entirely blank logical line (i.e. one containing not even
-whitespace or a comment) terminates a multi-line statement.
+comment, is ignored (i.e., no :data:`~token.NEWLINE` token is generated).
+During interactive input of statements, handling of a blank line may differ
+depending on the implementation of the read-eval-print loop.
+In the standard interactive interpreter, an entirely blank logical line (that
+is, one containing not even whitespace or a comment) terminates a multi-line
+statement.
.. _indentation:
@@ -202,19 +203,20 @@ the space count to zero).
.. index:: INDENT token, DEDENT token
-The indentation levels of consecutive lines are used to generate INDENT and
-DEDENT tokens, using a stack, as follows.
+The indentation levels of consecutive lines are used to generate
+:data:`~token.INDENT` and :data:`~token.DEDENT` tokens, using a stack,
+as follows.
Before the first line of the file is read, a single zero is pushed on the stack;
this will never be popped off again. The numbers pushed on the stack will
always be strictly increasing from bottom to top. At the beginning of each
logical line, the line's indentation level is compared to the top of the stack.
If it is equal, nothing happens. If it is larger, it is pushed on the stack, and
-one INDENT token is generated. If it is smaller, it *must* be one of the
+one :data:`!INDENT` token is generated. If it is smaller, it *must* be one of the
numbers occurring on the stack; all numbers on the stack that are larger are
-popped off, and for each number popped off a DEDENT token is generated. At the
-end of the file, a DEDENT token is generated for each number remaining on the
-stack that is larger than zero.
+popped off, and for each number popped off a :data:`!DEDENT` token is generated.
+At the end of the file, a :data:`!DEDENT` token is generated for each number
+remaining on the stack that is larger than zero.
Here is an example of a correctly (though confusingly) indented piece of Python
code::
@@ -254,8 +256,18 @@ Whitespace between tokens
Except at the beginning of a logical line or in string literals, the whitespace
characters space, tab and formfeed can be used interchangeably to separate
tokens. Whitespace is needed between two tokens only if their concatenation
-could otherwise be interpreted as a different token (e.g., ab is one token, but
-a b is two tokens).
+could otherwise be interpreted as a different token. For example, ``ab`` is one
+token, but ``a b`` is two tokens. However, ``+a`` and ``+ a`` both produce
+two tokens, ``+`` and ``a``, as ``+a`` is not a valid token.
+
+
+.. _endmarker-token:
+
+End marker
+----------
+
+At the end of non-interactive input, the lexical analyzer generates an
+:data:`~token.ENDMARKER` token.
.. _other-tokens:
@@ -263,67 +275,94 @@ a b is two tokens).
Other tokens
============
-Besides NEWLINE, INDENT and DEDENT, the following categories of tokens exist:
-*identifiers*, *keywords*, *literals*, *operators*, and *delimiters*. Whitespace
-characters (other than line terminators, discussed earlier) are not tokens, but
-serve to delimit tokens. Where ambiguity exists, a token comprises the longest
-possible string that forms a legal token, when read from left to right.
+Besides :data:`~token.NEWLINE`, :data:`~token.INDENT` and :data:`~token.DEDENT`,
+the following categories of tokens exist:
+*identifiers* and *keywords* (:data:`~token.NAME`), *literals* (such as
+:data:`~token.NUMBER` and :data:`~token.STRING`), and other symbols
+(*operators* and *delimiters*, :data:`~token.OP`).
+Whitespace characters (other than logical line terminators, discussed earlier)
+are not tokens, but serve to delimit tokens.
+Where ambiguity exists, a token comprises the longest possible string that
+forms a legal token, when read from left to right.
.. _identifiers:
-Identifiers and keywords
-========================
+Names (identifiers and keywords)
+================================
.. index:: identifier, name
-Identifiers (also referred to as *names*) are described by the following lexical
-definitions.
+:data:`~token.NAME` tokens represent *identifiers*, *keywords*, and
+*soft keywords*.
-The syntax of identifiers in Python is based on the Unicode standard annex
-UAX-31, with elaboration and changes as defined below; see also :pep:`3131` for
-further details.
-
-Within the ASCII range (U+0001..U+007F), the valid characters for identifiers
-include the uppercase and lowercase letters ``A`` through
-``Z``, the underscore ``_`` and, except for the first character, the digits
+Within the ASCII range (U+0001..U+007F), the valid characters for names
+include the uppercase and lowercase letters (``A-Z`` and ``a-z``),
+the underscore ``_`` and, except for the first character, the digits
``0`` through ``9``.
-Python 3.0 introduced additional characters from outside the ASCII range (see
-:pep:`3131`). For these characters, the classification uses the version of the
-Unicode Character Database as included in the :mod:`unicodedata` module.
-Identifiers are unlimited in length. Case is significant.
+Names must contain at least one character, but have no upper length limit.
+Case is significant.
-.. productionlist:: python-grammar
- identifier: `xid_start` `xid_continue`*
- id_start: <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
- id_continue: <all characters in `id_start`, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
- xid_start: <all characters in `id_start` whose NFKC normalization is in "id_start xid_continue*">
- xid_continue: <all characters in `id_continue` whose NFKC normalization is in "id_continue*">
-
-The Unicode category codes mentioned above stand for:
-
-* *Lu* - uppercase letters
-* *Ll* - lowercase letters
-* *Lt* - titlecase letters
-* *Lm* - modifier letters
-* *Lo* - other letters
-* *Nl* - letter numbers
-* *Mn* - nonspacing marks
-* *Mc* - spacing combining marks
-* *Nd* - decimal numbers
-* *Pc* - connector punctuations
-* *Other_ID_Start* - explicit list of characters in `PropList.txt
- <https://www.unicode.org/Public/16.0.0/ucd/PropList.txt>`_ to support backwards
- compatibility
-* *Other_ID_Continue* - likewise
-
-All identifiers are converted into the normal form NFKC while parsing; comparison
-of identifiers is based on NFKC.
-
-A non-normative HTML file listing all valid identifier characters for Unicode
-16.0.0 can be found at
-https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt
+Besides ``A-Z``, ``a-z``, ``_`` and ``0-9``, names can also use "letter-like"
+and "number-like" characters from outside the ASCII range, as detailed below.
+
+All identifiers are converted into the `normalization form`_ NFKC while
+parsing; comparison of identifiers is based on NFKC.
+
+Formally, the first character of a normalized identifier must belong to the
+set ``id_start``, which is the union of:
+
+* Unicode category ``<Lu>`` - uppercase letters (includes ``A`` to ``Z``)
+* Unicode category ``<Ll>`` - lowercase letters (includes ``a`` to ``z``)
+* Unicode category ``<Lt>`` - titlecase letters
+* Unicode category ``<Lm>`` - modifier letters
+* Unicode category ``<Lo>`` - other letters
+* Unicode category ``<Nl>`` - letter numbers
+* {``"_"``} - the underscore
+* ``<Other_ID_Start>`` - an explicit set of characters in `PropList.txt`_
+ to support backwards compatibility
+
+The remaining characters must belong to the set ``id_continue``, which is the
+union of:
+
+* all characters in ``id_start``
+* Unicode category ``<Nd>`` - decimal numbers (includes ``0`` to ``9``)
+* Unicode category ``<Pc>`` - connector punctuations
+* Unicode category ``<Mn>`` - nonspacing marks
+* Unicode category ``<Mc>`` - spacing combining marks
+* ``<Other_ID_Continue>`` - another explicit set of characters in
+ `PropList.txt`_ to support backwards compatibility
+
+Unicode categories use the version of the Unicode Character Database as
+included in the :mod:`unicodedata` module.
+
+These sets are based on the Unicode standard annex `UAX-31`_.
+See also :pep:`3131` for further details.
+
+Even more formally, names are described by the following lexical definitions:
+
+.. grammar-snippet::
+ :group: python-grammar
+
+ NAME: `xid_start` `xid_continue`*
+ id_start: <Lu> | <Ll> | <Lt> | <Lm> | <Lo> | <Nl> | "_" | <Other_ID_Start>
+ id_continue: `id_start` | <Nd> | <Pc> | <Mn> | <Mc> | <Other_ID_Continue>
+ xid_start: <all characters in `id_start` whose NFKC normalization is
+ in (`id_start` `xid_continue`*)">
+ xid_continue: <all characters in `id_continue` whose NFKC normalization is
+ in (`id_continue`*)">
+ identifier: <`NAME`, except keywords>
+
+A non-normative listing of all valid identifier characters as defined by
+Unicode is available in the `DerivedCoreProperties.txt`_ file in the Unicode
+Character Database.
+
+
+.. _UAX-31: https://www.unicode.org/reports/tr31/
+.. _PropList.txt: https://www.unicode.org/Public/16.0.0/ucd/PropList.txt
+.. _DerivedCoreProperties.txt: https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt
+.. _normalization form: https://www.unicode.org/reports/tr15/#Norm_Forms
.. _keywords:
@@ -335,7 +374,7 @@ Keywords
single: keyword
single: reserved word
-The following identifiers are used as reserved words, or *keywords* of the
+The following names are used as reserved words, or *keywords* of the
language, and cannot be used as ordinary identifiers. They must be spelled
exactly as written here:
@@ -359,18 +398,19 @@ Soft Keywords
.. versionadded:: 3.10
-Some identifiers are only reserved under specific contexts. These are known as
-*soft keywords*. The identifiers ``match``, ``case``, ``type`` and ``_`` can
-syntactically act as keywords in certain contexts,
+Some names are only reserved under specific contexts. These are known as
+*soft keywords*:
+
+- ``match``, ``case``, and ``_``, when used in the :keyword:`match` statement.
+- ``type``, when used in the :keyword:`type` statement.
+
+These syntactically act as keywords in their specific contexts,
but this distinction is done at the parser level, not when tokenizing.
As soft keywords, their use in the grammar is possible while still
preserving compatibility with existing code that uses these names as
identifier names.
-``match``, ``case``, and ``_`` are used in the :keyword:`match` statement.
-``type`` is used in the :keyword:`type` statement.
-
.. versionchanged:: 3.12
``type`` is now a soft keyword.
@@ -449,8 +489,9 @@ String literals are described by the following lexical definitions:
.. productionlist:: python-grammar
stringliteral: [`stringprefix`](`shortstring` | `longstring`)
- stringprefix: "r" | "u" | "R" | "U" | "f" | "F"
+ stringprefix: "r" | "u" | "R" | "U" | "f" | "F" | "t" | "T"
: | "fr" | "Fr" | "fR" | "FR" | "rf" | "rF" | "Rf" | "RF"
+ : | "tr" | "Tr" | "tR" | "TR" | "rt" | "rT" | "Rt" | "RT"
shortstring: "'" `shortstringitem`* "'" | '"' `shortstringitem`* '"'
longstring: "'''" `longstringitem`* "'''" | '"""' `longstringitem`* '"""'
shortstringitem: `shortstringchar` | `stringescapeseq`
@@ -881,11 +922,20 @@ Numeric literals
floating-point literal, hexadecimal literal
octal literal, binary literal, decimal literal, imaginary literal, complex literal
-There are three types of numeric literals: integers, floating-point numbers, and
-imaginary numbers. There are no complex literals (complex numbers can be formed
-by adding a real number and an imaginary number).
+:data:`~token.NUMBER` tokens represent numeric literals, of which there are
+three types: integers, floating-point numbers, and imaginary numbers.
+
+.. grammar-snippet::
+ :group: python-grammar
+
+ NUMBER: `integer` | `floatnumber` | `imagnumber`
-Note that numeric literals do not include a sign; a phrase like ``-1`` is
+The numeric value of a numeric literal is the same as if it were passed as a
+string to the :class:`int`, :class:`float` or :class:`complex` class
+constructor, respectively.
+Note that not all valid inputs for those constructors are also valid literals.
+
+Numeric literals do not include a sign; a phrase like ``-1`` is
actually an expression composed of the unary operator '``-``' and the literal
``1``.
@@ -899,38 +949,67 @@ actually an expression composed of the unary operator '``-``' and the literal
.. _integers:
Integer literals
-----------------
+^^^^^^^^^^^^^^^^
-Integer literals are described by the following lexical definitions:
+Integer literals denote whole numbers. For example::
-.. productionlist:: python-grammar
- integer: `decinteger` | `bininteger` | `octinteger` | `hexinteger`
- decinteger: `nonzerodigit` (["_"] `digit`)* | "0"+ (["_"] "0")*
- bininteger: "0" ("b" | "B") (["_"] `bindigit`)+
- octinteger: "0" ("o" | "O") (["_"] `octdigit`)+
- hexinteger: "0" ("x" | "X") (["_"] `hexdigit`)+
- nonzerodigit: "1"..."9"
- digit: "0"..."9"
- bindigit: "0" | "1"
- octdigit: "0"..."7"
- hexdigit: `digit` | "a"..."f" | "A"..."F"
+ 7
+ 3
+ 2147483647
There is no limit for the length of integer literals apart from what can be
-stored in available memory.
+stored in available memory::
+
+ 7922816251426433759354395033679228162514264337593543950336
+
+Underscores can be used to group digits for enhanced readability,
+and are ignored for determining the numeric value of the literal.
+For example, the following literals are equivalent::
+
+ 100_000_000_000
+ 100000000000
+ 1_00_00_00_00_000
-Underscores are ignored for determining the numeric value of the literal. They
-can be used to group digits for enhanced readability. One underscore can occur
-between digits, and after base specifiers like ``0x``.
+Underscores can only occur between digits.
+For example, ``_123``, ``321_``, and ``123__321`` are *not* valid literals.
-Note that leading zeros in a non-zero decimal number are not allowed. This is
-for disambiguation with C-style octal literals, which Python used before version
-3.0.
+Integers can be specified in binary (base 2), octal (base 8), or hexadecimal
+(base 16) using the prefixes ``0b``, ``0o`` and ``0x``, respectively.
+Hexadecimal digits 10 through 15 are represented by letters ``A``-``F``,
+case-insensitive. For example::
-Some examples of integer literals::
+ 0b100110111
+ 0b_1110_0101
+ 0o177
+ 0o377
+ 0xdeadbeef
+ 0xDead_Beef
- 7 2147483647 0o177 0b100110111
- 3 79228162514264337593543950336 0o377 0xdeadbeef
- 100_000_000_000 0b_1110_0101
+An underscore can follow the base specifier.
+For example, ``0x_1f`` is a valid literal, but ``0_x1f`` and ``0x__1f`` are
+not.
+
+Leading zeros in a non-zero decimal number are not allowed.
+For example, ``0123`` is not a valid literal.
+This is for disambiguation with C-style octal literals, which Python used
+before version 3.0.
+
+Formally, integer literals are described by the following lexical definitions:
+
+.. grammar-snippet::
+ :group: python-grammar
+
+ integer: `decinteger` | `bininteger` | `octinteger` | `hexinteger` | `zerointeger`
+ decinteger: `nonzerodigit` (["_"] `digit`)*
+ bininteger: "0" ("b" | "B") (["_"] `bindigit`)+
+ octinteger: "0" ("o" | "O") (["_"] `octdigit`)+
+ hexinteger: "0" ("x" | "X") (["_"] `hexdigit`)+
+ zerointeger: "0"+ (["_"] "0")*
+ nonzerodigit: "1"..."9"
+ digit: "0"..."9"
+ bindigit: "0" | "1"
+ octdigit: "0"..."7"
+ hexdigit: `digit` | "a"..."f" | "A"..."F"
.. versionchanged:: 3.6
Underscores are now allowed for grouping purposes in literals.
@@ -943,26 +1022,58 @@ Some examples of integer literals::
.. _floating:
Floating-point literals
------------------------
+^^^^^^^^^^^^^^^^^^^^^^^
-Floating-point literals are described by the following lexical definitions:
+Floating-point (float) literals, such as ``3.14`` or ``1.5``, denote
+:ref:`approximations of real numbers <datamodel-float>`.
-.. productionlist:: python-grammar
- floatnumber: `pointfloat` | `exponentfloat`
- pointfloat: [`digitpart`] `fraction` | `digitpart` "."
- exponentfloat: (`digitpart` | `pointfloat`) `exponent`
- digitpart: `digit` (["_"] `digit`)*
- fraction: "." `digitpart`
- exponent: ("e" | "E") ["+" | "-"] `digitpart`
+They consist of *integer* and *fraction* parts, each composed of decimal digits.
+The parts are separated by a decimal point, ``.``::
+
+ 2.71828
+ 4.0
+
+Unlike in integer literals, leading zeros are allowed in the numeric parts.
+For example, ``077.010`` is legal, and denotes the same number as ``77.10``.
+
+As in integer literals, single underscores may occur between digits to help
+readability::
+
+ 96_485.332_123
+ 3.14_15_93
+
+Either of these parts, but not both, can be empty. For example::
+
+ 10. # (equivalent to 10.0)
+ .001 # (equivalent to 0.001)
+
+Optionally, the integer and fraction may be followed by an *exponent*:
+the letter ``e`` or ``E``, followed by an optional sign, ``+`` or ``-``,
+and a number in the same format as the integer and fraction parts.
+The ``e`` or ``E`` represents "times ten raised to the power of"::
+
+ 1.0e3 # (represents 1.0×10³, or 1000.0)
+ 1.166e-5 # (represents 1.166×10⁻⁵, or 0.00001166)
+ 6.02214076e+23 # (represents 6.02214076×10²³, or 602214076000000000000000.)
+
+In floats with only integer and exponent parts, the decimal point may be
+omitted::
-Note that the integer and exponent parts are always interpreted using radix 10.
-For example, ``077e010`` is legal, and denotes the same number as ``77e10``. The
-allowed range of floating-point literals is implementation-dependent. As in
-integer literals, underscores are supported for digit grouping.
+ 1e3 # (equivalent to 1.e3 and 1.0e3)
+ 0e0 # (equivalent to 0.)
-Some examples of floating-point literals::
+Formally, floating-point literals are described by the following
+lexical definitions:
- 3.14 10. .001 1e100 3.14e-10 0e0 3.14_15_93
+.. grammar-snippet::
+ :group: python-grammar
+
+ floatnumber:
+ | `digitpart` "." [`digitpart`] [`exponent`]
+ | "." `digitpart` [`exponent`]
+ | `digitpart` `exponent`
+ digitpart: `digit` (["_"] `digit`)*
+ exponent: ("e" | "E") ["+" | "-"] `digitpart`
.. versionchanged:: 3.6
Underscores are now allowed for grouping purposes in literals.
@@ -973,20 +1084,62 @@ Some examples of floating-point literals::
.. _imaginary:
Imaginary literals
-------------------
+^^^^^^^^^^^^^^^^^^
-Imaginary literals are described by the following lexical definitions:
+Python has :ref:`complex number <typesnumeric>` objects, but no complex
+literals.
+Instead, *imaginary literals* denote complex numbers with a zero
+real part.
-.. productionlist:: python-grammar
- imagnumber: (`floatnumber` | `digitpart`) ("j" | "J")
+For example, in math, the complex number 3+4.2\ *i* is written
+as the real number 3 added to the imaginary number 4.2\ *i*.
+Python uses a similar syntax, except the imaginary unit is written as ``j``
+rather than *i*::
+
+ 3+4.2j
+
+This is an expression composed
+of the :ref:`integer literal <integers>` ``3``,
+the :ref:`operator <operators>` '``+``',
+and the :ref:`imaginary literal <imaginary>` ``4.2j``.
+Since these are three separate tokens, whitespace is allowed between them::
+
+ 3 + 4.2j
+
+No whitespace is allowed *within* each token.
+In particular, the ``j`` suffix, may not be separated from the number
+before it.
-An imaginary literal yields a complex number with a real part of 0.0. Complex
-numbers are represented as a pair of floating-point numbers and have the same
-restrictions on their range. To create a complex number with a nonzero real
-part, add a floating-point number to it, e.g., ``(3+4j)``. Some examples of
-imaginary literals::
+The number before the ``j`` has the same syntax as a floating-point literal.
+Thus, the following are valid imaginary literals::
- 3.14j 10.j 10j .001j 1e100j 3.14e-10j 3.14_15_93j
+ 4.2j
+ 3.14j
+ 10.j
+ .001j
+ 1e100j
+ 3.14e-10j
+ 3.14_15_93j
+
+Unlike in a floating-point literal the decimal point can be omitted if the
+imaginary number only has an integer part.
+The number is still evaluated as a floating-point number, not an integer::
+
+ 10j
+ 0j
+ 1000000000000000000000000j # equivalent to 1e+24j
+
+The ``j`` suffix is case-insensitive.
+That means you can use ``J`` instead::
+
+ 3.14J # equivalent to 3.14j
+
+Formally, imaginary literals are described by the following lexical definition:
+
+.. grammar-snippet::
+ :group: python-grammar
+
+ imagnumber: (`floatnumber` | `digitpart`) ("j" | "J")
.. _operators:
diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore
index 926d2cd42bd..e3bcb968128 100644
--- a/Doc/tools/.nitignore
+++ b/Doc/tools/.nitignore
@@ -14,7 +14,6 @@ Doc/c-api/typeobj.rst
Doc/extending/extending.rst
Doc/library/ast.rst
Doc/library/asyncio-extending.rst
-Doc/library/decimal.rst
Doc/library/email.charset.rst
Doc/library/email.compat32-message.rst
Doc/library/email.parser.rst
diff --git a/Doc/tools/extensions/audit_events.py b/Doc/tools/extensions/audit_events.py
index 23d82c0f441..385a58b2145 100644
--- a/Doc/tools/extensions/audit_events.py
+++ b/Doc/tools/extensions/audit_events.py
@@ -13,7 +13,7 @@ from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
if TYPE_CHECKING:
- from collections.abc import Iterator
+ from collections.abc import Iterator, Set
from sphinx.application import Sphinx
from sphinx.builders import Builder
@@ -33,7 +33,7 @@ _SYNONYMS = [
class AuditEvents:
def __init__(self) -> None:
self.events: dict[str, list[str]] = {}
- self.sources: dict[str, list[tuple[str, str]]] = {}
+ self.sources: dict[str, set[tuple[str, str]]] = {}
def __iter__(self) -> Iterator[tuple[str, list[str], tuple[str, str]]]:
for name, args in self.events.items():
@@ -47,7 +47,7 @@ class AuditEvents:
self._check_args_match(name, args)
else:
self.events[name] = args
- self.sources.setdefault(name, []).append(source)
+ self.sources.setdefault(name, set()).add(source)
def _check_args_match(self, name: str, args: list[str]) -> None:
current_args = self.events[name]
@@ -69,11 +69,11 @@ class AuditEvents:
return
def id_for(self, name) -> str:
- source_count = len(self.sources.get(name, ()))
+ source_count = len(self.sources.get(name, set()))
name_clean = re.sub(r"\W", "_", name)
return f"audit_event_{name_clean}_{source_count}"
- def rows(self) -> Iterator[tuple[str, list[str], list[tuple[str, str]]]]:
+ def rows(self) -> Iterator[tuple[str, list[str], Set[tuple[str, str]]]]:
for name in sorted(self.events.keys()):
yield name, self.events[name], self.sources[name]
@@ -218,7 +218,7 @@ class AuditEventListTransform(SphinxPostTransform):
docname: str,
name: str,
args: list[str],
- sources: list[tuple[str, str]],
+ sources: Set[tuple[str, str]],
) -> nodes.row:
row = nodes.row()
name_node = nodes.paragraph("", nodes.Text(name))
@@ -233,7 +233,7 @@ class AuditEventListTransform(SphinxPostTransform):
row += nodes.entry("", args_node)
backlinks_node = nodes.paragraph()
- backlinks = enumerate(sorted(set(sources)), start=1)
+ backlinks = enumerate(sorted(sources), start=1)
for i, (doc, label) in backlinks:
if isinstance(label, str):
ref = nodes.reference("", f"[{i}]", internal=True)
@@ -258,7 +258,7 @@ def setup(app: Sphinx):
app.connect("env-purge-doc", audit_events_purge)
app.connect("env-merge-info", audit_events_merge)
return {
- "version": "1.0",
+ "version": "2.0",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
diff --git a/Doc/tools/templates/customsourcelink.html b/Doc/tools/templates/customsourcelink.html
index eb9db9e341b..43d3a7a892a 100644
--- a/Doc/tools/templates/customsourcelink.html
+++ b/Doc/tools/templates/customsourcelink.html
@@ -1,11 +1,11 @@
{%- if show_source and has_source and sourcename %}
<div role="note" aria-label="source link">
- <h3>{{ _('This Page') }}</h3>
+ <h3>{{ _('This page') }}</h3>
<ul class="this-page-menu">
- <li><a href="{{ pathto('bugs') }}">{% trans %}Report a Bug{% endtrans %}</a></li>
+ <li><a href="{{ pathto('bugs') }}">{% trans %}Report a bug{% endtrans %}</a></li>
<li>
<a href="https://github.com/python/cpython/blob/main/Doc/{{ sourcename|replace('.rst.txt', '.rst') }}"
- rel="nofollow">{{ _('Show Source') }}
+ rel="nofollow">{{ _('Show source') }}
</a>
</li>
</ul>
diff --git a/Doc/tools/templates/download.html b/Doc/tools/templates/download.html
index 4645f7d394e..47a57eb111b 100644
--- a/Doc/tools/templates/download.html
+++ b/Doc/tools/templates/download.html
@@ -27,7 +27,7 @@
{%- endblock -%}
{% block body %}
-<h1>{% trans %}Download Python {{ dl_version }} Documentation{% endtrans %}</h1>
+<h1>{% trans %}Download Python {{ dl_version }} documentation{% endtrans %}</h1>
{% if last_updated %}<p><b>{% trans %}Last updated on: {{ last_updated }}.{% endtrans %}</b></p>{% endif %}
diff --git a/Doc/tools/templates/indexcontent.html b/Doc/tools/templates/indexcontent.html
index 06a4223643a..544cc4234f4 100644
--- a/Doc/tools/templates/indexcontent.html
+++ b/Doc/tools/templates/indexcontent.html
@@ -72,7 +72,7 @@
<table class="contentstable" align="center"><tr>
<td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("bugs") }}">{% trans %}Reporting issues{% endtrans %}</a></p>
- <p class="biglink"><a class="biglink" href="https://devguide.python.org/documentation/help-documenting/">{% trans %}Contributing to Docs{% endtrans %}</a></p>
+ <p class="biglink"><a class="biglink" href="https://devguide.python.org/documentation/help-documenting/">{% trans %}Contributing to docs{% endtrans %}</a></p>
<p class="biglink"><a class="biglink" href="{{ pathto("download") }}">{% trans %}Download the documentation{% endtrans %}</a></p>
</td><td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("license") }}">{% trans %}History and license of Python{% endtrans %}</a></p>
diff --git a/Doc/tools/templates/indexsidebar.html b/Doc/tools/templates/indexsidebar.html
index eea29e2449a..086f15662cf 100644
--- a/Doc/tools/templates/indexsidebar.html
+++ b/Doc/tools/templates/indexsidebar.html
@@ -9,9 +9,9 @@
<h3>{% trans %}Other resources{% endtrans %}</h3>
<ul>
{# XXX: many of these should probably be merged in the main docs #}
- <li><a href="https://peps.python.org/">{% trans %}PEP Index{% endtrans %}</a></li>
- <li><a href="https://wiki.python.org/moin/BeginnersGuide">{% trans %}Beginner's Guide{% endtrans %}</a></li>
- <li><a href="https://wiki.python.org/moin/PythonBooks">{% trans %}Book List{% endtrans %}</a></li>
- <li><a href="https://www.python.org/doc/av/">{% trans %}Audio/Visual Talks{% endtrans %}</a></li>
- <li><a href="https://devguide.python.org/">{% trans %}Python Developer’s Guide{% endtrans %}</a></li>
+ <li><a href="https://peps.python.org/">{% trans %}PEP index{% endtrans %}</a></li>
+ <li><a href="https://wiki.python.org/moin/BeginnersGuide">{% trans %}Beginner's guide{% endtrans %}</a></li>
+ <li><a href="https://wiki.python.org/moin/PythonBooks">{% trans %}Book list{% endtrans %}</a></li>
+ <li><a href="https://www.python.org/doc/av/">{% trans %}Audio/visual talks{% endtrans %}</a></li>
+ <li><a href="https://devguide.python.org/">{% trans %}Python developer’s guide{% endtrans %}</a></li>
</ul>
diff --git a/Doc/tools/templates/layout.html b/Doc/tools/templates/layout.html
index 56023ebf962..1cb0200822d 100644
--- a/Doc/tools/templates/layout.html
+++ b/Doc/tools/templates/layout.html
@@ -26,11 +26,11 @@
{% endblock %}
{% block extrahead %}
- {% if builder == "html" and enable_analytics %}
+ {% if builder == "html" %}
+ {% if enable_analytics %}
<script defer data-domain="docs.python.org" src="https://analytics.python.org/js/script.outbound-links.js"></script>
- {% endif %}
- <link rel="canonical" href="https://docs.python.org/3/{{pagename}}.html">
- {% if builder != "htmlhelp" %}
+ {% endif %}
+ <link rel="canonical" href="https://docs.python.org/3/{{pagename}}.html">
{% if pagename == 'whatsnew/changelog' and not embedded %}
<script type="text/javascript" src="{{ pathto('_static/changelog_search.js', 1) }}"></script>{% endif %}
{% endif %}
diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst
index 95939242fb7..5c0e8f34bf8 100644
--- a/Doc/tutorial/controlflow.rst
+++ b/Doc/tutorial/controlflow.rst
@@ -999,7 +999,8 @@ scope::
43
The above example uses a lambda expression to return a function. Another use
-is to pass a small function as an argument::
+is to pass a small function as an argument. For instance, :meth:`list.sort`
+takes a sorting key function *key* which can be a lambda function::
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
@@ -1055,7 +1056,7 @@ Here is an example of a multi-line docstring::
>>> print(my_function.__doc__)
Do nothing, but document it.
- No, really, it doesn't do anything.
+ No, really, it doesn't do anything.
.. _tut-annotations:
diff --git a/Doc/tutorial/index.rst b/Doc/tutorial/index.rst
index 96791f88c86..d0bf77dc40d 100644
--- a/Doc/tutorial/index.rst
+++ b/Doc/tutorial/index.rst
@@ -4,6 +4,10 @@
The Python Tutorial
######################
+.. Tip:: This tutorial is designed for
+ *programmers* that are new to the Python language,
+ **not** *beginners* who are new to programming.
+
Python is an easy to learn, powerful programming language. It has efficient
high-level data structures and a simple but effective approach to
object-oriented programming. Python's elegant syntax and dynamic typing,
@@ -21,7 +25,8 @@ implemented in C or C++ (or other languages callable from C). Python is also
suitable as an extension language for customizable applications.
This tutorial introduces the reader informally to the basic concepts and
-features of the Python language and system. It helps to have a Python
+features of the Python language and system. Be aware that it expects you to
+have a basic understanding of programming in general. It helps to have a Python
interpreter handy for hands-on experience, but all examples are self-contained,
so the tutorial can be read off-line as well.
diff --git a/Doc/tutorial/interpreter.rst b/Doc/tutorial/interpreter.rst
index 02e7de77322..cd526071424 100644
--- a/Doc/tutorial/interpreter.rst
+++ b/Doc/tutorial/interpreter.rst
@@ -16,7 +16,7 @@ Unix shell's search path makes it possible to start it by typing the command:
.. code-block:: text
- python3.14
+ python3.15
to the shell. [#]_ Since the choice of the directory where the interpreter lives
is an installation option, other places are possible; check with your local
@@ -97,8 +97,8 @@ before printing the first prompt:
.. code-block:: shell-session
- $ python3.14
- Python 3.14 (default, April 4 2024, 09:25:04)
+ $ python3.15
+ Python 3.15 (default, May 7 2025, 15:46:04)
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
diff --git a/Doc/tutorial/introduction.rst b/Doc/tutorial/introduction.rst
index bec5da8fd75..9e06e03991b 100644
--- a/Doc/tutorial/introduction.rst
+++ b/Doc/tutorial/introduction.rst
@@ -13,10 +13,9 @@ end a multi-line command.
.. only:: html
- You can toggle the display of prompts and output by clicking on ``>>>``
- in the upper-right corner of an example box. If you hide the prompts
- and output for an example, then you can easily copy and paste the input
- lines into your interpreter.
+ You can use the "Copy" button (it appears in the upper-right corner
+ when hovering over or tapping a code example), which strips prompts
+ and omits output, to copy and paste the input lines into your interpreter.
.. index:: single: # (hash); comment
@@ -147,6 +146,8 @@ Python can manipulate text (represented by type :class:`str`, so-called
"``Yay! :)``". They can be enclosed in single quotes (``'...'``) or double
quotes (``"..."``) with the same result [#]_.
+.. code-block:: pycon
+
>>> 'spam eggs' # single quotes
'spam eggs'
>>> "Paris rabbit got your back :)! Yay!" # double quotes
diff --git a/Doc/tutorial/modules.rst b/Doc/tutorial/modules.rst
index de7aa0e2342..47bf7547b4a 100644
--- a/Doc/tutorial/modules.rst
+++ b/Doc/tutorial/modules.rst
@@ -27,14 +27,16 @@ called :file:`fibo.py` in the current directory with the following contents::
# Fibonacci numbers module
- def fib(n): # write Fibonacci series up to n
+ def fib(n):
+ """Write Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
- def fib2(n): # return Fibonacci series up to n
+ def fib2(n):
+ """Return Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
diff --git a/Doc/tutorial/stdlib.rst b/Doc/tutorial/stdlib.rst
index 4b3eef313e7..d83ecca270b 100644
--- a/Doc/tutorial/stdlib.rst
+++ b/Doc/tutorial/stdlib.rst
@@ -15,7 +15,7 @@ operating system::
>>> import os
>>> os.getcwd() # Return the current working directory
- 'C:\\Python314'
+ 'C:\\Python315'
>>> os.chdir('/server/accesslogs') # Change current working directory
>>> os.system('mkdir today') # Run the command mkdir in the system shell
0
diff --git a/Doc/tutorial/stdlib2.rst b/Doc/tutorial/stdlib2.rst
index a2f96b34b2d..678b71c9274 100644
--- a/Doc/tutorial/stdlib2.rst
+++ b/Doc/tutorial/stdlib2.rst
@@ -279,7 +279,7 @@ applications include caching objects that are expensive to create::
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
d['primary'] # entry was automatically removed
- File "C:/python314/lib/weakref.py", line 46, in __getitem__
+ File "C:/python315/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
diff --git a/Doc/using/android.rst b/Doc/using/android.rst
index 65bf23dc994..cb762310328 100644
--- a/Doc/using/android.rst
+++ b/Doc/using/android.rst
@@ -63,3 +63,12 @@ link to the relevant file.
* Add code to your app to :source:`start Python in embedded mode
<Android/testbed/app/src/main/c/main_activity.c>`. This will need to be C code
called via JNI.
+
+Building a Python package for Android
+-------------------------------------
+
+Python packages can be built for Android as wheels and released on PyPI. The
+recommended tool for doing this is `cibuildwheel
+<https://cibuildwheel.pypa.io/en/stable/platforms/#android>`__, which automates
+all the details of setting up a cross-compilation environment, building the
+wheel, and testing it on an emulator.
diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst
index fa7c9cddf9c..cad49e2deeb 100644
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -73,7 +73,7 @@ source.
.. audit-event:: cpython.run_command command cmdoption-c
- .. versionchanged:: next
+ .. versionchanged:: 3.14
*command* is automatically dedented before execution.
.. option:: -m <module-name>
@@ -539,11 +539,21 @@ Miscellaneous options
* ``-X importtime`` to show how long each import takes. It shows module
name, cumulative time (including nested imports) and self time (excluding
nested imports). Note that its output may be broken in multi-threaded
- application. Typical usage is ``python3 -X importtime -c 'import
- asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`.
+ application. Typical usage is ``python -X importtime -c 'import asyncio'``.
+
+ ``-X importtime=2`` enables additional output that indicates when an
+ imported module has already been loaded. In such cases, the string
+ ``cached`` will be printed in both time columns.
+
+ See also :envvar:`PYTHONPROFILEIMPORTTIME`.
.. versionadded:: 3.7
+ .. versionchanged:: 3.14
+
+ Added ``-X importtime=2`` to also trace imports of loaded modules,
+ and reserved values other than ``1`` and ``2`` for future use.
+
* ``-X dev``: enable :ref:`Python Development Mode <devmode>`, introducing
additional runtime checks that are too expensive to be enabled by
default. See also :envvar:`PYTHONDEVMODE`.
@@ -643,7 +653,7 @@ Miscellaneous options
.. versionadded:: 3.13
* :samp:`-X thread_inherit_context={0,1}` causes :class:`~threading.Thread`
- to, by default, use a copy of context of of the caller of
+ to, by default, use a copy of context of the caller of
``Thread.start()`` when starting. Otherwise, threads will start
with an empty context. If unset, the value of this option defaults
to ``1`` on free-threaded builds and to ``0`` otherwise. See also
@@ -659,6 +669,13 @@ Miscellaneous options
.. versionadded:: 3.14
+ * :samp:`-X tlbc={0,1}` enables (1, the default) or disables (0) thread-local
+ bytecode in builds configured with :option:`--disable-gil`. When disabled,
+ this also disables the specializing interpreter. See also
+ :envvar:`PYTHON_TLBC`.
+
+ .. versionadded:: 3.14
+
It also allows passing arbitrary values and retrieving them through the
:data:`sys._xoptions` dictionary.
@@ -670,6 +687,13 @@ Miscellaneous options
.. versionchanged:: 3.10
Removed the ``-X oldparser`` option.
+.. versionremoved:: 3.14
+
+ :option:`!-J` is no longer reserved for use by Jython_,
+ and now has no special meaning.
+
+ .. _Jython: https://www.jython.org/
+
.. _using-on-controlling-color:
Controlling color
@@ -694,15 +718,6 @@ output. To control the color output only in the Python interpreter, the
precedence over ``NO_COLOR``, which in turn takes precedence over
``FORCE_COLOR``.
-Options you shouldn't use
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. option:: -J
-
- Reserved for use by Jython_.
-
-.. _Jython: https://www.jython.org/
-
.. _using-on-envvars:
@@ -984,12 +999,17 @@ conflict.
.. envvar:: PYTHONPROFILEIMPORTTIME
- If this environment variable is set to a non-empty string, Python will
- show how long each import takes.
+ If this environment variable is set to ``1``, Python will show
+ how long each import takes. If set to ``2``, Python will include output for
+ imported modules that have already been loaded.
This is equivalent to setting the :option:`-X` ``importtime`` option.
.. versionadded:: 3.7
+ .. versionchanged:: 3.14
+
+ Added ``PYTHONPROFILEIMPORTTIME=2`` to also trace imports of loaded modules.
+
.. envvar:: PYTHONASYNCIODEBUG
@@ -1264,7 +1284,7 @@ conflict.
.. envvar:: PYTHON_THREAD_INHERIT_CONTEXT
If this variable is set to ``1`` then :class:`~threading.Thread` will,
- by default, use a copy of context of of the caller of ``Thread.start()``
+ by default, use a copy of context of the caller of ``Thread.start()``
when starting. Otherwise, new threads will start with an empty context.
If unset, this variable defaults to ``1`` on free-threaded builds and to
``0`` otherwise. See also :option:`-X thread_inherit_context<-X>`.
@@ -1281,6 +1301,24 @@ conflict.
.. versionadded:: 3.14
+.. envvar:: PYTHON_JIT
+
+ On builds where experimental just-in-time compilation is available, this
+ variable can force the JIT to be disabled (``0``) or enabled (``1``) at
+ interpreter startup.
+
+ .. versionadded:: 3.13
+
+.. envvar:: PYTHON_TLBC
+
+ If set to ``1`` enables thread-local bytecode. If set to ``0`` thread-local
+ bytecode and the specializing interpreter are disabled. Only applies to
+ builds configured with :option:`--disable-gil`.
+
+ See also the :option:`-X tlbc <-X>` command-line option.
+
+ .. versionadded:: 3.14
+
Debug-mode variables
~~~~~~~~~~~~~~~~~~~~
diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
index 3d3776acbc7..a26d48d7d0a 100644
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -29,6 +29,9 @@ Features and minimum versions required to build CPython:
* Tcl/Tk 8.5.12 for the :mod:`tkinter` module.
+* `libmpdec <https://www.bytereef.org/mpdecimal/doc/libmpdec/>`_ 2.5.0
+ for the :mod:`decimal` module.
+
* Autoconf 2.72 and aclocal 1.16.5 are required to regenerate the
:file:`configure` script.
@@ -290,8 +293,8 @@ General Options
.. option:: --disable-gil
- Enables **experimental** support for running Python without the
- :term:`global interpreter lock` (GIL): free threading build.
+ Enables support for running Python without the :term:`global interpreter
+ lock` (GIL): free threading build.
Defines the ``Py_GIL_DISABLED`` macro and adds ``"t"`` to
:data:`sys.abiflags`.
@@ -302,14 +305,21 @@ General Options
.. option:: --enable-experimental-jit=[no|yes|yes-off|interpreter]
- Indicate how to integrate the :ref:`JIT compiler <whatsnew313-jit-compiler>`.
+ Indicate how to integrate the :ref:`experimental just-in-time compiler <whatsnew314-jit-compiler>`.
- * ``no`` - build the interpreter without the JIT.
- * ``yes`` - build the interpreter with the JIT.
- * ``yes-off`` - build the interpreter with the JIT but disable it by default.
- * ``interpreter`` - build the interpreter without the JIT, but with the tier 2 enabled interpreter.
+ * ``no``: Don't build the JIT.
+ * ``yes``: Enable the JIT. To disable it at runtime, set the environment
+ variable :envvar:`PYTHON_JIT=0 <PYTHON_JIT>`.
+ * ``yes-off``: Build the JIT, but disable it by default. To enable it at
+ runtime, set the environment variable :envvar:`PYTHON_JIT=1 <PYTHON_JIT>`.
+ * ``interpreter``: Enable the "JIT interpreter" (only useful for those
+ debugging the JIT itself). To disable it at runtime, set the environment
+ variable :envvar:`PYTHON_JIT=0 <PYTHON_JIT>`.
- By convention, ``--enable-experimental-jit`` is a shorthand for ``--enable-experimental-jit=yes``.
+ ``--enable-experimental-jit=no`` is the default behavior if the option is not
+ provided, and ``--enable-experimental-jit`` is shorthand for
+ ``--enable-experimental-jit=yes``. See :file:`Tools/jit/README.md` for more
+ information, including how to install the necessary build-time dependencies.
.. note::
@@ -438,6 +448,14 @@ Options for third-party dependencies
C compiler and linker flags for ``libuuid``, used by :mod:`uuid` module,
overriding ``pkg-config``.
+.. option:: LIBZSTD_CFLAGS
+.. option:: LIBZSTD_LIBS
+
+ C compiler and linker flags for ``libzstd``, used by :mod:`compression.zstd` module,
+ overriding ``pkg-config``.
+
+ .. versionadded:: 3.14
+
.. option:: PANEL_CFLAGS
.. option:: PANEL_LIBS
diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst
index 7d5c6331bef..0fb28f8c866 100644
--- a/Doc/using/ios.rst
+++ b/Doc/using/ios.rst
@@ -298,9 +298,9 @@ To add Python to an iOS Xcode project:
* Signal handlers (:c:member:`PyConfig.install_signal_handlers`) are *enabled*;
* System logging (:c:member:`PyConfig.use_system_logger`) is *enabled*
(optional, but strongly recommended; this is enabled by default);
- * ``PYTHONHOME`` for the interpreter is configured to point at the
+ * :envvar:`PYTHONHOME` for the interpreter is configured to point at the
``python`` subfolder of your app's bundle; and
- * The ``PYTHONPATH`` for the interpreter includes:
+ * The :envvar:`PYTHONPATH` for the interpreter includes:
- the ``python/lib/python3.X`` subfolder of your app's bundle,
- the ``python/lib/python3.X/lib-dynload`` subfolder of your app's bundle, and
@@ -324,7 +324,12 @@ modules in your app, some additional steps will be required:
the ``lib-dynload`` folder can be copied and adapted for this purpose.
* If you're using a separate folder for third-party packages, ensure that folder
- is included as part of the ``PYTHONPATH`` configuration in step 10.
+ is included as part of the :envvar:`PYTHONPATH` configuration in step 10.
+
+* If any of the folders that contain third-party packages will contain ``.pth``
+ files, you should add that folder as a *site directory* (using
+ :meth:`site.addsitedir`), rather than adding to :envvar:`PYTHONPATH` or
+ :attr:`sys.path` directly.
Testing a Python package
------------------------
diff --git a/Doc/using/mac.rst b/Doc/using/mac.rst
index 4b6c884f3d4..f88f3c2e078 100644
--- a/Doc/using/mac.rst
+++ b/Doc/using/mac.rst
@@ -20,13 +20,6 @@ the Pythons provided by the CPython release team for download from
the `python.org website <https://www.python.org/downloads/>`_. See
:ref:`alternative_bundles` for some other options.
-.. |usemac_x_dot_y| replace:: 3.13
-.. |usemac_python_x_dot_y_literal| replace:: ``python3.13``
-.. |usemac_python_x_dot_y_t_literal| replace:: ``python3.13t``
-.. |usemac_python_x_dot_y_t_literal_config| replace:: ``python3.13t-config``
-.. |usemac_applications_folder_name| replace:: ``Python 3.13``
-.. |usemac_applications_folder_version| replace:: ``/Applications/Python 3.13/``
-
.. _getting-osx:
.. _getting-and-installing-macpython:
@@ -64,7 +57,7 @@ Clicking on the **Continue** button brings up the **Read Me** for this installer
Besides other important information, the **Read Me** documents which Python version is
going to be installed and on what versions of macOS it is supported. You may need
to scroll through to read the whole file. By default, this **Read Me** will also be
-installed in |usemac_applications_folder_version| and available to read anytime.
+installed in |applications_python_version_literal| and available to read anytime.
.. image:: mac_installer_02_readme.png
@@ -83,7 +76,7 @@ display. For most uses, the standard set of installation operations is appropria
By pressing the **Customize** button, you can choose to omit or select certain package
components of the installer. Click on each package name to see a description of
what it installs.
-To also install support for the optional experimental free-threaded feature,
+To also install support for the optional free-threaded feature,
see :ref:`install-freethreaded-macos`.
.. image:: mac_installer_05_custom_install.png
@@ -97,7 +90,7 @@ When the installation is complete, the **Summary** window will appear.
.. image:: mac_installer_06_summary.png
Double-click on the :command:`Install Certificates.command`
-icon or file in the |usemac_applications_folder_version| window to complete the
+icon or file in the |applications_python_version_literal| window to complete the
installation.
.. image:: mac_installer_07_applications.png
@@ -114,7 +107,7 @@ Close this terminal window and the installer window.
A default install will include:
-* A |usemac_applications_folder_name| folder in your :file:`Applications` folder. In here
+* A |python_version_literal| folder in your :file:`Applications` folder. In here
you find :program:`IDLE`, the development environment that is a standard part of official
Python distributions; and :program:`Python Launcher`, which handles double-clicking Python
scripts from the macOS `Finder <https://support.apple.com/en-us/HT201732>`_.
@@ -141,7 +134,7 @@ How to run a Python script
There are two ways to invoke the Python interpreter.
If you are familiar with using a Unix shell in a terminal
-window, you can invoke |usemac_python_x_dot_y_literal| or ``python3`` optionally
+window, you can invoke |python_x_dot_y_literal| or ``python3`` optionally
followed by one or more command line options (described in :ref:`using-on-general`).
The Python tutorial also has a useful section on
:ref:`using Python interactively from a shell <tut-interac>`.
@@ -160,7 +153,7 @@ for more information.
To run a Python script file from the terminal window, you can
invoke the interpreter with the name of the script file:
- |usemac_python_x_dot_y_literal| ``myscript.py``
+ |python_x_dot_y_literal| ``myscript.py``
To run your script from the Finder, you can either:
@@ -259,20 +252,20 @@ Advanced Topics
Installing Free-threaded Binaries
---------------------------------
-.. versionadded:: 3.13 (Experimental)
-
-.. note::
-
- Everything described in this section is considered experimental,
- and should be expected to change in future releases.
+.. versionadded:: 3.13
The ``python.org`` :ref:`Python for macOS <getting-and-installing-macpython>`
installer package can optionally install an additional build of
-Python |usemac_x_dot_y| that supports :pep:`703`, the experimental free-threading feature
+Python |version| that supports :pep:`703`, the free-threading feature
(running with the :term:`global interpreter lock` disabled).
Check the release page on ``python.org`` for possible updated information.
-Because this feature is still considered experimental, the support for it
+The free-threaded mode is working and continues to be improved, but
+there is some additional overhead in single-threaded workloads compared
+to the regular build. Additionally, third-party packages, in particular ones
+with an :term:`extension module`, may not be ready for use in a
+free-threaded build, and will re-enable the :term:`GIL`.
+Therefore, the support for free-threading
is not installed by default. It is packaged as a separate install option,
available by clicking the **Customize** button on the **Installation Type**
step of the installer as described above.
@@ -282,46 +275,54 @@ step of the installer as described above.
If the box next to the **Free-threaded Python** package name is checked,
a separate :file:`PythonT.framework` will also be installed
alongside the normal :file:`Python.framework` in :file:`/Library/Frameworks`.
-This configuration allows a free-threaded Python |usemac_x_dot_y| build to co-exist
-on your system with a traditional (GIL only) Python |usemac_x_dot_y| build with
-minimal risk while installing or testing. This installation layout is itself
-experimental and is subject to change in future releases.
+This configuration allows a free-threaded Python |version| build to co-exist
+on your system with a traditional (GIL only) Python |version| build with
+minimal risk while installing or testing. This installation layout may
+change in future releases.
Known cautions and limitations:
- The **UNIX command-line tools** package, which is selected by default,
- will install links in :file:`/usr/local/bin` for |usemac_python_x_dot_y_t_literal|,
- the free-threaded interpreter, and |usemac_python_x_dot_y_t_literal_config|,
+ will install links in :file:`/usr/local/bin` for |python_x_dot_y_t_literal|,
+ the free-threaded interpreter, and |python_x_dot_y_t_literal_config|,
a configuration utility which may be useful for package builders.
Since :file:`/usr/local/bin` is typically included in your shell ``PATH``,
in most cases no changes to your ``PATH`` environment variables should
- be needed to use |usemac_python_x_dot_y_t_literal|.
+ be needed to use |python_x_dot_y_t_literal|.
- For this release, the **Shell profile updater** package and the
- :file:`Update Shell Profile.command` in |usemac_applications_folder_version|
+ :file:`Update Shell Profile.command` in |applications_python_version_literal|
do not support the free-threaded package.
- The free-threaded build and the traditional build have separate search
paths and separate :file:`site-packages` directories so, by default,
if you need a package available in both builds, it may need to be installed in both.
The free-threaded package will install a separate instance of :program:`pip` for use
- with |usemac_python_x_dot_y_t_literal|.
+ with |python_x_dot_y_t_literal|.
- To install a package using :command:`pip` without a :command:`venv`:
- |usemac_python_x_dot_y_t_literal| ``-m pip install <package_name>``
+ .. parsed-literal::
+
+ python\ |version|\ t -m pip install <package_name>
- When working with multiple Python environments, it is usually safest and easiest
to :ref:`create and use virtual environments <tut-venv>`.
This can avoid possible command name conflicts and confusion about which Python is in use:
- |usemac_python_x_dot_y_t_literal| ``-m venv <venv_name>``
+ .. parsed-literal::
+
+ python\ |version|\ t -m venv <venv_name>
+
then :command:`activate`.
- To run a free-threaded version of IDLE:
- |usemac_python_x_dot_y_t_literal| ``-m idlelib``
+ .. parsed-literal::
+
+ python\ |version|\ t -m idlelib
+
- The interpreters in both builds respond to the same
:ref:`PYTHON environment variables <using-on-envvars>`
@@ -337,28 +338,28 @@ Known cautions and limitations:
thus it only needs to be run once.
- If you cannot depend on the link in ``/usr/local/bin`` pointing to the
- ``python.org`` free-threaded |usemac_python_x_dot_y_t_literal| (for example, if you want
+ ``python.org`` free-threaded |python_x_dot_y_t_literal| (for example, if you want
to install your own version there or some other distribution does),
you can explicitly set your shell ``PATH`` environment variable to
include the ``PythonT`` framework ``bin`` directory:
- .. code-block:: sh
+ .. parsed-literal::
- export PATH="/Library/Frameworks/PythonT.framework/Versions/3.13/bin":"$PATH"
+ export PATH="/Library/Frameworks/PythonT.framework/Versions/\ |version|\ /bin":"$PATH"
The traditional framework installation by default does something similar,
except for :file:`Python.framework`. Be aware that having both framework ``bin``
directories in ``PATH`` can lead to confusion if there are duplicate names
- like ``python3.13`` in both; which one is actually used depends on the order
+ like |python_x_dot_y_literal| in both; which one is actually used depends on the order
they appear in ``PATH``. The ``which python3.x`` or ``which python3.xt``
commands can show which path is being used. Using virtual environments
can help avoid such ambiguities. Another option might be to create
a shell :command:`alias` to the desired interpreter, like:
- .. code-block:: sh
+ .. parsed-literal::
- alias py3.13="/Library/Frameworks/Python.framework/Versions/3.13/bin/python3.13"
- alias py3.13t="/Library/Frameworks/PythonT.framework/Versions/3.13/bin/python3.13t"
+ alias py\ |version|\ ="/Library/Frameworks/Python.framework/Versions/\ |version|\ /bin/python\ |version|\ "
+ alias py\ |version|\ t="/Library/Frameworks/PythonT.framework/Versions/\ |version|\ /bin/python\ |version|\ t"
Installing using the command line
---------------------------------
@@ -369,22 +370,22 @@ the macOS command line :command:`installer` utility lets you select non-default
options, too. If you are not familiar with :command:`installer`, it can be
somewhat cryptic (see :command:`man installer` for more information).
As an example, the following shell snippet shows one way to do it,
-using the ``3.13.0b2`` release and selecting the free-threaded interpreter
+using the |x_dot_y_b2_literal| release and selecting the free-threaded interpreter
option:
-.. code-block:: sh
+.. parsed-literal::
- RELEASE="python-3.13.0b2-macos11.pkg"
+ RELEASE="python-\ |version|\ 0b2-macos11.pkg"
# download installer pkg
- curl -O https://www.python.org/ftp/python/3.13.0/${RELEASE}
+ curl -O \https://www.python.org/ftp/python/\ |version|\ .0/${RELEASE}
# create installer choicechanges to customize the install:
- # enable the PythonTFramework-3.13 package
+ # enable the PythonTFramework-\ |version|\ package
# while accepting the other defaults (install all other packages)
cat > ./choicechanges.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "\http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
@@ -393,7 +394,7 @@ option:
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
- <string>org.python.Python.PythonTFramework-3.13</string>
+ <string>org.python.Python.PythonTFramework-\ |version|\ </string>
</dict>
</array>
</plist>
@@ -404,19 +405,19 @@ option:
You can then test that both installer builds are now available with something like:
-.. code-block:: console
+.. parsed-literal::
$ # test that the free-threaded interpreter was installed if the Unix Command Tools package was enabled
- $ /usr/local/bin/python3.13t -VV
- Python 3.13.0b2 experimental free-threading build (v3.13.0b2:3a83b172af, Jun 5 2024, 12:57:31) [Clang 15.0.0 (clang-1500.3.9.4)]
+ $ /usr/local/bin/python\ |version|\ t -VV
+ Python \ |version|\ .0b2 free-threading build (v\ |version|\ .0b2:3a83b172af, Jun 5 2024, 12:57:31) [Clang 15.0.0 (clang-1500.3.9.4)]
$ # and the traditional interpreter
- $ /usr/local/bin/python3.13 -VV
- Python 3.13.0b2 (v3.13.0b2:3a83b172af, Jun 5 2024, 12:50:24) [Clang 15.0.0 (clang-1500.3.9.4)]
+ $ /usr/local/bin/python\ |version|\ -VV
+ Python \ |version|\ .0b2 (v\ |version|\ .0b2:3a83b172af, Jun 5 2024, 12:50:24) [Clang 15.0.0 (clang-1500.3.9.4)]
$ # test that they are also available without the prefix if /usr/local/bin is on $PATH
- $ python3.13t -VV
- Python 3.13.0b2 experimental free-threading build (v3.13.0b2:3a83b172af, Jun 5 2024, 12:57:31) [Clang 15.0.0 (clang-1500.3.9.4)]
- $ python3.13 -VV
- Python 3.13.0b2 (v3.13.0b2:3a83b172af, Jun 5 2024, 12:50:24) [Clang 15.0.0 (clang-1500.3.9.4)]
+ $ python\ |version|\ t -VV
+ Python \ |version|\ .0b2 free-threading build (v\ |version|\ .0b2:3a83b172af, Jun 5 2024, 12:57:31) [Clang 15.0.0 (clang-1500.3.9.4)]
+ $ python\ |version|\ -VV
+ Python \ |version|\ .0b2 (v\ |version|\ .0b2:3a83b172af, Jun 5 2024, 12:50:24) [Clang 15.0.0 (clang-1500.3.9.4)]
.. note::
diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst
index 57c6062ee43..9628da3d2f6 100644
--- a/Doc/using/windows.rst
+++ b/Doc/using/windows.rst
@@ -77,31 +77,30 @@ To install the file downloaded from python.org, either double-click and select
"Install", or run ``Add-AppxPackage <path to MSIX>`` in Windows Powershell.
After installation, the ``python``, ``py``, and ``pymanager`` commands should be
-available. If they are not, click Start and search for "Manage app execution
-aliases". This settings page will let you enable the relevant commands. They
-will be labelled "Python (default)", "Python (default windowed)", and "Python
-install manager".
-
-If you have existing installations of Python, or you have modified your
-:envvar:`PATH` variable, you may need to remove them or undo the modifications
-in order for the commands to work. Old versions of Python can be reinstalled
-using the Python install manager.
+available. If you have existing installations of Python, or you have modified
+your :envvar:`PATH` variable, you may need to remove them or undo the
+modifications. See :ref:`pymanager-troubleshoot` for more help with fixing
+non-working commands.
When you first install a runtime, you will likely be prompted to add a directory
to your :envvar:`PATH`. This is optional, if you prefer to use the ``py``
command, but is offered for those who prefer the full range of aliases (such
as ``python3.14.exe``) to be available. The directory will be
-:file:`%LocalAppData%\Python\bin` by default, but may be customized by an
+:file:`%LocalAppData%\\Python\\bin` by default, but may be customized by an
administrator. Click Start and search for "Edit environment variables for your
account" for the system settings page to add the path.
+Each Python runtime you install will have its own directory for scripts. These
+also need to be added to :envvar:`PATH` if you want to use them.
+
The Python install manager will be automatically updated to new releases. This
does not affect any installs of Python runtimes. Uninstalling the Python install
manager does not uninstall any Python runtimes.
If you are not able to install an MSIX in your context, for example, you are
-using automated deployment software that does not support it, please see
-:ref:`pymanager-advancedinstall` below for more information.
+using automated deployment software that does not support it, or are targeting
+Windows Server 2019, please see :ref:`pymanager-advancedinstall` below for more
+information.
Basic Use
@@ -147,6 +146,10 @@ want to be passed to the runtime (such as script files or the module to launch):
$> py -m this
...
+The default runtime can be overridden with the :envvar:`PYTHON_MANAGER_DEFAULT`
+environment variable, or a configuration file. See :ref:`pymanager-config` for
+information about configuration settings.
+
To launch a specific runtime, the ``py`` command accepts a ``-V:<TAG>`` option.
This option must be specified before any others. The tag is part or all of the
identifier for the runtime; for those from the CPython team, it looks like the
@@ -469,6 +472,10 @@ directory (which you may have added to your :envvar:`PATH` environment variable)
can be used in a shebang, even if it is not on your :envvar:`PATH`. This allows
the use of shebangs like ``/usr/bin/python3.12`` to select a particular runtime.
+If no runtimes are installed, or if automatic installation is enabled, the
+requested runtime will be installed if necessary. See :ref:`pymanager-config`
+for information about configuration settings.
+
The ``/usr/bin/env`` form of shebang line will also search the :envvar:`PATH`
environment variable for unrecognized commands. This corresponds to the
behaviour of the Unix ``env`` program, which performs the same search, but
@@ -497,6 +504,14 @@ configuration option.
installing and uninstalling.
+.. _Add-AppxPackage: https://learn.microsoft.com/powershell/module/appx/add-appxpackage
+
+.. _Remove-AppxPackage: https://learn.microsoft.com/powershell/module/appx/remove-appxpackage
+
+.. _Add-AppxProvisionedPackage: https://learn.microsoft.com/powershell/module/dism/add-appxprovisionedpackage
+
+.. _PackageManager: https://learn.microsoft.com/uwp/api/windows.management.deployment.packagemanager
+
.. _pymanager-advancedinstall:
Advanced Installation
@@ -509,6 +524,11 @@ per-machine installs to its default location in Program Files. It will attempt
to modify the system :envvar:`PATH` environment variable to include this install
location, but be sure to validate this on your configuration.
+.. note::
+
+ Windows Server 2019 is the only version of Windows that CPython supports that
+ does not support MSIX. For Windows Server 2019, you should use the MSI.
+
Be aware that the MSI package does not bundle any runtimes, and so is not
suitable for installs into offline environments without also creating an offline
install index. See :ref:`pymanager-offline` and :ref:`pymanager-admin-config`
@@ -529,6 +549,62 @@ depending on whether it was installed from python.org or through the Windows
Store. Attempting to run the executable directly from Program Files is not
recommended.
+To programmatically install the Python install manager, it is easiest to use
+WinGet, which is included with all supported versions of Windows:
+
+.. code-block:: powershell
+
+ $> winget install 9NQ7512CXL7T -e --accept-package-agreements --disable-interactivity
+
+ # Optionally run the configuration checker and accept all changes
+ $> py install --configure -y
+
+To download the Python install manager and install on another machine, the
+following WinGet command will download the required files from the Store to your
+Downloads directory (add ``-d <location>`` to customize the output location).
+This also generates a YAML file that appears to be unnecessary, as the
+downloaded MSIX can be installed by launching or using the commands below.
+
+.. code-block:: powershell
+
+ $> winget download 9NQ7512CXL7T -e --skip-license --accept-package-agreements --accept-source-agreements
+
+To programmatically install or uninstall an MSIX using only PowerShell, the
+`Add-AppxPackage`_ and `Remove-AppxPackage`_ PowerShell cmdlets are recommended:
+
+.. code-block:: powershell
+
+ $> Add-AppxPackage C:\Downloads\python-manager-25.0.msix
+ ...
+ $> Get-AppxPackage PythonSoftwareFoundation.PythonManager | Remove-AppxPackage
+
+The latest release can be downloaded and installed by Windows by passing the
+AppInstaller file to the Add-AppxPackage command. This installs using the MSIX
+on python.org, and is only recommended for cases where installing via the Store
+(interactively or using WinGet) is not possible.
+
+.. code-block:: powershell
+
+ $> Add-AppxPackage -AppInstallerFile https://www.python.org/ftp/python/pymanager/pymanager.appinstaller
+
+Other tools and APIs may also be used to provision an MSIX package for all users
+on a machine, but Python does not consider this a supported scenario. We suggest
+looking into the PowerShell `Add-AppxProvisionedPackage`_ cmdlet, the native
+Windows `PackageManager`_ class, or the documentation and support for your
+deployment tool.
+
+Regardless of the install method, users will still need to install their own
+copies of Python itself, as there is no way to trigger those installs without
+being a logged in user. When using the MSIX, the latest version of Python will
+be available for all users to install without network access.
+
+Note that the MSIX downloadable from the Store and from the Python website are
+subtly different and cannot be installed at the same time. Wherever possible,
+we suggest using the above WinGet commands to download the package from the
+Store to reduce the risk of setting up conflicting installs. There are no
+licensing restrictions on the Python install manager that would prevent using
+the Store package in this way.
+
.. _pymanager-admin-config:
@@ -694,6 +770,16 @@ default).
your ``pythonw.exe`` and ``pyw.exe`` aliases are consistent with your
others.
"
+ "``pip`` gives me a ""command not found"" error when I type it in my
+ terminal.","Have you activated a virtual environment? Run the
+ ``.venv\Scripts\activate`` script in your terminal to activate.
+ "
+ "","The package may be available but missing the generated executable.
+ We recommend using the ``python -m pip`` command instead, or alternatively
+ the ``python -m pip install --force pip`` command will recreate the
+ executables and show you the path to add to :envvar:`PATH`. These scripts are
+ separated for each runtime, and so you may need to add multiple paths.
+ "
.. _windows-embeddable:
diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst
index fdccfb7deb1..0803eba99e6 100644
--- a/Doc/whatsnew/2.6.rst
+++ b/Doc/whatsnew/2.6.rst
@@ -1747,7 +1747,7 @@ Interpreter Changes
-------------------------------
Two command-line options have been reserved for use by other Python
-implementations. The :option:`-J` switch has been reserved for use by
+implementations. The :option:`!-J` switch has been reserved for use by
Jython for Jython-specific options, such as switches that are passed to
the underlying JVM. :option:`-X` has been reserved for options
specific to a particular implementation of Python such as CPython,
@@ -3043,7 +3043,7 @@ Changes to Python's build process and to the C API include:
* Importing modules simultaneously in two different threads no longer
deadlocks; it will now raise an :exc:`ImportError`. A new API
- function, :c:func:`PyImport_ImportModuleNoBlock`, will look for a
+ function, :c:func:`!PyImport_ImportModuleNoBlock`, will look for a
module in ``sys.modules`` first, then try to import it after
acquiring an import lock. If the import lock is held by another
thread, an :exc:`ImportError` is raised.
diff --git a/Doc/whatsnew/3.0.rst b/Doc/whatsnew/3.0.rst
index 6e1fda22ed2..d858586138e 100644
--- a/Doc/whatsnew/3.0.rst
+++ b/Doc/whatsnew/3.0.rst
@@ -870,7 +870,7 @@ to the C API.
* :c:func:`!PyNumber_Coerce`, :c:func:`!PyNumber_CoerceEx`,
:c:func:`!PyMember_Get`, and :c:func:`!PyMember_Set` C APIs are removed.
-* New C API :c:func:`PyImport_ImportModuleNoBlock`, works like
+* New C API :c:func:`!PyImport_ImportModuleNoBlock`, works like
:c:func:`PyImport_ImportModule` but won't block on the import lock
(returning an error instead).
diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index 3c815721a92..1067601c652 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -551,11 +551,12 @@ Patterns and classes
If you are using classes to structure your data, you can use as a pattern
the class name followed by an argument list resembling a constructor. This
-pattern has the ability to capture class attributes into variables::
+pattern has the ability to capture instance attributes into variables::
class Point:
- x: int
- y: int
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
def location(point):
match point:
@@ -2176,9 +2177,9 @@ Porting to Python 3.10
``unicodedata.ucnhash_CAPI`` has been moved to the internal C API.
(Contributed by Victor Stinner in :issue:`42157`.)
-* :c:func:`Py_GetPath`, :c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`,
- :c:func:`Py_GetProgramFullPath`, :c:func:`Py_GetPythonHome` and
- :c:func:`Py_GetProgramName` functions now return ``NULL`` if called before
+* :c:func:`!Py_GetPath`, :c:func:`!Py_GetPrefix`, :c:func:`!Py_GetExecPrefix`,
+ :c:func:`!Py_GetProgramFullPath`, :c:func:`!Py_GetPythonHome` and
+ :c:func:`!Py_GetProgramName` functions now return ``NULL`` if called before
:c:func:`Py_Initialize` (before Python is initialized). Use the new
:ref:`init-config` API to get the :ref:`init-path-config`.
(Contributed by Victor Stinner in :issue:`42260`.)
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index a65f59c0a72..7cfdc287b7f 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -2233,6 +2233,8 @@ Deprecated
.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
+.. include:: ../deprecations/c-api-pending-removal-in-3.16.rst
+
.. include:: ../deprecations/c-api-pending-removal-in-future.rst
Removed
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index e20e49325c0..0a3b3b30e01 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -730,6 +730,22 @@ asyncio
never awaited).
(Contributed by Arthur Tacca and Jason Zhang in :gh:`115957`.)
+* The function and methods named ``create_task`` have received a new
+ ``**kwargs`` argument that is passed through to the task constructor.
+ This change was accidentally added in 3.13.3,
+ and broke the API contract for custom task factories.
+ Several third-party task factories implemented workarounds for this.
+ In 3.13.4 and later releases the old factory contract is honored
+ once again (until 3.14).
+ To keep the workarounds working, the extra ``**kwargs`` argument still
+ allows passing additional keyword arguments to :class:`~asyncio.Task`
+ and to custom task factories.
+
+ This affects the following function and methods:
+ :meth:`asyncio.create_task`,
+ :meth:`asyncio.loop.create_task`,
+ :meth:`asyncio.TaskGroup.create_task`.
+ (Contributed by Thomas Grainger in :gh:`128307`.)
base64
------
@@ -1871,7 +1887,7 @@ New Deprecations
* :mod:`http.server`:
- * Deprecate :class:`~http.server.CGIHTTPRequestHandler`,
+ * Deprecate :class:`!CGIHTTPRequestHandler`,
to be removed in Python 3.15.
Process-based CGI HTTP servers have been out of favor for a very long time.
This code was outdated, unmaintained, and rarely used.
@@ -1908,7 +1924,7 @@ New Deprecations
* :mod:`platform`:
- * Deprecate :func:`~platform.java_ver`,
+ * Deprecate :func:`!platform.java_ver`,
to be removed in Python 3.15.
This function is only useful for Jython support, has a confusing API,
and is largely untested.
@@ -1980,7 +1996,7 @@ New Deprecations
(Contributed by Alex Waygood in :gh:`105566` and :gh:`105570`.)
* Deprecate the :func:`typing.no_type_check_decorator` decorator function,
- to be removed in in Python 3.15.
+ to be removed in Python 3.15.
After eight years in the :mod:`typing` module,
it has yet to be supported by any major type checker.
(Contributed by Alex Waygood in :gh:`106309`.)
@@ -1995,8 +2011,7 @@ New Deprecations
* :mod:`wave`:
- * Deprecate the :meth:`~wave.Wave_read.getmark`, :meth:`!setmark`,
- and :meth:`~wave.Wave_read.getmarkers` methods of
+ * Deprecate the ``getmark()``, ``setmark()`` and ``getmarkers()`` methods of
the :class:`~wave.Wave_read` and :class:`~wave.Wave_write` classes,
to be removed in Python 3.15.
(Contributed by Victor Stinner in :gh:`105096`.)
@@ -2477,17 +2492,17 @@ Deprecated C APIs
* :c:func:`PySys_ResetWarnOptions`:
Clear :data:`sys.warnoptions` and :data:`!warnings.filters` instead.
- * :c:func:`Py_GetExecPrefix`:
+ * :c:func:`!Py_GetExecPrefix`:
Get :data:`sys.exec_prefix` instead.
- * :c:func:`Py_GetPath`:
+ * :c:func:`!Py_GetPath`:
Get :data:`sys.path` instead.
- * :c:func:`Py_GetPrefix`:
+ * :c:func:`!Py_GetPrefix`:
Get :data:`sys.prefix` instead.
- * :c:func:`Py_GetProgramFullPath`:
+ * :c:func:`!Py_GetProgramFullPath`:
Get :data:`sys.executable` instead.
- * :c:func:`Py_GetProgramName`:
+ * :c:func:`!Py_GetProgramName`:
Get :data:`sys.executable` instead.
- * :c:func:`Py_GetPythonHome`:
+ * :c:func:`!Py_GetPythonHome`:
Get :c:member:`PyConfig.home`
or the :envvar:`PYTHONHOME` environment variable instead.
@@ -2499,7 +2514,7 @@ Deprecated C APIs
which return a :term:`borrowed reference`.
(Soft deprecated as part of :pep:`667`.)
-* Deprecate the :c:func:`PyImport_ImportModuleNoBlock` function,
+* Deprecate the :c:func:`!PyImport_ImportModuleNoBlock` function,
which is just an alias to :c:func:`PyImport_ImportModule` since Python 3.3.
(Contributed by Victor Stinner in :gh:`105396`.)
@@ -2531,6 +2546,8 @@ Deprecated C APIs
.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
+.. include:: ../deprecations/c-api-pending-removal-in-3.16.rst
+
.. include:: ../deprecations/c-api-pending-removal-in-3.18.rst
.. include:: ../deprecations/c-api-pending-removal-in-future.rst
@@ -2577,7 +2594,7 @@ Build Changes
* The :file:`configure` option :option:`--with-system-libmpdec`
now defaults to ``yes``.
- The bundled copy of ``libmpdecimal`` will be removed in Python 3.15.
+ The bundled copy of ``libmpdec`` will be removed in Python 3.16.
* Python built with :file:`configure` :option:`--with-trace-refs`
(tracing references) is now ABI compatible with the Python release build
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 1ebf6efffd0..c108a94692d 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -2,7 +2,7 @@
What's new in Python 3.14
****************************
-:Editor: TBD
+:Editor: Hugo van Kemenade
.. Rules for maintenance:
@@ -48,6 +48,10 @@ This article explains the new features in Python 3.14, compared to 3.13.
For full details, see the :ref:`changelog <changelog>`.
+.. seealso::
+
+ :pep:`745` -- Python 3.14 release schedule
+
.. note::
Prerelease users should be aware that this document is currently in draft
@@ -61,22 +65,41 @@ Summary -- release highlights
.. This section singles out the most important changes in Python 3.14.
Brevity is key.
+Python 3.14 beta is the pre-release of the next version of the Python
+programming language, with a mix of changes to the language, the
+implementation and the standard library.
+
+The biggest changes to the implementation include template strings (:pep:`750`),
+deferred evaluation of annotations (:pep:`649`),
+and a new type of interpreter that uses tail calls.
+
+The library changes include the addition of a new :mod:`!annotationlib` module
+for introspecting and wrapping annotations (:pep:`749`),
+a new :mod:`!compression.zstd` module for Zstandard support (:pep:`784`),
+plus syntax highlighting in the REPL,
+as well as the usual deprecations and removals,
+and improvements in user-friendliness and correctness.
.. PEP-sized items next.
-* :ref:`PEP 649: deferred evaluation of annotations <whatsnew314-pep649>`
-* :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
-* :ref:`PEP 750: Template Strings <whatsnew314-pep750>`
+* :ref:`PEP 779: Free-threaded Python is officially supported <whatsnew314-pep779>`
+* :ref:`PEP 649 and 749: deferred evaluation of annotations <whatsnew314-pep649>`
+* :ref:`PEP 734: Multiple interpreters in the stdlib <whatsnew314-pep734>`
+* :ref:`PEP 741: Python configuration C API <whatsnew314-pep741>`
+* :ref:`PEP 750: Template strings <whatsnew314-pep750>`
* :ref:`PEP 758: Allow except and except* expressions without parentheses <whatsnew314-pep758>`
* :ref:`PEP 761: Discontinuation of PGP signatures <whatsnew314-pep761>`
* :ref:`PEP 765: Disallow return/break/continue that exit a finally block <whatsnew314-pep765>`
+* :ref:`Free-threaded mode improvements <whatsnew314-free-threaded-cpython>`
* :ref:`PEP 768: Safe external debugger interface for CPython <whatsnew314-pep768>`
+* :ref:`PEP 784: Adding Zstandard to the standard library <whatsnew314-pep784>`
* :ref:`A new type of interpreter <whatsnew314-tail-call>`
* :ref:`Syntax highlighting in PyREPL <whatsnew314-pyrepl-highlighting>`,
and color output in :ref:`unittest <whatsnew314-color-unittest>`,
:ref:`argparse <whatsnew314-color-argparse>`,
:ref:`json <whatsnew314-color-json>` and
:ref:`calendar <whatsnew314-color-calendar>` CLIs
+* :ref:`Binary releases for the experimental just-in-time compiler <whatsnew314-jit-compiler>`
Incompatible changes
@@ -102,9 +125,130 @@ of Python. See :ref:`below <whatsnew314-refcount>` for details.
New features
============
+.. _whatsnew314-pep779:
+
+PEP 779: Free-threaded Python is officially supported
+-----------------------------------------------------
+
+The free-threaded build of Python is now supported and no longer experimental.
+This is the start of phase II where free-threaded Python is officially supported
+but still optional.
+
+We are confident that the project is on the right path, and we appreciate the
+continued dedication from everyone working to make free-threading ready for
+broader adoption across the Python community.
+
+With these recommendations and the acceptance of this PEP, we as the Python
+developer community should broadly advertise that free-threading is a supported
+Python build option now and into the future, and that it will not be removed
+without a proper deprecation schedule.
+
+Any decision to transition to phase III, with free-threading as the default or
+sole build of Python is still undecided, and dependent on many factors both
+within CPython itself and the community. This decision is for the future.
+
+.. seealso::
+ :pep:`779` and its `acceptance
+ <https://discuss.python.org/t/pep-779-criteria-for-supported-status-for-free-threaded-python/84319/123>`__.
+
+.. _whatsnew314-pep734:
+
+PEP 734: Multiple interpreters in the stdlib
+--------------------------------------------
+
+The CPython runtime supports running multiple copies of Python in the
+same process simultaneously and has done so for over 20 years.
+Each of these separate copies is called an "interpreter".
+However, the feature had been available only through the C-API.
+
+That limitation is removed in the 3.14 release,
+with the new :mod:`concurrent.interpreters` module.
+
+There are at least two notable reasons why using multiple interpreters
+is worth considering:
+
+* they support a new (to Python), human-friendly concurrency model
+* true multi-core parallelism
+
+For some use cases, concurrency in software enables efficiency and
+can simplify software, at a high level. At the same time, implementing
+and maintaining all but the simplest concurrency is often a struggle
+for the human brain. That especially applies to plain threads
+(for example, :mod:`threading`), where all memory is shared between all threads.
+
+With multiple isolated interpreters, you can take advantage of a class
+of concurrency models, like CSP or the actor model, that have found
+success in other programming languages, like Smalltalk, Erlang,
+Haskell, and Go. Think of multiple interpreters like threads
+but with opt-in sharing.
+
+Regarding multi-core parallelism: as of the 3.12 release, interpreters
+are now sufficiently isolated from one another to be used in parallel.
+(See :pep:`684`.) This unlocks a variety of CPU-intensive use cases
+for Python that were limited by the :term:`GIL`.
+
+Using multiple interpreters is similar in many ways to
+:mod:`multiprocessing`, in that they both provide isolated logical
+"processes" that can run in parallel, with no sharing by default.
+However, when using multiple interpreters, an application will use
+fewer system resources and will operate more efficiently (since it
+stays within the same process). Think of multiple interpreters as
+having the isolation of processes with the efficiency of threads.
+
+.. XXX Add an example or two.
+.. XXX Link to the not-yet-added HOWTO doc.
+
+While the feature has been around for decades, multiple interpreters
+have not been used widely, due to low awareness and the lack of a stdlib
+module. Consequently, they currently have several notable limitations,
+which will improve significantly now that the feature is finally
+going mainstream.
+
+Current limitations:
+
+* starting each interpreter has not been optimized yet
+* each interpreter uses more memory than necessary
+ (we will be working next on extensive internal sharing between
+ interpreters)
+* there aren't many options *yet* for truly sharing objects or other
+ data between interpreters (other than :type:`memoryview`)
+* many extension modules on PyPI are not compatible with multiple
+ interpreters yet (stdlib extension modules *are* compatible)
+* the approach to writing applications that use multiple isolated
+ interpreters is mostly unfamiliar to Python users, for now
+
+The impact of these limitations will depend on future CPython
+improvements, how interpreters are used, and what the community solves
+through PyPI packages. Depending on the use case, the limitations may
+not have much impact, so try it out!
+
+Furthermore, future CPython releases will reduce or eliminate overhead
+and provide utilities that are less appropriate on PyPI. In the
+meantime, most of the limitations can also be addressed through
+extension modules, meaning PyPI packages can fill any gap for 3.14, and
+even back to 3.12 where interpreters were finally properly isolated and
+stopped sharing the :term:`GIL`. Likewise, we expect to slowly see
+libraries on PyPI for high-level abstractions on top of interpreters.
+
+Regarding extension modules, work is in progress to update some PyPI
+projects, as well as tools like Cython, pybind11, nanobind, and PyO3.
+The steps for isolating an extension module are found at
+:ref:`isolating-extensions-howto`. Isolating a module has a lot of
+overlap with what is required to support
+:ref:`free-threading <whatsnew314-free-threaded-cpython>`,
+so the ongoing work in the community in that area will help accelerate
+support for multiple interpreters.
+
+Also added in 3.14: :ref:`concurrent.futures.InterpreterPoolExecutor
+<whatsnew314-concurrent-futures-interp-pool>`.
+
+.. seealso::
+ :pep:`734`.
+
+
.. _whatsnew314-pep750:
-PEP 750: Template Strings
+PEP 750: Template strings
-------------------------
Template string literals (t-strings) are a generalization of f-strings,
@@ -133,10 +277,10 @@ As another example, generating HTML attributes from data:
.. code-block:: python
attributes = {"src": "shrubbery.jpg", "alt": "looks nice"}
- template = t"<img {attributes} />"
- assert html(template) == '<img src="shrubbery.jpg" alt="looks nice" class="looks-nice" />'
+ template = t"<img {attributes}>"
+ assert html(template) == '<img src="shrubbery.jpg" alt="looks nice" />'
-Unlike f-strings, the ``html`` function has access to template attributes
+Compared to using an f-string, the ``html`` function has access to template attributes
containing the original information: static strings, interpolations, and values
from the original scope. Unlike existing templating approaches, t-strings build
from the well-known f-string syntax and rules. Template systems thus benefit
@@ -166,12 +310,14 @@ With this in place, developers can write template systems to sanitize SQL, make
safe shell operations, improve logging, tackle modern ideas in web development
(HTML, CSS, and so on), and implement lightweight, custom business DSLs.
-See :pep:`750` for more details.
-
(Contributed by Jim Baker, Guido van Rossum, Paul Everitt, Koudai Aono,
Lysandros Nikolaou, Dave Peck, Adam Turner, Jelle Zijlstra, Bénédikt Tran,
and Pablo Galindo Salgado in :gh:`132661`.)
+.. seealso::
+ :pep:`750`.
+
+
.. _whatsnew314-pep768:
PEP 768: Safe external debugger interface for CPython
@@ -227,10 +373,57 @@ A key implementation detail is that the interface piggybacks on the interpreter'
loop and safe points, ensuring zero overhead during normal execution while providing a reliable way
for external processes to coordinate debugging operations.
-See :pep:`768` for more details.
-
(Contributed by Pablo Galindo Salgado, Matt Wozniski, and Ivona Stojanovic in :gh:`131591`.)
+.. seealso::
+ :pep:`768`.
+
+
+.. _whatsnew314-pep784:
+
+PEP 784: Adding Zstandard to the standard library
+-------------------------------------------------
+
+The new ``compression`` package contains modules :mod:`!compression.lzma`,
+:mod:`!compression.bz2`, :mod:`!compression.gzip` and :mod:`!compression.zlib`
+which re-export the :mod:`lzma`, :mod:`bz2`, :mod:`gzip` and :mod:`zlib`
+modules respectively. The new import names under ``compression`` are the
+canonical names for importing these compression modules going forward. However,
+the existing modules names have not been deprecated. Any deprecation or removal
+of the existing compression modules will occur no sooner than five years after
+the release of 3.14.
+
+The new :mod:`!compression.zstd` module provides compression and decompression
+APIs for the Zstandard format via bindings to `Meta's zstd library
+<https://facebook.github.io/zstd/>`__. Zstandard is a widely adopted, highly
+efficient, and fast compression format. In addition to the APIs introduced in
+:mod:`!compression.zstd`, support for reading and writing Zstandard compressed
+archives has been added to the :mod:`tarfile`, :mod:`zipfile`, and
+:mod:`shutil` modules.
+
+Here's an example of using the new module to compress some data:
+
+.. code-block:: python
+
+ from compression import zstd
+ import math
+
+ data = str(math.pi).encode() * 20
+
+ compressed = zstd.compress(data)
+
+ ratio = len(compressed) / len(data)
+ print(f"Achieved compression ratio of {ratio}")
+
+As can be seen, the API is similar to the APIs of the :mod:`!lzma` and
+:mod:`!bz2` modules.
+
+(Contributed by Emma Harper Smith, Adam Turner, Gregory P. Smith, Tomas Roun,
+Victor Stinner, and Rogdham in :gh:`132983`.)
+
+.. seealso::
+ :pep:`784`.
+
.. _whatsnew314-remote-pdb:
@@ -250,12 +443,15 @@ attaching to a remote process that is blocked in a system call or waiting for
I/O will only work once the next bytecode instruction is executed or when the
process receives a signal.
-This feature leverages :pep:`768` and the :func:`sys.remote_exec` function
+This feature uses :pep:`768` and the :func:`sys.remote_exec` function
to attach to the remote process and send the PDB commands to it.
(Contributed by Matt Wozniski and Pablo Galindo in :gh:`131591`.)
+.. seealso::
+ :pep:`768`.
+
.. _whatsnew314-pep758:
@@ -269,35 +465,40 @@ For example the following expressions are now valid:
.. code-block:: python
try:
- release_new_sleep_token_album()
- except AlbumNotFound, SongsTooGoodToBeReleased:
- print("Sorry, no new album this year.")
+ connect_to_server()
+ except TimeoutError, ConnectionRefusedError:
+ print("Network issue encountered.")
# The same applies to except* (for exception groups):
+
try:
- release_new_sleep_token_album()
- except* AlbumNotFound, SongsTooGoodToBeReleased:
- print("Sorry, no new album this year.")
+ connect_to_server()
+ except* TimeoutError, ConnectionRefusedError:
+ print("Network issue encountered.")
Check :pep:`758` for more details.
(Contributed by Pablo Galindo and Brett Cannon in :gh:`131831`.)
+.. seealso::
+ :pep:`758`.
+
.. _whatsnew314-pep649:
-PEP 649: deferred evaluation of annotations
--------------------------------------------
+PEP 649 and 749: deferred evaluation of annotations
+---------------------------------------------------
The :term:`annotations <annotation>` on functions, classes, and modules are no
longer evaluated eagerly. Instead, annotations are stored in special-purpose
:term:`annotate functions <annotate function>` and evaluated only when
-necessary. This is specified in :pep:`649` and :pep:`749`.
+necessary (except if ``from __future__ import annotations`` is used).
+This is specified in :pep:`649` and :pep:`749`.
This change is designed to make annotations in Python more performant and more
usable in most circumstances. The runtime cost for defining annotations is
minimized, but it remains possible to introspect annotations at runtime.
-It is usually no longer necessary to enclose annotations in strings if they
+It is no longer necessary to enclose annotations in strings if they
contain forward references.
The new :mod:`annotationlib` module provides tools for inspecting deferred
@@ -333,7 +534,8 @@ writing annotations the same way you did with previous versions of Python.
You will likely be able to remove quoted strings in annotations, which are frequently
used for forward references. Similarly, if you use ``from __future__ import annotations``
to avoid having to write strings in annotations, you may well be able to
-remove that import. However, if you rely on third-party libraries that read annotations,
+remove that import once you support only Python 3.14 and newer.
+However, if you rely on third-party libraries that read annotations,
those libraries may need changes to support unquoted annotations before they
work as expected.
@@ -346,6 +548,11 @@ annotations. For example, you may want to use :func:`annotationlib.get_annotatio
with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses`
module now does.
+The external :pypi:`typing_extensions` package provides partial backports of some of the
+functionality of the :mod:`annotationlib` module, such as the :class:`~annotationlib.Format`
+enum and the :func:`~annotationlib.get_annotations` function. These can be used to
+write cross-version code that takes advantage of the new behavior in Python 3.14.
+
Related changes
^^^^^^^^^^^^^^^
@@ -357,6 +564,14 @@ functions in the standard library, there are many ways in which your code may
not work in Python 3.14. To safeguard your code against future changes,
use only the documented functionality of the :mod:`annotationlib` module.
+In particular, do not read annotations directly from the namespace dictionary
+attribute of type objects. Use :func:`annotationlib.get_annotate_from_class_namespace`
+during class construction and :func:`annotationlib.get_annotations` afterwards.
+
+In previous releases, it was sometimes possible to access class annotations from
+an instance of an annotated class. This behavior was undocumented and accidental,
+and will no longer work in Python 3.14.
+
``from __future__ import annotations``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -368,6 +583,11 @@ Python without deferred evaluation of annotations, reaches its end of life in 20
In Python 3.14, the behavior of code using ``from __future__ import annotations``
is unchanged.
+(Contributed by Jelle Zijlstra in :gh:`119180`; :pep:`649` was written by Larry Hastings.)
+
+.. seealso::
+ :pep:`649` and :pep:`749`.
+
Improved error messages
-----------------------
@@ -378,7 +598,7 @@ Improved error messages
feature helps programmers quickly identify and fix common typing mistakes. For
example:
- .. code-block:: python
+ .. code-block:: pycon
>>> whille True:
... pass
@@ -419,7 +639,7 @@ Improved error messages
error message prints the received number of values in more cases than before.
(Contributed by Tushar Sadhwani in :gh:`122239`.)
- .. code-block:: python
+ .. code-block:: pycon
>>> x, y, z = 1, 2, 3, 4
Traceback (most recent call last):
@@ -473,7 +693,7 @@ Improved error messages
that the string may be intended to be part of the string. (Contributed by
Pablo Galindo in :gh:`88535`.)
- .. code-block:: python
+ .. code-block:: pycon
>>> "The interesting object "The important object" is very important"
Traceback (most recent call last):
@@ -509,10 +729,32 @@ Improved error messages
^^^^^^
SyntaxError: cannot use subscript as import target
+* Improved error message when trying to add an instance of an unhashable type to
+ a :class:`dict` or :class:`set`. (Contributed by CF Bolz-Tereick and Victor Stinner
+ in :gh:`132828`.)
+
+ .. code-block:: pycon
+
+ >>> s = set()
+ >>> s.add({'pages': 12, 'grade': 'A'})
+ Traceback (most recent call last):
+ File "<python-input-1>", line 1, in <module>
+ s.add({'pages': 12, 'grade': 'A'})
+ ~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ TypeError: cannot use 'dict' as a set element (unhashable type: 'dict')
+ >>> d = {}
+ >>> l = [1, 2, 3]
+ >>> d[l] = 12
+ Traceback (most recent call last):
+ File "<python-input-4>", line 1, in <module>
+ d[l] = 12
+ ~^^^
+ TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
+
.. _whatsnew314-pep741:
-PEP 741: Python Configuration C API
+PEP 741: Python configuration C API
-----------------------------------
Add a :ref:`PyInitConfig C API <pyinitconfig_api>` to configure the Python
@@ -543,6 +785,120 @@ configuration mechanisms).
.. seealso::
:pep:`741`.
+.. _whatsnew314-asyncio-introspection:
+
+Asyncio introspection capabilities
+----------------------------------
+
+Added a new command-line interface to inspect running Python processes using
+asynchronous tasks, available via:
+
+.. code-block:: bash
+
+ python -m asyncio ps PID
+
+This tool inspects the given process ID (PID) and displays information about
+currently running asyncio tasks. It outputs a task table: a flat
+listing of all tasks, their names, their coroutine stacks, and which tasks are
+awaiting them.
+
+.. code-block:: bash
+
+ python -m asyncio pstree PID
+
+This tool fetches the same information, but renders a visual async call tree,
+showing coroutine relationships in a hierarchical format. This command is
+particularly useful for debugging long-running or stuck asynchronous programs.
+It can help developers quickly identify where a program is blocked, what tasks
+are pending, and how coroutines are chained together.
+
+For example given this code:
+
+.. code-block:: python
+
+ import asyncio
+
+ async def play(track):
+ await asyncio.sleep(5)
+ print(f"🎵 Finished: {track}")
+
+ async def album(name, tracks):
+ async with asyncio.TaskGroup() as tg:
+ for track in tracks:
+ tg.create_task(play(track), name=track)
+
+ async def main():
+ async with asyncio.TaskGroup() as tg:
+ tg.create_task(
+ album("Sundowning", ["TNDNBTG", "Levitate"]), name="Sundowning")
+ tg.create_task(
+ album("TMBTE", ["DYWTYLM", "Aqua Regia"]), name="TMBTE")
+
+ if __name__ == "__main__":
+ asyncio.run(main())
+
+Executing the new tool on the running process will yield a table like this:
+
+.. code-block:: bash
+
+ python -m asyncio ps 12345
+
+ tid task id task name coroutine stack awaiter chain awaiter name awaiter id
+ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1935500 0x7fc930c18050 Task-1 TaskGroup._aexit -> TaskGroup.__aexit__ -> main 0x0
+ 1935500 0x7fc930c18230 Sundowning TaskGroup._aexit -> TaskGroup.__aexit__ -> album TaskGroup._aexit -> TaskGroup.__aexit__ -> main Task-1 0x7fc930c18050
+ 1935500 0x7fc93173fa50 TMBTE TaskGroup._aexit -> TaskGroup.__aexit__ -> album TaskGroup._aexit -> TaskGroup.__aexit__ -> main Task-1 0x7fc930c18050
+ 1935500 0x7fc93173fdf0 TNDNBTG sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album Sundowning 0x7fc930c18230
+ 1935500 0x7fc930d32510 Levitate sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album Sundowning 0x7fc930c18230
+ 1935500 0x7fc930d32890 DYWTYLM sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album TMBTE 0x7fc93173fa50
+ 1935500 0x7fc93161ec30 Aqua Regia sleep -> play TaskGroup._aexit -> TaskGroup.__aexit__ -> album TMBTE 0x7fc93173fa50
+
+or a tree like this:
+
+.. code-block:: bash
+
+ python -m asyncio pstree 12345
+
+ └── (T) Task-1
+ └── main example.py:13
+ └── TaskGroup.__aexit__ Lib/asyncio/taskgroups.py:72
+ └── TaskGroup._aexit Lib/asyncio/taskgroups.py:121
+ ├── (T) Sundowning
+ │ └── album example.py:8
+ │ └── TaskGroup.__aexit__ Lib/asyncio/taskgroups.py:72
+ │ └── TaskGroup._aexit Lib/asyncio/taskgroups.py:121
+ │ ├── (T) TNDNBTG
+ │ │ └── play example.py:4
+ │ │ └── sleep Lib/asyncio/tasks.py:702
+ │ └── (T) Levitate
+ │ └── play example.py:4
+ │ └── sleep Lib/asyncio/tasks.py:702
+ └── (T) TMBTE
+ └── album example.py:8
+ └── TaskGroup.__aexit__ Lib/asyncio/taskgroups.py:72
+ └── TaskGroup._aexit Lib/asyncio/taskgroups.py:121
+ ├── (T) DYWTYLM
+ │ └── play example.py:4
+ │ └── sleep Lib/asyncio/tasks.py:702
+ └── (T) Aqua Regia
+ └── play example.py:4
+ └── sleep Lib/asyncio/tasks.py:702
+
+If a cycle is detected in the async await graph (which could indicate a
+programming issue), the tool raises an error and lists the cycle paths that
+prevent tree construction:
+
+.. code-block:: bash
+
+ python -m asyncio pstree 12345
+
+ ERROR: await-graph contains cycles - cannot print a tree!
+
+ cycle: Task-2 → Task-3 → Task-2
+
+(Contributed by Pablo Galindo, Łukasz Langa, Yury Selivanov, and Marta
+Gomez Macias in :gh:`91048`.)
+
.. _whatsnew314-tail-call:
A new type of interpreter
@@ -595,6 +951,50 @@ For further information on how to build Python, see
(Contributed by Ken Jin in :gh:`128563`, with ideas on how to implement this
in CPython by Mark Shannon, Garrett Gu, Haoran Xu, and Josh Haberman.)
+.. _whatsnew314-free-threaded-cpython:
+
+Free-threaded mode
+------------------
+
+Free-threaded mode (:pep:`703`), initially added in 3.13, has been significantly improved.
+The implementation described in PEP 703 was finished, including C API changes,
+and temporary workarounds in the interpreter were replaced with more permanent solutions.
+The specializing adaptive interpreter (:pep:`659`) is now enabled in free-threaded mode,
+which along with many other optimizations greatly improves its performance.
+The performance penalty on single-threaded code in free-threaded mode is now roughly 5-10%,
+depending on platform and C compiler used.
+
+This work was done by many contributors: Sam Gross, Matt Page, Neil Schemenauer,
+Thomas Wouters, Donghee Na, Kirill Podoprigora, Ken Jin, Itamar Oren,
+Brett Simmers, Dino Viehland, Nathan Goldbaum, Ralf Gommers, Lysandros Nikolaou,
+Kumar Aditya, Edgar Margffoy, and many others.
+
+Some of these contributors are employed by Meta, which has continued to provide
+significant engineering resources to support this project.
+
+From 3.14, when compiling extension modules for the free-threaded build of
+CPython on Windows, the preprocessor variable ``Py_GIL_DISABLED`` now needs to
+be specified by the build backend, as it will no longer be determined
+automatically by the C compiler. For a running interpreter, the setting that
+was used at compile time can be found using :func:`sysconfig.get_config_var`.
+
+A new flag has been added, :data:`~sys.flags.context_aware_warnings`. This
+flag defaults to true for the free-threaded build and false for the GIL-enabled
+build. If the flag is true then the :class:`warnings.catch_warnings` context
+manager uses a context variable for warning filters. This makes the context
+manager behave predicably when used with multiple threads or asynchronous
+tasks.
+
+A new flag has been added, :data:`~sys.flags.thread_inherit_context`. This flag
+defaults to true for the free-threaded build and false for the GIL-enabled
+build. If the flag is true then threads created with :class:`threading.Thread`
+start with a copy of the :class:`~contextvars.Context()` of the caller of
+:meth:`~threading.Thread.start`. Most significantly, this makes the warning
+filtering context established by :class:`~warnings.catch_warnings` be
+"inherited" by threads (or asyncio tasks) started within that context. It also
+affects other modules that use context variables, such as the :mod:`decimal`
+context manager.
+
.. _whatsnew314-pyrepl-highlighting:
@@ -616,9 +1016,58 @@ in the :envvar:`PYTHONSTARTUP` script.
(Contributed by Łukasz Langa in :gh:`131507`.)
+.. _whatsnew314-jit-compiler:
+
+Binary releases for the experimental just-in-time compiler
+----------------------------------------------------------
+
+The official macOS and Windows release binaries now include an *experimental*
+just-in-time (JIT) compiler. Although it is **not** recommended for production
+use, it can be tested by setting :envvar:`PYTHON_JIT=1 <PYTHON_JIT>` as an
+environment variable. Downstream source builds and redistributors can use the
+:option:`--enable-experimental-jit=yes-off` configuration option for similar
+behavior.
+
+The JIT is at an early stage and still in active development. As such, the
+typical performance impact of enabling it can range from 10% slower to 20%
+faster, depending on workload. To aid in testing and evaluation, a set of
+introspection functions has been provided in the :data:`sys._jit` namespace.
+:func:`sys._jit.is_available` can be used to determine if the current executable
+supports JIT compilation, while :func:`sys._jit.is_enabled` can be used to tell
+if JIT compilation has been enabled for the current process.
+
+Currently, the most significant missing functionality is that native debuggers
+and profilers like ``gdb`` and ``perf`` are unable to unwind through JIT frames
+(Python debuggers and profilers, like :mod:`pdb` or :mod:`profile`, continue to
+work without modification). Free-threaded builds do not support JIT compilation.
+
+Please report any bugs or major performance regressions that you encounter!
+
+.. seealso:: :pep:`744`
+
+Concurrent safe warnings control
+--------------------------------
+
+The :class:`warnings.catch_warnings` context manager will now optionally
+use a context variable for warning filters. This is enabled by setting
+the :data:`~sys.flags.context_aware_warnings` flag, either with the ``-X``
+command-line option or an environment variable. This gives predicable
+warnings control when using :class:`~warnings.catch_warnings` combined with
+multiple threads or asynchronous tasks. The flag defaults to true for the
+free-threaded build and false for the GIL-enabled build.
+
+(Contributed by Neil Schemenauer and Kumar Aditya in :gh:`130010`.)
+
Other language changes
======================
+* The default :term:`interactive` shell now supports import autocompletion.
+ This means that typing ``import foo`` and pressing ``<tab>`` will suggest
+ modules starting with ``foo``. Similarly, typing ``from foo import b`` will
+ suggest submodules of ``foo`` starting with ``b``. Note that autocompletion
+ of module attributes is not currently supported.
+ (Contributed by Tomas Roun in :gh:`69605`.)
+
* The :func:`map` built-in now has an optional keyword-only *strict* flag
like :func:`zip` to check that all the iterables are of equal length.
(Contributed by Wannes Boeykens in :gh:`119793`.)
@@ -679,9 +1128,9 @@ Other language changes
The testbed can also be used to run the test suite of projects other than
CPython itself. (Contributed by Russell Keith-Magee in :gh:`127592`.)
-* Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if necessary.
- Previously it was only called in two-argument :func:`!pow` and the binary
- power operator.
+* Three-argument :func:`pow` now tries calling :meth:`~object.__rpow__` if
+ necessary. Previously it was only called in two-argument :func:`!pow` and the
+ binary power operator.
(Contributed by Serhiy Storchaka in :gh:`130104`.)
* Add a built-in implementation for HMAC (:rfc:`2104`) using formally verified
@@ -690,27 +1139,39 @@ Other language changes
of HMAC is not available.
(Contributed by Bénédikt Tran in :gh:`99108`.)
+* The import time flag can now track modules that are already loaded ('cached'),
+ via the new :option:`-X importtime=2 <-X>`.
+ When such a module is imported, the ``self`` and ``cumulative`` times
+ are replaced by the string ``cached``.
+ Values above ``2`` for ``-X importtime`` are now reserved for future use.
+ (Contributed by Noah Kim and Adam Turner in :gh:`118655`.)
+
* When subclassing from a pure C type, the C slots for the new type are no
longer replaced with a wrapped version on class creation if they are not
explicitly overridden in the subclass.
(Contributed by Tomasz Pytel in :gh:`132329`.)
-* The command line option :option:`-c` now automatically dedents its code
+* The command-line option :option:`-c` now automatically dedents its code
argument before execution. The auto-dedentation behavior mirrors
:func:`textwrap.dedent`.
(Contributed by Jon Crall and Steven Sun in :gh:`103998`.)
-* Improve error message when an object supporting the synchronous (resp.
- asynchronous) context manager protocol is entered using :keyword:`async
- with` (resp. :keyword:`with`) instead of :keyword:`with` (resp.
- :keyword:`async with`).
+* Improve error message when an object supporting the synchronous
+ context manager protocol is entered using :keyword:`async
+ with` instead of :keyword:`with`.
+ And vice versa with the asynchronous context manager protocol.
(Contributed by Bénédikt Tran in :gh:`128398`.)
+* :option:`!-J` is no longer a reserved flag for Jython_,
+ and now has no special meaning.
+ (Contributed by Adam Turner in :gh:`133336`.)
+
+ .. _Jython: https://www.jython.org/
.. _whatsnew314-pep765:
-PEP 765: Disallow return/break/continue that exit a finally block
------------------------------------------------------------------
+PEP 765: Disallow ``return``/``break``/``continue`` that exit a ``finally`` block
+---------------------------------------------------------------------------------
The compiler emits a :exc:`SyntaxWarning` when a :keyword:`return`, :keyword:`break` or
:keyword:`continue` statements appears where it exits a :keyword:`finally` block.
@@ -745,10 +1206,9 @@ argparse
* Introduced the optional *color* parameter to
:class:`argparse.ArgumentParser`, enabling color for help text.
- This can be controlled via the :envvar:`PYTHON_COLORS` environment
- variable as well as the canonical |NO_COLOR|_
- and |FORCE_COLOR|_ environment variables.
- See also :ref:`using-on-controlling-color`.
+ This can be controlled by :ref:`environment variables
+ <using-on-controlling-color>`. Color has also been enabled for help in the
+ :ref:`stdlib CLIs <library-cmdline>` which use :mod:`!argparse`.
(Contributed by Hugo van Kemenade in :gh:`130645`.)
@@ -765,12 +1225,35 @@ ast
(Contributed by Irit Katriel in :gh:`123958`.)
* The ``repr()`` output for AST nodes now includes more information.
- (Contributed by Tomas R in :gh:`116022`.)
+ (Contributed by Tomas Roun in :gh:`116022`.)
* :func:`ast.parse`, when called with an AST as input, now always verifies
that the root node type is appropriate.
(Contributed by Irit Katriel in :gh:`130139`.)
+* Add new ``--feature-version``, ``--optimize``, ``--show-empty`` options to
+ command-line interface.
+ (Contributed by Semyon Moroz in :gh:`133367`.)
+
+
+asyncio
+-------
+
+* The function and methods named :func:`!create_task` now take an arbitrary
+ list of keyword arguments. All keyword arguments are passed to the
+ :class:`~asyncio.Task` constructor or the custom task factory.
+ (See :meth:`~asyncio.loop.set_task_factory` for details.)
+ The ``name`` and ``context`` keyword arguments are no longer special;
+ the name should now be set using the ``name`` keyword argument of the factory,
+ and ``context`` may be ``None``.
+
+ This affects the following function and methods:
+ :meth:`asyncio.create_task`,
+ :meth:`asyncio.loop.create_task`,
+ :meth:`asyncio.TaskGroup.create_task`.
+ (Contributed by Thomas Grainger in :gh:`128307`.)
+
+
bdb
---
@@ -785,18 +1268,18 @@ calendar
* By default, today's date is highlighted in color in :mod:`calendar`'s
:ref:`command-line <calendar-cli>` text output.
- This can be controlled via the :envvar:`PYTHON_COLORS` environment
- variable as well as the canonical |NO_COLOR|_
- and |FORCE_COLOR|_ environment variables.
- See also :ref:`using-on-controlling-color`.
+ This can be controlled by :ref:`environment variables
+ <using-on-controlling-color>`.
(Contributed by Hugo van Kemenade in :gh:`128317`.)
concurrent.futures
------------------
+.. _whatsnew314-concurrent-futures-interp-pool:
+
* Add :class:`~concurrent.futures.InterpreterPoolExecutor`,
- which exposes "subinterpreters (multiple Python interpreters in the
+ which exposes "subinterpreters" (multiple Python interpreters in the
same process) to Python code. This is separate from the proposed API
in :pep:`734`.
(Contributed by Eric Snow in :gh:`124548`.)
@@ -832,6 +1315,14 @@ concurrent.futures
buffer.
(Contributed by Enzo Bonnal and Josh Rosenberg in :gh:`74028`.)
+configparser
+------------
+
+* Security fix: will no longer write config files it cannot read. Attempting
+ to :meth:`configparser.ConfigParser.write` keys containing delimiters or
+ beginning with the section header pattern will raise a
+ :class:`configparser.InvalidWriteError`.
+ (Contributed by Jacob Lincoln in :gh:`129270`.)
contextvars
-----------
@@ -875,7 +1366,7 @@ ctypes
:class:`~ctypes.c_double_complex` and :class:`~ctypes.c_longdouble_complex`,
are now available if both the compiler and the ``libffi`` library support
complex C types.
- (Contributed by Sergey B Kirpichev in :gh:`61103`).
+ (Contributed by Sergey B Kirpichev in :gh:`61103`.)
* Add :func:`ctypes.util.dllist` for listing the shared libraries
loaded by the current process.
@@ -885,12 +1376,23 @@ ctypes
(``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
attribute of the corresponding :mod:`ctypes` types.
This will stop the cache from growing without limits in some situations.
- (Contributed by Sergey Miryanov in :gh:`100926`).
+ (Contributed by Sergey Miryanov in :gh:`100926`.)
* The :class:`ctypes.py_object` type now supports subscription,
making it a :term:`generic type`.
(Contributed by Brian Schubert in :gh:`132168`.)
+* :mod:`ctypes` now supports :term:`free-threading builds <free threading>`.
+ (Contributed by Kumar Aditya and Peter Bierma in :gh:`127945`.)
+
+curses
+------
+
+* Add the :func:`~curses.assume_default_colors` function,
+ a refinement of the :func:`~curses.use_default_colors` function which
+ allows to change the color pair ``0``.
+ (Contributed by Serhiy Storchaka in :gh:`133139`.)
+
datetime
--------
@@ -994,12 +1496,33 @@ getopt
(Contributed by Serhiy Storchaka in :gh:`126390`.)
+getpass
+-------
+
+* Support keyboard feedback by :func:`getpass.getpass` via the keyword-only
+ optional argument ``echo_char``. Placeholder characters are rendered whenever
+ a character is entered, and removed when a character is deleted.
+ (Contributed by Semyon Moroz in :gh:`77065`.)
+
+
graphlib
--------
* Allow :meth:`graphlib.TopologicalSorter.prepare` to be called more than once
as long as sorting has not started.
- (Contributed by Daniel Pope in :gh:`130914`)
+ (Contributed by Daniel Pope in :gh:`130914`.)
+
+
+heapq
+-----
+
+* Add functions for working with max-heaps:
+
+ * :func:`heapq.heapify_max`,
+ * :func:`heapq.heappush_max`,
+ * :func:`heapq.heappop_max`,
+ * :func:`heapq.heapreplace_max`
+ * :func:`heapq.heappushpop_max`
hmac
@@ -1080,11 +1603,10 @@ json
.. _whatsnew314-color-json:
-* By default, the output of the :ref:`JSON command-line interface <json-commandline>`
- is highlighted in color. This can be controlled via the
- :envvar:`PYTHON_COLORS` environment variable as well as the canonical
- |NO_COLOR|_ and |FORCE_COLOR|_ environment variables. See also
- :ref:`using-on-controlling-color`.
+* By default, the output of the :ref:`JSON command-line interface
+ <json-commandline>` is highlighted in color.
+ This can be controlled by :ref:`environment variables
+ <using-on-controlling-color>`.
(Contributed by Tomas Roun in :gh:`131952`.)
linecache
@@ -1110,7 +1632,7 @@ math
----
* Added more detailed error messages for domain errors in the module.
- (Contributed by by Charlie Zhao and Sergey B Kirpichev in :gh:`101410`.)
+ (Contributed by Charlie Zhao and Sergey B Kirpichev in :gh:`101410`.)
mimetypes
@@ -1220,9 +1742,9 @@ multiprocessing
* The :ref:`multiprocessing proxy objects <multiprocessing-proxy_objects>`
for *list* and *dict* types gain previously overlooked missing methods:
- * :meth:`!clear` and :meth:`!copy` for proxies of :class:`list`.
+ * :meth:`!clear` and :meth:`!copy` for proxies of :class:`list`
* :meth:`~dict.fromkeys`, ``reversed(d)``, ``d | {}``, ``{} | d``,
- ``d |= {'b': 2}`` for proxies of :class:`dict`.
+ ``d |= {'b': 2}`` for proxies of :class:`dict`
(Contributed by Roy Hyunjin Han for :gh:`103134`.)
@@ -1232,9 +1754,9 @@ multiprocessing
(Contributed by Mingyu Park in :gh:`129949`.)
* Add :func:`multiprocessing.Process.interrupt` which terminates the child
- process by sending :py:const:`~signal.SIGINT`. This enables "finally" clauses
- and printing stack trace for the terminated process.
- (Contributed by Artem Pulkin in :gh:`131913`.)
+ process by sending :py:const:`~signal.SIGINT`. This enables
+ :keyword:`finally` clauses to print a stack trace for the terminated
+ process. (Contributed by Artem Pulkin in :gh:`131913`.)
operator
--------
@@ -1264,6 +1786,16 @@ os
(Contributed by Cody Maloney in :gh:`129205`.)
+os.path
+-------
+
+* The *strict* parameter to :func:`os.path.realpath` accepts a new value,
+ :data:`os.path.ALLOW_MISSING`.
+ If used, errors other than :exc:`FileNotFoundError` will be re-raised;
+ the resulting path can be missing but it will be free of symlinks.
+ (Contributed by Petr Viktorin for :cve:`2025-4517`.)
+
+
pathlib
-------
@@ -1314,6 +1846,11 @@ pdb
fill in a 4-space indentation now, instead of inserting a ``\t`` character.
(Contributed by Tian Gao in :gh:`130471`.)
+* Auto-indent is introduced in :mod:`pdb` multi-line input. It will either
+ keep the indentation of the last line or insert a 4-space indentation when
+ it detects a new code block.
+ (Contributed by Tian Gao in :gh:`133350`.)
+
* ``$_asynctask`` is added to access the current asyncio task if applicable.
(Contributed by Tian Gao in :gh:`124367`.)
@@ -1329,6 +1866,11 @@ pdb
function.
(Contributed by Tian Gao in :gh:`132576`.)
+* Source code displayed in :mod:`pdb` will be syntax-highlighted. This feature
+ can be controlled using the same methods as PyREPL, in addition to the newly
+ added ``colorize`` argument of :class:`pdb.Pdb`.
+ (Contributed by Tian Gao and Łukasz Langa in :gh:`133355`.)
+
pickle
------
@@ -1374,7 +1916,7 @@ socket
:const:`~socket.BTPROTO_HCI` on Linux.
(Contributed by Serhiy Storchaka in :gh:`70145`.)
* Accept an integer as the address for
- :const:`~socket.BTPROTO_HCI` on Linux
+ :const:`~socket.BTPROTO_HCI` on Linux.
(Contributed by Serhiy Storchaka in :gh:`132099`.)
* Return *cid* in :meth:`~socket.socket.getsockname` for
:const:`~socket.BTPROTO_L2CAP`.
@@ -1442,6 +1984,28 @@ sysconfig
(Contributed by Xuehai Pan in :gh:`131799`.)
+tarfile
+-------
+
+* :func:`~tarfile.data_filter` now normalizes symbolic link targets in order to
+ avoid path traversal attacks.
+ (Contributed by Petr Viktorin in :gh:`127987` and :cve:`2025-4138`.)
+* :func:`~tarfile.TarFile.extractall` now skips fixing up directory attributes
+ when a directory was removed or replaced by another kind of file.
+ (Contributed by Petr Viktorin in :gh:`127987` and :cve:`2024-12718`.)
+* :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall`
+ now (re-)apply the extraction filter when substituting a link (hard or
+ symbolic) with a copy of another archive member, and when fixing up
+ directory attributes.
+ The former raises a new exception, :exc:`~tarfile.LinkFallbackError`.
+ (Contributed by Petr Viktorin for :cve:`2025-4330` and :cve:`2024-12718`.)
+* :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall`
+ no longer extract rejected members when
+ :func:`~tarfile.TarFile.errorlevel` is zero.
+ (Contributed by Matt Prodani and Petr Viktorin in :gh:`112887`
+ and :cve:`2025-4435`.)
+
+
threading
---------
@@ -1453,8 +2017,8 @@ threading
tkinter
-------
-* Make tkinter widget methods :meth:`!after` and :meth:`!after_idle` accept
- arguments passed by keyword.
+* Make :mod:`tkinter` widget methods :meth:`!after` and :meth:`!after_idle`
+ accept arguments passed by keyword.
(Contributed by Zhikang Yan in :gh:`126899`.)
* Add ability to specify name for :class:`!tkinter.OptionMenu` and
@@ -1532,10 +2096,8 @@ unittest
--------
* :mod:`unittest` output is now colored by default.
- This can be controlled via the :envvar:`PYTHON_COLORS` environment
- variable as well as the canonical |NO_COLOR|_
- and |FORCE_COLOR|_ environment variables.
- See also :ref:`using-on-controlling-color`.
+ This can be controlled by :ref:`environment variables
+ <using-on-controlling-color>`.
(Contributed by Hugo van Kemenade in :gh:`127221`.)
* unittest discovery supports :term:`namespace package` as start
@@ -1574,9 +2136,11 @@ urllib
- Accept a complete URL when the new *require_scheme* argument is set to
true.
- - Discard URL authorities that resolve to a local IP address.
- - Raise :exc:`~urllib.error.URLError` if a URL authority doesn't resolve
- to a local IP address, except on Windows where we return a UNC path.
+ - Discard URL authority if it matches the local hostname.
+ - Discard URL authority if it resolves to a local IP address when the new
+ *resolve_host* argument is set to true.
+ - Raise :exc:`~urllib.error.URLError` if a URL authority isn't local,
+ except on Windows where we return a UNC path as before.
In :func:`urllib.request.pathname2url`:
@@ -1650,11 +2214,19 @@ Optimizations
asyncio
-------
-* :mod:`asyncio` now uses double linked list implementation for native tasks
- which speeds up execution by 10% on standard pyperformance benchmarks and
- reduces memory usage.
+* :mod:`asyncio` has a new per-thread double linked list implementation internally for
+ :class:`native tasks <asyncio.Task>` which speeds up execution by 10-20% on standard
+ pyperformance benchmarks and reduces memory usage.
+ This enables external introspection tools such as
+ :ref:`python -m asyncio pstree <whatsnew314-asyncio-introspection>`
+ to introspect the call graph of asyncio tasks running in all threads.
(Contributed by Kumar Aditya in :gh:`107803`.)
+* :mod:`asyncio` has first class support for :term:`free-threading builds <free threading>`.
+ This enables parallel execution of multiple event loops across different threads and scales
+ linearly with the number of threads.
+ (Contributed by Kumar Aditya in :gh:`128002`.)
+
* :mod:`asyncio` has new utility functions for introspecting and printing
the program's call graph: :func:`asyncio.capture_call_graph` and
:func:`asyncio.print_call_graph`.
@@ -1736,7 +2308,6 @@ Deprecated
* :class:`asyncio.WindowsProactorEventLoopPolicy`
* :func:`asyncio.get_event_loop_policy`
* :func:`asyncio.set_event_loop_policy`
- * :func:`asyncio.set_event_loop`
Users should use :func:`asyncio.run` or :class:`asyncio.Runner` with
*loop_factory* to use the desired event loop implementation.
@@ -1763,11 +2334,17 @@ Deprecated
(Contributed by Inada Naoki in :gh:`133036`.)
* :mod:`ctypes`:
- Calling :func:`ctypes.POINTER` on a string is deprecated.
- Use :ref:`ctypes-incomplete-types` for self-referential structures.
- Also, the internal ``ctypes._pointer_type_cache`` is deprecated.
- See :func:`ctypes.POINTER` for updated implementation details.
- (Contributed by Sergey Myrianov in :gh:`100926`.)
+
+ * On non-Windows platforms, setting :attr:`.Structure._pack_` to use a
+ MSVC-compatible default memory layout is deprecated in favor of setting
+ :attr:`.Structure._layout_` to ``'ms'``.
+ (Contributed by Petr Viktorin in :gh:`131747`.)
+
+ * Calling :func:`ctypes.POINTER` on a string is deprecated.
+ Use :ref:`ctypes-incomplete-types` for self-referential structures.
+ Also, the internal ``ctypes._pointer_type_cache`` is deprecated.
+ See :func:`ctypes.POINTER` for updated implementation details.
+ (Contributed by Sergey Myrianov in :gh:`100926`.)
* :mod:`functools`:
Calling the Python implementation of :func:`functools.reduce` with *function*
@@ -1836,6 +2413,8 @@ Deprecated
.. include:: ../deprecations/pending-removal-in-3.17.rst
+.. include:: ../deprecations/pending-removal-in-3.19.rst
+
.. include:: ../deprecations/pending-removal-in-future.rst
Removed
@@ -1931,7 +2510,7 @@ asyncio
asyncio.run(main())
- If you need to start something, e.g. a server listening on a socket
+ If you need to start something, for example, a server listening on a socket
and then run forever, use :func:`asyncio.run` and an
:class:`asyncio.Event`.
@@ -2061,7 +2640,9 @@ pty
sqlite3
-------
-* Remove :data:`!version` and :data:`!version_info` from :mod:`sqlite3`.
+* Remove :data:`!version` and :data:`!version_info` from :mod:`sqlite3`;
+ use :data:`~sqlite3.sqlite_version` and :data:`~sqlite3.sqlite_version_info`
+ for the actual version number of the runtime SQLite library.
(Contributed by Hugo van Kemenade in :gh:`118924`.)
* Disallow using a sequence of parameters with named placeholders.
@@ -2107,7 +2688,7 @@ Others
:meth:`~object.__index__`. (Contributed by Mark Dickinson in :gh:`119743`.)
-CPython Bytecode Changes
+CPython bytecode changes
========================
* Replaced the opcode ``BINARY_SUBSCR`` by :opcode:`BINARY_OP` with oparg ``NB_SUBSCR``.
@@ -2136,6 +2717,11 @@ Changes in the Python API
See :ref:`above <whatsnew314-typing-union>` for more details.
(Contributed by Jelle Zijlstra in :gh:`105499`.)
+* The runtime behavior of annotations has changed in various ways; see
+ :ref:`above <whatsnew314-pep649>` for details. While most code that interacts
+ with annotations should continue to work, some undocumented details may behave
+ differently.
+
Build changes
=============
@@ -2177,6 +2763,7 @@ New features
* :c:func:`PyUnicodeWriter_Discard`
* :c:func:`PyUnicodeWriter_Finish`
* :c:func:`PyUnicodeWriter_Format`
+ * :c:func:`PyUnicodeWriter_WriteASCII`
* :c:func:`PyUnicodeWriter_WriteChar`
* :c:func:`PyUnicodeWriter_WriteRepr`
* :c:func:`PyUnicodeWriter_WriteStr`
@@ -2248,19 +2835,19 @@ New features
* Add a new import and export API for Python :class:`int` objects (:pep:`757`):
- * :c:func:`PyLong_GetNativeLayout`;
- * :c:func:`PyLong_Export`;
- * :c:func:`PyLong_FreeExport`;
- * :c:func:`PyLongWriter_Create`;
- * :c:func:`PyLongWriter_Finish`;
- * :c:func:`PyLongWriter_Discard`.
+ * :c:func:`PyLong_GetNativeLayout`
+ * :c:func:`PyLong_Export`
+ * :c:func:`PyLong_FreeExport`
+ * :c:func:`PyLongWriter_Create`
+ * :c:func:`PyLongWriter_Finish`
+ * :c:func:`PyLongWriter_Discard`
(Contributed by Sergey B Kirpichev and Victor Stinner in :gh:`102471`.)
* Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier
superclass identification, which attempts to resolve the `type checking issue
- <https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630`
- (:gh:`124153`).
+ <https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630`.
+ (Contributed in :gh:`124153`.)
* Add :c:func:`PyUnicode_Equal` function to the limited C API:
test if two strings are equal.
@@ -2314,6 +2901,10 @@ New features
be used in some cases as a replacement for checking if :c:func:`Py_REFCNT`
is ``1`` for Python objects passed as arguments to C API functions.
+* Add :c:func:`PyUnstable_Object_IsUniquelyReferenced` as a replacement for
+ ``Py_REFCNT(op) == 1`` on :term:`free threaded <free threading>` builds.
+ (Contributed by Peter Bierma in :gh:`133140`.)
+
Limited C API changes
---------------------
@@ -2361,24 +2952,24 @@ Porting to Python 3.14
* Private functions promoted to public C APIs:
- * ``_PyBytes_Join()``: :c:func:`PyBytes_Join`.
- * ``_PyLong_IsNegative()``: :c:func:`PyLong_IsNegative`.
- * ``_PyLong_IsPositive()``: :c:func:`PyLong_IsPositive`.
- * ``_PyLong_IsZero()``: :c:func:`PyLong_IsZero`.
- * ``_PyLong_Sign()``: :c:func:`PyLong_GetSign`.
- * ``_PyUnicodeWriter_Dealloc()``: :c:func:`PyUnicodeWriter_Discard`.
- * ``_PyUnicodeWriter_Finish()``: :c:func:`PyUnicodeWriter_Finish`.
- * ``_PyUnicodeWriter_Init()``: use :c:func:`PyUnicodeWriter_Create`.
- * ``_PyUnicodeWriter_Prepare()``: (no replacement).
- * ``_PyUnicodeWriter_PrepareKind()``: (no replacement).
- * ``_PyUnicodeWriter_WriteChar()``: :c:func:`PyUnicodeWriter_WriteChar`.
- * ``_PyUnicodeWriter_WriteStr()``: :c:func:`PyUnicodeWriter_WriteStr`.
- * ``_PyUnicodeWriter_WriteSubstring()``: :c:func:`PyUnicodeWriter_WriteSubstring`.
- * ``_PyUnicode_EQ()``: :c:func:`PyUnicode_Equal`.
- * ``_PyUnicode_Equal()``: :c:func:`PyUnicode_Equal`.
- * ``_Py_GetConfig()``: :c:func:`PyConfig_Get` and :c:func:`PyConfig_GetInt`.
- * ``_Py_HashBytes()``: :c:func:`Py_HashBuffer`.
- * ``_Py_fopen_obj()``: :c:func:`Py_fopen`.
+ * ``_PyBytes_Join()``: :c:func:`PyBytes_Join`
+ * ``_PyLong_IsNegative()``: :c:func:`PyLong_IsNegative`
+ * ``_PyLong_IsPositive()``: :c:func:`PyLong_IsPositive`
+ * ``_PyLong_IsZero()``: :c:func:`PyLong_IsZero`
+ * ``_PyLong_Sign()``: :c:func:`PyLong_GetSign`
+ * ``_PyUnicodeWriter_Dealloc()``: :c:func:`PyUnicodeWriter_Discard`
+ * ``_PyUnicodeWriter_Finish()``: :c:func:`PyUnicodeWriter_Finish`
+ * ``_PyUnicodeWriter_Init()``: use :c:func:`PyUnicodeWriter_Create`
+ * ``_PyUnicodeWriter_Prepare()``: (no replacement)
+ * ``_PyUnicodeWriter_PrepareKind()``: (no replacement)
+ * ``_PyUnicodeWriter_WriteChar()``: :c:func:`PyUnicodeWriter_WriteChar`
+ * ``_PyUnicodeWriter_WriteStr()``: :c:func:`PyUnicodeWriter_WriteStr`
+ * ``_PyUnicodeWriter_WriteSubstring()``: :c:func:`PyUnicodeWriter_WriteSubstring`
+ * ``_PyUnicode_EQ()``: :c:func:`PyUnicode_Equal`
+ * ``_PyUnicode_Equal()``: :c:func:`PyUnicode_Equal`
+ * ``_Py_GetConfig()``: :c:func:`PyConfig_Get` and :c:func:`PyConfig_GetInt`
+ * ``_Py_HashBytes()``: :c:func:`Py_HashBuffer`
+ * ``_Py_fopen_obj()``: :c:func:`Py_fopen`
The `pythoncapi-compat project`_ can be used to get most of these new
functions on Python 3.13 and older.
@@ -2449,7 +3040,7 @@ Deprecated
:c:func:`PyUnicodeWriter_WriteSubstring(writer, str, start, end) <PyUnicodeWriter_WriteSubstring>`.
* :c:func:`!_PyUnicodeWriter_WriteASCIIString`:
replace ``_PyUnicodeWriter_WriteASCIIString(&writer, str)`` with
- :c:func:`PyUnicodeWriter_WriteUTF8(writer, str) <PyUnicodeWriter_WriteUTF8>`.
+ :c:func:`PyUnicodeWriter_WriteASCII(writer, str) <PyUnicodeWriter_WriteASCII>`.
* :c:func:`!_PyUnicodeWriter_WriteLatin1String`:
replace ``_PyUnicodeWriter_WriteLatin1String(&writer, str)`` with
:c:func:`PyUnicodeWriter_WriteUTF8(writer, str) <PyUnicodeWriter_WriteUTF8>`.
@@ -2462,6 +3053,8 @@ Deprecated
.. include:: ../deprecations/c-api-pending-removal-in-3.15.rst
+.. include:: ../deprecations/c-api-pending-removal-in-3.16.rst
+
.. include:: ../deprecations/c-api-pending-removal-in-3.18.rst
.. include:: ../deprecations/c-api-pending-removal-in-future.rst
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
new file mode 100644
index 00000000000..706a816f888
--- /dev/null
+++ b/Doc/whatsnew/3.15.rst
@@ -0,0 +1,429 @@
+
+****************************
+ What's new in Python 3.15
+****************************
+
+:Editor: TBD
+
+.. Rules for maintenance:
+
+ * Anyone can add text to this document. Do not spend very much time
+ on the wording of your changes, because your text will probably
+ get rewritten to some degree.
+
+ * The maintainer will go through Misc/NEWS periodically and add
+ changes; it's therefore more important to add your changes to
+ Misc/NEWS than to this file.
+
+ * This is not a complete list of every single change; completeness
+ is the purpose of Misc/NEWS. Some changes I consider too small
+ or esoteric to include. If such a change is added to the text,
+ I'll just remove it. (This is another reason you shouldn't spend
+ too much time on writing your addition.)
+
+ * If you want to draw your new text to the attention of the
+ maintainer, add 'XXX' to the beginning of the paragraph or
+ section.
+
+ * It's OK to just add a fragmentary note about a change. For
+ example: "XXX Describe the transmogrify() function added to the
+ socket module." The maintainer will research the change and
+ write the necessary text.
+
+ * You can comment out your additions if you like, but it's not
+ necessary (especially when a final release is some months away).
+
+ * Credit the author of a patch or bugfix. Just the name is
+ sufficient; the e-mail address isn't necessary.
+
+ * It's helpful to add the issue number as a comment:
+
+ XXX Describe the transmogrify() function added to the socket
+ module.
+ (Contributed by P.Y. Developer in :gh:`12345`.)
+
+ This saves the maintainer the effort of going through the VCS log
+ when researching a change.
+
+This article explains the new features in Python 3.15, compared to 3.14.
+
+For full details, see the :ref:`changelog <changelog>`.
+
+.. note::
+
+ Prerelease users should be aware that this document is currently in draft
+ form. It will be updated substantially as Python 3.15 moves towards release,
+ so it's worth checking back even after reading earlier versions.
+
+
+Summary --- release highlights
+==============================
+
+.. This section singles out the most important changes in Python 3.15.
+ Brevity is key.
+
+
+.. PEP-sized items next.
+
+
+
+New features
+============
+
+
+
+Other language changes
+======================
+
+* Several error messages incorrectly using the term "argument" have been corrected.
+ (Contributed by Stan Ulbrych in :gh:`133382`.)
+
+
+
+New modules
+===========
+
+* None yet.
+
+
+Improved modules
+================
+
+dbm
+---
+
+* Added new :meth:`!reorganize` methods to :mod:`dbm.dumb` and :mod:`dbm.sqlite3`
+ which allow to recover unused free space previously occupied by deleted entries.
+ (Contributed by Andrea Oliveri in :gh:`134004`.)
+
+* Add the ``'m'`` flag for :func:`dbm.gnu.open` which allows to disable
+ the use of :manpage:`mmap(2)`.
+ This may harm performance, but improve crash tolerance.
+ (Contributed by Serhiy Storchaka in :gh:`66234`.)
+
+difflib
+-------
+
+* Improved the styling of HTML diff pages generated by the :class:`difflib.HtmlDiff`
+ class, and migrated the output to the HTML5 standard.
+ (Contributed by Jiahao Li in :gh:`134580`.)
+
+
+math
+----
+
+* Add :func:`math.isnormal` and :func:`math.issubnormal` functions.
+ (Contributed by Sergey B Kirpichev in :gh:`132908`.)
+
+* Add :func:`math.signbit` function.
+ (Contributed by Bénédikt Tran in :gh:`135853`.)
+
+
+os.path
+-------
+
+* The *strict* parameter to :func:`os.path.realpath` accepts a new value,
+ :data:`os.path.ALLOW_MISSING`.
+ If used, errors other than :exc:`FileNotFoundError` will be re-raised;
+ the resulting path can be missing but it will be free of symlinks.
+ (Contributed by Petr Viktorin for :cve:`2025-4517`.)
+
+
+shelve
+------
+
+* Added new :meth:`!reorganize` method to :mod:`shelve` used to recover unused free
+ space previously occupied by deleted entries.
+ (Contributed by Andrea Oliveri in :gh:`134004`.)
+
+
+sqlite3
+-------
+
+* The :ref:`command-line interface <sqlite3-cli>` has several new features:
+
+ * SQL keyword completion on <tab>.
+ (Contributed by Long Tan in :gh:`133393`.)
+
+ * Prompts, error messages, and help text are now colored.
+ This is enabled by default, see :ref:`using-on-controlling-color` for
+ details.
+ (Contributed by Stan Ulbrych and Łukasz Langa in :gh:`133461`)
+
+
+ssl
+---
+
+* Indicate through :data:`ssl.HAS_PSK_TLS13` whether the :mod:`ssl` module
+ supports "External PSKs" in TLSv1.3, as described in RFC 9258.
+ (Contributed by Will Childs-Klein in :gh:`133624`.)
+
+
+tarfile
+-------
+
+* :func:`~tarfile.data_filter` now normalizes symbolic link targets in order to
+ avoid path traversal attacks.
+ (Contributed by Petr Viktorin in :gh:`127987` and :cve:`2025-4138`.)
+* :func:`~tarfile.TarFile.extractall` now skips fixing up directory attributes
+ when a directory was removed or replaced by another kind of file.
+ (Contributed by Petr Viktorin in :gh:`127987` and :cve:`2024-12718`.)
+* :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall`
+ now (re-)apply the extraction filter when substituting a link (hard or
+ symbolic) with a copy of another archive member, and when fixing up
+ directory attributes.
+ The former raises a new exception, :exc:`~tarfile.LinkFallbackError`.
+ (Contributed by Petr Viktorin for :cve:`2025-4330` and :cve:`2024-12718`.)
+* :func:`~tarfile.TarFile.extract` and :func:`~tarfile.TarFile.extractall`
+ no longer extract rejected members when
+ :func:`~tarfile.TarFile.errorlevel` is zero.
+ (Contributed by Matt Prodani and Petr Viktorin in :gh:`112887`
+ and :cve:`2025-4435`.)
+
+
+zlib
+----
+
+* Allow combining two Adler-32 checksums via :func:`~zlib.adler32_combine`.
+ (Contributed by Callum Attryde and Bénédikt Tran in :gh:`134635`.)
+
+* Allow combining two CRC-32 checksums via :func:`~zlib.crc32_combine`.
+ (Contributed by Bénédikt Tran in :gh:`134635`.)
+
+
+.. Add improved modules above alphabetically, not here at the end.
+
+Optimizations
+=============
+
+module_name
+-----------
+
+* TODO
+
+
+
+Deprecated
+==========
+
+hashlib
+-------
+
+* In hash function constructors such as :func:`~hashlib.new` or the
+ direct hash-named constructors such as :func:`~hashlib.md5` and
+ :func:`~hashlib.sha256`, their optional initial data parameter could
+ also be passed a keyword argument named ``data=`` or ``string=`` in
+ various :mod:`hashlib` implementations.
+
+ Support for the ``string`` keyword argument name is now deprecated and
+ is slated for removal in Python 3.19. Prefer passing the initial data as
+ a positional argument for maximum backwards compatibility.
+
+ (Contributed by Bénédikt Tran in :gh:`134978`.)
+
+
+.. Add deprecations above alphabetically, not here at the end.
+
+Removed
+=======
+
+ctypes
+------
+
+* Removed the undocumented function :func:`!ctypes.SetPointerType`,
+ which has been deprecated since Python 3.13.
+ (Contributed by Bénédikt Tran in :gh:`133866`.)
+
+
+http.server
+-----------
+
+* Removed the :class:`!CGIHTTPRequestHandler` class
+ and the ``--cgi`` flag from the :program:`python -m http.server`
+ command-line interface. They were deprecated in Python 3.13.
+ (Contributed by Bénédikt Tran in :gh:`133810`.)
+
+
+platform
+--------
+
+* Removed the :func:`!platform.java_ver` function,
+ which was deprecated since Python 3.13.
+ (Contributed by Alexey Makridenko in :gh:`133604`.)
+
+
+sre_*
+-----
+
+* Removed :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules.
+ (Contributed by Stan Ulbrych in :gh:`135994`.)
+
+
+sysconfig
+---------
+
+* Removed the *check_home* parameter of :func:`sysconfig.is_python_build`.
+ (Contributed by Filipe Laíns in :gh:`92897`.)
+
+
+threading
+---------
+
+* Remove support for arbitrary positional or keyword arguments in the C
+ implementation of :class:`~threading.RLock` objects. This was deprecated
+ in Python 3.14.
+ (Contributed by Bénédikt Tran in :gh:`134087`.)
+
+
+typing
+------
+
+* The undocumented keyword argument syntax for creating
+ :class:`~typing.NamedTuple` classes (for example,
+ ``Point = NamedTuple("Point", x=int, y=int)``) is no longer supported.
+ Use the class-based syntax or the functional syntax instead.
+ (Contributed by Bénédikt Tran in :gh:`133817`.)
+
+* Using ``TD = TypedDict("TD")`` or ``TD = TypedDict("TD", None)`` to
+ construct a :class:`~typing.TypedDict` type with zero field is no
+ longer supported. Use ``class TD(TypedDict): pass``
+ or ``TD = TypedDict("TD", {})`` instead.
+ (Contributed by Bénédikt Tran in :gh:`133823`.)
+
+
+unittest
+--------
+
+* Lets users specify formatter in TestCase.assertLogs.
+ :func:`unittest.TestCase.assertLogs` will now accept a formatter
+ to control how messages are formatted.
+ (Contributed by Garry Cairns in :gh:`134567`.)
+
+
+wave
+----
+
+* Removed the ``getmark()``, ``setmark()`` and ``getmarkers()`` methods
+ of the :class:`~wave.Wave_read` and :class:`~wave.Wave_write` classes,
+ which were deprecated since Python 3.13.
+ (Contributed by Bénédikt Tran in :gh:`133873`.)
+
+
+Porting to Python 3.15
+======================
+
+This section lists previously described changes and other bugfixes
+that may require changes to your code.
+
+
+Build changes
+=============
+
+* Removed implicit fallback to the bundled copy of the ``libmpdec`` library.
+ Now this should be explicitly enabled with :option:`--with-system-libmpdec`
+ set to ``no`` or with :option:`!--without-system-libmpdec`.
+ (Contributed by Sergey B Kirpichev in :gh:`115119`.)
+
+
+C API changes
+=============
+
+New features
+------------
+
+* Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`,
+ :c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString`
+ functions as replacements for :c:func:`PySys_GetObject`.
+ (Contributed by Serhiy Storchaka in :gh:`108512`.)
+
+* Add :c:type:`PyUnstable_Unicode_GET_CACHED_HASH` to get the cached hash of
+ a string. See the documentation for caveats.
+ (Contributed by Petr Viktorin in :gh:`131510`)
+
+
+Porting to Python 3.15
+----------------------
+
+* :class:`sqlite3.Connection` APIs has been cleaned up.
+
+ * All parameters of :func:`sqlite3.connect` except *database* are now keyword-only.
+ * The first three parameters of methods :meth:`~sqlite3.Connection.create_function`
+ and :meth:`~sqlite3.Connection.create_aggregate` are now positional-only.
+ * The first parameter of methods :meth:`~sqlite3.Connection.set_authorizer`,
+ :meth:`~sqlite3.Connection.set_progress_handler` and
+ :meth:`~sqlite3.Connection.set_trace_callback` is now positional-only.
+
+ (Contributed by Serhiy Storchaka in :gh:`133595`.)
+
+* Private functions promoted to public C APIs:
+
+ * ``PyMutex_IsLocked()`` : :c:func:`PyMutex_IsLocked`
+
+ The |pythoncapi_compat_project| can be used to get most of these new
+ functions on Python 3.14 and older.
+
+Deprecated C APIs
+-----------------
+
+* TODO
+
+.. Add C API deprecations above alphabetically, not here at the end.
+
+Removed C APIs
+--------------
+
+* Remove deprecated ``PyUnicode`` functions:
+
+ * :c:func:`!PyUnicode_AsDecodedObject`:
+ Use :c:func:`PyCodec_Decode` instead.
+ * :c:func:`!PyUnicode_AsDecodedUnicode`:
+ Use :c:func:`PyCodec_Decode` instead; Note that some codecs (for example, "base64")
+ may return a type other than :class:`str`, such as :class:`bytes`.
+ * :c:func:`!PyUnicode_AsEncodedObject`:
+ Use :c:func:`PyCodec_Encode` instead.
+ * :c:func:`!PyUnicode_AsEncodedUnicode`:
+ Use :c:func:`PyCodec_Encode` instead; Note that some codecs (for example, "base64")
+ may return a type other than :class:`bytes`, such as :class:`str`.
+
+ (Contributed by Stan Ulbrych in :gh:`133612`)
+
+* :c:func:`!PyImport_ImportModuleNoBlock`: deprecated alias
+ of :c:func:`PyImport_ImportModule`.
+ (Contributed by Bénédikt Tran in :gh:`133644`.)
+
+The following functions are removed in favor of :c:func:`PyConfig_Get`.
+The |pythoncapi_compat_project| can be used to get :c:func:`!PyConfig_Get`
+on Python 3.13 and older.
+
+* Python initialization functions:
+
+ * :c:func:`!Py_GetExecPrefix`:
+ use :c:func:`PyConfig_Get("base_exec_prefix") <PyConfig_Get>`
+ (:data:`sys.base_exec_prefix`) instead.
+ Use :c:func:`PyConfig_Get("exec_prefix") <PyConfig_Get>`
+ (:data:`sys.exec_prefix`) if :ref:`virtual environments <venv-def>`
+ need to be handled.
+ * :c:func:`!Py_GetPath`:
+ use :c:func:`PyConfig_Get("module_search_paths") <PyConfig_Get>`
+ (:data:`sys.path`) instead.
+ * :c:func:`!Py_GetPrefix`:
+ use :c:func:`PyConfig_Get("base_prefix") <PyConfig_Get>`
+ (:data:`sys.base_prefix`) instead.
+ Use :c:func:`PyConfig_Get("prefix") <PyConfig_Get>`
+ (:data:`sys.prefix`) if :ref:`virtual environments <venv-def>`
+ need to be handled.
+ * :c:func:`!Py_GetProgramFullPath`:
+ use :c:func:`PyConfig_Get("executable") <PyConfig_Get>`
+ (:data:`sys.executable`) instead.
+ * :c:func:`!Py_GetProgramName`:
+ use :c:func:`PyConfig_Get("executable") <PyConfig_Get>`
+ (:data:`sys.executable`) instead.
+ * :c:func:`!Py_GetPythonHome`:
+ use :c:func:`PyConfig_Get("home") <PyConfig_Get>` or the
+ :envvar:`PYTHONHOME` environment variable instead.
+
+ (Contributed by Bénédikt Tran in :gh:`133644`.)
+
+.. |pythoncapi_compat_project| replace:: |pythoncapi_compat_project_link|_
+.. |pythoncapi_compat_project_link| replace:: pythoncapi-compat project
+.. _pythoncapi_compat_project_link: https://github.com/python/pythoncapi-compat
diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst
index 7a8eb47cbdb..89fd6868645 100644
--- a/Doc/whatsnew/3.3.rst
+++ b/Doc/whatsnew/3.3.rst
@@ -829,7 +829,7 @@ Previous versions of CPython have always relied on a global import lock.
This led to unexpected annoyances, such as deadlocks when importing a module
would trigger code execution in a different thread as a side-effect.
Clumsy workarounds were sometimes employed, such as the
-:c:func:`PyImport_ImportModuleNoBlock` C API function.
+:c:func:`!PyImport_ImportModuleNoBlock` C API function.
In Python 3.3, importing a module takes a per-module lock. This correctly
serializes importation of a given module from multiple threads (preventing
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index 7aca35b2959..bc2eb1d0e26 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -1629,8 +1629,8 @@ Build and C API Changes
(Contributed by Pablo Galindo in :issue:`37221`.)
* :c:func:`!Py_SetPath` now sets :data:`sys.executable` to the program full
- path (:c:func:`Py_GetProgramFullPath`) rather than to the program name
- (:c:func:`Py_GetProgramName`).
+ path (:c:func:`!Py_GetProgramFullPath`) rather than to the program name
+ (:c:func:`!Py_GetProgramName`).
(Contributed by Victor Stinner in :issue:`38234`.)
diff --git a/Doc/whatsnew/index.rst b/Doc/whatsnew/index.rst
index 6ff722a1894..38194db670b 100644
--- a/Doc/whatsnew/index.rst
+++ b/Doc/whatsnew/index.rst
@@ -11,6 +11,7 @@ anyone wishing to stay up-to-date after a new release.
.. toctree::
:maxdepth: 2
+ 3.15.rst
3.14.rst
3.13.rst
3.12.rst
diff --git a/Grammar/Tokens b/Grammar/Tokens
index e40a4437afb..0547e6ed08f 100644
--- a/Grammar/Tokens
+++ b/Grammar/Tokens
@@ -1,3 +1,8 @@
+# When adding new tokens, remember to update the PEG generator in
+# Tools/peg_generator/pegen/parser_generator.py
+# This will ensure that older versions of Python can generate a Python parser
+# using "python -m pegen python <GRAMMAR FILE>".
+
ENDMARKER
NAME
NUMBER
diff --git a/Grammar/python.gram b/Grammar/python.gram
index f3ef990923e..d1af7704e9b 100644
--- a/Grammar/python.gram
+++ b/Grammar/python.gram
@@ -96,12 +96,12 @@ func_type[mod_ty]: '(' a=[type_expressions] ')' '->' b=expression NEWLINE* ENDMA
statements[asdl_stmt_seq*]: a=statement+ { _PyPegen_register_stmts(p, (asdl_stmt_seq*)_PyPegen_seq_flatten(p, a)) }
-statement[asdl_stmt_seq*]:
- | a=compound_stmt { (asdl_stmt_seq*)_PyPegen_singleton_seq(p, a) }
+statement[asdl_stmt_seq*]:
+ | a=compound_stmt { (asdl_stmt_seq*)_PyPegen_singleton_seq(p, a) }
| a[asdl_stmt_seq*]=simple_stmts { a }
single_compound_stmt[asdl_stmt_seq*]:
- | a=compound_stmt {
+ | a=compound_stmt {
_PyPegen_register_stmts(p, (asdl_stmt_seq*)_PyPegen_singleton_seq(p, a)) }
statement_newline[asdl_stmt_seq*]:
@@ -184,7 +184,9 @@ return_stmt[stmt_ty]:
| 'return' a=[star_expressions] { _PyAST_Return(a, EXTRA) }
raise_stmt[stmt_ty]:
- | 'raise' a=expression b=['from' z=expression { z }] { _PyAST_Raise(a, b, EXTRA) }
+ | 'raise' a=expression 'from' b=expression { _PyAST_Raise(a, b, EXTRA) }
+ | invalid_raise_stmt
+ | 'raise' a=expression { _PyAST_Raise(a, NULL, EXTRA) }
| 'raise' { _PyAST_Raise(NULL, NULL, EXTRA) }
pass_stmt[stmt_ty]:
@@ -449,9 +451,9 @@ except_block[excepthandler_ty]:
_PyAST_ExceptHandler(e, ((expr_ty) t)->v.Name.id, b, EXTRA) }
| 'except' e=expressions ':' b=block {
CHECK_VERSION(
- excepthandler_ty,
- 14,
- "except expressions without parentheses are",
+ excepthandler_ty,
+ 14,
+ "except expressions without parentheses are",
_PyAST_ExceptHandler(e, NULL, b, EXTRA)) }
| 'except' ':' b=block { _PyAST_ExceptHandler(NULL, NULL, b, EXTRA) }
| invalid_except_stmt
@@ -463,9 +465,9 @@ except_star_block[excepthandler_ty]:
_PyAST_ExceptHandler(e, ((expr_ty) t)->v.Name.id, b, EXTRA) }
| 'except' '*' e=expressions ':' b=block {
CHECK_VERSION(
- excepthandler_ty,
- 14,
- "except expressions without parentheses are",
+ excepthandler_ty,
+ 14,
+ "except expressions without parentheses are",
_PyAST_ExceptHandler(e, NULL, b, EXTRA)) }
| invalid_except_star_stmt
finally_block[asdl_stmt_seq*]:
@@ -977,11 +979,11 @@ tstring_middle[expr_ty]:
| tstring_replacement_field
| t=TSTRING_MIDDLE { _PyPegen_constant_from_token(p, t) }
tstring[expr_ty] (memo):
- | a=TSTRING_START b=tstring_middle* c=TSTRING_END {
+ | a=TSTRING_START b=tstring_middle* c=TSTRING_END {
CHECK_VERSION(
- expr_ty,
- 14,
- "t-strings are",
+ expr_ty,
+ 14,
+ "t-strings are",
_PyPegen_template_str(p, a, (asdl_expr_seq*)b, c)) }
string[expr_ty]: s[Token*]=STRING { _PyPegen_constant_from_string(p, s) }
@@ -1287,6 +1289,11 @@ invalid_ann_assign_target[expr_ty]:
| list
| tuple
| '(' a=invalid_ann_assign_target ')' { a }
+invalid_raise_stmt:
+ | a='raise' b='from' {
+ RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "did you forget an expression between 'raise' and 'from'?") }
+ | 'raise' expression a='from' {
+ RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "did you forget an expression after 'from'?") }
invalid_del_stmt:
| 'del' a=star_expressions {
RAISE_SYNTAX_ERROR_INVALID_TARGET(DEL_TARGETS, a) }
@@ -1305,7 +1312,7 @@ invalid_dict_comprehension:
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in dict comprehension") }
invalid_parameters:
| a="/" ',' {
- RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") }
+ RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one parameter must precede /") }
| (slash_no_default | slash_with_default) param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") }
| slash_no_default? param_no_default* invalid_parameters_helper a=param_no_default {
@@ -1319,21 +1326,21 @@ invalid_parameters:
invalid_default:
| a='=' &(')'|',') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected default value expression") }
invalid_star_etc:
- | a='*' (')' | ',' (')' | '**')) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "named arguments must follow bare *") }
+ | a='*' (')' | ',' (')' | '**')) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "named parameters must follow bare *") }
| '*' ',' TYPE_COMMENT { RAISE_SYNTAX_ERROR("bare * has associated type comment") }
- | '*' param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-positional argument cannot have default value") }
+ | '*' param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-positional parameter cannot have default value") }
| '*' (param_no_default | ',') param_maybe_default* a='*' (param_no_default | ',') {
- RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "* argument may appear only once") }
+ RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "* may appear only once") }
invalid_kwds:
- | '**' param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-keyword argument cannot have default value") }
- | '**' param ',' a=param { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
- | '**' param ',' a[Token*]=('*'|'**'|'/') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
+ | '**' param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-keyword parameter cannot have default value") }
+ | '**' param ',' a=param { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameters cannot follow var-keyword parameter") }
+ | '**' param ',' a[Token*]=('*'|'**'|'/') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameters cannot follow var-keyword parameter") }
invalid_parameters_helper: # This is only there to avoid type errors
| a=slash_with_default { _PyPegen_singleton_seq(p, a) }
| param_with_default+
invalid_lambda_parameters:
| a="/" ',' {
- RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") }
+ RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one parameter must precede /") }
| (lambda_slash_no_default | lambda_slash_with_default) lambda_param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") }
| lambda_slash_no_default? lambda_param_no_default* invalid_lambda_parameters_helper a=lambda_param_no_default {
@@ -1348,14 +1355,14 @@ invalid_lambda_parameters_helper:
| a=lambda_slash_with_default { _PyPegen_singleton_seq(p, a) }
| lambda_param_with_default+
invalid_lambda_star_etc:
- | '*' (':' | ',' (':' | '**')) { RAISE_SYNTAX_ERROR("named arguments must follow bare *") }
- | '*' lambda_param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-positional argument cannot have default value") }
+ | '*' (':' | ',' (':' | '**')) { RAISE_SYNTAX_ERROR("named parameters must follow bare *") }
+ | '*' lambda_param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-positional parameter cannot have default value") }
| '*' (lambda_param_no_default | ',') lambda_param_maybe_default* a='*' (lambda_param_no_default | ',') {
- RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "* argument may appear only once") }
+ RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "* may appear only once") }
invalid_lambda_kwds:
- | '**' lambda_param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-keyword argument cannot have default value") }
- | '**' lambda_param ',' a=lambda_param { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
- | '**' lambda_param ',' a[Token*]=('*'|'**'|'/') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
+ | '**' lambda_param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-keyword parameter cannot have default value") }
+ | '**' lambda_param ',' a=lambda_param { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameters cannot follow var-keyword parameter") }
+ | '**' lambda_param ',' a[Token*]=('*'|'**'|'/') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameters cannot follow var-keyword parameter") }
invalid_double_type_comments:
| TYPE_COMMENT NEWLINE TYPE_COMMENT NEWLINE INDENT {
RAISE_SYNTAX_ERROR("Cannot have two type comments on def") }
@@ -1383,11 +1390,11 @@ invalid_import:
RAISE_SYNTAX_ERROR_STARTING_FROM(token, "Expected one or more names after 'import'") }
invalid_dotted_as_name:
| dotted_name 'as' !(NAME (',' | ')' | NEWLINE)) a=expression {
- RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a,
+ RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a,
"cannot use %s as import target", _PyPegen_get_expr_name(a)) }
invalid_import_from_as_name:
| NAME 'as' !(NAME (',' | ')' | NEWLINE)) a=expression {
- RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a,
+ RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a,
"cannot use %s as import target", _PyPegen_get_expr_name(a)) }
invalid_import_from_targets:
@@ -1418,7 +1425,7 @@ invalid_except_stmt:
RAISE_SYNTAX_ERROR_STARTING_FROM(a, "multiple exception types must be parenthesized when using 'as'") }
| a='except' expression ['as' NAME ] NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
| a='except' NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
- | 'except' expression 'as' a=expression {
+ | 'except' expression 'as' a=expression ':' block {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
a, "cannot use except statement with %s", _PyPegen_get_expr_name(a)) }
invalid_except_star_stmt:
@@ -1426,7 +1433,7 @@ invalid_except_star_stmt:
RAISE_SYNTAX_ERROR_STARTING_FROM(a, "multiple exception types must be parenthesized when using 'as'") }
| a='except' '*' expression ['as' NAME ] NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
| a='except' '*' (NEWLINE | ':') { RAISE_SYNTAX_ERROR("expected one or more exception types") }
- | 'except' '*' expression 'as' a=expression {
+ | 'except' '*' expression 'as' a=expression ':' block {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
a, "cannot use except* statement with %s", _PyPegen_get_expr_name(a)) }
invalid_finally_stmt:
diff --git a/Include/abstract.h b/Include/abstract.h
index b9199fc03a3..80f3298701d 100644
--- a/Include/abstract.h
+++ b/Include/abstract.h
@@ -138,7 +138,12 @@ extern "C" {
Delete attribute named attr_name, for object o. Returns
-1 on failure.
- This is the equivalent of the Python statement: del o.attr_name. */
+ This is the equivalent of the Python statement: del o.attr_name.
+
+ Implemented as a macro in the limited C API 3.12 and older. */
+#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 0x030d0000
+# define PyObject_DelAttrString(O, A) PyObject_SetAttrString((O), (A), NULL)
+#endif
/* Implemented elsewhere:
@@ -147,7 +152,12 @@ extern "C" {
Delete attribute named attr_name, for object o. Returns -1
on failure. This is the equivalent of the Python
- statement: del o.attr_name. */
+ statement: del o.attr_name.
+
+ Implemented as a macro in the limited C API 3.12 and older. */
+#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 0x030d0000
+# define PyObject_DelAttr(O, A) PyObject_SetAttr((O), (A), NULL)
+#endif
/* Implemented elsewhere:
diff --git a/Include/audit.h b/Include/audit.h
index 793b7077e10..9be54ad4411 100644
--- a/Include/audit.h
+++ b/Include/audit.h
@@ -1,5 +1,5 @@
-#ifndef Py_AUDIT_H
-#define Py_AUDIT_H
+#ifndef _Py_AUDIT_H
+#define _Py_AUDIT_H
#ifdef __cplusplus
extern "C" {
#endif
@@ -18,13 +18,13 @@ PyAPI_FUNC(int) PySys_AuditTuple(
#ifndef Py_LIMITED_API
-# define Py_CPYTHON_AUDIT_H
+# define _Py_CPYTHON_AUDIT_H
# include "cpython/audit.h"
-# undef Py_CPYTHON_AUDIT_H
+# undef _Py_CPYTHON_AUDIT_H
#endif
#ifdef __cplusplus
}
#endif
-#endif /* !Py_AUDIT_H */
+#endif /* !_Py_AUDIT_H */
diff --git a/Include/boolobject.h b/Include/boolobject.h
index 3037e61bbf6..b56e2baecaa 100644
--- a/Include/boolobject.h
+++ b/Include/boolobject.h
@@ -34,9 +34,16 @@ PyAPI_FUNC(int) Py_IsTrue(PyObject *x);
PyAPI_FUNC(int) Py_IsFalse(PyObject *x);
#define Py_IsFalse(x) Py_Is((x), Py_False)
-/* Macros for returning Py_True or Py_False, respectively */
-#define Py_RETURN_TRUE return Py_True
-#define Py_RETURN_FALSE return Py_False
+/* Macros for returning Py_True or Py_False, respectively.
+ * Only treat Py_True and Py_False as immortal in the limited C API 3.12
+ * and newer. */
+#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 0x030c0000
+# define Py_RETURN_TRUE return Py_NewRef(Py_True)
+# define Py_RETURN_FALSE return Py_NewRef(Py_False)
+#else
+# define Py_RETURN_TRUE return Py_True
+# define Py_RETURN_FALSE return Py_False
+#endif
/* Function to return a bool from a C long */
PyAPI_FUNC(PyObject *) PyBool_FromLong(long);
diff --git a/Include/ceval.h b/Include/ceval.h
index 32ab38972e5..e9df8684996 100644
--- a/Include/ceval.h
+++ b/Include/ceval.h
@@ -133,13 +133,6 @@ PyAPI_FUNC(void) PyEval_ReleaseThread(PyThreadState *tstate);
#define FVS_MASK 0x4
#define FVS_HAVE_SPEC 0x4
-/* Special methods used by LOAD_SPECIAL */
-#define SPECIAL___ENTER__ 0
-#define SPECIAL___EXIT__ 1
-#define SPECIAL___AENTER__ 2
-#define SPECIAL___AEXIT__ 3
-#define SPECIAL_MAX 3
-
#ifndef Py_LIMITED_API
# define Py_CPYTHON_CEVAL_H
# include "cpython/ceval.h"
diff --git a/Include/cpython/audit.h b/Include/cpython/audit.h
index 3c5c7a8c060..536f9248632 100644
--- a/Include/cpython/audit.h
+++ b/Include/cpython/audit.h
@@ -1,4 +1,4 @@
-#ifndef Py_CPYTHON_AUDIT_H
+#ifndef _Py_CPYTHON_AUDIT_H
# error "this header file must not be included directly"
#endif
diff --git a/Include/cpython/lock.h b/Include/cpython/lock.h
index 8ee03e82f74..63886fca28e 100644
--- a/Include/cpython/lock.h
+++ b/Include/cpython/lock.h
@@ -36,6 +36,9 @@ PyAPI_FUNC(void) PyMutex_Lock(PyMutex *m);
// exported function for unlocking the mutex
PyAPI_FUNC(void) PyMutex_Unlock(PyMutex *m);
+// exported function for checking if the mutex is locked
+PyAPI_FUNC(int) PyMutex_IsLocked(PyMutex *m);
+
// Locks the mutex.
//
// If the mutex is currently locked, the calling thread will be parked until
@@ -61,3 +64,11 @@ _PyMutex_Unlock(PyMutex *m)
}
}
#define PyMutex_Unlock _PyMutex_Unlock
+
+// Checks if the mutex is currently locked.
+static inline int
+_PyMutex_IsLocked(PyMutex *m)
+{
+ return (_Py_atomic_load_uint8(&m->_bits) & _Py_LOCKED) != 0;
+}
+#define PyMutex_IsLocked _PyMutex_IsLocked
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index 3a4d65f7712..973d358ed8e 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -489,3 +489,5 @@ PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *);
// before calling this function in order to avoid spurious failures.
PyAPI_FUNC(int) PyUnstable_TryIncRef(PyObject *);
PyAPI_FUNC(void) PyUnstable_EnableTryIncRef(PyObject *);
+
+PyAPI_FUNC(int) PyUnstable_Object_IsUniquelyReferenced(PyObject *);
diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h
index 97c097aa01c..be582122118 100644
--- a/Include/cpython/pystate.h
+++ b/Include/cpython/pystate.h
@@ -28,10 +28,10 @@ typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);
#define PyTrace_OPCODE 7
/* Remote debugger support */
-#define MAX_SCRIPT_PATH_SIZE 512
-typedef struct _remote_debugger_support {
+#define Py_MAX_SCRIPT_PATH_SIZE 512
+typedef struct {
int32_t debugger_pending_call;
- char debugger_script_path[MAX_SCRIPT_PATH_SIZE];
+ char debugger_script_path[Py_MAX_SCRIPT_PATH_SIZE];
} _PyRemoteDebuggerSupport;
typedef struct _err_stackitem {
@@ -61,6 +61,8 @@ typedef struct _stack_chunk {
PyObject * data[1]; /* Variable sized */
} _PyStackChunk;
+/* Minimum size of data stack chunk */
+#define _PY_DATA_STACK_CHUNK_SIZE (16*1024)
struct _ts {
/* See Python/ceval.c for comments explaining most fields */
@@ -194,7 +196,7 @@ struct _ts {
/* The thread's exception stack entry. (Always the last entry.) */
_PyErr_StackItem exc_state;
- PyObject *previous_executor;
+ PyObject *current_executor;
uint64_t dict_global_version;
diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h
index 136f5d5c5f8..86c502730f4 100644
--- a/Include/cpython/unicodeobject.h
+++ b/Include/cpython/unicodeobject.h
@@ -47,6 +47,63 @@ static inline Py_UCS4 Py_UNICODE_LOW_SURROGATE(Py_UCS4 ch) {
/* --- Unicode Type ------------------------------------------------------- */
+struct _PyUnicodeObject_state {
+ /* If interned is non-zero, the two references from the
+ dictionary to this object are *not* counted in ob_refcnt.
+ The possible values here are:
+ 0: Not Interned
+ 1: Interned
+ 2: Interned and Immortal
+ 3: Interned, Immortal, and Static
+ This categorization allows the runtime to determine the right
+ cleanup mechanism at runtime shutdown. */
+#ifdef Py_GIL_DISABLED
+ // Needs to be accessed atomically, so can't be a bit field.
+ unsigned char interned;
+#else
+ unsigned int interned:2;
+#endif
+ /* Character size:
+
+ - PyUnicode_1BYTE_KIND (1):
+
+ * character type = Py_UCS1 (8 bits, unsigned)
+ * all characters are in the range U+0000-U+00FF (latin1)
+ * if ascii is set, all characters are in the range U+0000-U+007F
+ (ASCII), otherwise at least one character is in the range
+ U+0080-U+00FF
+
+ - PyUnicode_2BYTE_KIND (2):
+
+ * character type = Py_UCS2 (16 bits, unsigned)
+ * all characters are in the range U+0000-U+FFFF (BMP)
+ * at least one character is in the range U+0100-U+FFFF
+
+ - PyUnicode_4BYTE_KIND (4):
+
+ * character type = Py_UCS4 (32 bits, unsigned)
+ * all characters are in the range U+0000-U+10FFFF
+ * at least one character is in the range U+10000-U+10FFFF
+ */
+ unsigned int kind:3;
+ /* Compact is with respect to the allocation scheme. Compact unicode
+ objects only require one memory block while non-compact objects use
+ one block for the PyUnicodeObject struct and another for its data
+ buffer. */
+ unsigned int compact:1;
+ /* The string only contains characters in the range U+0000-U+007F (ASCII)
+ and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is
+ set, use the PyASCIIObject structure. */
+ unsigned int ascii:1;
+ /* The object is statically allocated. */
+ unsigned int statically_allocated:1;
+#ifndef Py_GIL_DISABLED
+ /* Historical: padding to ensure that PyUnicode_DATA() is always aligned to
+ 4 bytes (see issue gh-63736 on m68k) */
+ unsigned int :24;
+#endif
+};
+
/* ASCII-only strings created through PyUnicode_New use the PyASCIIObject
structure. state.ascii and state.compact are set, and the data
immediately follow the structure. utf8_length can be found
@@ -99,67 +156,8 @@ typedef struct {
PyObject_HEAD
Py_ssize_t length; /* Number of code points in the string */
Py_hash_t hash; /* Hash value; -1 if not set */
-#ifdef Py_GIL_DISABLED
- /* Ensure 4 byte alignment for PyUnicode_DATA(), see gh-63736 on m68k.
- In the non-free-threaded build, we'll use explicit padding instead */
- _Py_ALIGN_AS(4)
-#endif
- struct {
- /* If interned is non-zero, the two references from the
- dictionary to this object are *not* counted in ob_refcnt.
- The possible values here are:
- 0: Not Interned
- 1: Interned
- 2: Interned and Immortal
- 3: Interned, Immortal, and Static
- This categorization allows the runtime to determine the right
- cleanup mechanism at runtime shutdown. */
-#ifdef Py_GIL_DISABLED
- // Needs to be accessed atomically, so can't be a bit field.
- unsigned char interned;
-#else
- unsigned int interned:2;
-#endif
- /* Character size:
-
- - PyUnicode_1BYTE_KIND (1):
-
- * character type = Py_UCS1 (8 bits, unsigned)
- * all characters are in the range U+0000-U+00FF (latin1)
- * if ascii is set, all characters are in the range U+0000-U+007F
- (ASCII), otherwise at least one character is in the range
- U+0080-U+00FF
-
- - PyUnicode_2BYTE_KIND (2):
-
- * character type = Py_UCS2 (16 bits, unsigned)
- * all characters are in the range U+0000-U+FFFF (BMP)
- * at least one character is in the range U+0100-U+FFFF
-
- - PyUnicode_4BYTE_KIND (4):
-
- * character type = Py_UCS4 (32 bits, unsigned)
- * all characters are in the range U+0000-U+10FFFF
- * at least one character is in the range U+10000-U+10FFFF
- */
- unsigned int kind:3;
- /* Compact is with respect to the allocation scheme. Compact unicode
- objects only require one memory block while non-compact objects use
- one block for the PyUnicodeObject struct and another for its data
- buffer. */
- unsigned int compact:1;
- /* The string only contains characters in the range U+0000-U+007F (ASCII)
- and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is
- set, use the PyASCIIObject structure. */
- unsigned int ascii:1;
- /* The object is statically allocated. */
- unsigned int statically_allocated:1;
-#ifndef Py_GIL_DISABLED
- /* Padding to ensure that PyUnicode_DATA() is always aligned to
- 4 bytes (see issue gh-63736 on m68k) */
- unsigned int :24;
-#endif
- } state;
+ /* Ensure 4 byte alignment for PyUnicode_DATA(), see gh-63736 on m68k. */
+ _Py_ALIGNED_DEF(4, struct _PyUnicodeObject_state) state;
} PyASCIIObject;
/* Non-ASCII strings allocated through PyUnicode_New use the
@@ -300,6 +298,17 @@ static inline Py_ssize_t PyUnicode_GET_LENGTH(PyObject *op) {
}
#define PyUnicode_GET_LENGTH(op) PyUnicode_GET_LENGTH(_PyObject_CAST(op))
+/* Returns the cached hash, or -1 if not cached yet. */
+static inline Py_hash_t
+PyUnstable_Unicode_GET_CACHED_HASH(PyObject *op) {
+ assert(PyUnicode_Check(op));
+#ifdef Py_GIL_DISABLED
+ return _Py_atomic_load_ssize_relaxed(&_PyASCIIObject_CAST(op)->hash);
+#else
+ return _PyASCIIObject_CAST(op)->hash;
+#endif
+}
+
/* Write into the canonical representation, this function does not do any sanity
checks and is intended for usage in loops. The caller should cache the
kind and data pointers obtained from other function calls.
@@ -478,6 +487,10 @@ PyAPI_FUNC(int) PyUnicodeWriter_WriteUTF8(
PyUnicodeWriter *writer,
const char *str,
Py_ssize_t size);
+PyAPI_FUNC(int) PyUnicodeWriter_WriteASCII(
+ PyUnicodeWriter *writer,
+ const char *str,
+ Py_ssize_t size);
PyAPI_FUNC(int) PyUnicodeWriter_WriteWideChar(
PyUnicodeWriter *writer,
const wchar_t *str,
diff --git a/Include/import.h b/Include/import.h
index 24b23b91191..d91ebe96ca8 100644
--- a/Include/import.h
+++ b/Include/import.h
@@ -51,9 +51,6 @@ PyAPI_FUNC(PyObject *) PyImport_AddModuleRef(
PyAPI_FUNC(PyObject *) PyImport_ImportModule(
const char *name /* UTF-8 encoded string */
);
-Py_DEPRECATED(3.13) PyAPI_FUNC(PyObject *) PyImport_ImportModuleNoBlock(
- const char *name /* UTF-8 encoded string */
- );
PyAPI_FUNC(PyObject *) PyImport_ImportModuleLevel(
const char *name, /* UTF-8 encoded string */
PyObject *globals,
diff --git a/Include/internal/mimalloc/mimalloc/internal.h b/Include/internal/mimalloc/mimalloc/internal.h
index d97f51b8eef..a7daa3a40a4 100644
--- a/Include/internal/mimalloc/mimalloc/internal.h
+++ b/Include/internal/mimalloc/mimalloc/internal.h
@@ -634,10 +634,10 @@ static inline mi_block_t* mi_block_nextx( const void* null, const mi_block_t* bl
mi_track_mem_defined(block,sizeof(mi_block_t));
mi_block_t* next;
#ifdef MI_ENCODE_FREELIST
- next = (mi_block_t*)mi_ptr_decode(null, block->next, keys);
+ next = (mi_block_t*)mi_ptr_decode(null, mi_atomic_load_relaxed((_Atomic(mi_encoded_t)*)&block->next), keys);
#else
MI_UNUSED(keys); MI_UNUSED(null);
- next = (mi_block_t*)block->next;
+ next = (mi_block_t*)mi_atomic_load_relaxed((_Atomic(mi_encoded_t)*)&block->next);
#endif
mi_track_mem_noaccess(block,sizeof(mi_block_t));
return next;
@@ -646,10 +646,10 @@ static inline mi_block_t* mi_block_nextx( const void* null, const mi_block_t* bl
static inline void mi_block_set_nextx(const void* null, mi_block_t* block, const mi_block_t* next, const uintptr_t* keys) {
mi_track_mem_undefined(block,sizeof(mi_block_t));
#ifdef MI_ENCODE_FREELIST
- block->next = mi_ptr_encode(null, next, keys);
+ mi_atomic_store_relaxed(&block->next, mi_ptr_encode(null, next, keys));
#else
MI_UNUSED(keys); MI_UNUSED(null);
- block->next = (mi_encoded_t)next;
+ mi_atomic_store_relaxed(&block->next, (mi_encoded_t)next);
#endif
mi_track_mem_noaccess(block,sizeof(mi_block_t));
}
diff --git a/Include/internal/mimalloc/mimalloc/types.h b/Include/internal/mimalloc/mimalloc/types.h
index 354839ba955..a17f637fe68 100644
--- a/Include/internal/mimalloc/mimalloc/types.h
+++ b/Include/internal/mimalloc/mimalloc/types.h
@@ -50,6 +50,32 @@ terms of the MIT license. A copy of the license can be found in the file
#define mi_decl_cache_align
#endif
+#if (MI_DEBUG)
+#if defined(_MSC_VER)
+#define mi_decl_noreturn __declspec(noreturn)
+#elif (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__)
+#define mi_decl_noreturn __attribute__((__noreturn__))
+#else
+#define mi_decl_noreturn
+#endif
+
+/*
+ * 'cold' attribute seems to have been fully supported since GCC 4.x.
+ * See https://github.com/gcc-mirror/gcc/commit/52bf96d2f299e9e6.
+ */
+#if (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__)
+#define mi_decl_cold __attribute__((cold))
+#else
+#define mi_decl_cold
+#endif
+
+#if (defined(__GNUC__) && defined(__THROW))
+#define mi_decl_throw __THROW
+#else
+#define mi_decl_throw
+#endif
+#endif
+
// ------------------------------------------------------
// Variants
// ------------------------------------------------------
@@ -235,7 +261,7 @@ typedef size_t mi_threadid_t;
// free lists contain blocks
typedef struct mi_block_s {
- mi_encoded_t next;
+ _Atomic(mi_encoded_t) next;
} mi_block_t;
@@ -582,7 +608,8 @@ struct mi_heap_s {
#if (MI_DEBUG)
// use our own assertion to print without memory allocation
-void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func );
+mi_decl_noreturn mi_decl_cold mi_decl_throw
+void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func);
#define mi_assert(expr) ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__))
#else
#define mi_assert(x)
@@ -678,7 +705,7 @@ void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);
// Thread Local data
// ------------------------------------------------------
-// A "span" is is an available range of slices. The span queues keep
+// A "span" is an available range of slices. The span queues keep
// track of slice spans of at most the given `slice_count` (but more than the previous size class).
typedef struct mi_span_queue_s {
mi_slice_t* first;
diff --git a/Include/internal/pycore_backoff.h b/Include/internal/pycore_backoff.h
index 942d8b107a7..454c8dde031 100644
--- a/Include/internal/pycore_backoff.h
+++ b/Include/internal/pycore_backoff.h
@@ -95,8 +95,10 @@ backoff_counter_triggers(_Py_BackoffCounter counter)
return counter.value_and_backoff < UNREACHABLE_BACKOFF;
}
-/* Initial JUMP_BACKWARD counter.
- * This determines when we create a trace for a loop. */
+// Initial JUMP_BACKWARD counter.
+// Must be larger than ADAPTIVE_COOLDOWN_VALUE, otherwise when JIT code is
+// invalidated we may construct a new trace before the bytecode has properly
+// re-specialized:
#define JUMP_BACKWARD_INITIAL_VALUE 4095
#define JUMP_BACKWARD_INITIAL_BACKOFF 12
static inline _Py_BackoffCounter
diff --git a/Include/internal/pycore_bytesobject.h b/Include/internal/pycore_bytesobject.h
index 300e7f4896a..8ea9b3ebb88 100644
--- a/Include/internal/pycore_bytesobject.h
+++ b/Include/internal/pycore_bytesobject.h
@@ -20,8 +20,9 @@ extern PyObject* _PyBytes_FromHex(
// Helper for PyBytes_DecodeEscape that detects invalid escape chars.
// Export for test_peg_generator.
-PyAPI_FUNC(PyObject*) _PyBytes_DecodeEscape(const char *, Py_ssize_t,
- const char *, const char **);
+PyAPI_FUNC(PyObject*) _PyBytes_DecodeEscape2(const char *, Py_ssize_t,
+ const char *,
+ int *, const char **);
// Substring Search.
diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index 3d8247df31c..cc2defbdf77 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -239,6 +239,16 @@ static inline void _Py_LeaveRecursiveCall(void) {
extern _PyInterpreterFrame* _PyEval_GetFrame(void);
+extern PyObject * _PyEval_GetGlobalsFromRunningMain(PyThreadState *);
+extern int _PyEval_EnsureBuiltins(
+ PyThreadState *,
+ PyObject *,
+ PyObject **p_builtins);
+extern int _PyEval_EnsureBuiltinsWithModule(
+ PyThreadState *,
+ PyObject *,
+ PyObject **p_builtins);
+
PyAPI_FUNC(PyObject *)_Py_MakeCoro(PyFunctionObject *func);
/* Handle signals, pending calls, GIL drop request
@@ -353,6 +363,16 @@ PyAPI_FUNC(_PyStackRef) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyS
extern int _PyRunRemoteDebugger(PyThreadState *tstate);
#endif
+PyAPI_FUNC(_PyStackRef)
+_PyForIter_VirtualIteratorNext(PyThreadState* tstate, struct _PyInterpreterFrame* frame, _PyStackRef iter, _PyStackRef *index_ptr);
+
+/* Special methods used by LOAD_SPECIAL */
+#define SPECIAL___ENTER__ 0
+#define SPECIAL___EXIT__ 1
+#define SPECIAL___AENTER__ 2
+#define SPECIAL___AEXIT__ 3
+#define SPECIAL_MAX 3
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index 635d2b24f4b..8e1415f27b6 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -313,7 +313,7 @@ extern void _Py_Specialize_CompareOp(_PyStackRef lhs, _PyStackRef rhs,
_Py_CODEUNIT *instr, int oparg);
extern void _Py_Specialize_UnpackSequence(_PyStackRef seq, _Py_CODEUNIT *instr,
int oparg);
-extern void _Py_Specialize_ForIter(_PyStackRef iter, _Py_CODEUNIT *instr, int oparg);
+extern void _Py_Specialize_ForIter(_PyStackRef iter, _PyStackRef null_or_index, _Py_CODEUNIT *instr, int oparg);
extern void _Py_Specialize_Send(_PyStackRef receiver, _Py_CODEUNIT *instr);
extern void _Py_Specialize_ToBool(_PyStackRef value, _Py_CODEUNIT *instr);
extern void _Py_Specialize_ContainsOp(_PyStackRef value, _Py_CODEUNIT *instr);
@@ -434,8 +434,6 @@ write_location_entry_start(uint8_t *ptr, int code, int length)
* On a specialization failure, the backoff counter is restarted.
*/
-#include "pycore_backoff.h"
-
// A value of 1 means that we attempt to specialize the *second* time each
// instruction is executed. Executing twice is a much better indicator of
// "hotness" than executing once, but additional warmup delays only prevent
@@ -453,6 +451,9 @@ write_location_entry_start(uint8_t *ptr, int code, int length)
#define ADAPTIVE_COOLDOWN_BACKOFF 0
// Can't assert this in pycore_backoff.h because of header order dependencies
+#if JUMP_BACKWARD_INITIAL_VALUE <= ADAPTIVE_COOLDOWN_VALUE
+# error "JIT threshold value should be larger than adaptive cooldown value"
+#endif
#if SIDE_EXIT_INITIAL_VALUE <= ADAPTIVE_COOLDOWN_VALUE
# error "Cold exit value should be larger than adaptive cooldown value"
#endif
@@ -565,6 +566,98 @@ extern int _Py_ClearUnusedTLBC(PyInterpreterState *interp);
#endif
+typedef struct {
+ int total;
+ struct co_locals_counts {
+ int total;
+ struct {
+ int total;
+ int numposonly;
+ int numposorkw;
+ int numkwonly;
+ int varargs;
+ int varkwargs;
+ } args;
+ int numpure;
+ struct {
+ int total;
+ // numargs does not contribute to locals.total.
+ int numargs;
+ int numothers;
+ } cells;
+ struct {
+ int total;
+ int numpure;
+ int numcells;
+ } hidden;
+ } locals;
+ int numfree; // nonlocal
+ struct co_unbound_counts {
+ int total;
+ struct {
+ int total;
+ int numglobal;
+ int numbuiltin;
+ int numunknown;
+ } globals;
+ int numattrs;
+ int numunknown;
+ } unbound;
+} _PyCode_var_counts_t;
+
+PyAPI_FUNC(void) _PyCode_GetVarCounts(
+ PyCodeObject *,
+ _PyCode_var_counts_t *);
+PyAPI_FUNC(int) _PyCode_SetUnboundVarCounts(
+ PyThreadState *,
+ PyCodeObject *,
+ _PyCode_var_counts_t *,
+ PyObject *globalnames,
+ PyObject *attrnames,
+ PyObject *globalsns,
+ PyObject *builtinsns);
+
+
+/* "Stateless" code is a function or code object which does not rely on
+ * external state or internal state. It may rely on arguments and
+ * builtins, but not globals or a closure. Thus it does not rely
+ * on __globals__ or __closure__, and a stateless function
+ * is equivalent to its code object.
+ *
+ * Stateless code also does not keep any persistent state
+ * of its own, so it can't have any executors, monitoring,
+ * instrumentation, or "extras" (i.e. co_extra).
+ *
+ * Stateless code may create nested functions, including closures.
+ * However, nested functions must themselves be stateless, except they
+ * *can* close on the enclosing locals.
+ *
+ * Stateless code may return any value, including nested functions and closures.
+ *
+ * Stateless code that takes no arguments and doesn't return anything
+ * may be treated like a script.
+ *
+ * We consider stateless code to be "portable" if it does not return
+ * any object that holds a reference to any of the code's locals. Thus
+ * generators and coroutines are not portable. Likewise a function
+ * that returns a closure is not portable. The concept of
+ * portability is useful in cases where the code is run
+ * in a different execution context than where
+ * the return value will be used. */
+
+PyAPI_FUNC(int) _PyCode_CheckNoInternalState(PyCodeObject *, const char **);
+PyAPI_FUNC(int) _PyCode_CheckNoExternalState(
+ PyCodeObject *,
+ _PyCode_var_counts_t *,
+ const char **);
+PyAPI_FUNC(int) _PyCode_VerifyStateless(
+ PyThreadState *,
+ PyCodeObject *,
+ PyObject *globalnames,
+ PyObject *globalsns,
+ PyObject *builtinsns);
+
+PyAPI_FUNC(int) _PyCode_CheckPureFunction(PyCodeObject *, const char **);
PyAPI_FUNC(int) _PyCode_ReturnsOnlyNone(PyCodeObject *);
diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h
index a606c2afe0a..c18e04bf67a 100644
--- a/Include/internal/pycore_compile.h
+++ b/Include/internal/pycore_compile.h
@@ -34,8 +34,8 @@ PyAPI_FUNC(PyCodeObject*) _PyAST_Compile(
int optimize,
struct _arena *arena);
-/* AST optimizations */
-extern int _PyCompile_AstOptimize(
+/* AST preprocessing */
+extern int _PyCompile_AstPreprocess(
struct _mod *mod,
PyObject *filename,
PyCompilerFlags *flags,
@@ -43,7 +43,7 @@ extern int _PyCompile_AstOptimize(
struct _arena *arena,
int syntax_check_only);
-extern int _PyAST_Optimize(
+extern int _PyAST_Preprocess(
struct _mod *,
struct _arena *arena,
PyObject *filename,
@@ -95,6 +95,7 @@ typedef enum {
enum _PyCompile_FBlockType {
COMPILE_FBLOCK_WHILE_LOOP,
COMPILE_FBLOCK_FOR_LOOP,
+ COMPILE_FBLOCK_ASYNC_FOR_LOOP,
COMPILE_FBLOCK_TRY_EXCEPT,
COMPILE_FBLOCK_FINALLY_TRY,
COMPILE_FBLOCK_FINALLY_END,
diff --git a/Include/internal/pycore_critical_section.h b/Include/internal/pycore_critical_section.h
index 42f06b935bd..62460c5f8fa 100644
--- a/Include/internal/pycore_critical_section.h
+++ b/Include/internal/pycore_critical_section.h
@@ -64,7 +64,7 @@ extern "C" {
# define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) \
if (Py_REFCNT(op) != 1) { \
- _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyObject_CAST(op)->ob_mutex); \
+ _PyCriticalSection_AssertHeldObj(_PyObject_CAST(op)); \
}
#else /* Py_DEBUG */
@@ -239,6 +239,28 @@ _PyCriticalSection_AssertHeld(PyMutex *mutex)
#endif
}
+static inline void
+_PyCriticalSection_AssertHeldObj(PyObject *op)
+{
+#ifdef Py_DEBUG
+ PyMutex *mutex = &_PyObject_CAST(op)->ob_mutex;
+ PyThreadState *tstate = _PyThreadState_GET();
+ uintptr_t prev = tstate->critical_section;
+ if (prev & _Py_CRITICAL_SECTION_TWO_MUTEXES) {
+ PyCriticalSection2 *cs = (PyCriticalSection2 *)(prev & ~_Py_CRITICAL_SECTION_MASK);
+ _PyObject_ASSERT_WITH_MSG(op,
+ (cs != NULL && (cs->_cs_base._cs_mutex == mutex || cs->_cs_mutex2 == mutex)),
+ "Critical section of object is not held");
+ }
+ else {
+ PyCriticalSection *cs = (PyCriticalSection *)(prev & ~_Py_CRITICAL_SECTION_MASK);
+ _PyObject_ASSERT_WITH_MSG(op,
+ (cs != NULL && cs->_cs_mutex == mutex),
+ "Critical section of object is not held");
+ }
+
+#endif
+}
#endif /* Py_GIL_DISABLED */
#ifdef __cplusplus
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 4b4617fdbcb..81faffac194 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -131,7 +131,23 @@ PyAPI_FUNC(void) _PyXIData_Clear(PyInterpreterState *, _PyXIData_t *);
/* getting cross-interpreter data */
-typedef int (*xidatafunc)(PyThreadState *tstate, PyObject *, _PyXIData_t *);
+typedef int xidata_fallback_t;
+#define _PyXIDATA_XIDATA_ONLY (0)
+#define _PyXIDATA_FULL_FALLBACK (1)
+
+// Technically, we don't need two different function types;
+// we could go with just the fallback one. However, only container
+// types like tuple need it, so always having the extra arg would be
+// a bit unfortunate. It's also nice to be able to clearly distinguish
+// between types that might call _PyObject_GetXIData() and those that won't.
+//
+typedef int (*xidatafunc)(PyThreadState *, PyObject *, _PyXIData_t *);
+typedef int (*xidatafbfunc)(
+ PyThreadState *, PyObject *, xidata_fallback_t, _PyXIData_t *);
+typedef struct {
+ xidatafunc basic;
+ xidatafbfunc fallback;
+} _PyXIData_getdata_t;
PyAPI_FUNC(PyObject *) _PyXIData_GetNotShareableErrorType(PyThreadState *);
PyAPI_FUNC(void) _PyXIData_SetNotShareableError(PyThreadState *, const char *);
@@ -140,16 +156,21 @@ PyAPI_FUNC(void) _PyXIData_FormatNotShareableError(
const char *,
...);
-PyAPI_FUNC(xidatafunc) _PyXIData_Lookup(
+PyAPI_FUNC(_PyXIData_getdata_t) _PyXIData_Lookup(
PyThreadState *,
PyObject *);
PyAPI_FUNC(int) _PyObject_CheckXIData(
PyThreadState *,
PyObject *);
+PyAPI_FUNC(int) _PyObject_GetXIDataNoFallback(
+ PyThreadState *,
+ PyObject *,
+ _PyXIData_t *);
PyAPI_FUNC(int) _PyObject_GetXIData(
PyThreadState *,
PyObject *,
+ xidata_fallback_t,
_PyXIData_t *);
// _PyObject_GetXIData() for bytes
@@ -185,6 +206,28 @@ PyAPI_FUNC(int) _PyMarshal_GetXIData(
PyObject *,
_PyXIData_t *);
+// _PyObject_GetXIData() for code objects
+PyAPI_FUNC(PyObject *) _PyCode_FromXIData(_PyXIData_t *);
+PyAPI_FUNC(int) _PyCode_GetXIData(
+ PyThreadState *,
+ PyObject *,
+ _PyXIData_t *);
+PyAPI_FUNC(int) _PyCode_GetScriptXIData(
+ PyThreadState *,
+ PyObject *,
+ _PyXIData_t *);
+PyAPI_FUNC(int) _PyCode_GetPureScriptXIData(
+ PyThreadState *,
+ PyObject *,
+ _PyXIData_t *);
+
+// _PyObject_GetXIData() for functions
+PyAPI_FUNC(PyObject *) _PyFunction_FromXIData(_PyXIData_t *);
+PyAPI_FUNC(int) _PyFunction_GetXIData(
+ PyThreadState *,
+ PyObject *,
+ _PyXIData_t *);
+
/* using cross-interpreter data */
@@ -260,10 +303,10 @@ typedef struct _excinfo {
const char *errdisplay;
} _PyXI_excinfo;
-PyAPI_FUNC(int) _PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc);
+PyAPI_FUNC(_PyXI_excinfo *) _PyXI_NewExcInfo(PyObject *exc);
+PyAPI_FUNC(void) _PyXI_FreeExcInfo(_PyXI_excinfo *info);
PyAPI_FUNC(PyObject *) _PyXI_FormatExcInfo(_PyXI_excinfo *info);
PyAPI_FUNC(PyObject *) _PyXI_ExcInfoAsObject(_PyXI_excinfo *info);
-PyAPI_FUNC(void) _PyXI_ClearExcInfo(_PyXI_excinfo *info);
typedef enum error_code {
@@ -274,42 +317,30 @@ typedef enum error_code {
_PyXI_ERR_ALREADY_RUNNING = -4,
_PyXI_ERR_MAIN_NS_FAILURE = -5,
_PyXI_ERR_APPLY_NS_FAILURE = -6,
- _PyXI_ERR_NOT_SHAREABLE = -7,
+ _PyXI_ERR_PRESERVE_FAILURE = -7,
+ _PyXI_ERR_EXC_PROPAGATION_FAILURE = -8,
+ _PyXI_ERR_NOT_SHAREABLE = -9,
} _PyXI_errcode;
+typedef struct xi_failure _PyXI_failure;
-typedef struct _sharedexception {
- // The originating interpreter.
- PyInterpreterState *interp;
- // The kind of error to propagate.
- _PyXI_errcode code;
- // The exception information to propagate, if applicable.
- // This is populated only for some error codes,
- // but always for _PyXI_ERR_UNCAUGHT_EXCEPTION.
- _PyXI_excinfo uncaught;
-} _PyXI_error;
-
-PyAPI_FUNC(PyObject *) _PyXI_ApplyError(_PyXI_error *err);
-
-
-typedef struct xi_session _PyXI_session;
-typedef struct _sharedns _PyXI_namespace;
+PyAPI_FUNC(_PyXI_failure *) _PyXI_NewFailure(void);
+PyAPI_FUNC(void) _PyXI_FreeFailure(_PyXI_failure *);
+PyAPI_FUNC(_PyXI_errcode) _PyXI_GetFailureCode(_PyXI_failure *);
+PyAPI_FUNC(int) _PyXI_InitFailure(_PyXI_failure *, _PyXI_errcode, PyObject *);
+PyAPI_FUNC(void) _PyXI_InitFailureUTF8(
+ _PyXI_failure *,
+ _PyXI_errcode,
+ const char *);
-PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
-PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
-PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
- _PyXI_namespace *ns,
- PyObject *nsobj,
- _PyXI_session *session);
-PyAPI_FUNC(int) _PyXI_ApplyNamespace(
- _PyXI_namespace *ns,
- PyObject *nsobj,
- PyObject *dflt);
+PyAPI_FUNC(int) _PyXI_UnwrapNotShareableError(
+ PyThreadState *,
+ _PyXI_failure *);
// A cross-interpreter session involves entering an interpreter
-// (_PyXI_Enter()), doing some work with it, and finally exiting
-// that interpreter (_PyXI_Exit()).
+// with _PyXI_Enter(), doing some work with it, and finally exiting
+// that interpreter with _PyXI_Exit().
//
// At the boundaries of the session, both entering and exiting,
// data may be exchanged between the previous interpreter and the
@@ -317,48 +348,40 @@ PyAPI_FUNC(int) _PyXI_ApplyNamespace(
// isolation between interpreters. This includes setting objects
// in the target's __main__ module on the way in, and capturing
// uncaught exceptions on the way out.
-struct xi_session {
- // Once a session has been entered, this is the tstate that was
- // current before the session. If it is different from cur_tstate
- // then we must have switched interpreters. Either way, this will
- // be the current tstate once we exit the session.
- PyThreadState *prev_tstate;
- // Once a session has been entered, this is the current tstate.
- // It must be current when the session exits.
- PyThreadState *init_tstate;
- // This is true if init_tstate needs cleanup during exit.
- int own_init_tstate;
-
- // This is true if, while entering the session, init_thread took
- // "ownership" of the interpreter's __main__ module. This means
- // it is the only thread that is allowed to run code there.
- // (Caveat: for now, users may still run exec() against the
- // __main__ module's dict, though that isn't advisable.)
- int running;
- // This is a cached reference to the __dict__ of the entered
- // interpreter's __main__ module. It is looked up when at the
- // beginning of the session as a convenience.
- PyObject *main_ns;
-
- // This is set if the interpreter is entered and raised an exception
- // that needs to be handled in some special way during exit.
- _PyXI_errcode *error_override;
- // This is set if exit captured an exception to propagate.
- _PyXI_error *error;
-
- // -- pre-allocated memory --
- _PyXI_error _error;
- _PyXI_errcode _error_override;
-};
+typedef struct xi_session _PyXI_session;
+
+PyAPI_FUNC(_PyXI_session *) _PyXI_NewSession(void);
+PyAPI_FUNC(void) _PyXI_FreeSession(_PyXI_session *);
+
+typedef struct {
+ PyObject *preserved;
+ PyObject *excinfo;
+ _PyXI_errcode errcode;
+} _PyXI_session_result;
+PyAPI_FUNC(void) _PyXI_ClearResult(_PyXI_session_result *);
PyAPI_FUNC(int) _PyXI_Enter(
_PyXI_session *session,
PyInterpreterState *interp,
- PyObject *nsupdates);
-PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
-
-PyAPI_FUNC(PyObject *) _PyXI_ApplyCapturedException(_PyXI_session *session);
-PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
+ PyObject *nsupdates,
+ _PyXI_session_result *);
+PyAPI_FUNC(int) _PyXI_Exit(
+ _PyXI_session *,
+ _PyXI_failure *,
+ _PyXI_session_result *);
+
+PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(
+ _PyXI_session *,
+ _PyXI_failure *);
+
+PyAPI_FUNC(int) _PyXI_Preserve(
+ _PyXI_session *,
+ const char *,
+ PyObject *,
+ _PyXI_failure *);
+PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(
+ _PyXI_session_result *,
+ const char *);
/*************/
diff --git a/Include/internal/pycore_crossinterp_data_registry.h b/Include/internal/pycore_crossinterp_data_registry.h
index 8f4bcb948e5..fbb4cad5cac 100644
--- a/Include/internal/pycore_crossinterp_data_registry.h
+++ b/Include/internal/pycore_crossinterp_data_registry.h
@@ -17,7 +17,7 @@ typedef struct _xid_regitem {
/* This is NULL for builtin types. */
PyObject *weakref;
size_t refcount;
- xidatafunc getdata;
+ _PyXIData_getdata_t getdata;
} _PyXIData_regitem_t;
typedef struct {
@@ -30,7 +30,7 @@ typedef struct {
PyAPI_FUNC(int) _PyXIData_RegisterClass(
PyThreadState *,
PyTypeObject *,
- xidatafunc);
+ _PyXIData_getdata_t);
PyAPI_FUNC(int) _PyXIData_UnregisterClass(
PyThreadState *,
PyTypeObject *);
diff --git a/Include/internal/pycore_debug_offsets.h b/Include/internal/pycore_debug_offsets.h
index 59d2c9d5377..1b59fa2ef60 100644
--- a/Include/internal/pycore_debug_offsets.h
+++ b/Include/internal/pycore_debug_offsets.h
@@ -52,9 +52,15 @@ extern "C" {
#ifdef Py_GIL_DISABLED
# define _Py_Debug_gilruntimestate_enabled offsetof(struct _gil_runtime_state, enabled)
# define _Py_Debug_Free_Threaded 1
+# define _Py_Debug_code_object_co_tlbc offsetof(PyCodeObject, co_tlbc)
+# define _Py_Debug_interpreter_frame_tlbc_index offsetof(_PyInterpreterFrame, tlbc_index)
+# define _Py_Debug_interpreter_state_tlbc_generation offsetof(PyInterpreterState, tlbc_indices.tlbc_generation)
#else
# define _Py_Debug_gilruntimestate_enabled 0
# define _Py_Debug_Free_Threaded 0
+# define _Py_Debug_code_object_co_tlbc 0
+# define _Py_Debug_interpreter_frame_tlbc_index 0
+# define _Py_Debug_interpreter_state_tlbc_generation 0
#endif
@@ -85,6 +91,8 @@ typedef struct _Py_DebugOffsets {
uint64_t gil_runtime_state_enabled;
uint64_t gil_runtime_state_locked;
uint64_t gil_runtime_state_holder;
+ uint64_t code_object_generation;
+ uint64_t tlbc_generation;
} interpreter_state;
// Thread state offset;
@@ -109,6 +117,7 @@ typedef struct _Py_DebugOffsets {
uint64_t localsplus;
uint64_t owner;
uint64_t stackpointer;
+ uint64_t tlbc_index;
} interpreter_frame;
// Code object offset;
@@ -123,6 +132,7 @@ typedef struct _Py_DebugOffsets {
uint64_t localsplusnames;
uint64_t localspluskinds;
uint64_t co_code_adaptive;
+ uint64_t co_tlbc;
} code_object;
// PyObject offset;
@@ -210,6 +220,11 @@ typedef struct _Py_DebugOffsets {
uint64_t gi_frame_state;
} gen_object;
+ struct _llist_node {
+ uint64_t next;
+ uint64_t prev;
+ } llist_node;
+
struct _debugger_support {
uint64_t eval_breaker;
uint64_t remote_debugger_support;
@@ -245,6 +260,8 @@ typedef struct _Py_DebugOffsets {
.gil_runtime_state_enabled = _Py_Debug_gilruntimestate_enabled, \
.gil_runtime_state_locked = offsetof(PyInterpreterState, _gil.locked), \
.gil_runtime_state_holder = offsetof(PyInterpreterState, _gil.last_holder), \
+ .code_object_generation = offsetof(PyInterpreterState, _code_object_generation), \
+ .tlbc_generation = _Py_Debug_interpreter_state_tlbc_generation, \
}, \
.thread_state = { \
.size = sizeof(PyThreadState), \
@@ -265,6 +282,7 @@ typedef struct _Py_DebugOffsets {
.localsplus = offsetof(_PyInterpreterFrame, localsplus), \
.owner = offsetof(_PyInterpreterFrame, owner), \
.stackpointer = offsetof(_PyInterpreterFrame, stackpointer), \
+ .tlbc_index = _Py_Debug_interpreter_frame_tlbc_index, \
}, \
.code_object = { \
.size = sizeof(PyCodeObject), \
@@ -277,6 +295,7 @@ typedef struct _Py_DebugOffsets {
.localsplusnames = offsetof(PyCodeObject, co_localsplusnames), \
.localspluskinds = offsetof(PyCodeObject, co_localspluskinds), \
.co_code_adaptive = offsetof(PyCodeObject, co_code_adaptive), \
+ .co_tlbc = _Py_Debug_code_object_co_tlbc, \
}, \
.pyobject = { \
.size = sizeof(PyObject), \
@@ -339,13 +358,17 @@ typedef struct _Py_DebugOffsets {
.gi_iframe = offsetof(PyGenObject, gi_iframe), \
.gi_frame_state = offsetof(PyGenObject, gi_frame_state), \
}, \
+ .llist_node = { \
+ .next = offsetof(struct llist_node, next), \
+ .prev = offsetof(struct llist_node, prev), \
+ }, \
.debugger_support = { \
.eval_breaker = offsetof(PyThreadState, eval_breaker), \
.remote_debugger_support = offsetof(PyThreadState, remote_debugger_support), \
.remote_debugging_enabled = offsetof(PyInterpreterState, config.remote_debug), \
.debugger_pending_call = offsetof(_PyRemoteDebuggerSupport, debugger_pending_call), \
.debugger_script_path = offsetof(_PyRemoteDebuggerSupport, debugger_script_path), \
- .debugger_script_path_size = MAX_SCRIPT_PATH_SIZE, \
+ .debugger_script_path_size = Py_MAX_SCRIPT_PATH_SIZE, \
}, \
}
diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index 754eb88a85c..25bb224921a 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -150,6 +150,8 @@ extern int _PyDict_Pop_KnownHash(
Py_hash_t hash,
PyObject **result);
+extern void _PyDict_Clear_LockHeld(PyObject *op);
+
#ifdef Py_GIL_DISABLED
PyAPI_FUNC(void) _PyDict_EnsureSharedOnRead(PyDictObject *mp);
#endif
diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h
index 4828dfd948f..59beb92f3f7 100644
--- a/Include/internal/pycore_freelist_state.h
+++ b/Include/internal/pycore_freelist_state.h
@@ -16,6 +16,7 @@ extern "C" {
# define Py_dicts_MAXFREELIST 80
# define Py_dictkeys_MAXFREELIST 80
# define Py_floats_MAXFREELIST 100
+# define Py_complexes_MAXFREELIST 100
# define Py_ints_MAXFREELIST 100
# define Py_slices_MAXFREELIST 1
# define Py_ranges_MAXFREELIST 6
@@ -43,6 +44,7 @@ struct _Py_freelist {
struct _Py_freelists {
struct _Py_freelist floats;
+ struct _Py_freelist complexes;
struct _Py_freelist ints;
struct _Py_freelist tuples[PyTuple_MAXSAVESIZE];
struct _Py_freelist lists;
diff --git a/Include/internal/pycore_function.h b/Include/internal/pycore_function.h
index 209252b2ddc..6e120965956 100644
--- a/Include/internal/pycore_function.h
+++ b/Include/internal/pycore_function.h
@@ -35,6 +35,18 @@ PyFunctionObject *_PyFunction_LookupByVersion(uint32_t version, PyObject **p_cod
extern PyObject *_Py_set_function_type_params(
PyThreadState* unused, PyObject *func, PyObject *type_params);
+
+/* See pycore_code.h for explanation about what "stateless" means. */
+
+PyAPI_FUNC(int)
+_PyFunction_VerifyStateless(PyThreadState *, PyObject *);
+
+static inline PyObject* _PyFunction_GET_BUILTINS(PyObject *func) {
+ return _PyFunction_CAST(func)->func_builtins;
+}
+#define _PyFunction_GET_BUILTINS(func) _PyFunction_GET_BUILTINS(_PyObject_CAST(func))
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h
index 121466dd2ec..c461bc1786d 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -792,10 +792,10 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(add_done_callback));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_child));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_parent));
- _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(aggregate_class));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alias));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(align));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all_threads));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(allow_code));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(any));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(append));
@@ -809,7 +809,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ast));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(athrow));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(attribute));
- _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(authorizer_callback));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(autocommit));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(backtick));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(base));
@@ -834,6 +833,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bytes_per_sep));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(c_call));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(c_exception));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(c_parameter_type));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(c_return));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cached_datetime_module));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cached_statements));
@@ -888,9 +888,11 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(count));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(covariant));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cwd));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(d_parameter_type));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(data));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(database));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(day));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(debug));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decode));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decoder));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(default));
@@ -968,6 +970,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(follow_symlinks));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(format));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(format_spec));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(frame_buffer));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(from_param));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fromlist));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fromtimestamp));
@@ -1026,6 +1029,8 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(interval));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(io));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_compress));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_raw));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_struct));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty));
@@ -1102,7 +1107,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(msg));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mutex));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mycmp));
- _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(n_arg));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(n_fields));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(n_sequence_fields));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(n_unnamed_fields));
@@ -1110,7 +1114,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(name_from));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(namespace_separator));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(namespaces));
- _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(narg));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ndigits));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(nested));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(new_file_name));
@@ -1133,6 +1136,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(offset_src));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(on_type_read));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(onceregistry));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(only_active_thread));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(only_keys));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(oparg));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(opcode));
@@ -1149,6 +1153,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(overlapped));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(owner));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pages));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(parameter));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(parent));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(password));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(path));
@@ -1167,7 +1172,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(print_file_and_line));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(priority));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress));
- _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_handler));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_routine));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(proto));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(protocol));
@@ -1273,7 +1277,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timetuple));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timeunit));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(top));
- _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(trace_callback));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(traceback));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(trailers));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(translate));
@@ -1310,6 +1313,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(write_through));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(year));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(zdict));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(zstd_dict));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_SINGLETON(strings).ascii[0]);
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_SINGLETON(strings).ascii[1]);
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_SINGLETON(strings).ascii[2]);
diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index 20e2e6f2a7f..72c2051bd97 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -283,10 +283,10 @@ struct _Py_global_strings {
STRUCT_FOR_ID(add_done_callback)
STRUCT_FOR_ID(after_in_child)
STRUCT_FOR_ID(after_in_parent)
- STRUCT_FOR_ID(aggregate_class)
STRUCT_FOR_ID(alias)
STRUCT_FOR_ID(align)
STRUCT_FOR_ID(all)
+ STRUCT_FOR_ID(all_threads)
STRUCT_FOR_ID(allow_code)
STRUCT_FOR_ID(any)
STRUCT_FOR_ID(append)
@@ -300,7 +300,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(ast)
STRUCT_FOR_ID(athrow)
STRUCT_FOR_ID(attribute)
- STRUCT_FOR_ID(authorizer_callback)
STRUCT_FOR_ID(autocommit)
STRUCT_FOR_ID(backtick)
STRUCT_FOR_ID(base)
@@ -325,6 +324,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(bytes_per_sep)
STRUCT_FOR_ID(c_call)
STRUCT_FOR_ID(c_exception)
+ STRUCT_FOR_ID(c_parameter_type)
STRUCT_FOR_ID(c_return)
STRUCT_FOR_ID(cached_datetime_module)
STRUCT_FOR_ID(cached_statements)
@@ -379,9 +379,11 @@ struct _Py_global_strings {
STRUCT_FOR_ID(count)
STRUCT_FOR_ID(covariant)
STRUCT_FOR_ID(cwd)
+ STRUCT_FOR_ID(d_parameter_type)
STRUCT_FOR_ID(data)
STRUCT_FOR_ID(database)
STRUCT_FOR_ID(day)
+ STRUCT_FOR_ID(debug)
STRUCT_FOR_ID(decode)
STRUCT_FOR_ID(decoder)
STRUCT_FOR_ID(default)
@@ -459,6 +461,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(follow_symlinks)
STRUCT_FOR_ID(format)
STRUCT_FOR_ID(format_spec)
+ STRUCT_FOR_ID(frame_buffer)
STRUCT_FOR_ID(from_param)
STRUCT_FOR_ID(fromlist)
STRUCT_FOR_ID(fromtimestamp)
@@ -517,6 +520,8 @@ struct _Py_global_strings {
STRUCT_FOR_ID(intersection)
STRUCT_FOR_ID(interval)
STRUCT_FOR_ID(io)
+ STRUCT_FOR_ID(is_compress)
+ STRUCT_FOR_ID(is_raw)
STRUCT_FOR_ID(is_running)
STRUCT_FOR_ID(is_struct)
STRUCT_FOR_ID(isatty)
@@ -593,7 +598,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(msg)
STRUCT_FOR_ID(mutex)
STRUCT_FOR_ID(mycmp)
- STRUCT_FOR_ID(n_arg)
STRUCT_FOR_ID(n_fields)
STRUCT_FOR_ID(n_sequence_fields)
STRUCT_FOR_ID(n_unnamed_fields)
@@ -601,7 +605,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(name_from)
STRUCT_FOR_ID(namespace_separator)
STRUCT_FOR_ID(namespaces)
- STRUCT_FOR_ID(narg)
STRUCT_FOR_ID(ndigits)
STRUCT_FOR_ID(nested)
STRUCT_FOR_ID(new_file_name)
@@ -624,6 +627,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(offset_src)
STRUCT_FOR_ID(on_type_read)
STRUCT_FOR_ID(onceregistry)
+ STRUCT_FOR_ID(only_active_thread)
STRUCT_FOR_ID(only_keys)
STRUCT_FOR_ID(oparg)
STRUCT_FOR_ID(opcode)
@@ -640,6 +644,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(overlapped)
STRUCT_FOR_ID(owner)
STRUCT_FOR_ID(pages)
+ STRUCT_FOR_ID(parameter)
STRUCT_FOR_ID(parent)
STRUCT_FOR_ID(password)
STRUCT_FOR_ID(path)
@@ -658,7 +663,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(print_file_and_line)
STRUCT_FOR_ID(priority)
STRUCT_FOR_ID(progress)
- STRUCT_FOR_ID(progress_handler)
STRUCT_FOR_ID(progress_routine)
STRUCT_FOR_ID(proto)
STRUCT_FOR_ID(protocol)
@@ -764,7 +768,6 @@ struct _Py_global_strings {
STRUCT_FOR_ID(timetuple)
STRUCT_FOR_ID(timeunit)
STRUCT_FOR_ID(top)
- STRUCT_FOR_ID(trace_callback)
STRUCT_FOR_ID(traceback)
STRUCT_FOR_ID(trailers)
STRUCT_FOR_ID(translate)
@@ -801,6 +804,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(write_through)
STRUCT_FOR_ID(year)
STRUCT_FOR_ID(zdict)
+ STRUCT_FOR_ID(zstd_dict)
} identifiers;
struct {
PyASCIIObject _ascii;
diff --git a/Include/internal/pycore_importdl.h b/Include/internal/pycore_importdl.h
index 525a16f6b97..3ba9229cc21 100644
--- a/Include/internal/pycore_importdl.h
+++ b/Include/internal/pycore_importdl.h
@@ -107,7 +107,7 @@ extern int _PyImport_RunModInitFunc(
#include <windows.h>
typedef FARPROC dl_funcptr;
-#ifdef _DEBUG
+#ifdef Py_DEBUG
# define PYD_DEBUG_SUFFIX "_d"
#else
# define PYD_DEBUG_SUFFIX ""
diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h
index af6ee3ab489..f1f427d99de 100644
--- a/Include/internal/pycore_interp_structs.h
+++ b/Include/internal/pycore_interp_structs.h
@@ -159,10 +159,11 @@ struct atexit_state {
typedef struct {
// Tagged pointer to next object in the list.
// 0 means the object is not tracked
- uintptr_t _gc_next;
+ _Py_ALIGNED_DEF(_PyObject_MIN_ALIGNMENT, uintptr_t) _gc_next;
// Tagged pointer to previous object in the list.
// Lowest two bits are used for flags documented later.
+ // Those bits are made available by the struct's minimum alignment.
uintptr_t _gc_prev;
} PyGC_Head;
@@ -245,6 +246,16 @@ struct _gc_runtime_state {
/* True if gc.freeze() has been used. */
int freeze_active;
+
+ /* Memory usage of the process (RSS + swap) after last GC. */
+ Py_ssize_t last_mem;
+
+ /* This accumulates the new object count whenever collection is deferred
+ due to the RSS increase condition not being meet. Reset on collection. */
+ Py_ssize_t deferred_count;
+
+ /* Mutex held for gc_should_collect_mem_usage(). */
+ PyMutex mutex;
#endif
};
@@ -667,8 +678,11 @@ struct _Py_interp_cached_objects {
/* object.__reduce__ */
PyObject *objreduce;
+#ifndef Py_GIL_DISABLED
+ /* resolve_slotdups() */
PyObject *type_slots_pname;
pytype_slotdef *type_slots_ptrs[MAX_EQUIV];
+#endif
/* TypeVar and related types */
PyTypeObject *generic_type;
@@ -716,6 +730,10 @@ typedef struct _PyIndexPool {
// Next index to allocate if no free indices are available
int32_t next_index;
+
+ // Generation counter incremented on thread creation/destruction
+ // Used for TLBC cache invalidation in remote debugging
+ uint32_t tlbc_generation;
} _PyIndexPool;
typedef union _Py_unique_id_entry {
@@ -833,6 +851,8 @@ struct _is {
/* The per-interpreter GIL, which might not be used. */
struct _gil_runtime_state _gil;
+ uint64_t _code_object_generation;
+
/* ---------- IMPORTANT ---------------------------
The fields above this line are declared as early as
possible to facilitate out-of-process observability
@@ -923,6 +943,8 @@ struct _is {
PyObject *common_consts[NUM_COMMON_CONSTANTS];
bool jit;
struct _PyExecutorObject *executor_list_head;
+ struct _PyExecutorObject *executor_deletion_list_head;
+ int executor_deletion_list_remaining_capacity;
size_t trace_run_counter;
_rare_events rare_events;
PyDict_WatchCallback builtins_dict_watcher;
diff --git a/Include/internal/pycore_interpframe.h b/Include/internal/pycore_interpframe.h
index d3fd218b27e..2ee3696317c 100644
--- a/Include/internal/pycore_interpframe.h
+++ b/Include/internal/pycore_interpframe.h
@@ -48,13 +48,13 @@ static inline _PyStackRef *_PyFrame_Stackbase(_PyInterpreterFrame *f) {
}
static inline _PyStackRef _PyFrame_StackPeek(_PyInterpreterFrame *f) {
- assert(f->stackpointer > f->localsplus + _PyFrame_GetCode(f)->co_nlocalsplus);
+ assert(f->stackpointer > _PyFrame_Stackbase(f));
assert(!PyStackRef_IsNull(f->stackpointer[-1]));
return f->stackpointer[-1];
}
static inline _PyStackRef _PyFrame_StackPop(_PyInterpreterFrame *f) {
- assert(f->stackpointer > f->localsplus + _PyFrame_GetCode(f)->co_nlocalsplus);
+ assert(f->stackpointer > _PyFrame_Stackbase(f));
f->stackpointer--;
return *f->stackpointer;
}
diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h
index 7484b05d7f2..585120108cf 100644
--- a/Include/internal/pycore_lock.h
+++ b/Include/internal/pycore_lock.h
@@ -25,13 +25,6 @@ PyMutex_LockFast(PyMutex *m)
return _Py_atomic_compare_exchange_uint8(lock_bits, &expected, _Py_LOCKED);
}
-// Checks if the mutex is currently locked.
-static inline int
-PyMutex_IsLocked(PyMutex *m)
-{
- return (_Py_atomic_load_uint8(&m->_bits) & _Py_LOCKED) != 0;
-}
-
// Re-initializes the mutex after a fork to the unlocked state.
static inline void
_PyMutex_at_fork_reinit(PyMutex *m)
@@ -48,6 +41,14 @@ typedef enum _PyLockFlags {
// Handle signals if interrupted while waiting on the lock.
_PY_LOCK_HANDLE_SIGNALS = 2,
+
+ // Fail if interrupted by a signal while waiting on the lock.
+ _PY_FAIL_IF_INTERRUPTED = 4,
+
+ // Locking & unlocking this lock requires attached thread state.
+ // If locking returns PY_LOCK_FAILURE, a Python exception *may* be raised.
+ // (Intended for use with _PY_LOCK_HANDLE_SIGNALS and _PY_LOCK_DETACH.)
+ _PY_LOCK_PYTHONLOCK = 8,
} _PyLockFlags;
// Lock a mutex with an optional timeout and additional options. See
diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h
index ed6c4353167..3c213783cd4 100644
--- a/Include/internal/pycore_long.h
+++ b/Include/internal/pycore_long.h
@@ -112,9 +112,9 @@ PyAPI_DATA(PyObject*) _PyLong_Rshift(PyObject *, int64_t);
// Export for 'math' shared extension
PyAPI_DATA(PyObject*) _PyLong_Lshift(PyObject *, int64_t);
-PyAPI_FUNC(PyObject*) _PyLong_Add(PyLongObject *left, PyLongObject *right);
-PyAPI_FUNC(PyObject*) _PyLong_Multiply(PyLongObject *left, PyLongObject *right);
-PyAPI_FUNC(PyObject*) _PyLong_Subtract(PyLongObject *left, PyLongObject *right);
+PyAPI_FUNC(_PyStackRef) _PyCompactLong_Add(PyLongObject *left, PyLongObject *right);
+PyAPI_FUNC(_PyStackRef) _PyCompactLong_Multiply(PyLongObject *left, PyLongObject *right);
+PyAPI_FUNC(_PyStackRef) _PyCompactLong_Subtract(PyLongObject *left, PyLongObject *right);
// Export for 'binascii' shared extension.
PyAPI_DATA(unsigned char) _PyLong_DigitValue[256];
@@ -158,6 +158,11 @@ PyAPI_FUNC(int) _PyLong_UnsignedLongLong_Converter(PyObject *, void *);
// Export for '_testclinic' shared extension (Argument Clinic code)
PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void *);
+PyAPI_FUNC(int) _PyLong_UInt8_Converter(PyObject *, void *);
+PyAPI_FUNC(int) _PyLong_UInt16_Converter(PyObject *, void *);
+PyAPI_FUNC(int) _PyLong_UInt32_Converter(PyObject *, void *);
+PyAPI_FUNC(int) _PyLong_UInt64_Converter(PyObject *, void *);
+
/* Long value tag bits:
* 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1.
* 2: Set to 1 for the small ints
@@ -208,7 +213,6 @@ _PyLong_BothAreCompact(const PyLongObject* a, const PyLongObject* b) {
assert(PyLong_Check(b));
return (a->long_value.lv_tag | b->long_value.lv_tag) < (2 << NON_SIZE_BITS);
}
-
static inline bool
_PyLong_IsZero(const PyLongObject *op)
{
@@ -308,6 +312,12 @@ _PyLong_FlipSign(PyLongObject *op) {
#define _PyLong_FALSE_TAG TAG_FROM_SIGN_AND_SIZE(0, 0)
#define _PyLong_TRUE_TAG TAG_FROM_SIGN_AND_SIZE(1, 1)
+static inline int
+_PyLong_CheckExactAndCompact(PyObject *op)
+{
+ return PyLong_CheckExact(op) && _PyLong_IsCompact((const PyLongObject *)op);
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h
index a96cb6236f7..347d9762f26 100644
--- a/Include/internal/pycore_magic_number.h
+++ b/Include/internal/pycore_magic_number.h
@@ -276,8 +276,14 @@ Known values:
Python 3.14a7 3621 (Optimize LOAD_FAST opcodes into LOAD_FAST_BORROW)
Python 3.14a7 3622 (Store annotations in different class dict keys)
Python 3.14a7 3623 (Add BUILD_INTERPOLATION & BUILD_TEMPLATE opcodes)
+ Python 3.14b1 3624 (Don't optimize LOAD_FAST when local is killed by DELETE_FAST)
+ Python 3.15a0 3650 (Initial version)
+ Python 3.15a1 3651 (Simplify LOAD_CONST)
+ Python 3.15a1 3652 (Virtual iterators)
+ Python 3.15a1 3653 (Fix handling of opcodes that may leave operands on the stack when optimizing LOAD_FAST)
- Python 3.15 will start with 3650
+
+ Python 3.16 will start with 3700
Please don't copy-paste the same pre-release tag for new entries above!!!
You should always use the *upcoming* tag. For example, if 3.12a6 came out
@@ -288,7 +294,7 @@ PC/launcher.c must also be updated.
*/
-#define PYC_MAGIC_NUMBER 3623
+#define PYC_MAGIC_NUMBER 3653
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \
diff --git a/Include/internal/pycore_modsupport.h b/Include/internal/pycore_modsupport.h
index 614e9f93751..d90f42e9cd8 100644
--- a/Include/internal/pycore_modsupport.h
+++ b/Include/internal/pycore_modsupport.h
@@ -27,9 +27,8 @@ PyAPI_FUNC(int) _PyArg_NoKeywords(const char *funcname, PyObject *kwargs);
// Export for 'zlib' shared extension
PyAPI_FUNC(int) _PyArg_CheckPositional(const char *, Py_ssize_t,
Py_ssize_t, Py_ssize_t);
-#define _Py_ANY_VARARGS(n) ((n) == PY_SSIZE_T_MAX)
#define _PyArg_CheckPositional(funcname, nargs, min, max) \
- ((!_Py_ANY_VARARGS(max) && (min) <= (nargs) && (nargs) <= (max)) \
+ (((min) <= (nargs) && (nargs) <= (max)) \
|| _PyArg_CheckPositional((funcname), (nargs), (min), (max)))
extern PyObject ** _Py_VaBuildStack(
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index b7e162c8abc..8fe9875fae0 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -313,7 +313,7 @@ extern int _PyDict_CheckConsistency(PyObject *mp, int check_content);
// Fast inlined version of PyType_HasFeature()
static inline int
_PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
- return ((FT_ATOMIC_LOAD_ULONG_RELAXED(type->tp_flags) & feature) != 0);
+ return ((type->tp_flags) & feature) != 0;
}
extern void _PyType_InitCache(PyInterpreterState *interp);
@@ -767,6 +767,27 @@ _Py_TryIncref(PyObject *op)
#endif
}
+// Enqueue an object to be freed possibly after some delay
+#ifdef Py_GIL_DISABLED
+PyAPI_FUNC(void) _PyObject_XDecRefDelayed(PyObject *obj);
+#else
+static inline void _PyObject_XDecRefDelayed(PyObject *obj)
+{
+ Py_XDECREF(obj);
+}
+#endif
+
+#ifdef Py_GIL_DISABLED
+// Same as `Py_XSETREF` but in free-threading, it stores the object atomically
+// and queues the old object to be decrefed at a safe point using QSBR.
+PyAPI_FUNC(void) _PyObject_XSetRefDelayed(PyObject **p_obj, PyObject *obj);
+#else
+static inline void _PyObject_XSetRefDelayed(PyObject **p_obj, PyObject *obj)
+{
+ Py_XSETREF(*p_obj, obj);
+}
+#endif
+
#ifdef Py_REF_DEBUG
extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *);
extern void _Py_FinalizeRefTotal(_PyRuntimeState *);
@@ -897,6 +918,9 @@ extern PyObject *_PyType_LookupRefAndVersion(PyTypeObject *, PyObject *,
extern unsigned int
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out);
+PyAPI_FUNC(int) _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj,
+ PyObject *name, _PyStackRef *method);
+
// Cache the provided init method in the specialization cache of type if the
// provided type version matches the current version of the type.
//
@@ -1007,6 +1031,22 @@ enum _PyAnnotateFormat {
_Py_ANNOTATE_FORMAT_STRING = 4,
};
+int _PyObject_SetDict(PyObject *obj, PyObject *value);
+
+#ifndef Py_GIL_DISABLED
+static inline Py_ALWAYS_INLINE void _Py_INCREF_MORTAL(PyObject *op)
+{
+ assert(!_Py_IsStaticImmortal(op));
+ op->ob_refcnt++;
+ _Py_INCREF_STAT_INC();
+#if defined(Py_REF_DEBUG) && !defined(Py_LIMITED_API)
+ if (!_Py_IsImmortal(op)) {
+ _Py_INCREF_IncRefTotal();
+ }
+#endif
+}
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h
index 4006a99382d..dd1bf2d1d2b 100644
--- a/Include/internal/pycore_opcode_metadata.h
+++ b/Include/internal/pycore_opcode_metadata.h
@@ -113,7 +113,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case CALL_INTRINSIC_2:
return 2;
case CALL_ISINSTANCE:
- return 2 + oparg;
+ return 4;
case CALL_KW:
return 3 + oparg;
case CALL_KW_BOUND_METHOD:
@@ -123,7 +123,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case CALL_KW_PY:
return 3 + oparg;
case CALL_LEN:
- return 2 + oparg;
+ return 3;
case CALL_LIST_APPEND:
return 3;
case CALL_METHOD_DESCRIPTOR_FAST:
@@ -205,15 +205,15 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case FORMAT_WITH_SPEC:
return 2;
case FOR_ITER:
- return 1;
+ return 2;
case FOR_ITER_GEN:
- return 1;
+ return 2;
case FOR_ITER_LIST:
- return 1;
+ return 2;
case FOR_ITER_RANGE:
- return 1;
+ return 2;
case FOR_ITER_TUPLE:
- return 1;
+ return 2;
case GET_AITER:
return 1;
case GET_ANEXT:
@@ -239,11 +239,11 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case INSTRUMENTED_END_ASYNC_FOR:
return 2;
case INSTRUMENTED_END_FOR:
- return 2;
+ return 3;
case INSTRUMENTED_END_SEND:
return 2;
case INSTRUMENTED_FOR_ITER:
- return 1;
+ return 2;
case INSTRUMENTED_INSTRUCTION:
return 0;
case INSTRUMENTED_JUMP_BACKWARD:
@@ -257,7 +257,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case INSTRUMENTED_NOT_TAKEN:
return 0;
case INSTRUMENTED_POP_ITER:
- return 1;
+ return 2;
case INSTRUMENTED_POP_JUMP_IF_FALSE:
return 1;
case INSTRUMENTED_POP_JUMP_IF_NONE:
@@ -334,10 +334,6 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 0;
case LOAD_CONST:
return 0;
- case LOAD_CONST_IMMORTAL:
- return 0;
- case LOAD_CONST_MORTAL:
- return 0;
case LOAD_DEREF:
return 0;
case LOAD_FAST:
@@ -399,7 +395,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
case POP_EXCEPT:
return 1;
case POP_ITER:
- return 1;
+ return 2;
case POP_JUMP_IF_FALSE:
return 1;
case POP_JUMP_IF_NONE:
@@ -692,15 +688,15 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
case FORMAT_WITH_SPEC:
return 1;
case FOR_ITER:
- return 2;
+ return 3;
case FOR_ITER_GEN:
- return 1;
- case FOR_ITER_LIST:
return 2;
+ case FOR_ITER_LIST:
+ return 3;
case FOR_ITER_RANGE:
- return 2;
+ return 3;
case FOR_ITER_TUPLE:
- return 2;
+ return 3;
case GET_AITER:
return 1;
case GET_ANEXT:
@@ -708,7 +704,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
case GET_AWAITABLE:
return 1;
case GET_ITER:
- return 1;
+ return 2;
case GET_LEN:
return 2;
case GET_YIELD_FROM_ITER:
@@ -726,11 +722,11 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
case INSTRUMENTED_END_ASYNC_FOR:
return 0;
case INSTRUMENTED_END_FOR:
- return 1;
+ return 2;
case INSTRUMENTED_END_SEND:
return 1;
case INSTRUMENTED_FOR_ITER:
- return 2;
+ return 3;
case INSTRUMENTED_INSTRUCTION:
return 0;
case INSTRUMENTED_JUMP_BACKWARD:
@@ -821,10 +817,6 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 1;
case LOAD_CONST:
return 1;
- case LOAD_CONST_IMMORTAL:
- return 1;
- case LOAD_CONST_MORTAL:
- return 1;
case LOAD_DEREF:
return 1;
case LOAD_FAST:
@@ -1080,27 +1072,27 @@ extern const struct opcode_metadata _PyOpcode_opcode_metadata[267];
const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[BINARY_OP] = { true, INSTR_FMT_IBC0000, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
- [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
+ [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG },
[BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
[BINARY_OP_EXTEND] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
- [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
+ [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG },
[BINARY_OP_SUBSCR_DICT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG },
[BINARY_OP_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_SUBSCR_LIST_SLICE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_SUBSCR_STR_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
- [BINARY_OP_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG },
+ [BINARY_OP_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG },
- [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
+ [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG },
[BINARY_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BUILD_INTERPOLATION] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[BUILD_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BUILD_SET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
- [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG },
- [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG },
+ [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
+ [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BUILD_TEMPLATE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[BUILD_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG },
[CACHE] = { true, INSTR_FMT_IX, 0 },
@@ -1115,13 +1107,13 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[CALL_FUNCTION_EX] = { true, INSTR_FMT_IX, HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[CALL_INTRINSIC_1] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[CALL_INTRINSIC_2] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
- [CALL_ISINSTANCE] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
+ [CALL_ISINSTANCE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[CALL_KW] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[CALL_KW_BOUND_METHOD] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[CALL_KW_NON_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[CALL_KW_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
- [CALL_LEN] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
- [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
+ [CALL_LEN] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
+ [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@@ -1137,7 +1129,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[CLEANUP_THROW] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EXIT_FLAG },
- [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG },
+ [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EXIT_FLAG },
[COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EXIT_FLAG },
[CONTAINS_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[CONTAINS_OP_DICT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@@ -1165,7 +1157,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[FOR_ITER_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG },
[FOR_ITER_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG },
- [FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
+ [FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EXIT_FLAG },
[GET_AITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[GET_ANEXT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[GET_AWAITABLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@@ -1195,8 +1187,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[INSTRUMENTED_RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
[INSTRUMENTED_RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[INSTRUMENTED_YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
- [INTERPRETER_EXIT] = { true, INSTR_FMT_IX, 0 },
- [IS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG },
+ [INTERPRETER_EXIT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG },
+ [IS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG },
[JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[JUMP_BACKWARD_JIT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[JUMP_BACKWARD_NO_INTERRUPT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG },
@@ -1205,8 +1197,8 @@ 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_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 },
- [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG },
+ [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
+ [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG },
[LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG },
@@ -1216,13 +1208,11 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[LOAD_ATTR_PROPERTY] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG },
- [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG },
+ [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[LOAD_BUILD_CLASS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[LOAD_COMMON_CONSTANT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG },
[LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG },
- [LOAD_CONST_IMMORTAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG },
- [LOAD_CONST_MORTAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG },
[LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG },
[LOAD_FAST_AND_CLEAR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG },
@@ -1252,10 +1242,10 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[NOP] = { true, INSTR_FMT_IX, HAS_PURE_FLAG },
[NOT_TAKEN] = { true, INSTR_FMT_IX, HAS_PURE_FLAG },
[POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG },
- [POP_ITER] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG },
+ [POP_ITER] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG },
[POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG },
- [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG },
- [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG },
+ [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ESCAPES_FLAG },
+ [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ESCAPES_FLAG },
[POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG },
[POP_TOP] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG },
[PUSH_EXC_INFO] = { true, INSTR_FMT_IX, 0 },
@@ -1292,7 +1282,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG },
[TO_BOOL_INT] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
- [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG },
+ [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG },
[TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@@ -1300,8 +1290,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
[UNARY_NOT] = { true, INSTR_FMT_IX, HAS_PURE_FLAG },
[UNPACK_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
- [UNPACK_SEQUENCE_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG },
- [UNPACK_SEQUENCE_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG },
+ [UNPACK_SEQUENCE_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
+ [UNPACK_SEQUENCE_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
[WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
[YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG },
@@ -1363,12 +1353,12 @@ _PyOpcode_macro_expansion[256] = {
[CALL_BUILTIN_O] = { .nuops = 2, .uops = { { _CALL_BUILTIN_O, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC, OPARG_SIMPLE, 3 } } },
[CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { _CALL_INTRINSIC_1, OPARG_SIMPLE, 0 } } },
[CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { _CALL_INTRINSIC_2, OPARG_SIMPLE, 0 } } },
- [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { _CALL_ISINSTANCE, OPARG_SIMPLE, 3 } } },
+ [CALL_ISINSTANCE] = { .nuops = 3, .uops = { { _GUARD_THIRD_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_ISINSTANCE, OPARG_SIMPLE, 3 }, { _CALL_ISINSTANCE, OPARG_SIMPLE, 3 } } },
[CALL_KW_BOUND_METHOD] = { .nuops = 6, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_METHOD_VERSION_KW, 2, 1 }, { _EXPAND_METHOD_KW, OPARG_SIMPLE, 3 }, { _PY_FRAME_KW, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } },
[CALL_KW_NON_PY] = { .nuops = 3, .uops = { { _CHECK_IS_NOT_PY_CALLABLE_KW, OPARG_SIMPLE, 3 }, { _CALL_KW_NON_PY, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC, OPARG_SIMPLE, 3 } } },
[CALL_KW_PY] = { .nuops = 5, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_FUNCTION_VERSION_KW, 2, 1 }, { _PY_FRAME_KW, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } },
- [CALL_LEN] = { .nuops = 1, .uops = { { _CALL_LEN, OPARG_SIMPLE, 3 } } },
- [CALL_LIST_APPEND] = { .nuops = 1, .uops = { { _CALL_LIST_APPEND, OPARG_SIMPLE, 3 } } },
+ [CALL_LEN] = { .nuops = 3, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_LEN, OPARG_SIMPLE, 3 }, { _CALL_LEN, OPARG_SIMPLE, 3 } } },
+ [CALL_LIST_APPEND] = { .nuops = 4, .uops = { { _GUARD_CALLABLE_LIST_APPEND, OPARG_SIMPLE, 3 }, { _GUARD_NOS_NOT_NULL, OPARG_SIMPLE, 3 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 3 }, { _CALL_LIST_APPEND, OPARG_SIMPLE, 3 } } },
[CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC, OPARG_SIMPLE, 3 } } },
[CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC, OPARG_SIMPLE, 3 } } },
[CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_NOARGS, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC, OPARG_SIMPLE, 3 } } },
@@ -1435,8 +1425,7 @@ _PyOpcode_macro_expansion[256] = {
[LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_WITH_HINT, 1, 3 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
[LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { _LOAD_BUILD_CLASS, OPARG_SIMPLE, 0 } } },
[LOAD_COMMON_CONSTANT] = { .nuops = 1, .uops = { { _LOAD_COMMON_CONSTANT, OPARG_SIMPLE, 0 } } },
- [LOAD_CONST_IMMORTAL] = { .nuops = 1, .uops = { { _LOAD_CONST_IMMORTAL, OPARG_SIMPLE, 0 } } },
- [LOAD_CONST_MORTAL] = { .nuops = 1, .uops = { { _LOAD_CONST_MORTAL, OPARG_SIMPLE, 0 } } },
+ [LOAD_CONST] = { .nuops = 1, .uops = { { _LOAD_CONST, OPARG_SIMPLE, 0 } } },
[LOAD_DEREF] = { .nuops = 1, .uops = { { _LOAD_DEREF, OPARG_SIMPLE, 0 } } },
[LOAD_FAST] = { .nuops = 1, .uops = { { _LOAD_FAST, OPARG_SIMPLE, 0 } } },
[LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { _LOAD_FAST_AND_CLEAR, OPARG_SIMPLE, 0 } } },
@@ -1464,7 +1453,7 @@ _PyOpcode_macro_expansion[256] = {
[NOP] = { .nuops = 1, .uops = { { _NOP, OPARG_SIMPLE, 0 } } },
[NOT_TAKEN] = { .nuops = 1, .uops = { { _NOP, OPARG_SIMPLE, 0 } } },
[POP_EXCEPT] = { .nuops = 1, .uops = { { _POP_EXCEPT, OPARG_SIMPLE, 0 } } },
- [POP_ITER] = { .nuops = 1, .uops = { { _POP_TOP, OPARG_SIMPLE, 0 } } },
+ [POP_ITER] = { .nuops = 1, .uops = { { _POP_ITER, OPARG_SIMPLE, 0 } } },
[POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, OPARG_REPLACED, 1 } } },
[POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, OPARG_SIMPLE, 1 }, { _POP_JUMP_IF_TRUE, OPARG_REPLACED, 1 } } },
[POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, OPARG_SIMPLE, 1 }, { _POP_JUMP_IF_FALSE, OPARG_REPLACED, 1 } } },
@@ -1667,8 +1656,6 @@ const char *_PyOpcode_OpName[267] = {
[LOAD_CLOSURE] = "LOAD_CLOSURE",
[LOAD_COMMON_CONSTANT] = "LOAD_COMMON_CONSTANT",
[LOAD_CONST] = "LOAD_CONST",
- [LOAD_CONST_IMMORTAL] = "LOAD_CONST_IMMORTAL",
- [LOAD_CONST_MORTAL] = "LOAD_CONST_MORTAL",
[LOAD_DEREF] = "LOAD_DEREF",
[LOAD_FAST] = "LOAD_FAST",
[LOAD_FAST_AND_CLEAR] = "LOAD_FAST_AND_CLEAR",
@@ -1787,6 +1774,37 @@ const uint8_t _PyOpcode_Caches[256] = {
extern const uint8_t _PyOpcode_Deopt[256];
#ifdef NEED_OPCODE_METADATA
const uint8_t _PyOpcode_Deopt[256] = {
+ [121] = 121,
+ [122] = 122,
+ [123] = 123,
+ [124] = 124,
+ [125] = 125,
+ [126] = 126,
+ [127] = 127,
+ [210] = 210,
+ [211] = 211,
+ [212] = 212,
+ [213] = 213,
+ [214] = 214,
+ [215] = 215,
+ [216] = 216,
+ [217] = 217,
+ [218] = 218,
+ [219] = 219,
+ [220] = 220,
+ [221] = 221,
+ [222] = 222,
+ [223] = 223,
+ [224] = 224,
+ [225] = 225,
+ [226] = 226,
+ [227] = 227,
+ [228] = 228,
+ [229] = 229,
+ [230] = 230,
+ [231] = 231,
+ [232] = 232,
+ [233] = 233,
[BINARY_OP] = BINARY_OP,
[BINARY_OP_ADD_FLOAT] = BINARY_OP,
[BINARY_OP_ADD_INT] = BINARY_OP,
@@ -1930,8 +1948,6 @@ const uint8_t _PyOpcode_Deopt[256] = {
[LOAD_BUILD_CLASS] = LOAD_BUILD_CLASS,
[LOAD_COMMON_CONSTANT] = LOAD_COMMON_CONSTANT,
[LOAD_CONST] = LOAD_CONST,
- [LOAD_CONST_IMMORTAL] = LOAD_CONST,
- [LOAD_CONST_MORTAL] = LOAD_CONST,
[LOAD_DEREF] = LOAD_DEREF,
[LOAD_FAST] = LOAD_FAST,
[LOAD_FAST_AND_CLEAR] = LOAD_FAST_AND_CLEAR,
@@ -2026,6 +2042,8 @@ const uint8_t _PyOpcode_Deopt[256] = {
case 125: \
case 126: \
case 127: \
+ case 210: \
+ case 211: \
case 212: \
case 213: \
case 214: \
diff --git a/Include/internal/pycore_opcode_utils.h b/Include/internal/pycore_opcode_utils.h
index 62af06dc01c..79a1a242556 100644
--- a/Include/internal/pycore_opcode_utils.h
+++ b/Include/internal/pycore_opcode_utils.h
@@ -56,6 +56,8 @@ extern "C" {
#define IS_RETURN_OPCODE(opcode) \
(opcode == RETURN_VALUE)
+#define IS_RAISE_OPCODE(opcode) \
+ (opcode == RAISE_VARARGS || opcode == RERAISE)
/* Flags used in the oparg for MAKE_FUNCTION */
diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h
index 4af1fa63ac1..8b7f12bf03d 100644
--- a/Include/internal/pycore_optimizer.h
+++ b/Include/internal/pycore_optimizer.h
@@ -10,6 +10,7 @@ extern "C" {
#include "pycore_typedefs.h" // _PyInterpreterFrame
#include "pycore_uop_ids.h"
+#include "pycore_stackref.h" // _PyStackRef
#include <stdbool.h>
@@ -69,7 +70,7 @@ typedef struct {
typedef struct {
uint32_t target;
_Py_BackoffCounter temperature;
- const struct _PyExecutorObject *executor;
+ struct _PyExecutorObject *executor;
} _PyExitData;
typedef struct _PyExecutorObject {
@@ -84,6 +85,10 @@ typedef struct _PyExecutorObject {
_PyExitData exits[1];
} _PyExecutorObject;
+/* If pending deletion list gets large enough, then scan,
+ * and free any executors that aren't executing
+ * i.e. any that aren't a thread's current_executor. */
+#define EXECUTOR_DELETE_LIST_MAX 100
// Export for '_opcode' shared extension (JIT compiler).
PyAPI_FUNC(_PyExecutorObject*) _Py_GetExecutor(PyCodeObject *code, int offset);
@@ -174,6 +179,7 @@ typedef enum _JitSymType {
JIT_SYM_KNOWN_VALUE_TAG = 7,
JIT_SYM_TUPLE_TAG = 8,
JIT_SYM_TRUTHINESS_TAG = 9,
+ JIT_SYM_COMPACT_INT = 10,
} JitSymType;
typedef struct _jit_opt_known_class {
@@ -206,6 +212,10 @@ typedef struct {
uint16_t value;
} JitOptTruthiness;
+typedef struct {
+ uint8_t tag;
+} JitOptCompactInt;
+
typedef union _jit_opt_symbol {
uint8_t tag;
JitOptKnownClass cls;
@@ -213,18 +223,62 @@ typedef union _jit_opt_symbol {
JitOptKnownVersion version;
JitOptTuple tuple;
JitOptTruthiness truthiness;
+ JitOptCompactInt compact;
} JitOptSymbol;
+// This mimics the _PyStackRef API
+typedef union {
+ uintptr_t bits;
+} JitOptRef;
+
+#define REF_IS_BORROWED 1
+
+#define JIT_BITS_TO_PTR_MASKED(REF) ((JitOptSymbol *)(((REF).bits) & (~REF_IS_BORROWED)))
+
+static inline JitOptSymbol *
+PyJitRef_Unwrap(JitOptRef ref)
+{
+ return JIT_BITS_TO_PTR_MASKED(ref);
+}
+
+bool _Py_uop_symbol_is_immortal(JitOptSymbol *sym);
+
+
+static inline JitOptRef
+PyJitRef_Wrap(JitOptSymbol *sym)
+{
+ return (JitOptRef){.bits=(uintptr_t)sym};
+}
+
+static inline JitOptRef
+PyJitRef_Borrow(JitOptRef ref)
+{
+ return (JitOptRef){ .bits = ref.bits | REF_IS_BORROWED };
+}
+
+static const JitOptRef PyJitRef_NULL = {.bits = REF_IS_BORROWED};
+
+static inline bool
+PyJitRef_IsNull(JitOptRef ref)
+{
+ return ref.bits == PyJitRef_NULL.bits;
+}
+
+static inline int
+PyJitRef_IsBorrowed(JitOptRef ref)
+{
+ return (ref.bits & REF_IS_BORROWED) == REF_IS_BORROWED;
+}
struct _Py_UOpsAbstractFrame {
// Max stacklen
int stack_len;
int locals_len;
- JitOptSymbol **stack_pointer;
- JitOptSymbol **stack;
- JitOptSymbol **locals;
+ JitOptRef *stack_pointer;
+ JitOptRef *stack;
+ JitOptRef *locals;
};
typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame;
@@ -247,37 +301,43 @@ typedef struct _JitOptContext {
// Arena for the symbolic types.
ty_arena t_arena;
- JitOptSymbol **n_consumed;
- JitOptSymbol **limit;
- JitOptSymbol *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE];
+ JitOptRef *n_consumed;
+ JitOptRef *limit;
+ JitOptRef locals_and_stack[MAX_ABSTRACT_INTERP_SIZE];
} JitOptContext;
-extern bool _Py_uop_sym_is_null(JitOptSymbol *sym);
-extern bool _Py_uop_sym_is_not_null(JitOptSymbol *sym);
-extern bool _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym);
-extern PyObject *_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym);
-extern JitOptSymbol *_Py_uop_sym_new_unknown(JitOptContext *ctx);
-extern JitOptSymbol *_Py_uop_sym_new_not_null(JitOptContext *ctx);
-extern JitOptSymbol *_Py_uop_sym_new_type(
+extern bool _Py_uop_sym_is_null(JitOptRef sym);
+extern bool _Py_uop_sym_is_not_null(JitOptRef sym);
+extern bool _Py_uop_sym_is_const(JitOptContext *ctx, JitOptRef sym);
+extern PyObject *_Py_uop_sym_get_const(JitOptContext *ctx, JitOptRef sym);
+extern JitOptRef _Py_uop_sym_new_unknown(JitOptContext *ctx);
+extern JitOptRef _Py_uop_sym_new_not_null(JitOptContext *ctx);
+extern JitOptRef _Py_uop_sym_new_type(
JitOptContext *ctx, PyTypeObject *typ);
-extern JitOptSymbol *_Py_uop_sym_new_const(JitOptContext *ctx, PyObject *const_val);
-extern JitOptSymbol *_Py_uop_sym_new_null(JitOptContext *ctx);
-extern bool _Py_uop_sym_has_type(JitOptSymbol *sym);
-extern bool _Py_uop_sym_matches_type(JitOptSymbol *sym, PyTypeObject *typ);
-extern bool _Py_uop_sym_matches_type_version(JitOptSymbol *sym, unsigned int version);
-extern void _Py_uop_sym_set_null(JitOptContext *ctx, JitOptSymbol *sym);
-extern void _Py_uop_sym_set_non_null(JitOptContext *ctx, JitOptSymbol *sym);
-extern void _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeObject *typ);
-extern bool _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int version);
-extern void _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val);
-extern bool _Py_uop_sym_is_bottom(JitOptSymbol *sym);
-extern int _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym);
-extern PyTypeObject *_Py_uop_sym_get_type(JitOptSymbol *sym);
-extern bool _Py_uop_sym_is_immortal(JitOptSymbol *sym);
-extern JitOptSymbol *_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args);
-extern JitOptSymbol *_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptSymbol *sym, int item);
-extern int _Py_uop_sym_tuple_length(JitOptSymbol *sym);
-extern JitOptSymbol *_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy);
+
+extern JitOptRef _Py_uop_sym_new_const(JitOptContext *ctx, PyObject *const_val);
+extern JitOptRef _Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val);
+bool _Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptRef sym);
+_PyStackRef _Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptRef sym);
+extern JitOptRef _Py_uop_sym_new_null(JitOptContext *ctx);
+extern bool _Py_uop_sym_has_type(JitOptRef sym);
+extern bool _Py_uop_sym_matches_type(JitOptRef sym, PyTypeObject *typ);
+extern bool _Py_uop_sym_matches_type_version(JitOptRef sym, unsigned int version);
+extern void _Py_uop_sym_set_null(JitOptContext *ctx, JitOptRef sym);
+extern void _Py_uop_sym_set_non_null(JitOptContext *ctx, JitOptRef sym);
+extern void _Py_uop_sym_set_type(JitOptContext *ctx, JitOptRef sym, PyTypeObject *typ);
+extern bool _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptRef sym, unsigned int version);
+extern void _Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef sym, PyObject *const_val);
+extern bool _Py_uop_sym_is_bottom(JitOptRef sym);
+extern int _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptRef sym);
+extern PyTypeObject *_Py_uop_sym_get_type(JitOptRef sym);
+extern JitOptRef _Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptRef *args);
+extern JitOptRef _Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptRef sym, int item);
+extern int _Py_uop_sym_tuple_length(JitOptRef sym);
+extern JitOptRef _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptRef value, bool truthy);
+extern bool _Py_uop_sym_is_compact_int(JitOptRef sym);
+extern JitOptRef _Py_uop_sym_new_compact_int(JitOptContext *ctx);
+extern void _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef sym);
extern void _Py_uop_abstractcontext_init(JitOptContext *ctx);
extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx);
@@ -286,7 +346,7 @@ extern _Py_UOpsAbstractFrame *_Py_uop_frame_new(
JitOptContext *ctx,
PyCodeObject *co,
int curr_stackentries,
- JitOptSymbol **args,
+ JitOptRef *args,
int arg_len);
extern int _Py_uop_frame_pop(JitOptContext *ctx);
@@ -304,6 +364,9 @@ static inline int is_terminator(const _PyUOpInstruction *uop)
}
PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
+#ifdef _Py_TIER2
+extern void _Py_ClearExecutorDeletionList(PyInterpreterState *interp);
+#endif
#ifdef __cplusplus
}
diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h
index f357b88e220..2c2048f7e12 100644
--- a/Include/internal/pycore_pyerrors.h
+++ b/Include/internal/pycore_pyerrors.h
@@ -94,13 +94,13 @@ extern void _PyErr_Fetch(
PyObject **value,
PyObject **traceback);
-extern PyObject* _PyErr_GetRaisedException(PyThreadState *tstate);
+PyAPI_FUNC(PyObject*) _PyErr_GetRaisedException(PyThreadState *tstate);
PyAPI_FUNC(int) _PyErr_ExceptionMatches(
PyThreadState *tstate,
PyObject *exc);
-extern void _PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc);
+PyAPI_FUNC(void) _PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc);
extern void _PyErr_Restore(
PyThreadState *tstate,
diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h
index 02537bdfef8..f3f2ae0a140 100644
--- a/Include/internal/pycore_pymem.h
+++ b/Include/internal/pycore_pymem.h
@@ -88,17 +88,7 @@ extern wchar_t *_PyMem_DefaultRawWcsdup(const wchar_t *str);
extern int _PyMem_DebugEnabled(void);
// Enqueue a pointer to be freed possibly after some delay.
-extern void _PyMem_FreeDelayed(void *ptr);
-
-// Enqueue an object to be freed possibly after some delay
-#ifdef Py_GIL_DISABLED
-PyAPI_FUNC(void) _PyObject_XDecRefDelayed(PyObject *obj);
-#else
-static inline void _PyObject_XDecRefDelayed(PyObject *obj)
-{
- Py_XDECREF(obj);
-}
-#endif
+extern void _PyMem_FreeDelayed(void *ptr, size_t size);
// Periodically process delayed free requests.
extern void _PyMem_ProcessDelayed(PyThreadState *tstate);
diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h
index 633e5cf77db..ea3dfbd2eef 100644
--- a/Include/internal/pycore_pystate.h
+++ b/Include/internal/pycore_pystate.h
@@ -8,6 +8,7 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif
+#include "pycore_pythonrun.h" // _PyOS_STACK_MARGIN_SHIFT
#include "pycore_typedefs.h" // _PyRuntimeState
#include "pycore_tstate.h"
@@ -325,7 +326,7 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate)
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
assert(_tstate->c_stack_hard_limit != 0);
intptr_t here_addr = _Py_get_machine_stack_pointer();
- return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, PYOS_STACK_MARGIN_SHIFT);
+ return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, _PyOS_STACK_MARGIN_SHIFT);
}
#ifdef __cplusplus
diff --git a/Include/internal/pycore_pythonrun.h b/Include/internal/pycore_pythonrun.h
index 0bfc5704dc4..c2832098ddb 100644
--- a/Include/internal/pycore_pythonrun.h
+++ b/Include/internal/pycore_pythonrun.h
@@ -25,6 +25,7 @@ extern int _PyRun_InteractiveLoopObject(
PyObject *filename,
PyCompilerFlags *flags);
+extern int _PyObject_SupportedAsScript(PyObject *);
extern const char* _Py_SourceAsString(
PyObject *cmd,
const char *funcname,
@@ -32,6 +33,28 @@ extern const char* _Py_SourceAsString(
PyCompilerFlags *cf,
PyObject **cmd_copy);
+
+/* Stack size, in "pointers". This must be large enough, so
+ * no two calls to check recursion depth are more than this far
+ * apart. In practice, that means it must be larger than the C
+ * stack consumption of PyEval_EvalDefault */
+#if defined(_Py_ADDRESS_SANITIZER) || defined(_Py_THREAD_SANITIZER)
+# define _PyOS_LOG2_STACK_MARGIN 12
+#elif defined(Py_DEBUG) && defined(WIN32)
+# define _PyOS_LOG2_STACK_MARGIN 12
+#else
+# define _PyOS_LOG2_STACK_MARGIN 11
+#endif
+#define _PyOS_STACK_MARGIN (1 << _PyOS_LOG2_STACK_MARGIN)
+#define _PyOS_STACK_MARGIN_BYTES (_PyOS_STACK_MARGIN * sizeof(void *))
+
+#if SIZEOF_VOID_P == 8
+# define _PyOS_STACK_MARGIN_SHIFT (_PyOS_LOG2_STACK_MARGIN + 3)
+#else
+# define _PyOS_STACK_MARGIN_SHIFT (_PyOS_LOG2_STACK_MARGIN + 2)
+#endif
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/internal/pycore_qsbr.h b/Include/internal/pycore_qsbr.h
index b835c3abaf5..1f9b3fcf777 100644
--- a/Include/internal/pycore_qsbr.h
+++ b/Include/internal/pycore_qsbr.h
@@ -48,8 +48,21 @@ struct _qsbr_thread_state {
// Thread state (or NULL)
PyThreadState *tstate;
- // Used to defer advancing write sequence a fixed number of times
- int deferrals;
+ // Number of held items added by this thread since the last write sequence
+ // advance
+ int deferred_count;
+
+ // Estimate for the amount of memory that is held by this thread since
+ // the last write sequence advance
+ size_t deferred_memory;
+
+ // Amount of memory in mimalloc pages deferred from collection. When
+ // deferred, they are prevented from being used for a different size class
+ // and in a different thread.
+ size_t deferred_page_memory;
+
+ // True if the deferred memory frees should be processed.
+ bool should_process;
// Is this thread state allocated?
bool allocated;
@@ -109,11 +122,17 @@ _Py_qbsr_goal_reached(struct _qsbr_thread_state *qsbr, uint64_t goal)
extern uint64_t
_Py_qsbr_advance(struct _qsbr_shared *shared);
-// Batches requests to advance the write sequence. This advances the write
-// sequence every N calls, which reduces overhead but increases time to
-// reclamation. Returns the new goal.
+// Return the next value for the write sequence (current plus the increment).
extern uint64_t
-_Py_qsbr_deferred_advance(struct _qsbr_thread_state *qsbr);
+_Py_qsbr_shared_next(struct _qsbr_shared *shared);
+
+// Return true if deferred memory frees held by QSBR should be processed to
+// determine if they can be safely freed.
+static inline bool
+_Py_qsbr_should_process(struct _qsbr_thread_state *qsbr)
+{
+ return qsbr->should_process;
+}
// Have the read sequences advanced to the given goal? If this returns true,
// it safe to reclaim any memory tagged with the goal (or earlier goal).
diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h
index 2b2e439681f..b182f7825a2 100644
--- a/Include/internal/pycore_runtime_init.h
+++ b/Include/internal/pycore_runtime_init.h
@@ -61,9 +61,6 @@ extern PyTypeObject _PyExc_MemoryError;
}, \
}, \
}, \
- /* A TSS key must be initialized with Py_tss_NEEDS_INIT \
- in accordance with the specification. */ \
- .autoTSSkey = Py_tss_NEEDS_INIT, \
.parser = _parser_runtime_state_INIT, \
.ceval = { \
.pending_mainthread = { \
@@ -233,9 +230,7 @@ extern PyTypeObject _PyExc_MemoryError;
._data = (LITERAL), \
}
-#include "pycore_runtime_init_generated.h"
-
#ifdef __cplusplus
}
#endif
-#endif /* !Py_INTERNAL_RUNTIME_INIT_H */
+#endif /* !Py_INTERNAL_RUNTIME_INIT_H */ \ No newline at end of file
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index de1dfd0cce8..d378fcae26c 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -790,10 +790,10 @@ extern "C" {
INIT_ID(add_done_callback), \
INIT_ID(after_in_child), \
INIT_ID(after_in_parent), \
- INIT_ID(aggregate_class), \
INIT_ID(alias), \
INIT_ID(align), \
INIT_ID(all), \
+ INIT_ID(all_threads), \
INIT_ID(allow_code), \
INIT_ID(any), \
INIT_ID(append), \
@@ -807,7 +807,6 @@ extern "C" {
INIT_ID(ast), \
INIT_ID(athrow), \
INIT_ID(attribute), \
- INIT_ID(authorizer_callback), \
INIT_ID(autocommit), \
INIT_ID(backtick), \
INIT_ID(base), \
@@ -832,6 +831,7 @@ extern "C" {
INIT_ID(bytes_per_sep), \
INIT_ID(c_call), \
INIT_ID(c_exception), \
+ INIT_ID(c_parameter_type), \
INIT_ID(c_return), \
INIT_ID(cached_datetime_module), \
INIT_ID(cached_statements), \
@@ -886,9 +886,11 @@ extern "C" {
INIT_ID(count), \
INIT_ID(covariant), \
INIT_ID(cwd), \
+ INIT_ID(d_parameter_type), \
INIT_ID(data), \
INIT_ID(database), \
INIT_ID(day), \
+ INIT_ID(debug), \
INIT_ID(decode), \
INIT_ID(decoder), \
INIT_ID(default), \
@@ -966,6 +968,7 @@ extern "C" {
INIT_ID(follow_symlinks), \
INIT_ID(format), \
INIT_ID(format_spec), \
+ INIT_ID(frame_buffer), \
INIT_ID(from_param), \
INIT_ID(fromlist), \
INIT_ID(fromtimestamp), \
@@ -1024,6 +1027,8 @@ extern "C" {
INIT_ID(intersection), \
INIT_ID(interval), \
INIT_ID(io), \
+ INIT_ID(is_compress), \
+ INIT_ID(is_raw), \
INIT_ID(is_running), \
INIT_ID(is_struct), \
INIT_ID(isatty), \
@@ -1100,7 +1105,6 @@ extern "C" {
INIT_ID(msg), \
INIT_ID(mutex), \
INIT_ID(mycmp), \
- INIT_ID(n_arg), \
INIT_ID(n_fields), \
INIT_ID(n_sequence_fields), \
INIT_ID(n_unnamed_fields), \
@@ -1108,7 +1112,6 @@ extern "C" {
INIT_ID(name_from), \
INIT_ID(namespace_separator), \
INIT_ID(namespaces), \
- INIT_ID(narg), \
INIT_ID(ndigits), \
INIT_ID(nested), \
INIT_ID(new_file_name), \
@@ -1131,6 +1134,7 @@ extern "C" {
INIT_ID(offset_src), \
INIT_ID(on_type_read), \
INIT_ID(onceregistry), \
+ INIT_ID(only_active_thread), \
INIT_ID(only_keys), \
INIT_ID(oparg), \
INIT_ID(opcode), \
@@ -1147,6 +1151,7 @@ extern "C" {
INIT_ID(overlapped), \
INIT_ID(owner), \
INIT_ID(pages), \
+ INIT_ID(parameter), \
INIT_ID(parent), \
INIT_ID(password), \
INIT_ID(path), \
@@ -1165,7 +1170,6 @@ extern "C" {
INIT_ID(print_file_and_line), \
INIT_ID(priority), \
INIT_ID(progress), \
- INIT_ID(progress_handler), \
INIT_ID(progress_routine), \
INIT_ID(proto), \
INIT_ID(protocol), \
@@ -1271,7 +1275,6 @@ extern "C" {
INIT_ID(timetuple), \
INIT_ID(timeunit), \
INIT_ID(top), \
- INIT_ID(trace_callback), \
INIT_ID(traceback), \
INIT_ID(trailers), \
INIT_ID(translate), \
@@ -1308,6 +1311,7 @@ extern "C" {
INIT_ID(write_through), \
INIT_ID(year), \
INIT_ID(zdict), \
+ INIT_ID(zstd_dict), \
}
#define _Py_str_ascii_INIT { \
diff --git a/Include/internal/pycore_runtime_structs.h b/Include/internal/pycore_runtime_structs.h
index 6bf3aae7175..12164c7fdd9 100644
--- a/Include/internal/pycore_runtime_structs.h
+++ b/Include/internal/pycore_runtime_structs.h
@@ -223,9 +223,6 @@ struct pyruntimestate {
struct _pythread_runtime_state threads;
struct _signals_runtime_state signals;
- /* Used for the thread state bound to the current thread. */
- Py_tss_t autoTSSkey;
-
/* Used instead of PyThreadState.trash when there is not current tstate. */
Py_tss_t trashTSSkey;
diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h
index a2acf311ff4..48a40a4c347 100644
--- a/Include/internal/pycore_stackref.h
+++ b/Include/internal/pycore_stackref.h
@@ -62,14 +62,15 @@ PyAPI_FUNC(void) _Py_stackref_record_borrow(_PyStackRef ref, const char *filenam
extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref);
static const _PyStackRef PyStackRef_NULL = { .index = 0 };
+static const _PyStackRef PyStackRef_ERROR = { .index = 2 };
// Use the first 3 even numbers for None, True and False.
// Odd numbers are reserved for (tagged) integers
-#define PyStackRef_None ((_PyStackRef){ .index = 2 } )
-#define PyStackRef_False ((_PyStackRef){ .index = 4 })
-#define PyStackRef_True ((_PyStackRef){ .index = 6 })
+#define PyStackRef_None ((_PyStackRef){ .index = 4 } )
+#define PyStackRef_False ((_PyStackRef){ .index = 6 })
+#define PyStackRef_True ((_PyStackRef){ .index = 8 })
-#define INITIAL_STACKREF_INDEX 8
+#define INITIAL_STACKREF_INDEX 10
static inline int
PyStackRef_IsNull(_PyStackRef ref)
@@ -77,6 +78,19 @@ PyStackRef_IsNull(_PyStackRef ref)
return ref.index == 0;
}
+static inline bool
+PyStackRef_IsError(_PyStackRef ref)
+{
+ return ref.index == 2;
+}
+
+static inline bool
+PyStackRef_IsValid(_PyStackRef ref)
+{
+ /* Invalid values are ERROR and NULL */
+ return !PyStackRef_IsError(ref) && !PyStackRef_IsNull(ref);
+}
+
static inline int
PyStackRef_IsTrue(_PyStackRef ref)
{
@@ -95,10 +109,17 @@ PyStackRef_IsNone(_PyStackRef ref)
return _Py_stackref_get_object(ref) == Py_None;
}
+static inline bool
+PyStackRef_IsTaggedInt(_PyStackRef ref)
+{
+ return (ref.index & 1) == 1;
+}
+
static inline PyObject *
_PyStackRef_AsPyObjectBorrow(_PyStackRef ref, const char *filename, int linenumber)
{
- assert((ref.index & 1) == 0);
+ assert(!PyStackRef_IsError(ref));
+ assert(!PyStackRef_IsTaggedInt(ref));
_Py_stackref_record_borrow(ref, filename, linenumber);
return _Py_stackref_get_object(ref);
}
@@ -128,18 +149,11 @@ _PyStackRef_FromPyObjectSteal(PyObject *obj, const char *filename, int linenumbe
#define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj), __FILE__, __LINE__)
static inline _PyStackRef
-_PyStackRef_FromPyObjectImmortal(PyObject *obj, const char *filename, int linenumber)
+_PyStackRef_FromPyObjectBorrow(PyObject *obj, const char *filename, int linenumber)
{
- assert(_Py_IsImmortal(obj));
return _Py_stackref_create(obj, filename, linenumber);
}
-#define PyStackRef_FromPyObjectImmortal(obj) _PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj), __FILE__, __LINE__)
-
-static inline bool
-PyStackRef_IsTaggedInt(_PyStackRef ref)
-{
- return (ref.index & 1) == 1;
-}
+#define PyStackRef_FromPyObjectBorrow(obj) _PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj), __FILE__, __LINE__)
static inline void
_PyStackRef_CLOSE(_PyStackRef ref, const char *filename, int linenumber)
@@ -156,6 +170,7 @@ _PyStackRef_CLOSE(_PyStackRef ref, const char *filename, int linenumber)
static inline void
_PyStackRef_XCLOSE(_PyStackRef ref, const char *filename, int linenumber)
{
+ assert(!PyStackRef_IsError(ref));
if (PyStackRef_IsNull(ref)) {
return;
}
@@ -166,6 +181,7 @@ _PyStackRef_XCLOSE(_PyStackRef ref, const char *filename, int linenumber)
static inline _PyStackRef
_PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber)
{
+ assert(!PyStackRef_IsError(ref));
if (PyStackRef_IsTaggedInt(ref)) {
return ref;
}
@@ -233,17 +249,64 @@ extern intptr_t PyStackRef_UntagInt(_PyStackRef ref);
extern _PyStackRef PyStackRef_TagInt(intptr_t i);
+/* Increments a tagged int, but does not check for overflow */
+extern _PyStackRef PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref);
+
extern bool
PyStackRef_IsNullOrInt(_PyStackRef ref);
#else
#define Py_INT_TAG 3
+#define Py_TAG_INVALID 2
+#define Py_TAG_REFCNT 1
+#define Py_TAG_BITS 3
+
+static const _PyStackRef PyStackRef_ERROR = { .bits = Py_TAG_INVALID };
+
+/* Wrap a pointer in a stack ref.
+ * The resulting stack reference is not safe and should only be used
+ * in the interpreter to pass values from one uop to another.
+ * The GC should never see one of these stack refs. */
+static inline _PyStackRef
+PyStackRef_Wrap(void *ptr)
+{
+ assert(ptr != NULL);
+#ifdef Py_DEBUG
+ return (_PyStackRef){ .bits = ((uintptr_t)ptr) | Py_TAG_INVALID };
+#else
+ return (_PyStackRef){ .bits = (uintptr_t)ptr };
+#endif
+}
+
+static inline void *
+PyStackRef_Unwrap(_PyStackRef ref)
+{
+#ifdef Py_DEBUG
+ assert ((ref.bits & Py_TAG_BITS) == Py_TAG_INVALID);
+ return (void *)(ref.bits & ~Py_TAG_BITS);
+#else
+ return (void *)(ref.bits);
+#endif
+}
+
+static inline bool
+PyStackRef_IsError(_PyStackRef ref)
+{
+ return ref.bits == Py_TAG_INVALID;
+}
+
+static inline bool
+PyStackRef_IsValid(_PyStackRef ref)
+{
+ /* Invalid values are ERROR and NULL */
+ return ref.bits >= Py_INT_TAG;
+}
static inline bool
PyStackRef_IsTaggedInt(_PyStackRef i)
{
- return (i.bits & Py_INT_TAG) == Py_INT_TAG;
+ return (i.bits & Py_TAG_BITS) == Py_INT_TAG;
}
static inline _PyStackRef
@@ -256,21 +319,31 @@ PyStackRef_TagInt(intptr_t i)
static inline intptr_t
PyStackRef_UntagInt(_PyStackRef i)
{
- assert((i.bits & Py_INT_TAG) == Py_INT_TAG);
+ assert(PyStackRef_IsTaggedInt(i));
intptr_t val = (intptr_t)i.bits;
return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, 2);
}
+static inline _PyStackRef
+PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref)
+{
+ assert((ref.bits & Py_TAG_BITS) == Py_INT_TAG); // Is tagged int
+ assert((ref.bits & (~Py_TAG_BITS)) != (INT_MAX & (~Py_TAG_BITS))); // Isn't about to overflow
+ return (_PyStackRef){ .bits = ref.bits + 4 };
+}
+
+#define PyStackRef_IsDeferredOrTaggedInt(ref) (((ref).bits & Py_TAG_REFCNT) != 0)
+
#ifdef Py_GIL_DISABLED
-#define Py_TAG_DEFERRED (1)
+#define Py_TAG_DEFERRED Py_TAG_REFCNT
#define Py_TAG_PTR ((uintptr_t)0)
-#define Py_TAG_BITS ((uintptr_t)1)
static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
+
#define PyStackRef_IsNull(stackref) ((stackref).bits == PyStackRef_NULL.bits)
#define PyStackRef_True ((_PyStackRef){.bits = ((uintptr_t)&_Py_TrueStruct) | Py_TAG_DEFERRED })
#define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) | Py_TAG_DEFERRED })
@@ -286,6 +359,7 @@ static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
static inline PyObject *
PyStackRef_AsPyObjectBorrow(_PyStackRef stackref)
{
+ assert(!PyStackRef_IsTaggedInt(stackref));
PyObject *cleared = ((PyObject *)((stackref).bits & (~Py_TAG_BITS)));
return cleared;
}
@@ -365,21 +439,20 @@ PyStackRef_FromPyObjectNew(PyObject *obj)
#define PyStackRef_FromPyObjectNew(obj) PyStackRef_FromPyObjectNew(_PyObject_CAST(obj))
static inline _PyStackRef
-PyStackRef_FromPyObjectImmortal(PyObject *obj)
+PyStackRef_FromPyObjectBorrow(PyObject *obj)
{
// Make sure we don't take an already tagged value.
assert(((uintptr_t)obj & Py_TAG_BITS) == 0);
assert(obj != NULL);
- assert(_Py_IsImmortal(obj));
return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED };
}
-#define PyStackRef_FromPyObjectImmortal(obj) PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj))
+#define PyStackRef_FromPyObjectBorrow(obj) PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj))
#define PyStackRef_CLOSE(REF) \
do { \
_PyStackRef _close_tmp = (REF); \
assert(!PyStackRef_IsNull(_close_tmp)); \
- if (!PyStackRef_IsDeferred(_close_tmp)) { \
+ if (!PyStackRef_IsDeferredOrTaggedInt(_close_tmp)) { \
Py_DECREF(PyStackRef_AsPyObjectBorrow(_close_tmp)); \
} \
} while (0)
@@ -395,7 +468,7 @@ static inline _PyStackRef
PyStackRef_DUP(_PyStackRef stackref)
{
assert(!PyStackRef_IsNull(stackref));
- if (PyStackRef_IsDeferred(stackref)) {
+ if (PyStackRef_IsDeferredOrTaggedInt(stackref)) {
return stackref;
}
Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref));
@@ -442,14 +515,12 @@ PyStackRef_AsStrongReference(_PyStackRef stackref)
/* References to immortal objects always have their tag bit set to Py_TAG_REFCNT
* as they can (must) have their reclamation deferred */
-#define Py_TAG_BITS 1
-#define Py_TAG_REFCNT 1
#if _Py_IMMORTAL_FLAGS != Py_TAG_REFCNT
# error "_Py_IMMORTAL_FLAGS != Py_TAG_REFCNT"
#endif
#define BITS_TO_PTR(REF) ((PyObject *)((REF).bits))
-#define BITS_TO_PTR_MASKED(REF) ((PyObject *)(((REF).bits) & (~Py_TAG_BITS)))
+#define BITS_TO_PTR_MASKED(REF) ((PyObject *)(((REF).bits) & (~Py_TAG_REFCNT)))
#define PyStackRef_NULL_BITS Py_TAG_REFCNT
static const _PyStackRef PyStackRef_NULL = { .bits = PyStackRef_NULL_BITS };
@@ -529,7 +600,7 @@ PyStackRef_FromPyObjectSteal(PyObject *obj)
{
assert(obj != NULL);
#if SIZEOF_VOID_P > 4
- unsigned int tag = obj->ob_flags & Py_TAG_BITS;
+ unsigned int tag = obj->ob_flags & Py_TAG_REFCNT;
#else
unsigned int tag = _Py_IsImmortal(obj) ? Py_TAG_REFCNT : 0;
#endif
@@ -548,12 +619,6 @@ PyStackRef_FromPyObjectStealMortal(PyObject *obj)
return ref;
}
-// Check if a stackref is exactly the same as another stackref, including the
-// the deferred bit. This can only be used safely if you know that the deferred
-// bits of `a` and `b` match.
-#define PyStackRef_IsExactly(a, b) \
- (assert(((a).bits & Py_TAG_BITS) == ((b).bits & Py_TAG_BITS)), (a).bits == (b).bits)
-
static inline _PyStackRef
_PyStackRef_FromPyObjectNew(PyObject *obj)
{
@@ -561,7 +626,7 @@ _PyStackRef_FromPyObjectNew(PyObject *obj)
if (_Py_IsImmortal(obj)) {
return (_PyStackRef){ .bits = ((uintptr_t)obj) | Py_TAG_REFCNT};
}
- Py_INCREF_MORTAL(obj);
+ _Py_INCREF_MORTAL(obj);
_PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj };
PyStackRef_CheckValid(ref);
return ref;
@@ -572,7 +637,7 @@ static inline _PyStackRef
_PyStackRef_FromPyObjectNewMortal(PyObject *obj)
{
assert(obj != NULL);
- Py_INCREF_MORTAL(obj);
+ _Py_INCREF_MORTAL(obj);
_PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj };
PyStackRef_CheckValid(ref);
return ref;
@@ -581,23 +646,22 @@ _PyStackRef_FromPyObjectNewMortal(PyObject *obj)
/* Create a new reference from an object with an embedded reference count */
static inline _PyStackRef
-PyStackRef_FromPyObjectImmortal(PyObject *obj)
+PyStackRef_FromPyObjectBorrow(PyObject *obj)
{
- assert(_Py_IsImmortal(obj));
return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT};
}
/* WARNING: This macro evaluates its argument more than once */
#ifdef _WIN32
#define PyStackRef_DUP(REF) \
- (PyStackRef_RefcountOnObject(REF) ? (Py_INCREF_MORTAL(BITS_TO_PTR(REF)), (REF)) : (REF))
+ (PyStackRef_RefcountOnObject(REF) ? (_Py_INCREF_MORTAL(BITS_TO_PTR(REF)), (REF)) : (REF))
#else
static inline _PyStackRef
PyStackRef_DUP(_PyStackRef ref)
{
assert(!PyStackRef_IsNull(ref));
if (PyStackRef_RefcountOnObject(ref)) {
- Py_INCREF_MORTAL(BITS_TO_PTR(ref));
+ _Py_INCREF_MORTAL(BITS_TO_PTR(ref));
}
return ref;
}
@@ -606,7 +670,7 @@ PyStackRef_DUP(_PyStackRef ref)
static inline bool
PyStackRef_IsHeapSafe(_PyStackRef ref)
{
- return (ref.bits & Py_TAG_BITS) == 0 || ref.bits == PyStackRef_NULL_BITS || _Py_IsImmortal(BITS_TO_PTR_MASKED(ref));
+ return (ref.bits & Py_TAG_BITS) != Py_TAG_REFCNT || ref.bits == PyStackRef_NULL_BITS || _Py_IsImmortal(BITS_TO_PTR_MASKED(ref));
}
static inline _PyStackRef
@@ -681,12 +745,18 @@ PyStackRef_XCLOSE(_PyStackRef ref)
// Note: this is a macro because MSVC (Windows) has trouble inlining it.
-#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_BITS)) == ((b).bits & (~Py_TAG_BITS)))
+#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_REFCNT)) == ((b).bits & (~Py_TAG_REFCNT)))
#endif // !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
-#define PyStackRef_TYPE(stackref) Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref))
+static inline PyTypeObject *
+PyStackRef_TYPE(_PyStackRef stackref) {
+ if (PyStackRef_IsTaggedInt(stackref)) {
+ return &PyLong_Type;
+ }
+ return Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref));
+}
// Converts a PyStackRef back to a PyObject *, converting the
// stackref to a new reference.
@@ -694,42 +764,30 @@ PyStackRef_XCLOSE(_PyStackRef ref)
// StackRef type checks
-static inline bool
-PyStackRef_GenCheck(_PyStackRef stackref)
-{
- return PyGen_Check(PyStackRef_AsPyObjectBorrow(stackref));
-}
+#define STACKREF_CHECK_FUNC(T) \
+ static inline bool \
+ PyStackRef_ ## T ## Check(_PyStackRef stackref) { \
+ if (PyStackRef_IsTaggedInt(stackref)) { \
+ return false; \
+ } \
+ return Py ## T ## _Check(PyStackRef_AsPyObjectBorrow(stackref)); \
+ }
-static inline bool
-PyStackRef_BoolCheck(_PyStackRef stackref)
-{
- return PyBool_Check(PyStackRef_AsPyObjectBorrow(stackref));
-}
+STACKREF_CHECK_FUNC(Gen)
+STACKREF_CHECK_FUNC(Bool)
+STACKREF_CHECK_FUNC(ExceptionInstance)
+STACKREF_CHECK_FUNC(Code)
+STACKREF_CHECK_FUNC(Function)
static inline bool
PyStackRef_LongCheck(_PyStackRef stackref)
{
+ if (PyStackRef_IsTaggedInt(stackref)) {
+ return true;
+ }
return PyLong_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
-static inline bool
-PyStackRef_ExceptionInstanceCheck(_PyStackRef stackref)
-{
- return PyExceptionInstance_Check(PyStackRef_AsPyObjectBorrow(stackref));
-}
-
-static inline bool
-PyStackRef_CodeCheck(_PyStackRef stackref)
-{
- return PyCode_Check(PyStackRef_AsPyObjectBorrow(stackref));
-}
-
-static inline bool
-PyStackRef_FunctionCheck(_PyStackRef stackref)
-{
- return PyFunction_Check(PyStackRef_AsPyObjectBorrow(stackref));
-}
-
static inline void
_PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
{
diff --git a/Include/internal/pycore_sysmodule.h b/Include/internal/pycore_sysmodule.h
index 008a2da0d04..347b0a7a790 100644
--- a/Include/internal/pycore_sysmodule.h
+++ b/Include/internal/pycore_sysmodule.h
@@ -8,11 +8,6 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif
-PyAPI_FUNC(int) _PySys_GetOptionalAttr(PyObject *, PyObject **);
-PyAPI_FUNC(int) _PySys_GetOptionalAttrString(const char *, PyObject **);
-PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttr(PyObject *);
-PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttrString(const char *);
-
// Export for '_pickle' shared extension
PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *);
diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h
index 1a4f89fd244..0ee7d555c56 100644
--- a/Include/internal/pycore_typeobject.h
+++ b/Include/internal/pycore_typeobject.h
@@ -134,7 +134,6 @@ extern int _PyType_AddMethod(PyTypeObject *, PyMethodDef *);
extern void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask,
unsigned long flags);
-extern unsigned int _PyType_GetVersionForCurrentState(PyTypeObject *tp);
PyAPI_FUNC(void) _PyType_SetVersion(PyTypeObject *tp, unsigned int version);
PyTypeObject *_PyType_LookupByVersion(unsigned int version);
diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h
index c85d53b89ac..3791b913c17 100644
--- a/Include/internal/pycore_unicodeobject.h
+++ b/Include/internal/pycore_unicodeobject.h
@@ -139,14 +139,18 @@ extern PyObject* _PyUnicode_DecodeUnicodeEscapeStateful(
// Helper for PyUnicode_DecodeUnicodeEscape that detects invalid escape
// chars.
// Export for test_peg_generator.
-PyAPI_FUNC(PyObject*) _PyUnicode_DecodeUnicodeEscapeInternal(
+PyAPI_FUNC(PyObject*) _PyUnicode_DecodeUnicodeEscapeInternal2(
const char *string, /* Unicode-Escape encoded string */
Py_ssize_t length, /* size of string */
const char *errors, /* error handling */
Py_ssize_t *consumed, /* bytes consumed */
- const char **first_invalid_escape); /* on return, points to first
- invalid escaped char in
- string. */
+ int *first_invalid_escape_char, /* on return, if not -1, contain the first
+ invalid escaped char (<= 0xff) or invalid
+ octal escape (> 0xff) in string. */
+ const char **first_invalid_escape_ptr); /* on return, if not NULL, may
+ point to the first invalid escaped
+ char in string.
+ May be NULL if errors is not NULL. */
/* --- Raw-Unicode-Escape Codecs ---------------------------------------------- */
diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h
index ad78dc8c4d5..e516211f6c6 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -920,10 +920,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
- string = &_Py_ID(aggregate_class);
- _PyUnicode_InternStatic(interp, &string);
- assert(_PyUnicode_CheckConsistency(string, 1));
- assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(alias);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -936,6 +932,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_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));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(allow_code);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -988,10 +988,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
- string = &_Py_ID(authorizer_callback);
- _PyUnicode_InternStatic(interp, &string);
- assert(_PyUnicode_CheckConsistency(string, 1));
- assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(autocommit);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1088,6 +1084,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(c_parameter_type);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(c_return);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1304,6 +1304,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(d_parameter_type);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(data);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1316,6 +1320,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(debug);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(decode);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1624,6 +1632,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(frame_buffer);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(from_param);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1856,6 +1868,14 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(is_compress);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(is_raw);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(is_running);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -2160,10 +2180,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
- string = &_Py_ID(n_arg);
- _PyUnicode_InternStatic(interp, &string);
- assert(_PyUnicode_CheckConsistency(string, 1));
- assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(n_fields);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -2192,10 +2208,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
- string = &_Py_ID(narg);
- _PyUnicode_InternStatic(interp, &string);
- assert(_PyUnicode_CheckConsistency(string, 1));
- assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(ndigits);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -2284,6 +2296,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(only_active_thread);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(only_keys);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -2348,6 +2364,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(parameter);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(parent);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -2420,10 +2440,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
- string = &_Py_ID(progress_handler);
- _PyUnicode_InternStatic(interp, &string);
- assert(_PyUnicode_CheckConsistency(string, 1));
- assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(progress_routine);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -2844,10 +2860,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
- string = &_Py_ID(trace_callback);
- _PyUnicode_InternStatic(interp, &string);
- assert(_PyUnicode_CheckConsistency(string, 1));
- assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(traceback);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -2992,6 +3004,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(zstd_dict);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_STR(empty);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h
index b4eef763da3..a9432401525 100644
--- a/Include/internal/pycore_uop_ids.h
+++ b/Include/internal/pycore_uop_ids.h
@@ -13,22 +13,25 @@ extern "C" {
#define _SET_IP 301
#define _BINARY_OP 302
#define _BINARY_OP_ADD_FLOAT 303
-#define _BINARY_OP_ADD_INT 304
-#define _BINARY_OP_ADD_UNICODE 305
-#define _BINARY_OP_EXTEND 306
-#define _BINARY_OP_INPLACE_ADD_UNICODE 307
-#define _BINARY_OP_MULTIPLY_FLOAT 308
-#define _BINARY_OP_MULTIPLY_INT 309
-#define _BINARY_OP_SUBSCR_CHECK_FUNC 310
-#define _BINARY_OP_SUBSCR_DICT 311
-#define _BINARY_OP_SUBSCR_INIT_CALL 312
-#define _BINARY_OP_SUBSCR_LIST_INT 313
-#define _BINARY_OP_SUBSCR_LIST_SLICE 314
-#define _BINARY_OP_SUBSCR_STR_INT 315
-#define _BINARY_OP_SUBSCR_TUPLE_INT 316
-#define _BINARY_OP_SUBTRACT_FLOAT 317
-#define _BINARY_OP_SUBTRACT_INT 318
-#define _BINARY_SLICE 319
+#define _BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS 304
+#define _BINARY_OP_ADD_INT 305
+#define _BINARY_OP_ADD_UNICODE 306
+#define _BINARY_OP_EXTEND 307
+#define _BINARY_OP_INPLACE_ADD_UNICODE 308
+#define _BINARY_OP_MULTIPLY_FLOAT 309
+#define _BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS 310
+#define _BINARY_OP_MULTIPLY_INT 311
+#define _BINARY_OP_SUBSCR_CHECK_FUNC 312
+#define _BINARY_OP_SUBSCR_DICT 313
+#define _BINARY_OP_SUBSCR_INIT_CALL 314
+#define _BINARY_OP_SUBSCR_LIST_INT 315
+#define _BINARY_OP_SUBSCR_LIST_SLICE 316
+#define _BINARY_OP_SUBSCR_STR_INT 317
+#define _BINARY_OP_SUBSCR_TUPLE_INT 318
+#define _BINARY_OP_SUBTRACT_FLOAT 319
+#define _BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS 320
+#define _BINARY_OP_SUBTRACT_INT 321
+#define _BINARY_SLICE 322
#define _BUILD_INTERPOLATION BUILD_INTERPOLATION
#define _BUILD_LIST BUILD_LIST
#define _BUILD_MAP BUILD_MAP
@@ -37,130 +40,140 @@ extern "C" {
#define _BUILD_STRING BUILD_STRING
#define _BUILD_TEMPLATE BUILD_TEMPLATE
#define _BUILD_TUPLE BUILD_TUPLE
-#define _CALL_BUILTIN_CLASS 320
-#define _CALL_BUILTIN_FAST 321
-#define _CALL_BUILTIN_FAST_WITH_KEYWORDS 322
-#define _CALL_BUILTIN_O 323
+#define _CALL_BUILTIN_CLASS 323
+#define _CALL_BUILTIN_FAST 324
+#define _CALL_BUILTIN_FAST_WITH_KEYWORDS 325
+#define _CALL_BUILTIN_O 326
#define _CALL_INTRINSIC_1 CALL_INTRINSIC_1
#define _CALL_INTRINSIC_2 CALL_INTRINSIC_2
-#define _CALL_ISINSTANCE CALL_ISINSTANCE
-#define _CALL_KW_NON_PY 324
-#define _CALL_LEN CALL_LEN
-#define _CALL_LIST_APPEND CALL_LIST_APPEND
-#define _CALL_METHOD_DESCRIPTOR_FAST 325
-#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS 326
-#define _CALL_METHOD_DESCRIPTOR_NOARGS 327
-#define _CALL_METHOD_DESCRIPTOR_O 328
-#define _CALL_NON_PY_GENERAL 329
-#define _CALL_STR_1 330
-#define _CALL_TUPLE_1 331
-#define _CALL_TYPE_1 332
-#define _CHECK_AND_ALLOCATE_OBJECT 333
-#define _CHECK_ATTR_CLASS 334
-#define _CHECK_ATTR_METHOD_LAZY_DICT 335
-#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 336
+#define _CALL_ISINSTANCE 327
+#define _CALL_KW_NON_PY 328
+#define _CALL_LEN 329
+#define _CALL_LIST_APPEND 330
+#define _CALL_METHOD_DESCRIPTOR_FAST 331
+#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS 332
+#define _CALL_METHOD_DESCRIPTOR_NOARGS 333
+#define _CALL_METHOD_DESCRIPTOR_O 334
+#define _CALL_NON_PY_GENERAL 335
+#define _CALL_STR_1 336
+#define _CALL_TUPLE_1 337
+#define _CALL_TYPE_1 338
+#define _CHECK_AND_ALLOCATE_OBJECT 339
+#define _CHECK_ATTR_CLASS 340
+#define _CHECK_ATTR_METHOD_LAZY_DICT 341
+#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 342
#define _CHECK_EG_MATCH CHECK_EG_MATCH
#define _CHECK_EXC_MATCH CHECK_EXC_MATCH
-#define _CHECK_FUNCTION 337
-#define _CHECK_FUNCTION_EXACT_ARGS 338
-#define _CHECK_FUNCTION_VERSION 339
-#define _CHECK_FUNCTION_VERSION_INLINE 340
-#define _CHECK_FUNCTION_VERSION_KW 341
-#define _CHECK_IS_NOT_PY_CALLABLE 342
-#define _CHECK_IS_NOT_PY_CALLABLE_KW 343
-#define _CHECK_MANAGED_OBJECT_HAS_VALUES 344
-#define _CHECK_METHOD_VERSION 345
-#define _CHECK_METHOD_VERSION_KW 346
-#define _CHECK_PEP_523 347
-#define _CHECK_PERIODIC 348
-#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM 349
-#define _CHECK_RECURSION_REMAINING 350
-#define _CHECK_STACK_SPACE 351
-#define _CHECK_STACK_SPACE_OPERAND 352
-#define _CHECK_VALIDITY 353
-#define _COMPARE_OP 354
-#define _COMPARE_OP_FLOAT 355
-#define _COMPARE_OP_INT 356
-#define _COMPARE_OP_STR 357
-#define _CONTAINS_OP 358
-#define _CONTAINS_OP_DICT 359
-#define _CONTAINS_OP_SET 360
+#define _CHECK_FUNCTION 343
+#define _CHECK_FUNCTION_EXACT_ARGS 344
+#define _CHECK_FUNCTION_VERSION 345
+#define _CHECK_FUNCTION_VERSION_INLINE 346
+#define _CHECK_FUNCTION_VERSION_KW 347
+#define _CHECK_IS_NOT_PY_CALLABLE 348
+#define _CHECK_IS_NOT_PY_CALLABLE_KW 349
+#define _CHECK_MANAGED_OBJECT_HAS_VALUES 350
+#define _CHECK_METHOD_VERSION 351
+#define _CHECK_METHOD_VERSION_KW 352
+#define _CHECK_PEP_523 353
+#define _CHECK_PERIODIC 354
+#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM 355
+#define _CHECK_RECURSION_REMAINING 356
+#define _CHECK_STACK_SPACE 357
+#define _CHECK_STACK_SPACE_OPERAND 358
+#define _CHECK_VALIDITY 359
+#define _COMPARE_OP 360
+#define _COMPARE_OP_FLOAT 361
+#define _COMPARE_OP_INT 362
+#define _COMPARE_OP_STR 363
+#define _CONTAINS_OP 364
+#define _CONTAINS_OP_DICT 365
+#define _CONTAINS_OP_SET 366
#define _CONVERT_VALUE CONVERT_VALUE
-#define _COPY COPY
+#define _COPY 367
+#define _COPY_1 368
+#define _COPY_2 369
+#define _COPY_3 370
#define _COPY_FREE_VARS COPY_FREE_VARS
-#define _CREATE_INIT_FRAME 361
+#define _CREATE_INIT_FRAME 371
#define _DELETE_ATTR DELETE_ATTR
#define _DELETE_DEREF DELETE_DEREF
#define _DELETE_FAST DELETE_FAST
#define _DELETE_GLOBAL DELETE_GLOBAL
#define _DELETE_NAME DELETE_NAME
#define _DELETE_SUBSCR DELETE_SUBSCR
-#define _DEOPT 362
+#define _DEOPT 372
#define _DICT_MERGE DICT_MERGE
#define _DICT_UPDATE DICT_UPDATE
-#define _DO_CALL 363
-#define _DO_CALL_FUNCTION_EX 364
-#define _DO_CALL_KW 365
+#define _DO_CALL 373
+#define _DO_CALL_FUNCTION_EX 374
+#define _DO_CALL_KW 375
#define _END_FOR END_FOR
#define _END_SEND END_SEND
-#define _ERROR_POP_N 366
+#define _ERROR_POP_N 376
#define _EXIT_INIT_CHECK EXIT_INIT_CHECK
-#define _EXPAND_METHOD 367
-#define _EXPAND_METHOD_KW 368
-#define _FATAL_ERROR 369
+#define _EXPAND_METHOD 377
+#define _EXPAND_METHOD_KW 378
+#define _FATAL_ERROR 379
#define _FORMAT_SIMPLE FORMAT_SIMPLE
#define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC
-#define _FOR_ITER 370
-#define _FOR_ITER_GEN_FRAME 371
-#define _FOR_ITER_TIER_TWO 372
+#define _FOR_ITER 380
+#define _FOR_ITER_GEN_FRAME 381
+#define _FOR_ITER_TIER_TWO 382
#define _GET_AITER GET_AITER
#define _GET_ANEXT GET_ANEXT
#define _GET_AWAITABLE GET_AWAITABLE
#define _GET_ITER GET_ITER
#define _GET_LEN GET_LEN
#define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER
-#define _GUARD_BINARY_OP_EXTEND 373
-#define _GUARD_CALLABLE_STR_1 374
-#define _GUARD_CALLABLE_TUPLE_1 375
-#define _GUARD_CALLABLE_TYPE_1 376
-#define _GUARD_DORV_NO_DICT 377
-#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 378
-#define _GUARD_GLOBALS_VERSION 379
-#define _GUARD_IS_FALSE_POP 380
-#define _GUARD_IS_NONE_POP 381
-#define _GUARD_IS_NOT_NONE_POP 382
-#define _GUARD_IS_TRUE_POP 383
-#define _GUARD_KEYS_VERSION 384
-#define _GUARD_NOS_DICT 385
-#define _GUARD_NOS_FLOAT 386
-#define _GUARD_NOS_INT 387
-#define _GUARD_NOS_LIST 388
-#define _GUARD_NOS_NULL 389
-#define _GUARD_NOS_TUPLE 390
-#define _GUARD_NOS_UNICODE 391
-#define _GUARD_NOT_EXHAUSTED_LIST 392
-#define _GUARD_NOT_EXHAUSTED_RANGE 393
-#define _GUARD_NOT_EXHAUSTED_TUPLE 394
-#define _GUARD_TOS_ANY_SET 395
-#define _GUARD_TOS_DICT 396
-#define _GUARD_TOS_FLOAT 397
-#define _GUARD_TOS_INT 398
-#define _GUARD_TOS_LIST 399
-#define _GUARD_TOS_SLICE 400
-#define _GUARD_TOS_TUPLE 401
-#define _GUARD_TOS_UNICODE 402
-#define _GUARD_TYPE_VERSION 403
-#define _GUARD_TYPE_VERSION_AND_LOCK 404
+#define _GUARD_BINARY_OP_EXTEND 383
+#define _GUARD_CALLABLE_ISINSTANCE 384
+#define _GUARD_CALLABLE_LEN 385
+#define _GUARD_CALLABLE_LIST_APPEND 386
+#define _GUARD_CALLABLE_STR_1 387
+#define _GUARD_CALLABLE_TUPLE_1 388
+#define _GUARD_CALLABLE_TYPE_1 389
+#define _GUARD_DORV_NO_DICT 390
+#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 391
+#define _GUARD_GLOBALS_VERSION 392
+#define _GUARD_IS_FALSE_POP 393
+#define _GUARD_IS_NONE_POP 394
+#define _GUARD_IS_NOT_NONE_POP 395
+#define _GUARD_IS_TRUE_POP 396
+#define _GUARD_KEYS_VERSION 397
+#define _GUARD_NOS_DICT 398
+#define _GUARD_NOS_FLOAT 399
+#define _GUARD_NOS_INT 400
+#define _GUARD_NOS_LIST 401
+#define _GUARD_NOS_NOT_NULL 402
+#define _GUARD_NOS_NULL 403
+#define _GUARD_NOS_OVERFLOWED 404
+#define _GUARD_NOS_TUPLE 405
+#define _GUARD_NOS_UNICODE 406
+#define _GUARD_NOT_EXHAUSTED_LIST 407
+#define _GUARD_NOT_EXHAUSTED_RANGE 408
+#define _GUARD_NOT_EXHAUSTED_TUPLE 409
+#define _GUARD_THIRD_NULL 410
+#define _GUARD_TOS_ANY_SET 411
+#define _GUARD_TOS_DICT 412
+#define _GUARD_TOS_FLOAT 413
+#define _GUARD_TOS_INT 414
+#define _GUARD_TOS_LIST 415
+#define _GUARD_TOS_OVERFLOWED 416
+#define _GUARD_TOS_SLICE 417
+#define _GUARD_TOS_TUPLE 418
+#define _GUARD_TOS_UNICODE 419
+#define _GUARD_TYPE_VERSION 420
+#define _GUARD_TYPE_VERSION_AND_LOCK 421
#define _IMPORT_FROM IMPORT_FROM
#define _IMPORT_NAME IMPORT_NAME
-#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 405
-#define _INIT_CALL_PY_EXACT_ARGS 406
-#define _INIT_CALL_PY_EXACT_ARGS_0 407
-#define _INIT_CALL_PY_EXACT_ARGS_1 408
-#define _INIT_CALL_PY_EXACT_ARGS_2 409
-#define _INIT_CALL_PY_EXACT_ARGS_3 410
-#define _INIT_CALL_PY_EXACT_ARGS_4 411
-#define _INSERT_NULL 412
+#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 422
+#define _INIT_CALL_PY_EXACT_ARGS 423
+#define _INIT_CALL_PY_EXACT_ARGS_0 424
+#define _INIT_CALL_PY_EXACT_ARGS_1 425
+#define _INIT_CALL_PY_EXACT_ARGS_2 426
+#define _INIT_CALL_PY_EXACT_ARGS_3 427
+#define _INIT_CALL_PY_EXACT_ARGS_4 428
+#define _INSERT_NULL 429
#define _INSTRUMENTED_FOR_ITER INSTRUMENTED_FOR_ITER
#define _INSTRUMENTED_INSTRUCTION INSTRUMENTED_INSTRUCTION
#define _INSTRUMENTED_JUMP_FORWARD INSTRUMENTED_JUMP_FORWARD
@@ -170,163 +183,177 @@ extern "C" {
#define _INSTRUMENTED_POP_JUMP_IF_NONE INSTRUMENTED_POP_JUMP_IF_NONE
#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE INSTRUMENTED_POP_JUMP_IF_NOT_NONE
#define _INSTRUMENTED_POP_JUMP_IF_TRUE INSTRUMENTED_POP_JUMP_IF_TRUE
-#define _IS_NONE 413
+#define _IS_NONE 430
#define _IS_OP IS_OP
-#define _ITER_CHECK_LIST 414
-#define _ITER_CHECK_RANGE 415
-#define _ITER_CHECK_TUPLE 416
-#define _ITER_JUMP_LIST 417
-#define _ITER_JUMP_RANGE 418
-#define _ITER_JUMP_TUPLE 419
-#define _ITER_NEXT_LIST 420
-#define _ITER_NEXT_LIST_TIER_TWO 421
-#define _ITER_NEXT_RANGE 422
-#define _ITER_NEXT_TUPLE 423
-#define _JUMP_TO_TOP 424
+#define _ITER_CHECK_LIST 431
+#define _ITER_CHECK_RANGE 432
+#define _ITER_CHECK_TUPLE 433
+#define _ITER_JUMP_LIST 434
+#define _ITER_JUMP_RANGE 435
+#define _ITER_JUMP_TUPLE 436
+#define _ITER_NEXT_LIST 437
+#define _ITER_NEXT_LIST_TIER_TWO 438
+#define _ITER_NEXT_RANGE 439
+#define _ITER_NEXT_TUPLE 440
+#define _JUMP_TO_TOP 441
#define _LIST_APPEND LIST_APPEND
#define _LIST_EXTEND LIST_EXTEND
-#define _LOAD_ATTR 425
-#define _LOAD_ATTR_CLASS 426
+#define _LOAD_ATTR 442
+#define _LOAD_ATTR_CLASS 443
#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN
-#define _LOAD_ATTR_INSTANCE_VALUE 427
-#define _LOAD_ATTR_METHOD_LAZY_DICT 428
-#define _LOAD_ATTR_METHOD_NO_DICT 429
-#define _LOAD_ATTR_METHOD_WITH_VALUES 430
-#define _LOAD_ATTR_MODULE 431
-#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 432
-#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 433
-#define _LOAD_ATTR_PROPERTY_FRAME 434
-#define _LOAD_ATTR_SLOT 435
-#define _LOAD_ATTR_WITH_HINT 436
+#define _LOAD_ATTR_INSTANCE_VALUE 444
+#define _LOAD_ATTR_METHOD_LAZY_DICT 445
+#define _LOAD_ATTR_METHOD_NO_DICT 446
+#define _LOAD_ATTR_METHOD_WITH_VALUES 447
+#define _LOAD_ATTR_MODULE 448
+#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 449
+#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 450
+#define _LOAD_ATTR_PROPERTY_FRAME 451
+#define _LOAD_ATTR_SLOT 452
+#define _LOAD_ATTR_WITH_HINT 453
#define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS
-#define _LOAD_BYTECODE 437
+#define _LOAD_BYTECODE 454
#define _LOAD_COMMON_CONSTANT LOAD_COMMON_CONSTANT
#define _LOAD_CONST LOAD_CONST
-#define _LOAD_CONST_IMMORTAL LOAD_CONST_IMMORTAL
-#define _LOAD_CONST_INLINE 438
-#define _LOAD_CONST_INLINE_BORROW 439
-#define _LOAD_CONST_MORTAL LOAD_CONST_MORTAL
+#define _LOAD_CONST_INLINE 455
+#define _LOAD_CONST_INLINE_BORROW 456
+#define _LOAD_CONST_UNDER_INLINE 457
+#define _LOAD_CONST_UNDER_INLINE_BORROW 458
#define _LOAD_DEREF LOAD_DEREF
-#define _LOAD_FAST 440
-#define _LOAD_FAST_0 441
-#define _LOAD_FAST_1 442
-#define _LOAD_FAST_2 443
-#define _LOAD_FAST_3 444
-#define _LOAD_FAST_4 445
-#define _LOAD_FAST_5 446
-#define _LOAD_FAST_6 447
-#define _LOAD_FAST_7 448
+#define _LOAD_FAST 459
+#define _LOAD_FAST_0 460
+#define _LOAD_FAST_1 461
+#define _LOAD_FAST_2 462
+#define _LOAD_FAST_3 463
+#define _LOAD_FAST_4 464
+#define _LOAD_FAST_5 465
+#define _LOAD_FAST_6 466
+#define _LOAD_FAST_7 467
#define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR
-#define _LOAD_FAST_BORROW 449
-#define _LOAD_FAST_BORROW_0 450
-#define _LOAD_FAST_BORROW_1 451
-#define _LOAD_FAST_BORROW_2 452
-#define _LOAD_FAST_BORROW_3 453
-#define _LOAD_FAST_BORROW_4 454
-#define _LOAD_FAST_BORROW_5 455
-#define _LOAD_FAST_BORROW_6 456
-#define _LOAD_FAST_BORROW_7 457
+#define _LOAD_FAST_BORROW 468
+#define _LOAD_FAST_BORROW_0 469
+#define _LOAD_FAST_BORROW_1 470
+#define _LOAD_FAST_BORROW_2 471
+#define _LOAD_FAST_BORROW_3 472
+#define _LOAD_FAST_BORROW_4 473
+#define _LOAD_FAST_BORROW_5 474
+#define _LOAD_FAST_BORROW_6 475
+#define _LOAD_FAST_BORROW_7 476
#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW LOAD_FAST_BORROW_LOAD_FAST_BORROW
#define _LOAD_FAST_CHECK LOAD_FAST_CHECK
#define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST
#define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF
#define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS
-#define _LOAD_GLOBAL 458
-#define _LOAD_GLOBAL_BUILTINS 459
-#define _LOAD_GLOBAL_MODULE 460
+#define _LOAD_GLOBAL 477
+#define _LOAD_GLOBAL_BUILTINS 478
+#define _LOAD_GLOBAL_MODULE 479
#define _LOAD_LOCALS LOAD_LOCALS
#define _LOAD_NAME LOAD_NAME
-#define _LOAD_SMALL_INT 461
-#define _LOAD_SMALL_INT_0 462
-#define _LOAD_SMALL_INT_1 463
-#define _LOAD_SMALL_INT_2 464
-#define _LOAD_SMALL_INT_3 465
-#define _LOAD_SPECIAL 466
+#define _LOAD_SMALL_INT 480
+#define _LOAD_SMALL_INT_0 481
+#define _LOAD_SMALL_INT_1 482
+#define _LOAD_SMALL_INT_2 483
+#define _LOAD_SMALL_INT_3 484
+#define _LOAD_SPECIAL 485
#define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR
#define _LOAD_SUPER_ATTR_METHOD LOAD_SUPER_ATTR_METHOD
-#define _MAKE_CALLARGS_A_TUPLE 467
+#define _MAKE_CALLARGS_A_TUPLE 486
#define _MAKE_CELL MAKE_CELL
#define _MAKE_FUNCTION MAKE_FUNCTION
-#define _MAKE_WARM 468
+#define _MAKE_WARM 487
#define _MAP_ADD MAP_ADD
#define _MATCH_CLASS MATCH_CLASS
#define _MATCH_KEYS MATCH_KEYS
#define _MATCH_MAPPING MATCH_MAPPING
#define _MATCH_SEQUENCE MATCH_SEQUENCE
-#define _MAYBE_EXPAND_METHOD 469
-#define _MAYBE_EXPAND_METHOD_KW 470
-#define _MONITOR_CALL 471
-#define _MONITOR_CALL_KW 472
-#define _MONITOR_JUMP_BACKWARD 473
-#define _MONITOR_RESUME 474
+#define _MAYBE_EXPAND_METHOD 488
+#define _MAYBE_EXPAND_METHOD_KW 489
+#define _MONITOR_CALL 490
+#define _MONITOR_CALL_KW 491
+#define _MONITOR_JUMP_BACKWARD 492
+#define _MONITOR_RESUME 493
#define _NOP NOP
+#define _POP_CALL 494
+#define _POP_CALL_LOAD_CONST_INLINE_BORROW 495
+#define _POP_CALL_ONE 496
+#define _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW 497
+#define _POP_CALL_TWO 498
+#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW 499
#define _POP_EXCEPT POP_EXCEPT
-#define _POP_JUMP_IF_FALSE 475
-#define _POP_JUMP_IF_TRUE 476
+#define _POP_ITER POP_ITER
+#define _POP_JUMP_IF_FALSE 500
+#define _POP_JUMP_IF_TRUE 501
#define _POP_TOP POP_TOP
-#define _POP_TOP_LOAD_CONST_INLINE 477
-#define _POP_TOP_LOAD_CONST_INLINE_BORROW 478
-#define _POP_TWO_LOAD_CONST_INLINE_BORROW 479
+#define _POP_TOP_FLOAT 502
+#define _POP_TOP_INT 503
+#define _POP_TOP_LOAD_CONST_INLINE 504
+#define _POP_TOP_LOAD_CONST_INLINE_BORROW 505
+#define _POP_TOP_NOP 506
+#define _POP_TOP_UNICODE 507
+#define _POP_TWO 508
+#define _POP_TWO_LOAD_CONST_INLINE_BORROW 509
#define _PUSH_EXC_INFO PUSH_EXC_INFO
-#define _PUSH_FRAME 480
+#define _PUSH_FRAME 510
#define _PUSH_NULL PUSH_NULL
-#define _PUSH_NULL_CONDITIONAL 481
-#define _PY_FRAME_GENERAL 482
-#define _PY_FRAME_KW 483
-#define _QUICKEN_RESUME 484
-#define _REPLACE_WITH_TRUE 485
+#define _PUSH_NULL_CONDITIONAL 511
+#define _PY_FRAME_GENERAL 512
+#define _PY_FRAME_KW 513
+#define _QUICKEN_RESUME 514
+#define _REPLACE_WITH_TRUE 515
#define _RESUME_CHECK RESUME_CHECK
#define _RETURN_GENERATOR RETURN_GENERATOR
#define _RETURN_VALUE RETURN_VALUE
-#define _SAVE_RETURN_OFFSET 486
-#define _SEND 487
-#define _SEND_GEN_FRAME 488
+#define _SAVE_RETURN_OFFSET 516
+#define _SEND 517
+#define _SEND_GEN_FRAME 518
#define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS
#define _SET_ADD SET_ADD
#define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE
#define _SET_UPDATE SET_UPDATE
-#define _START_EXECUTOR 489
-#define _STORE_ATTR 490
-#define _STORE_ATTR_INSTANCE_VALUE 491
-#define _STORE_ATTR_SLOT 492
-#define _STORE_ATTR_WITH_HINT 493
+#define _START_EXECUTOR 519
+#define _STORE_ATTR 520
+#define _STORE_ATTR_INSTANCE_VALUE 521
+#define _STORE_ATTR_SLOT 522
+#define _STORE_ATTR_WITH_HINT 523
#define _STORE_DEREF STORE_DEREF
-#define _STORE_FAST 494
-#define _STORE_FAST_0 495
-#define _STORE_FAST_1 496
-#define _STORE_FAST_2 497
-#define _STORE_FAST_3 498
-#define _STORE_FAST_4 499
-#define _STORE_FAST_5 500
-#define _STORE_FAST_6 501
-#define _STORE_FAST_7 502
+#define _STORE_FAST 524
+#define _STORE_FAST_0 525
+#define _STORE_FAST_1 526
+#define _STORE_FAST_2 527
+#define _STORE_FAST_3 528
+#define _STORE_FAST_4 529
+#define _STORE_FAST_5 530
+#define _STORE_FAST_6 531
+#define _STORE_FAST_7 532
#define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST
#define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST
#define _STORE_GLOBAL STORE_GLOBAL
#define _STORE_NAME STORE_NAME
-#define _STORE_SLICE 503
-#define _STORE_SUBSCR 504
-#define _STORE_SUBSCR_DICT 505
-#define _STORE_SUBSCR_LIST_INT 506
-#define _SWAP SWAP
-#define _TIER2_RESUME_CHECK 507
-#define _TO_BOOL 508
+#define _STORE_SLICE 533
+#define _STORE_SUBSCR 534
+#define _STORE_SUBSCR_DICT 535
+#define _STORE_SUBSCR_LIST_INT 536
+#define _SWAP 537
+#define _SWAP_2 538
+#define _SWAP_3 539
+#define _TIER2_RESUME_CHECK 540
+#define _TO_BOOL 541
#define _TO_BOOL_BOOL TO_BOOL_BOOL
#define _TO_BOOL_INT TO_BOOL_INT
-#define _TO_BOOL_LIST 509
+#define _TO_BOOL_LIST 542
#define _TO_BOOL_NONE TO_BOOL_NONE
-#define _TO_BOOL_STR 510
+#define _TO_BOOL_STR 543
#define _UNARY_INVERT UNARY_INVERT
#define _UNARY_NEGATIVE UNARY_NEGATIVE
#define _UNARY_NOT UNARY_NOT
#define _UNPACK_EX UNPACK_EX
-#define _UNPACK_SEQUENCE 511
-#define _UNPACK_SEQUENCE_LIST 512
-#define _UNPACK_SEQUENCE_TUPLE 513
-#define _UNPACK_SEQUENCE_TWO_TUPLE 514
+#define _UNPACK_SEQUENCE 544
+#define _UNPACK_SEQUENCE_LIST 545
+#define _UNPACK_SEQUENCE_TUPLE 546
+#define _UNPACK_SEQUENCE_TWO_TUPLE 547
#define _WITH_EXCEPT_START WITH_EXCEPT_START
#define _YIELD_VALUE YIELD_VALUE
-#define MAX_UOP_ID 514
+#define MAX_UOP_ID 547
#ifdef __cplusplus
}
diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h
index 7304fc6e299..ff7e800aa9b 100644
--- a/Include/internal/pycore_uop_metadata.h
+++ b/Include/internal/pycore_uop_metadata.h
@@ -12,7 +12,8 @@ extern "C" {
#include <stdint.h>
#include "pycore_uop_ids.h"
extern const uint16_t _PyUop_Flags[MAX_UOP_ID+1];
-extern const uint8_t _PyUop_Replication[MAX_UOP_ID+1];
+typedef struct _rep_range { uint8_t start; uint8_t stop; } ReplicationRange;
+extern const ReplicationRange _PyUop_Replication[MAX_UOP_ID+1];
extern const char * const _PyOpcode_uop_name[MAX_UOP_ID+1];
extern int _PyUop_num_popped(int opcode, int oparg);
@@ -45,8 +46,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_LOAD_FAST_AND_CLEAR] = HAS_ARG_FLAG | HAS_LOCAL_FLAG,
[_LOAD_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG,
[_LOAD_FAST_BORROW_LOAD_FAST_BORROW] = HAS_ARG_FLAG | HAS_LOCAL_FLAG,
- [_LOAD_CONST_MORTAL] = HAS_ARG_FLAG | HAS_CONST_FLAG,
- [_LOAD_CONST_IMMORTAL] = HAS_ARG_FLAG | HAS_CONST_FLAG,
+ [_LOAD_CONST] = HAS_ARG_FLAG | HAS_CONST_FLAG,
[_LOAD_SMALL_INT_0] = 0,
[_LOAD_SMALL_INT_1] = 0,
[_LOAD_SMALL_INT_2] = 0,
@@ -64,8 +64,14 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_STORE_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG,
[_STORE_FAST_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG,
[_POP_TOP] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
+ [_POP_TOP_NOP] = 0,
+ [_POP_TOP_INT] = 0,
+ [_POP_TOP_FLOAT] = 0,
+ [_POP_TOP_UNICODE] = 0,
+ [_POP_TWO] = HAS_ESCAPES_FLAG,
[_PUSH_NULL] = HAS_PURE_FLAG,
[_END_FOR] = HAS_ESCAPES_FLAG | HAS_NO_SAVE_IP_FLAG,
+ [_POP_ITER] = HAS_ESCAPES_FLAG,
[_END_SEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
[_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_UNARY_NOT] = HAS_PURE_FLAG,
@@ -75,7 +81,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_GUARD_NOS_LIST] = HAS_EXIT_FLAG,
[_GUARD_TOS_LIST] = HAS_EXIT_FLAG,
[_GUARD_TOS_SLICE] = HAS_EXIT_FLAG,
- [_TO_BOOL_LIST] = 0,
+ [_TO_BOOL_LIST] = HAS_ESCAPES_FLAG,
[_TO_BOOL_NONE] = HAS_EXIT_FLAG,
[_GUARD_NOS_UNICODE] = HAS_EXIT_FLAG,
[_GUARD_TOS_UNICODE] = HAS_EXIT_FLAG,
@@ -84,18 +90,23 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_GUARD_NOS_INT] = HAS_EXIT_FLAG,
[_GUARD_TOS_INT] = HAS_EXIT_FLAG,
- [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
- [_BINARY_OP_ADD_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
- [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
+ [_GUARD_NOS_OVERFLOWED] = HAS_EXIT_FLAG,
+ [_GUARD_TOS_OVERFLOWED] = HAS_EXIT_FLAG,
+ [_BINARY_OP_MULTIPLY_INT] = HAS_EXIT_FLAG | HAS_PURE_FLAG,
+ [_BINARY_OP_ADD_INT] = HAS_EXIT_FLAG | HAS_PURE_FLAG,
+ [_BINARY_OP_SUBTRACT_INT] = HAS_EXIT_FLAG | HAS_PURE_FLAG,
[_GUARD_NOS_FLOAT] = HAS_EXIT_FLAG,
[_GUARD_TOS_FLOAT] = HAS_EXIT_FLAG,
[_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_ADD_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_SUBTRACT_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
+ [_BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
+ [_BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
+ [_BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG,
[_BINARY_OP_INPLACE_ADD_UNICODE] = HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_GUARD_BINARY_OP_EXTEND] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
- [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
+ [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG,
[_BINARY_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_BINARY_OP_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
@@ -103,7 +114,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_BINARY_OP_SUBSCR_STR_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_GUARD_NOS_TUPLE] = HAS_EXIT_FLAG,
[_GUARD_TOS_TUPLE] = HAS_EXIT_FLAG,
- [_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG,
+ [_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_GUARD_NOS_DICT] = HAS_EXIT_FLAG,
[_GUARD_TOS_DICT] = HAS_EXIT_FLAG,
[_BINARY_OP_SUBSCR_DICT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
@@ -130,8 +141,8 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_DELETE_NAME] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
[_UNPACK_SEQUENCE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_UNPACK_SEQUENCE_TWO_TUPLE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
- [_UNPACK_SEQUENCE_TUPLE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG,
- [_UNPACK_SEQUENCE_LIST] = HAS_ARG_FLAG | HAS_DEOPT_FLAG,
+ [_UNPACK_SEQUENCE_TUPLE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
+ [_UNPACK_SEQUENCE_LIST] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_UNPACK_EX] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_STORE_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_DELETE_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
@@ -151,7 +162,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_LOAD_DEREF] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_STORE_DEREF] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG,
[_COPY_FREE_VARS] = HAS_ARG_FLAG,
- [_BUILD_STRING] = HAS_ARG_FLAG | HAS_ERROR_FLAG,
+ [_BUILD_STRING] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_BUILD_INTERPOLATION] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_BUILD_TEMPLATE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_BUILD_TUPLE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG,
@@ -173,9 +184,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_LOAD_ATTR_INSTANCE_VALUE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_LOAD_ATTR_MODULE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
- [_LOAD_ATTR_SLOT] = HAS_DEOPT_FLAG,
+ [_LOAD_ATTR_SLOT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_CHECK_ATTR_CLASS] = HAS_EXIT_FLAG,
- [_LOAD_ATTR_CLASS] = 0,
+ [_LOAD_ATTR_CLASS] = HAS_ESCAPES_FLAG,
[_LOAD_ATTR_PROPERTY_FRAME] = HAS_ARG_FLAG | HAS_DEOPT_FLAG,
[_GUARD_DORV_NO_DICT] = HAS_EXIT_FLAG,
[_STORE_ATTR_INSTANCE_VALUE] = HAS_ESCAPES_FLAG,
@@ -183,9 +194,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_STORE_ATTR_SLOT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_COMPARE_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_COMPARE_OP_FLOAT] = HAS_ARG_FLAG,
- [_COMPARE_OP_INT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG,
+ [_COMPARE_OP_INT] = HAS_ARG_FLAG,
[_COMPARE_OP_STR] = HAS_ARG_FLAG,
- [_IS_OP] = HAS_ARG_FLAG,
+ [_IS_OP] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG,
[_CONTAINS_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_GUARD_TOS_ANY_SET] = HAS_DEOPT_FLAG,
[_CONTAINS_OP_SET] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
@@ -194,7 +205,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_CHECK_EXC_MATCH] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
[_IMPORT_NAME] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_IMPORT_FROM] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
- [_IS_NONE] = 0,
+ [_IS_NONE] = HAS_ESCAPES_FLAG,
[_GET_LEN] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_MATCH_CLASS] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_MATCH_MAPPING] = 0,
@@ -205,7 +216,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_FOR_ITER_TIER_TWO] = HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
[_ITER_CHECK_LIST] = HAS_EXIT_FLAG,
[_GUARD_NOT_EXHAUSTED_LIST] = HAS_EXIT_FLAG,
- [_ITER_NEXT_LIST_TIER_TWO] = HAS_EXIT_FLAG | HAS_ESCAPES_FLAG,
+ [_ITER_NEXT_LIST_TIER_TWO] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
[_ITER_CHECK_TUPLE] = HAS_EXIT_FLAG,
[_GUARD_NOT_EXHAUSTED_TUPLE] = HAS_EXIT_FLAG,
[_ITER_NEXT_TUPLE] = 0,
@@ -247,6 +258,8 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG | HAS_PURE_FLAG,
[_PUSH_FRAME] = 0,
[_GUARD_NOS_NULL] = HAS_DEOPT_FLAG,
+ [_GUARD_NOS_NOT_NULL] = HAS_EXIT_FLAG,
+ [_GUARD_THIRD_NULL] = HAS_DEOPT_FLAG,
[_GUARD_CALLABLE_TYPE_1] = HAS_DEOPT_FLAG,
[_CALL_TYPE_1] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG,
[_GUARD_CALLABLE_STR_1] = HAS_DEOPT_FLAG,
@@ -260,8 +273,11 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_CALL_BUILTIN_O] = HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_CALL_BUILTIN_FAST] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_CALL_BUILTIN_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
- [_CALL_LEN] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
- [_CALL_ISINSTANCE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
+ [_GUARD_CALLABLE_LEN] = HAS_DEOPT_FLAG,
+ [_CALL_LEN] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
+ [_GUARD_CALLABLE_ISINSTANCE] = HAS_DEOPT_FLAG,
+ [_CALL_ISINSTANCE] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
+ [_GUARD_CALLABLE_LIST_APPEND] = HAS_DEOPT_FLAG,
[_CALL_LIST_APPEND] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_CALL_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
@@ -278,12 +294,17 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_MAKE_FUNCTION] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_SET_FUNCTION_ATTRIBUTE] = HAS_ARG_FLAG,
[_RETURN_GENERATOR] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
- [_BUILD_SLICE] = HAS_ARG_FLAG | HAS_ERROR_FLAG,
+ [_BUILD_SLICE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_CONVERT_VALUE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_FORMAT_SIMPLE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
[_FORMAT_WITH_SPEC] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
+ [_COPY_1] = HAS_PURE_FLAG,
+ [_COPY_2] = HAS_PURE_FLAG,
+ [_COPY_3] = HAS_PURE_FLAG,
[_COPY] = HAS_ARG_FLAG | HAS_PURE_FLAG,
[_BINARY_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
+ [_SWAP_2] = HAS_PURE_FLAG,
+ [_SWAP_3] = HAS_PURE_FLAG,
[_SWAP] = HAS_ARG_FLAG | HAS_PURE_FLAG,
[_GUARD_IS_TRUE_POP] = HAS_EXIT_FLAG,
[_GUARD_IS_FALSE_POP] = HAS_EXIT_FLAG,
@@ -298,10 +319,18 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_LOAD_CONST_INLINE] = HAS_PURE_FLAG,
[_POP_TOP_LOAD_CONST_INLINE] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
[_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG,
- [_POP_TOP_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
- [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG,
+ [_POP_CALL] = HAS_ESCAPES_FLAG,
+ [_POP_CALL_ONE] = HAS_ESCAPES_FLAG,
+ [_POP_CALL_TWO] = HAS_ESCAPES_FLAG,
+ [_POP_TOP_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
+ [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
+ [_POP_CALL_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
+ [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
+ [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG,
+ [_LOAD_CONST_UNDER_INLINE] = 0,
+ [_LOAD_CONST_UNDER_INLINE_BORROW] = 0,
[_CHECK_FUNCTION] = HAS_DEOPT_FLAG,
- [_START_EXECUTOR] = HAS_ESCAPES_FLAG,
+ [_START_EXECUTOR] = 0,
[_MAKE_WARM] = 0,
[_FATAL_ERROR] = 0,
[_DEOPT] = 0,
@@ -309,22 +338,26 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
[_TIER2_RESUME_CHECK] = HAS_DEOPT_FLAG,
};
-const uint8_t _PyUop_Replication[MAX_UOP_ID+1] = {
- [_LOAD_FAST] = 8,
- [_LOAD_FAST_BORROW] = 8,
- [_LOAD_SMALL_INT] = 4,
- [_STORE_FAST] = 8,
- [_INIT_CALL_PY_EXACT_ARGS] = 5,
+const ReplicationRange _PyUop_Replication[MAX_UOP_ID+1] = {
+ [_LOAD_FAST] = { 0, 8 },
+ [_LOAD_FAST_BORROW] = { 0, 8 },
+ [_LOAD_SMALL_INT] = { 0, 4 },
+ [_STORE_FAST] = { 0, 8 },
+ [_INIT_CALL_PY_EXACT_ARGS] = { 0, 5 },
+ [_COPY] = { 1, 4 },
+ [_SWAP] = { 2, 4 },
};
const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_BINARY_OP] = "_BINARY_OP",
[_BINARY_OP_ADD_FLOAT] = "_BINARY_OP_ADD_FLOAT",
+ [_BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS] = "_BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS",
[_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT",
[_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE",
[_BINARY_OP_EXTEND] = "_BINARY_OP_EXTEND",
[_BINARY_OP_INPLACE_ADD_UNICODE] = "_BINARY_OP_INPLACE_ADD_UNICODE",
[_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT",
+ [_BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS] = "_BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS",
[_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT",
[_BINARY_OP_SUBSCR_CHECK_FUNC] = "_BINARY_OP_SUBSCR_CHECK_FUNC",
[_BINARY_OP_SUBSCR_DICT] = "_BINARY_OP_SUBSCR_DICT",
@@ -334,6 +367,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_BINARY_OP_SUBSCR_STR_INT] = "_BINARY_OP_SUBSCR_STR_INT",
[_BINARY_OP_SUBSCR_TUPLE_INT] = "_BINARY_OP_SUBSCR_TUPLE_INT",
[_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT",
+ [_BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS] = "_BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS",
[_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT",
[_BINARY_SLICE] = "_BINARY_SLICE",
[_BUILD_INTERPOLATION] = "_BUILD_INTERPOLATION",
@@ -394,6 +428,9 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_CONTAINS_OP_SET] = "_CONTAINS_OP_SET",
[_CONVERT_VALUE] = "_CONVERT_VALUE",
[_COPY] = "_COPY",
+ [_COPY_1] = "_COPY_1",
+ [_COPY_2] = "_COPY_2",
+ [_COPY_3] = "_COPY_3",
[_COPY_FREE_VARS] = "_COPY_FREE_VARS",
[_CREATE_INIT_FRAME] = "_CREATE_INIT_FRAME",
[_DELETE_ATTR] = "_DELETE_ATTR",
@@ -424,6 +461,9 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_GET_LEN] = "_GET_LEN",
[_GET_YIELD_FROM_ITER] = "_GET_YIELD_FROM_ITER",
[_GUARD_BINARY_OP_EXTEND] = "_GUARD_BINARY_OP_EXTEND",
+ [_GUARD_CALLABLE_ISINSTANCE] = "_GUARD_CALLABLE_ISINSTANCE",
+ [_GUARD_CALLABLE_LEN] = "_GUARD_CALLABLE_LEN",
+ [_GUARD_CALLABLE_LIST_APPEND] = "_GUARD_CALLABLE_LIST_APPEND",
[_GUARD_CALLABLE_STR_1] = "_GUARD_CALLABLE_STR_1",
[_GUARD_CALLABLE_TUPLE_1] = "_GUARD_CALLABLE_TUPLE_1",
[_GUARD_CALLABLE_TYPE_1] = "_GUARD_CALLABLE_TYPE_1",
@@ -439,17 +479,21 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_GUARD_NOS_FLOAT] = "_GUARD_NOS_FLOAT",
[_GUARD_NOS_INT] = "_GUARD_NOS_INT",
[_GUARD_NOS_LIST] = "_GUARD_NOS_LIST",
+ [_GUARD_NOS_NOT_NULL] = "_GUARD_NOS_NOT_NULL",
[_GUARD_NOS_NULL] = "_GUARD_NOS_NULL",
+ [_GUARD_NOS_OVERFLOWED] = "_GUARD_NOS_OVERFLOWED",
[_GUARD_NOS_TUPLE] = "_GUARD_NOS_TUPLE",
[_GUARD_NOS_UNICODE] = "_GUARD_NOS_UNICODE",
[_GUARD_NOT_EXHAUSTED_LIST] = "_GUARD_NOT_EXHAUSTED_LIST",
[_GUARD_NOT_EXHAUSTED_RANGE] = "_GUARD_NOT_EXHAUSTED_RANGE",
[_GUARD_NOT_EXHAUSTED_TUPLE] = "_GUARD_NOT_EXHAUSTED_TUPLE",
+ [_GUARD_THIRD_NULL] = "_GUARD_THIRD_NULL",
[_GUARD_TOS_ANY_SET] = "_GUARD_TOS_ANY_SET",
[_GUARD_TOS_DICT] = "_GUARD_TOS_DICT",
[_GUARD_TOS_FLOAT] = "_GUARD_TOS_FLOAT",
[_GUARD_TOS_INT] = "_GUARD_TOS_INT",
[_GUARD_TOS_LIST] = "_GUARD_TOS_LIST",
+ [_GUARD_TOS_OVERFLOWED] = "_GUARD_TOS_OVERFLOWED",
[_GUARD_TOS_SLICE] = "_GUARD_TOS_SLICE",
[_GUARD_TOS_TUPLE] = "_GUARD_TOS_TUPLE",
[_GUARD_TOS_UNICODE] = "_GUARD_TOS_UNICODE",
@@ -490,10 +534,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT",
[_LOAD_BUILD_CLASS] = "_LOAD_BUILD_CLASS",
[_LOAD_COMMON_CONSTANT] = "_LOAD_COMMON_CONSTANT",
- [_LOAD_CONST_IMMORTAL] = "_LOAD_CONST_IMMORTAL",
+ [_LOAD_CONST] = "_LOAD_CONST",
[_LOAD_CONST_INLINE] = "_LOAD_CONST_INLINE",
[_LOAD_CONST_INLINE_BORROW] = "_LOAD_CONST_INLINE_BORROW",
- [_LOAD_CONST_MORTAL] = "_LOAD_CONST_MORTAL",
+ [_LOAD_CONST_UNDER_INLINE] = "_LOAD_CONST_UNDER_INLINE",
+ [_LOAD_CONST_UNDER_INLINE_BORROW] = "_LOAD_CONST_UNDER_INLINE_BORROW",
[_LOAD_DEREF] = "_LOAD_DEREF",
[_LOAD_FAST] = "_LOAD_FAST",
[_LOAD_FAST_0] = "_LOAD_FAST_0",
@@ -543,10 +588,22 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_MAYBE_EXPAND_METHOD] = "_MAYBE_EXPAND_METHOD",
[_MAYBE_EXPAND_METHOD_KW] = "_MAYBE_EXPAND_METHOD_KW",
[_NOP] = "_NOP",
+ [_POP_CALL] = "_POP_CALL",
+ [_POP_CALL_LOAD_CONST_INLINE_BORROW] = "_POP_CALL_LOAD_CONST_INLINE_BORROW",
+ [_POP_CALL_ONE] = "_POP_CALL_ONE",
+ [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW] = "_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW",
+ [_POP_CALL_TWO] = "_POP_CALL_TWO",
+ [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = "_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW",
[_POP_EXCEPT] = "_POP_EXCEPT",
+ [_POP_ITER] = "_POP_ITER",
[_POP_TOP] = "_POP_TOP",
+ [_POP_TOP_FLOAT] = "_POP_TOP_FLOAT",
+ [_POP_TOP_INT] = "_POP_TOP_INT",
[_POP_TOP_LOAD_CONST_INLINE] = "_POP_TOP_LOAD_CONST_INLINE",
[_POP_TOP_LOAD_CONST_INLINE_BORROW] = "_POP_TOP_LOAD_CONST_INLINE_BORROW",
+ [_POP_TOP_NOP] = "_POP_TOP_NOP",
+ [_POP_TOP_UNICODE] = "_POP_TOP_UNICODE",
+ [_POP_TWO] = "_POP_TWO",
[_POP_TWO_LOAD_CONST_INLINE_BORROW] = "_POP_TWO_LOAD_CONST_INLINE_BORROW",
[_PUSH_EXC_INFO] = "_PUSH_EXC_INFO",
[_PUSH_FRAME] = "_PUSH_FRAME",
@@ -589,6 +646,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
[_STORE_SUBSCR_DICT] = "_STORE_SUBSCR_DICT",
[_STORE_SUBSCR_LIST_INT] = "_STORE_SUBSCR_LIST_INT",
[_SWAP] = "_SWAP",
+ [_SWAP_2] = "_SWAP_2",
+ [_SWAP_3] = "_SWAP_3",
[_TIER2_RESUME_CHECK] = "_TIER2_RESUME_CHECK",
[_TO_BOOL] = "_TO_BOOL",
[_TO_BOOL_BOOL] = "_TO_BOOL_BOOL",
@@ -662,9 +721,7 @@ int _PyUop_num_popped(int opcode, int oparg)
return 0;
case _LOAD_FAST_BORROW_LOAD_FAST_BORROW:
return 0;
- case _LOAD_CONST_MORTAL:
- return 0;
- case _LOAD_CONST_IMMORTAL:
+ case _LOAD_CONST:
return 0;
case _LOAD_SMALL_INT_0:
return 0;
@@ -700,10 +757,22 @@ int _PyUop_num_popped(int opcode, int oparg)
return 2;
case _POP_TOP:
return 1;
+ case _POP_TOP_NOP:
+ return 1;
+ case _POP_TOP_INT:
+ return 1;
+ case _POP_TOP_FLOAT:
+ return 1;
+ case _POP_TOP_UNICODE:
+ return 1;
+ case _POP_TWO:
+ return 2;
case _PUSH_NULL:
return 0;
case _END_FOR:
return 1;
+ case _POP_ITER:
+ return 2;
case _END_SEND:
return 2;
case _UNARY_NEGATIVE:
@@ -740,6 +809,10 @@ int _PyUop_num_popped(int opcode, int oparg)
return 0;
case _GUARD_TOS_INT:
return 0;
+ case _GUARD_NOS_OVERFLOWED:
+ return 0;
+ case _GUARD_TOS_OVERFLOWED:
+ return 0;
case _BINARY_OP_MULTIPLY_INT:
return 2;
case _BINARY_OP_ADD_INT:
@@ -756,6 +829,12 @@ int _PyUop_num_popped(int opcode, int oparg)
return 2;
case _BINARY_OP_SUBTRACT_FLOAT:
return 2;
+ case _BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS:
+ return 2;
+ case _BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS:
+ return 2;
+ case _BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS:
+ return 2;
case _BINARY_OP_ADD_UNICODE:
return 2;
case _BINARY_OP_INPLACE_ADD_UNICODE:
@@ -1066,6 +1145,10 @@ int _PyUop_num_popped(int opcode, int oparg)
return 1;
case _GUARD_NOS_NULL:
return 0;
+ case _GUARD_NOS_NOT_NULL:
+ return 0;
+ case _GUARD_THIRD_NULL:
+ return 0;
case _GUARD_CALLABLE_TYPE_1:
return 0;
case _CALL_TYPE_1:
@@ -1092,10 +1175,16 @@ int _PyUop_num_popped(int opcode, int oparg)
return 2 + oparg;
case _CALL_BUILTIN_FAST_WITH_KEYWORDS:
return 2 + oparg;
+ case _GUARD_CALLABLE_LEN:
+ return 0;
case _CALL_LEN:
- return 2 + oparg;
+ return 3;
+ case _GUARD_CALLABLE_ISINSTANCE:
+ return 0;
case _CALL_ISINSTANCE:
- return 2 + oparg;
+ return 4;
+ case _GUARD_CALLABLE_LIST_APPEND:
+ return 0;
case _CALL_LIST_APPEND:
return 3;
case _CALL_METHOD_DESCRIPTOR_O:
@@ -1136,10 +1225,20 @@ int _PyUop_num_popped(int opcode, int oparg)
return 1;
case _FORMAT_WITH_SPEC:
return 2;
+ case _COPY_1:
+ return 0;
+ case _COPY_2:
+ return 0;
+ case _COPY_3:
+ return 0;
case _COPY:
return 0;
case _BINARY_OP:
return 2;
+ case _SWAP_2:
+ return 0;
+ case _SWAP_3:
+ return 0;
case _SWAP:
return 0;
case _GUARD_IS_TRUE_POP:
@@ -1168,10 +1267,26 @@ int _PyUop_num_popped(int opcode, int oparg)
return 1;
case _LOAD_CONST_INLINE_BORROW:
return 0;
+ case _POP_CALL:
+ return 2;
+ case _POP_CALL_ONE:
+ return 3;
+ case _POP_CALL_TWO:
+ return 4;
case _POP_TOP_LOAD_CONST_INLINE_BORROW:
return 1;
case _POP_TWO_LOAD_CONST_INLINE_BORROW:
return 2;
+ case _POP_CALL_LOAD_CONST_INLINE_BORROW:
+ return 2;
+ case _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW:
+ return 3;
+ case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW:
+ return 4;
+ case _LOAD_CONST_UNDER_INLINE:
+ return 1;
+ case _LOAD_CONST_UNDER_INLINE_BORROW:
+ return 1;
case _CHECK_FUNCTION:
return 0;
case _START_EXECUTOR:
diff --git a/Include/internal/pycore_weakref.h b/Include/internal/pycore_weakref.h
index 950aa0af290..4ed8928c0b9 100644
--- a/Include/internal/pycore_weakref.h
+++ b/Include/internal/pycore_weakref.h
@@ -29,6 +29,12 @@ extern "C" {
PyMutex_LockFlags(wr->weakrefs_lock, _Py_LOCK_DONT_DETACH)
#define UNLOCK_WEAKREFS_FOR_WR(wr) PyMutex_Unlock(wr->weakrefs_lock)
+#define FT_CLEAR_WEAKREFS(obj, weakref_list) \
+ do { \
+ assert(Py_REFCNT(obj) == 0); \
+ PyObject_ClearWeakRefs(obj); \
+ } while (0)
+
#else
#define LOCK_WEAKREFS(obj)
@@ -37,6 +43,14 @@ extern "C" {
#define LOCK_WEAKREFS_FOR_WR(wr)
#define UNLOCK_WEAKREFS_FOR_WR(wr)
+#define FT_CLEAR_WEAKREFS(obj, weakref_list) \
+ do { \
+ assert(Py_REFCNT(obj) == 0); \
+ if (weakref_list != NULL) { \
+ PyObject_ClearWeakRefs(obj); \
+ } \
+ } while (0)
+
#endif
static inline int _is_dead(PyObject *obj)
diff --git a/Include/object.h b/Include/object.h
index 8cc83abb857..c75e9db0cbd 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -101,6 +101,12 @@ whose size is determined when the object is allocated.
#define PyObject_VAR_HEAD PyVarObject ob_base;
#define Py_INVALID_SIZE (Py_ssize_t)-1
+/* PyObjects are given a minimum alignment so that the least significant bits
+ * of an object pointer become available for other purposes.
+ * This must be an integer literal with the value (1 << _PyGC_PREV_SHIFT), number of bytes.
+ */
+#define _PyObject_MIN_ALIGNMENT 4
+
/* Nothing is actually declared to be a PyObject, but every pointer to
* a Python object can be cast to a PyObject*. This is inheritance built
* by hand. Similarly every pointer to a variable-size Python object can,
@@ -136,6 +142,7 @@ struct _object {
#else
Py_ssize_t ob_refcnt;
#endif
+ _Py_ALIGNED_DEF(_PyObject_MIN_ALIGNMENT, char) _aligner;
};
#ifdef _MSC_VER
__pragma(warning(pop))
@@ -153,7 +160,7 @@ struct _object {
// ob_tid stores the thread id (or zero). It is also used by the GC and the
// trashcan mechanism as a linked list pointer and by the GC to store the
// computed "gc_refs" refcount.
- uintptr_t ob_tid;
+ _Py_ALIGNED_DEF(_PyObject_MIN_ALIGNMENT, uintptr_t) ob_tid;
uint16_t ob_flags;
PyMutex ob_mutex; // per-object lock
uint8_t ob_gc_bits; // gc-related state
@@ -620,6 +627,12 @@ given type object has a specified feature.
#define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0)
#define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18)
+// Flag values for ob_flags (16 bits available, if SIZEOF_VOID_P > 4).
+#define _Py_IMMORTAL_FLAGS (1 << 0)
+#define _Py_STATICALLY_ALLOCATED_FLAG (1 << 2)
+#if defined(Py_GIL_DISABLED) && defined(Py_DEBUG)
+#define _Py_TYPE_REVEALED_FLAG (1 << 3)
+#endif
#define Py_CONSTANT_NONE 0
#define Py_CONSTANT_FALSE 1
@@ -654,8 +667,13 @@ PyAPI_DATA(PyObject) _Py_NoneStruct; /* Don't use this directly */
PyAPI_FUNC(int) Py_IsNone(PyObject *x);
#define Py_IsNone(x) Py_Is((x), Py_None)
-/* Macro for returning Py_None from a function */
-#define Py_RETURN_NONE return Py_None
+/* Macro for returning Py_None from a function.
+ * Only treat Py_None as immortal in the limited C API 3.12 and newer. */
+#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 < 0x030c0000
+# define Py_RETURN_NONE return Py_NewRef(Py_None)
+#else
+# define Py_RETURN_NONE return Py_None
+#endif
/*
Py_NotImplemented is a singleton used to signal that an operation is
@@ -776,11 +794,7 @@ PyType_HasFeature(PyTypeObject *type, unsigned long feature)
// PyTypeObject is opaque in the limited C API
flags = PyType_GetFlags(type);
#else
-# ifdef Py_GIL_DISABLED
- flags = _Py_atomic_load_ulong_relaxed(&type->tp_flags);
-# else
- flags = type->tp_flags;
-# endif
+ flags = type->tp_flags;
#endif
return ((flags & feature) != 0);
}
diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h
index 209103c83b3..1d5c74adefc 100644
--- a/Include/opcode_ids.h
+++ b/Include/opcode_ids.h
@@ -193,28 +193,26 @@ extern "C" {
#define LOAD_ATTR_PROPERTY 187
#define LOAD_ATTR_SLOT 188
#define LOAD_ATTR_WITH_HINT 189
-#define LOAD_CONST_IMMORTAL 190
-#define LOAD_CONST_MORTAL 191
-#define LOAD_GLOBAL_BUILTIN 192
-#define LOAD_GLOBAL_MODULE 193
-#define LOAD_SUPER_ATTR_ATTR 194
-#define LOAD_SUPER_ATTR_METHOD 195
-#define RESUME_CHECK 196
-#define SEND_GEN 197
-#define STORE_ATTR_INSTANCE_VALUE 198
-#define STORE_ATTR_SLOT 199
-#define STORE_ATTR_WITH_HINT 200
-#define STORE_SUBSCR_DICT 201
-#define STORE_SUBSCR_LIST_INT 202
-#define TO_BOOL_ALWAYS_TRUE 203
-#define TO_BOOL_BOOL 204
-#define TO_BOOL_INT 205
-#define TO_BOOL_LIST 206
-#define TO_BOOL_NONE 207
-#define TO_BOOL_STR 208
-#define UNPACK_SEQUENCE_LIST 209
-#define UNPACK_SEQUENCE_TUPLE 210
-#define UNPACK_SEQUENCE_TWO_TUPLE 211
+#define LOAD_GLOBAL_BUILTIN 190
+#define LOAD_GLOBAL_MODULE 191
+#define LOAD_SUPER_ATTR_ATTR 192
+#define LOAD_SUPER_ATTR_METHOD 193
+#define RESUME_CHECK 194
+#define SEND_GEN 195
+#define STORE_ATTR_INSTANCE_VALUE 196
+#define STORE_ATTR_SLOT 197
+#define STORE_ATTR_WITH_HINT 198
+#define STORE_SUBSCR_DICT 199
+#define STORE_SUBSCR_LIST_INT 200
+#define TO_BOOL_ALWAYS_TRUE 201
+#define TO_BOOL_BOOL 202
+#define TO_BOOL_INT 203
+#define TO_BOOL_LIST 204
+#define TO_BOOL_NONE 205
+#define TO_BOOL_STR 206
+#define UNPACK_SEQUENCE_LIST 207
+#define UNPACK_SEQUENCE_TUPLE 208
+#define UNPACK_SEQUENCE_TWO_TUPLE 209
#define INSTRUMENTED_END_FOR 234
#define INSTRUMENTED_POP_ITER 235
#define INSTRUMENTED_END_SEND 236
diff --git a/Include/patchlevel.h b/Include/patchlevel.h
index ed570864f6b..532873b51e6 100644
--- a/Include/patchlevel.h
+++ b/Include/patchlevel.h
@@ -18,13 +18,13 @@
/* Version parsed out into numeric values */
/*--start constants--*/
#define PY_MAJOR_VERSION 3
-#define PY_MINOR_VERSION 14
+#define PY_MINOR_VERSION 15
#define PY_MICRO_VERSION 0
#define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_ALPHA
-#define PY_RELEASE_SERIAL 7
+#define PY_RELEASE_SERIAL 0
/* Version as a string */
-#define PY_VERSION "3.14.0a7+"
+#define PY_VERSION "3.15.0a0"
/*--end constants--*/
diff --git a/Include/py_curses.h b/Include/py_curses.h
index e11bfedb17d..0948aabedd4 100644
--- a/Include/py_curses.h
+++ b/Include/py_curses.h
@@ -75,10 +75,11 @@ extern "C" {
/* Type declarations */
-typedef struct {
+typedef struct PyCursesWindowObject {
PyObject_HEAD
WINDOW *win;
char *encoding;
+ struct PyCursesWindowObject *orig;
} PyCursesWindowObject;
#define PyCurses_CAPSULE_NAME "_curses._C_API"
@@ -108,6 +109,13 @@ static void **PyCurses_API;
static const char catchall_ERR[] = "curses function returned ERR";
static const char catchall_NULL[] = "curses function returned NULL";
+#if defined(CURSES_MODULE) || defined(CURSES_PANEL_MODULE)
+/* Error messages shared by the curses package */
+# define CURSES_ERROR_FORMAT "%s() returned %s"
+# define CURSES_ERROR_VERBOSE_FORMAT "%s() (called by %s()) returned %s"
+# define CURSES_ERROR_MUST_CALL_FORMAT "must call %s() first"
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/pylifecycle.h b/Include/pylifecycle.h
index de1bcb1d2cb..4b3474035ce 100644
--- a/Include/pylifecycle.h
+++ b/Include/pylifecycle.h
@@ -35,15 +35,8 @@ PyAPI_FUNC(int) Py_BytesMain(int argc, char **argv);
/* In pathconfig.c */
Py_DEPRECATED(3.11) PyAPI_FUNC(void) Py_SetProgramName(const wchar_t *);
-Py_DEPRECATED(3.13) PyAPI_FUNC(wchar_t *) Py_GetProgramName(void);
-
Py_DEPRECATED(3.11) PyAPI_FUNC(void) Py_SetPythonHome(const wchar_t *);
-Py_DEPRECATED(3.13) PyAPI_FUNC(wchar_t *) Py_GetPythonHome(void);
-Py_DEPRECATED(3.13) PyAPI_FUNC(wchar_t *) Py_GetProgramFullPath(void);
-Py_DEPRECATED(3.13) PyAPI_FUNC(wchar_t *) Py_GetPrefix(void);
-Py_DEPRECATED(3.13) PyAPI_FUNC(wchar_t *) Py_GetExecPrefix(void);
-Py_DEPRECATED(3.13) PyAPI_FUNC(wchar_t *) Py_GetPath(void);
#ifdef MS_WINDOWS
int _Py_CheckPython3(void);
#endif
diff --git a/Include/pymacro.h b/Include/pymacro.h
index 218987a80b0..b2886ddac5d 100644
--- a/Include/pymacro.h
+++ b/Include/pymacro.h
@@ -24,44 +24,66 @@
#endif
-// _Py_ALIGN_AS: this compiler's spelling of `alignas` keyword,
-// We currently use alignas for free-threaded builds only; additional compat
-// checking would be great before we add it to the default build.
-// Standards/compiler support:
+// _Py_ALIGNED_DEF(N, T): Define a variable/member with increased alignment
+//
+// `N`: the desired minimum alignment, an integer literal, number of bytes
+// `T`: the type of the defined variable
+// (or a type with at least the defined variable's alignment)
+//
+// May not be used on a struct definition.
+//
+// Standards/compiler support for `alignas` alternatives:
// - `alignas` is a keyword in C23 and C++11.
// - `_Alignas` is a keyword in C11
// - GCC & clang has __attribute__((aligned))
// (use that for older standards in pedantic mode)
// - MSVC has __declspec(align)
// - `_Alignas` is common C compiler extension
-// Older compilers may name it differently; to allow compilation on such
-// unsupported platforms, we don't redefine _Py_ALIGN_AS if it's already
+// Older compilers may name `alignas` differently; to allow compilation on such
+// unsupported platforms, we don't redefine _Py_ALIGNED_DEF if it's already
// defined. Note that defining it wrong (including defining it to nothing) will
// cause ABI incompatibilities.
-#ifdef Py_GIL_DISABLED
-# ifndef _Py_ALIGN_AS
-# ifdef __cplusplus
-# if __cplusplus >= 201103L
-# define _Py_ALIGN_AS(V) alignas(V)
-# elif defined(__GNUC__) || defined(__clang__)
-# define _Py_ALIGN_AS(V) __attribute__((aligned(V)))
-# elif defined(_MSC_VER)
-# define _Py_ALIGN_AS(V) __declspec(align(V))
-# else
-# define _Py_ALIGN_AS(V) alignas(V)
-# endif
-# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
-# define _Py_ALIGN_AS(V) alignas(V)
-# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
-# define _Py_ALIGN_AS(V) _Alignas(V)
-# elif (defined(__GNUC__) || defined(__clang__))
-# define _Py_ALIGN_AS(V) __attribute__((aligned(V)))
-# elif defined(_MSC_VER)
-# define _Py_ALIGN_AS(V) __declspec(align(V))
-# else
-# define _Py_ALIGN_AS(V) _Alignas(V)
-# endif
-# endif
+//
+// Behavior of `alignas` alternatives:
+// - `alignas` & `_Alignas`:
+// - Can be used multiple times; the greatest alignment applies.
+// - It is an *error* if the combined effect of all `alignas` modifiers would
+// decrease the alignment.
+// - Takes types or numbers.
+// - May not be used on a struct definition, unless also defining a variable.
+// - `__declspec(align)`:
+// - Has no effect if it would decrease alignment.
+// - Only takes an integer literal.
+// - May be used on struct or variable definitions.
+// However, when defining both the struct and the variable at once,
+// `declspec(aligned)` causes compiler warning 5274 and possible ABI
+// incompatibility.
+// - ` __attribute__((aligned))`:
+// - Has no effect if it would decrease alignment.
+// - Takes types or numbers
+// - May be used on struct or variable definitions.
+#ifndef _Py_ALIGNED_DEF
+# ifdef __cplusplus
+# if __cplusplus >= 201103L
+# define _Py_ALIGNED_DEF(N, T) alignas(N) alignas(T) T
+# elif defined(__GNUC__) || defined(__clang__)
+# define _Py_ALIGNED_DEF(N, T) __attribute__((aligned(N))) T
+# elif defined(_MSC_VER)
+# define _Py_ALIGNED_DEF(N, T) __declspec(align(N)) T
+# else
+# define _Py_ALIGNED_DEF(N, T) alignas(N) alignas(T) T
+# endif
+# elif defined(_MSC_VER)
+# define _Py_ALIGNED_DEF(N, T) __declspec(align(N)) T
+# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+# define _Py_ALIGNED_DEF(N, T) alignas(N) alignas(T) T
+# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+# define _Py_ALIGNED_DEF(N, T) _Alignas(N) _Alignas(T) T
+# elif (defined(__GNUC__) || defined(__clang__))
+# define _Py_ALIGNED_DEF(N, T) __attribute__((aligned(N))) T
+# else
+# define _Py_ALIGNED_DEF(N, T) _Alignas(N) _Alignas(T) T
+# endif
#endif
/* Minimum value between x and y */
@@ -231,12 +253,13 @@
// "comparison of unsigned expression in '< 0' is always false".
#define _Py_IS_TYPE_SIGNED(type) ((type)(-1) <= 0)
-#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000 // 3.14
// Version helpers. These are primarily macros, but have exported equivalents.
+#define _Py_PACK_VERSION(X, Y) _Py_PACK_FULL_VERSION(X, Y, 0, 0, 0)
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 14)
PyAPI_FUNC(uint32_t) Py_PACK_FULL_VERSION(int x, int y, int z, int level, int serial);
PyAPI_FUNC(uint32_t) Py_PACK_VERSION(int x, int y);
#define Py_PACK_FULL_VERSION _Py_PACK_FULL_VERSION
-#define Py_PACK_VERSION(X, Y) Py_PACK_FULL_VERSION(X, Y, 0, 0, 0)
+#define Py_PACK_VERSION _Py_PACK_VERSION
#endif // Py_LIMITED_API < 3.14
diff --git a/Include/pyport.h b/Include/pyport.h
index 3eac119bf8e..89829373be2 100644
--- a/Include/pyport.h
+++ b/Include/pyport.h
@@ -49,8 +49,9 @@
// Static inline functions should use _Py_NULL rather than using directly NULL
// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer,
// _Py_NULL is defined as nullptr.
-#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \
- || (defined(__cplusplus) && __cplusplus >= 201103)
+#if !defined(_MSC_VER) && \
+ ((defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \
+ || (defined(__cplusplus) && __cplusplus >= 201103))
# define _Py_NULL nullptr
#else
# define _Py_NULL NULL
@@ -666,25 +667,6 @@ extern "C" {
#endif
-// _Py_NO_SANITIZE_UNDEFINED(): Disable Undefined Behavior sanitizer (UBsan)
-// on a function.
-//
-// Clang and GCC 9.0+ use __attribute__((no_sanitize("undefined"))).
-// GCC 4.9+ uses __attribute__((no_sanitize_undefined)).
-#if defined(__has_feature)
-# if __has_feature(undefined_behavior_sanitizer)
-# define _Py_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined")))
-# endif
-#endif
-#if !defined(_Py_NO_SANITIZE_UNDEFINED) && defined(__GNUC__) \
- && ((__GNUC__ >= 5) || (__GNUC__ == 4) && (__GNUC_MINOR__ >= 9))
-# define _Py_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize_undefined))
-#endif
-#ifndef _Py_NO_SANITIZE_UNDEFINED
-# define _Py_NO_SANITIZE_UNDEFINED
-#endif
-
-
// _Py_NONSTRING: The nonstring variable attribute specifies that an object or
// member declaration with type array of char, signed char, or unsigned char,
// or pointer to such a type is intended to store character arrays that do not
diff --git a/Include/pythonrun.h b/Include/pythonrun.h
index fad2b3c7747..92b50aa807b 100644
--- a/Include/pythonrun.h
+++ b/Include/pythonrun.h
@@ -21,39 +21,15 @@ PyAPI_FUNC(void) PyErr_DisplayException(PyObject *);
/* Stuff with no proper home (yet) */
PyAPI_DATA(int) (*PyOS_InputHook)(void);
-/* Stack size, in "pointers". This must be large enough, so
- * no two calls to check recursion depth are more than this far
- * apart. In practice, that means it must be larger than the C
- * stack consumption of PyEval_EvalDefault */
-#if defined(_Py_ADDRESS_SANITIZER) || defined(_Py_THREAD_SANITIZER)
-# define PYOS_LOG2_STACK_MARGIN 12
-#elif defined(Py_DEBUG) && defined(WIN32)
-# define PYOS_LOG2_STACK_MARGIN 12
-#elif defined(__wasi__)
- /* Web assembly has two stacks, so this isn't really a size */
-# define PYOS_LOG2_STACK_MARGIN 9
-#else
-# define PYOS_LOG2_STACK_MARGIN 11
-#endif
-#define PYOS_STACK_MARGIN (1 << PYOS_LOG2_STACK_MARGIN)
-#define PYOS_STACK_MARGIN_BYTES (PYOS_STACK_MARGIN * sizeof(void *))
-
-#if SIZEOF_VOID_P == 8
-#define PYOS_STACK_MARGIN_SHIFT (PYOS_LOG2_STACK_MARGIN + 3)
-#else
-#define PYOS_STACK_MARGIN_SHIFT (PYOS_LOG2_STACK_MARGIN + 2)
-#endif
-
-
#if defined(WIN32)
-#define USE_STACKCHECK
+# define USE_STACKCHECK
#endif
-
#ifdef USE_STACKCHECK
/* Check that we aren't overflowing our stack */
PyAPI_FUNC(int) PyOS_CheckStack(void);
#endif
+
#ifndef Py_LIMITED_API
# define Py_CPYTHON_PYTHONRUN_H
# include "cpython/pythonrun.h"
diff --git a/Include/refcount.h b/Include/refcount.h
index 177bbdaf0c5..457972b6dcf 100644
--- a/Include/refcount.h
+++ b/Include/refcount.h
@@ -1,5 +1,5 @@
-#ifndef Py_REFCOUNT_H
-#define Py_REFCOUNT_H
+#ifndef _Py_REFCOUNT_H
+#define _Py_REFCOUNT_H
#ifdef __cplusplus
extern "C" {
#endif
@@ -19,9 +19,6 @@ immortal. The latter should be the only instances that require
cleanup during runtime finalization.
*/
-#define _Py_STATICALLY_ALLOCATED_FLAG 4
-#define _Py_IMMORTAL_FLAGS 1
-
#if SIZEOF_VOID_P > 4
/*
In 64+ bit systems, any object whose 32 bit reference count is >= 2**31
@@ -33,7 +30,7 @@ increase and decrease the objects reference count.
In order to offer sufficient resilience to C extensions using the stable ABI
compiled against 3.11 or earlier, we set the initial value near the
-middle of the range (2**31, 2**32). That way the the refcount can be
+middle of the range (2**31, 2**32). That way the refcount can be
off by ~1 billion without affecting immortality.
Reference count increases will use saturated arithmetic, taking advantage of
@@ -247,20 +244,6 @@ PyAPI_FUNC(void) Py_DecRef(PyObject *);
PyAPI_FUNC(void) _Py_IncRef(PyObject *);
PyAPI_FUNC(void) _Py_DecRef(PyObject *);
-#ifndef Py_GIL_DISABLED
-static inline Py_ALWAYS_INLINE void Py_INCREF_MORTAL(PyObject *op)
-{
- assert(!_Py_IsStaticImmortal(op));
- op->ob_refcnt++;
- _Py_INCREF_STAT_INC();
-#if defined(Py_REF_DEBUG) && !defined(Py_LIMITED_API)
- if (!_Py_IsImmortal(op)) {
- _Py_INCREF_IncRefTotal();
- }
-#endif
-}
-#endif
-
static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
{
#if defined(Py_LIMITED_API) && (Py_LIMITED_API+0 >= 0x030c0000 || defined(Py_REF_DEBUG))
@@ -564,4 +547,4 @@ static inline PyObject* _Py_XNewRef(PyObject *obj)
#ifdef __cplusplus
}
#endif
-#endif // !Py_REFCOUNT_H
+#endif // !_Py_REFCOUNT_H
diff --git a/Include/sysmodule.h b/Include/sysmodule.h
index c1d5f610fe0..2f362791797 100644
--- a/Include/sysmodule.h
+++ b/Include/sysmodule.h
@@ -4,6 +4,12 @@
extern "C" {
#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030f0000
+PyAPI_FUNC(PyObject *) PySys_GetAttr(PyObject *);
+PyAPI_FUNC(PyObject *) PySys_GetAttrString(const char *);
+PyAPI_FUNC(int) PySys_GetOptionalAttr(PyObject *, PyObject **);
+PyAPI_FUNC(int) PySys_GetOptionalAttrString(const char *, PyObject **);
+#endif
PyAPI_FUNC(PyObject *) PySys_GetObject(const char *);
PyAPI_FUNC(int) PySys_SetObject(const char *, PyObject *);
diff --git a/Include/unicodeobject.h b/Include/unicodeobject.h
index f8bcaecb98f..b72d581ec25 100644
--- a/Include/unicodeobject.h
+++ b/Include/unicodeobject.h
@@ -341,49 +341,6 @@ PyAPI_FUNC(PyObject*) PyUnicode_Decode(
const char *errors /* error handling */
);
-/* Decode a Unicode object unicode and return the result as Python
- object.
-
- This API is DEPRECATED and will be removed in 3.15.
- The only supported standard encoding is rot13.
- Use PyCodec_Decode() to decode with rot13 and non-standard codecs
- that decode from str. */
-
-Py_DEPRECATED(3.6) PyAPI_FUNC(PyObject*) PyUnicode_AsDecodedObject(
- PyObject *unicode, /* Unicode object */
- const char *encoding, /* encoding */
- const char *errors /* error handling */
- );
-
-/* Decode a Unicode object unicode and return the result as Unicode
- object.
-
- This API is DEPRECATED and will be removed in 3.15.
- The only supported standard encoding is rot13.
- Use PyCodec_Decode() to decode with rot13 and non-standard codecs
- that decode from str to str. */
-
-Py_DEPRECATED(3.6) PyAPI_FUNC(PyObject*) PyUnicode_AsDecodedUnicode(
- PyObject *unicode, /* Unicode object */
- const char *encoding, /* encoding */
- const char *errors /* error handling */
- );
-
-/* Encodes a Unicode object and returns the result as Python
- object.
-
- This API is DEPRECATED and will be removed in 3.15.
- It is superseded by PyUnicode_AsEncodedString()
- since all standard encodings (except rot13) encode str to bytes.
- Use PyCodec_Encode() for encoding with rot13 and non-standard codecs
- that encode form str to non-bytes. */
-
-Py_DEPRECATED(3.6) PyAPI_FUNC(PyObject*) PyUnicode_AsEncodedObject(
- PyObject *unicode, /* Unicode object */
- const char *encoding, /* encoding */
- const char *errors /* error handling */
- );
-
/* Encodes a Unicode object and returns the result as Python string
object. */
@@ -393,20 +350,6 @@ PyAPI_FUNC(PyObject*) PyUnicode_AsEncodedString(
const char *errors /* error handling */
);
-/* Encodes a Unicode object and returns the result as Unicode
- object.
-
- This API is DEPRECATED and will be removed in 3.15.
- The only supported standard encodings is rot13.
- Use PyCodec_Encode() to encode with rot13 and non-standard codecs
- that encode from str to str. */
-
-Py_DEPRECATED(3.6) PyAPI_FUNC(PyObject*) PyUnicode_AsEncodedUnicode(
- PyObject *unicode, /* Unicode object */
- const char *encoding, /* encoding */
- const char *errors /* error handling */
- );
-
/* Build an encoding map. */
PyAPI_FUNC(PyObject*) PyUnicode_BuildEncodingMap(
diff --git a/InternalDocs/README.md b/InternalDocs/README.md
index 4502902307c..6b1d9198264 100644
--- a/InternalDocs/README.md
+++ b/InternalDocs/README.md
@@ -41,3 +41,10 @@ Program Execution
- [Garbage Collector Design](garbage_collector.md)
- [Exception Handling](exception_handling.md)
+
+- [Quiescent-State Based Reclamation (QSBR)](qsbr.md)
+
+Modules
+---
+
+- [asyncio](asyncio.md)
diff --git a/InternalDocs/asyncio.md b/InternalDocs/asyncio.md
new file mode 100644
index 00000000000..22159852ca5
--- /dev/null
+++ b/InternalDocs/asyncio.md
@@ -0,0 +1,328 @@
+asyncio
+=======
+
+
+This document describes the working and implementation details of the
+[`asyncio`](https://docs.python.org/3/library/asyncio.html) module.
+
+**The following section describes the implementation details of the C implementation**.
+
+# Task management
+
+## Pre-Python 3.14 implementation
+
+Before Python 3.14, the C implementation of `asyncio` used a
+[`WeakSet`](https://docs.python.org/3/library/weakref.html#weakref.WeakSet)
+to store all the tasks created by the event loop. `WeakSet` was used
+so that the event loop doesn't hold strong references to the tasks,
+allowing them to be garbage collected when they are no longer needed.
+The current task of the event loop was stored in a dict mapping the
+event loop to the current task.
+
+```c
+ /* Dictionary containing tasks that are currently active in
+ all running event loops. {EventLoop: Task} */
+ PyObject *current_tasks;
+
+ /* WeakSet containing all tasks scheduled to run on event loops. */
+ PyObject *scheduled_tasks;
+```
+
+This implementation had a few drawbacks:
+
+1. **Performance**: Using a `WeakSet` for storing tasks is
+inefficient, as it requires maintaining a full set of weak references
+to tasks along with corresponding weakref callback to cleanup the
+tasks when they are garbage collected. This increases the work done
+by the garbage collector, and in applications with a large number of
+tasks, this becomes a bottleneck, with increased memory usage and
+lower performance. Looking up the current task was slow as it required
+a dictionary lookup on the `current_tasks` dict.
+
+2. **Thread safety**: Before Python 3.14, concurrent iterations over
+`WeakSet` was not thread safe[^1]. This meant calling APIs like
+`asyncio.all_tasks()` could lead to inconsistent results or even
+`RuntimeError` if used in multiple threads[^2].
+
+3. **Poor scaling in free-threading**: Using global `WeakSet` for
+storing all tasks across all threads lead to contention when adding
+and removing tasks from the set which is a frequent operation. As such
+it performed poorly in free-threading and did not scale well with the
+number of threads. Similarly, accessing the current task in multiple
+threads did not scale due to contention on the global `current_tasks`
+dictionary.
+
+## Python 3.14 implementation
+
+To address these issues, Python 3.14 implements several changes to
+improve the performance and thread safety of tasks management.
+
+- **Per-thread double linked list for tasks**: Python 3.14 introduces
+ a per-thread circular double linked list implementation for
+ storing tasks. This allows each thread to maintain its own list of
+ tasks and allows for lock free addition and removal of tasks. This
+ is designed to be efficient, and thread-safe and scales well with
+ the number of threads in free-threading. This also allows external
+ introspection tools such as `python -m asyncio pstree` to inspect
+ tasks running in all threads and was implemented as part of [Audit
+ asyncio thread
+ safety](https://github.com/python/cpython/issues/128002).
+
+- **Per-thread current task**: Python 3.14 stores the current task on
+ the current thread state instead of a global dictionary. This
+ allows for faster access to the current task without the need for
+ a dictionary lookup. Each thread maintains its own current task,
+ which is stored in the `PyThreadState` structure. This was
+ implemented in https://github.com/python/cpython/issues/129898.
+
+Storing the current task and list of all tasks per-thread instead of
+storing it per-loop was chosen primarily to support external
+introspection tools such as `python -m asyncio pstree` as looking up
+arbitrary attributes on the loop object is not possible
+externally. Storing data per-thread also makes it easy to support
+third party event loop implementations such as `uvloop`, and is more
+efficient for the single threaded asyncio use-case as it avoids the
+overhead of attribute lookups on the loop object and several other
+calls on the performance critical path of adding and removing tasks
+from the per-loop task list.
+
+## Per-thread double linked list for tasks
+
+This implementation uses a circular doubly linked list to store tasks
+on the thread states. This is used for all tasks which are instances
+of `asyncio.Task` or subclasses of it, for third-party tasks a
+fallback `WeakSet` implementation is used. The linked list is
+implemented using an embedded `llist_node` structure within each
+`TaskObj`. By embedding the list node directly into the task object,
+the implementation avoids additional memory allocations for linked
+list nodes.
+
+The `PyThreadState` structure gained a new field `asyncio_tasks_head`,
+which serves as the head of the circular linked list of tasks. This
+allows for lock free addition and removal of tasks from the list.
+
+It is possible that when a thread state is deallocated, there are
+lingering tasks in its list; this can happen if another thread has
+references to the tasks of this thread. Therefore, the
+`PyInterpreterState` structure also gains a new `asyncio_tasks_head`
+field to store any lingering tasks. When a thread state is
+deallocated, any remaining lingering tasks are moved to the
+interpreter state tasks list, and the thread state tasks list is
+cleared. The `asyncio_tasks_lock` is used protect the interpreter's
+tasks list from concurrent modifications.
+
+```c
+typedef struct TaskObj {
+ ...
+ struct llist_node asyncio_node;
+} TaskObj;
+
+typedef struct PyThreadState {
+ ...
+ struct llist_node asyncio_tasks_head;
+} PyThreadState;
+
+typedef struct PyInterpreterState {
+ ...
+ struct llist_node asyncio_tasks_head;
+ PyMutex asyncio_tasks_lock;
+} PyInterpreterState;
+```
+
+When a task is created, it is added to the current thread's list of
+tasks by the `register_task` function. When the task is done, it is
+removed from the list by the `unregister_task` function. In
+free-threading, the thread id of the thread which created the task is
+stored in `task_tid` field of the `TaskObj`. This is used to check if
+the task is being removed from the correct thread's task list. If the
+current thread is same as the thread which created it then no locking
+is required, otherwise in free-threading, the `stop-the-world` pause
+is used to pause all other threads and then safely remove the task
+from the tasks list.
+
+```mermaid
+
+flowchart TD
+ subgraph one["Executing Thread"]
+ A["task = asyncio.create_task(coro())"] -->B("register_task(task)")
+ B --> C{"task->task_state?"}
+ C -->|pending| D["task_step(task)"]
+ C -->|done| F["unregister_task(task)"]
+ C -->|cancelled| F["unregister_task(task)"]
+ D --> C
+ F --> G{"free-threading?"}
+ G --> |false| H["unregister_task_safe(task)"]
+ G --> |true| J{"correct thread? <br>task->task_tid == _Py_ThreadId()"}
+ J --> |true| H
+ J --> |false| I["stop the world <br> pause all threads"]
+ I --> H["unregister_task_safe(task)"]
+ end
+ subgraph two["Thread deallocating"]
+ A1{"thread's task list empty? <br> llist_empty(tstate->asyncio_tasks_head)"}
+ A1 --> |true| B1["deallocate thread<br>free_threadstate(tstate)"]
+ A1 --> |false| C1["add tasks to interpreter's task list<br> llist_concat(&tstate->interp->asyncio_tasks_head,
+ &tstate->asyncio_tasks_head)"]
+ C1 --> B1
+ end
+
+ one --> two
+```
+
+`asyncio.all_tasks` now iterates over the per-thread task lists of all
+threads and the interpreter's task list to get all the tasks. In
+free-threading, this is done by pausing all the threads using the
+`stop-the-world` pause to ensure that no tasks are being added or
+removed while iterating over the lists. This allows for a consistent
+view of all task lists across all threads and is thread safe.
+
+This design allows for lock free execution and scales well in
+free-threading with multiple event loops running in different threads.
+
+## Per-thread current task
+
+This implementation stores the current task in the `PyThreadState`
+structure, which allows for faster access to the current task without
+the need for a dictionary lookup.
+
+```c
+typedef struct PyThreadState {
+ ...
+ PyObject *asyncio_current_loop;
+ PyObject *asyncio_current_task;
+} PyThreadState;
+```
+
+When a task is entered or left, the current task is updated in the
+thread state using `enter_task` and `leave_task` functions. When
+`current_task(loop)` is called where `loop` is the current running
+event loop of the current thread, no locking is required as the
+current task is stored in the thread state and is returned directly
+(general case). Otherwise, if the `loop` is not current running event
+loop, the `stop-the-world` pause is used to pause all threads in
+free-threading and then by iterating over all the thread states and
+checking if the `loop` matches with `tstate->asyncio_current_loop`,
+the current task is found and returned. If no matching thread state is
+found, `None` is returned.
+
+In free-threading, it avoids contention on a global dictionary as
+threads can access the current task of thier running loop without any
+locking.
+
+---
+
+**The following section describes the implementation details of the Python implementation**.
+
+# async generators
+
+This section describes the implementation details of async generators in `asyncio`.
+
+Since async generators are meant to be used from coroutines,
+their finalization (execution of finally blocks) needs
+to be done while the loop is running.
+Most async generators are closed automatically
+when they are fully iterated over and exhausted; however,
+if the async generator is not fully iterated over,
+it may not be closed properly, leading to the `finally` blocks not being executed.
+
+Consider the following code:
+```py
+import asyncio
+
+async def agen():
+ try:
+ yield 1
+ finally:
+ await asyncio.sleep(1)
+ print("finally executed")
+
+
+async def main():
+ async for i in agen():
+ break
+
+loop = asyncio.EventLoop()
+loop.run_until_complete(main())
+```
+
+The above code will not print "finally executed", because the
+async generator `agen` is not fully iterated over
+and it is not closed manually by awaiting `agen.aclose()`.
+
+To solve this, `asyncio` uses the `sys.set_asyncgen_hooks` function to
+set hooks for finalizing async generators as described in
+[PEP 525](https://peps.python.org/pep-0525/).
+
+- **firstiter hook**: When the async generator is iterated over for the first time,
+the *firstiter hook* is called. The async generator is added to `loop._asyncgens` WeakSet
+and the event loop tracks all active async generators.
+
+- **finalizer hook**: When the async generator is about to be finalized,
+the *finalizer hook* is called. The event loop removes the async generator
+from `loop._asyncgens` WeakSet, and schedules the finalization of the async
+generator by creating a task calling `agen.aclose()`. This ensures that the
+finally block is executed while the event loop is running. When the loop is
+shutting down, the loop checks if there are active async generators and if so,
+it similarly schedules the finalization of all active async generators by calling
+`agen.aclose()` on each of them and waits for them to complete before shutting
+down the loop.
+
+This ensures that the async generator's `finally` blocks are executed even
+if the generator is not explicitly closed.
+
+Consider the following example:
+
+```python
+import asyncio
+
+async def agen():
+ try:
+ yield 1
+ yield 2
+ finally:
+ print("executing finally block")
+
+async def main():
+ async for item in agen():
+ print(item)
+ break # not fully iterated
+
+asyncio.run(main())
+```
+
+```mermaid
+flowchart TD
+ subgraph one["Loop running"]
+ A["asyncio.run(main())"] --> B
+ B["set async generator hooks <br> sys.set_asyncgen_hooks()"] --> C
+ C["async for item in agen"] --> F
+ F{"first iteration?"} --> |true|D
+ F{"first iteration?"} --> |false|H
+ D["calls firstiter hook<br>loop._asyncgen_firstiter_hook(agen)"] --> E
+ E["add agen to WeakSet<br> loop._asyncgens.add(agen)"] --> H
+ H["item = await agen.\_\_anext\_\_()"] --> J
+ J{"StopAsyncIteration?"} --> |true|M
+ J{"StopAsyncIteration?"} --> |false|I
+ I["print(item)"] --> S
+ S{"continue iterating?"} --> |true|C
+ S{"continue iterating?"} --> |false|M
+ M{"agen is no longer referenced?"} --> |true|N
+ M{"agen is no longer referenced?"} --> |false|two
+ N["finalize agen<br>_PyGen_Finalize(agen)"] --> O
+ O["calls finalizer hook<br>loop._asyncgen_finalizer_hook(agen)"] --> P
+ P["remove agen from WeakSet<br>loop._asyncgens.discard(agen)"] --> Q
+ Q["schedule task to close it<br>self.create_task(agen.aclose())"] --> R
+ R["print('executing finally block')"] --> E1
+
+ end
+
+ subgraph two["Loop shutting down"]
+ A1{"check for alive async generators?"} --> |true|B1
+ B1["close all async generators <br> await asyncio.gather\(*\[ag.aclose\(\) for ag in loop._asyncgens\]"] --> R
+ A1{"check for alive async generators?"} --> |false|E1
+ E1["loop.close()"]
+ end
+
+```
+
+[^1]: https://github.com/python/cpython/issues/123089
+[^2]: https://github.com/python/cpython/issues/80788
diff --git a/InternalDocs/compiler.md b/InternalDocs/compiler.md
index 8ca19a42b91..02bbdf6071f 100644
--- a/InternalDocs/compiler.md
+++ b/InternalDocs/compiler.md
@@ -505,8 +505,8 @@ Important files
* [Python/ast.c](../Python/ast.c):
Used for validating the AST.
- * [Python/ast_opt.c](../Python/ast_opt.c):
- Optimizes the AST.
+ * [Python/ast_preprocess.c](../Python/ast_preprocess.c):
+ Preprocesses the AST before compiling.
* [Python/ast_unparse.c](../Python/ast_unparse.c):
Converts the AST expression node back into a string (for string annotations).
diff --git a/InternalDocs/exception_handling.md b/InternalDocs/exception_handling.md
index 28589787e1f..9e38da4c862 100644
--- a/InternalDocs/exception_handling.md
+++ b/InternalDocs/exception_handling.md
@@ -8,7 +8,7 @@ The cost of raising an exception is increased, but not by much.
The following code:
-```
+```python
try:
g(0)
except:
@@ -18,7 +18,7 @@ except:
compiles into intermediate code like the following:
-```
+```python
RESUME 0
1 SETUP_FINALLY 8 (to L1)
@@ -118,13 +118,13 @@ All offsets and lengths are in code units, not bytes.
We want the format to be compact, but quickly searchable.
For it to be compact, it needs to have variable sized entries so that we can store common (small) offsets compactly, but handle large offsets if needed.
-For it to be searchable quickly, we need to support binary search giving us log(n) performance in all cases.
+For it to be searchable quickly, we need to support binary search giving us `log(n)` performance in all cases.
Binary search typically assumes fixed size entries, but that is not necessary, as long as we can identify the start of an entry.
It is worth noting that the size (end-start) is always smaller than the end, so we encode the entries as:
`start, size, target, depth, push-lasti`.
-Also, sizes are limited to 2**30 as the code length cannot exceed 2**31 and each code unit takes 2 bytes.
+Also, sizes are limited to `2**30` as the code length cannot exceed `2**31` and each code unit takes 2 bytes.
It also happens that depth is generally quite small.
So, we need to encode:
@@ -140,7 +140,7 @@ lasti (1 bit)
We need a marker for the start of the entry, so the first byte of entry will have the most significant bit set.
Since the most significant bit is reserved for marking the start of an entry, we have 7 bits per byte to encode offsets.
Encoding uses a standard varint encoding, but with only 7 bits instead of the usual 8.
-The 8 bits of a byte are (msb left) SXdddddd where S is the start bit. X is the extend bit meaning that the next byte is required to extend the offset.
+The 8 bits of a byte are (msb left) `SXdddddd` where `S` is the start bit. `X` is the extend bit meaning that the next byte is required to extend the offset.
In addition, we combine `depth` and `lasti` into a single value, `((depth<<1)+lasti)`, before encoding.
diff --git a/InternalDocs/garbage_collector.md b/InternalDocs/garbage_collector.md
index 4da6cd47dc8..9c35684c945 100644
--- a/InternalDocs/garbage_collector.md
+++ b/InternalDocs/garbage_collector.md
@@ -286,7 +286,7 @@ object, the GC does not process it twice.
Notice that an object that was marked as "tentatively unreachable" and was later
moved back to the reachable list will be visited again by the garbage collector
-as now all the references that that object has need to be processed as well. This
+as now all the references that the object has need to be processed as well. This
process is really a breadth first search over the object graph. Once all the objects
are scanned, the GC knows that all container objects in the tentatively unreachable
list are really unreachable and can thus be garbage collected.
diff --git a/InternalDocs/generators.md b/InternalDocs/generators.md
index 87fbb912368..979a5b51521 100644
--- a/InternalDocs/generators.md
+++ b/InternalDocs/generators.md
@@ -28,9 +28,9 @@ interpreter state.
The `frame` of a generator is embedded in the generator object struct as a
[`_PyInterpreterFrame`](frames.md) (see `_PyGenObject_HEAD` in
-[`pycore_genobject.h`](../Include/internal/pycore_genobject.h)).
+[`pycore_interpframe_structs.h`](../Include/internal/pycore_interpframe_structs.h)).
This means that we can get the frame from the generator or the generator
-from the frame (see `_PyGen_GetGeneratorFromFrame` in the same file).
+from the frame (see `_PyGen_GetGeneratorFromFrame` in [`pycore_genobject.h`](../Include/internal/pycore_genobject.h)).
Other fields of the generator struct include metadata (such as the name of
the generator function) and runtime state information (such as whether its
frame is executing, suspended, cleared, etc.).
diff --git a/InternalDocs/qsbr.md b/InternalDocs/qsbr.md
new file mode 100644
index 00000000000..1c4a79a7b44
--- /dev/null
+++ b/InternalDocs/qsbr.md
@@ -0,0 +1,129 @@
+# Quiescent-State Based Reclamation (QSBR)
+
+## Introduction
+
+When implementing lock-free data structures, a key challenge is determining
+when it is safe to free memory that has been logically removed from a
+structure. Freeing memory too early can lead to use-after-free bugs if another
+thread is still accessing it. Freeing it too late results in excessive memory
+consumption.
+
+Safe memory reclamation (SMR) schemes address this by delaying the free
+operation until all concurrent read accesses are guaranteed to have completed.
+Quiescent-State Based Reclamation (QSBR) is a SMR scheme used in Python's
+free-threaded build to manage the lifecycle of shared memory.
+
+QSBR requires threads to periodically report that they are in a quiescent
+state. A thread is in a quiescent state if it holds no references to shared
+objects that might be reclaimed. Think of it as a checkpoint where a thread
+signals, "I am not in the middle of any operation that relies on a shared
+resource." In Python, the eval_breaker provides a natural and convenient place
+for threads to report this state.
+
+
+## Use in Free-Threaded Python
+
+While CPython's memory management is dominated by reference counting and a
+tracing garbage collector, these mechanisms are not suitable for all data
+structures. For example, the backing array of a list object is not individually
+reference-counted but may have a shorter lifetime than the `PyListObject` that
+contains it. We could delay reclamation until the next GC run, but we want
+reclamation to be prompt and to run the GC less frequently in the free-threaded
+build, as it requires pausing all threads.
+
+Many operations in the free-threaded build are protected by locks. However, for
+performance-critical code, we want to allow reads to happen concurrently with
+updates. For instance, we want to avoid locking during most list read accesses.
+If a list is resized while another thread is reading it, QSBR provides the
+mechanism to determine when it is safe to free the list's old backing array.
+
+Specific use cases for QSBR include:
+
+* Dictionary keys (`PyDictKeysObject`) and list arrays (`_PyListArray`): When a
+dictionary or list that may be shared between threads is resized, we use QSBR
+to delay freeing the old keys or array until it's safe. For dicts and lists
+that are not shared, their storage can be freed immediately upon resize.
+
+* Mimalloc `mi_page_t`: Non-locking dictionary and list accesses require
+cooperation from the memory allocator. If an object is freed and its memory is
+reused, we must ensure the new object's reference count field is at the same
+memory location. In practice, this means when a mimalloc page (`mi_page_t`)
+becomes empty, we don't immediately allow it to be reused for allocations of a
+different size class. QSBR is used to determine when it's safe to repurpose the
+page or return its memory to the OS.
+
+
+## Implementation Details
+
+
+### Core Implementation
+
+The proposal to add QSBR to Python is contained in
+[Github issue 115103](https://github.com/python/cpython/issues/115103).
+Many details of that proposal have been copied here, so they can be kept
+up-to-date with the actual implementation.
+
+Python's QSBR implementation is based on FreeBSD's "Global Unbounded
+Sequences." [^1][^2][^3]. It relies on a few key counters:
+
+* Global Write Sequence (`wr_seq`): A per-interpreter counter, `wr_seq`, is started
+at 1 and incremented by 2 each time it is advanced. This ensures its value is
+always odd, which can be used to distinguish it from other state values. When
+an object needs to be reclaimed, `wr_seq` is advanced, and the object is tagged
+with this new sequence number.
+
+* Per-Thread Read Sequence: Each thread has a local read sequence counter. When
+a thread reaches a quiescent state (e.g., at the eval_breaker), it copies the
+current global `wr_seq` to its local counter.
+
+* Global Read Sequence (`rd_seq`): This per-interpreter value stores the minimum
+of all per-thread read sequence counters (excluding detached threads). It is
+updated by a "polling" operation.
+
+To free an object, the following steps are taken:
+
+1. Advance the global `wr_seq`.
+
+2. Add the object's pointer to a deferred-free list, tagging it with the new
+ `wr_seq` value as its qsbr_goal.
+
+Periodically, a polling mechanism processes this deferred-free list:
+
+1. The minimum read sequence value across all active threads is calculated and
+ stored as the global `rd_seq`.
+
+2. For each item on the deferred-free list, if its qsbr_goal is less than or
+ equal to the new `rd_seq`, its memory is freed, and it is removed from the:
+ list. Otherwise, it remains on the list for a future attempt.
+
+
+### Deferred Advance Optimization
+
+To reduce memory contention from frequent updates to the global `wr_seq`, its
+advancement is sometimes deferred. Instead of incrementing `wr_seq` on every
+reclamation request, each thread tracks its number of deferrals locally. Once
+the deferral count reaches a limit (QSBR_DEFERRED_LIMIT, currently 10), the
+thread advances the global `wr_seq` and resets its local count.
+
+When an object is added to the deferred-free list, its qsbr_goal is set to
+`wr_seq` + 2. By setting the goal to the next sequence value, we ensure it's safe
+to defer the global counter advancement. This optimization improves runtime
+speed but may increase peak memory usage by slightly delaying when memory can
+be reclaimed.
+
+
+## Limitations
+
+Determining the `rd_seq` requires scanning over all thread states. This operation
+could become a bottleneck in applications with a very large number of threads
+(e.g., >1,000). Future work may address this with more advanced mechanisms,
+such as a tree-based structure or incremental scanning. For now, the
+implementation prioritizes simplicity, with plans for refinement if
+multi-threaded benchmarks reveal performance issues.
+
+
+## References
+
+[^1]: https://youtu.be/ZXUIFj4nRjk?t=694
+[^2]: https://people.kernel.org/joelfernandes/gus-vs-rcu
+[^3]: http://bxr.su/FreeBSD/sys/kern/subr_smr.c#44
diff --git a/Lib/_ast_unparse.py b/Lib/_ast_unparse.py
index 0b669edb2ff..c25066eb107 100644
--- a/Lib/_ast_unparse.py
+++ b/Lib/_ast_unparse.py
@@ -627,6 +627,9 @@ class Unparser(NodeVisitor):
self._ftstring_helper(fstring_parts)
def _tstring_helper(self, node):
+ if not node.values:
+ self._write_ftstring([], "t")
+ return
last_idx = 0
for i, value in enumerate(node.values):
# This can happen if we have an implicit concat of a t-string
@@ -679,9 +682,12 @@ class Unparser(NodeVisitor):
unparser.set_precedence(_Precedence.TEST.next(), inner)
return unparser.visit(inner)
- def _write_interpolation(self, node):
+ def _write_interpolation(self, node, is_interpolation=False):
with self.delimit("{", "}"):
- expr = self._unparse_interpolation_value(node.value)
+ if is_interpolation:
+ expr = node.str
+ else:
+ expr = self._unparse_interpolation_value(node.value)
if expr.startswith("{"):
# Separate pair of opening brackets as "{ {"
self.write(" ")
@@ -696,7 +702,7 @@ class Unparser(NodeVisitor):
self._write_interpolation(node)
def visit_Interpolation(self, node):
- self._write_interpolation(node)
+ self._write_interpolation(node, is_interpolation=True)
def visit_Name(self, node):
self.write(node.id)
diff --git a/Lib/_colorize.py b/Lib/_colorize.py
index 54895488e74..4a310a40235 100644
--- a/Lib/_colorize.py
+++ b/Lib/_colorize.py
@@ -1,28 +1,17 @@
-from __future__ import annotations
import io
import os
import sys
+from collections.abc import Callable, Iterator, Mapping
+from dataclasses import dataclass, field, Field
+
COLORIZE = True
+
# types
if False:
- from typing import IO, Literal
-
- type ColorTag = Literal[
- "PROMPT",
- "KEYWORD",
- "BUILTIN",
- "COMMENT",
- "STRING",
- "NUMBER",
- "OP",
- "DEFINITION",
- "SOFT_KEYWORD",
- "RESET",
- ]
-
- theme: dict[ColorTag, str]
+ from typing import IO, Self, ClassVar
+ _theme: Theme
class ANSIColors:
@@ -86,6 +75,186 @@ for attr, code in ANSIColors.__dict__.items():
setattr(NoColors, attr, "")
+#
+# Experimental theming support (see gh-133346)
+#
+
+# - Create a theme by copying an existing `Theme` with one or more sections
+# replaced, using `default_theme.copy_with()`;
+# - create a theme section by copying an existing `ThemeSection` with one or
+# more colors replaced, using for example `default_theme.syntax.copy_with()`;
+# - create a theme from scratch by instantiating a `Theme` data class with
+# the required sections (which are also dataclass instances).
+#
+# Then call `_colorize.set_theme(your_theme)` to set it.
+#
+# Put your theme configuration in $PYTHONSTARTUP for the interactive shell,
+# or sitecustomize.py in your virtual environment or Python installation for
+# other uses. Your applications can call `_colorize.set_theme()` too.
+#
+# Note that thanks to the dataclasses providing default values for all fields,
+# creating a new theme or theme section from scratch is possible without
+# specifying all keys.
+#
+# For example, here's a theme that makes punctuation and operators less prominent:
+#
+# try:
+# from _colorize import set_theme, default_theme, Syntax, ANSIColors
+# except ImportError:
+# pass
+# else:
+# theme_with_dim_operators = default_theme.copy_with(
+# syntax=Syntax(op=ANSIColors.INTENSE_BLACK),
+# )
+# set_theme(theme_with_dim_operators)
+# del set_theme, default_theme, Syntax, ANSIColors, theme_with_dim_operators
+#
+# Guarding the import ensures that your .pythonstartup file will still work in
+# Python 3.13 and older. Deleting the variables ensures they don't remain in your
+# interactive shell's global scope.
+
+class ThemeSection(Mapping[str, str]):
+ """A mixin/base class for theme sections.
+
+ It enables dictionary access to a section, as well as implements convenience
+ methods.
+ """
+
+ # The two types below are just that: types to inform the type checker that the
+ # mixin will work in context of those fields existing
+ __dataclass_fields__: ClassVar[dict[str, Field[str]]]
+ _name_to_value: Callable[[str], str]
+
+ def __post_init__(self) -> None:
+ name_to_value = {}
+ for color_name in self.__dataclass_fields__:
+ name_to_value[color_name] = getattr(self, color_name)
+ super().__setattr__('_name_to_value', name_to_value.__getitem__)
+
+ def copy_with(self, **kwargs: str) -> Self:
+ color_state: dict[str, str] = {}
+ for color_name in self.__dataclass_fields__:
+ color_state[color_name] = getattr(self, color_name)
+ color_state.update(kwargs)
+ return type(self)(**color_state)
+
+ @classmethod
+ def no_colors(cls) -> Self:
+ color_state: dict[str, str] = {}
+ for color_name in cls.__dataclass_fields__:
+ color_state[color_name] = ""
+ return cls(**color_state)
+
+ def __getitem__(self, key: str) -> str:
+ return self._name_to_value(key)
+
+ def __len__(self) -> int:
+ return len(self.__dataclass_fields__)
+
+ def __iter__(self) -> Iterator[str]:
+ return iter(self.__dataclass_fields__)
+
+
+@dataclass(frozen=True)
+class Argparse(ThemeSection):
+ usage: str = ANSIColors.BOLD_BLUE
+ prog: str = ANSIColors.BOLD_MAGENTA
+ prog_extra: str = ANSIColors.MAGENTA
+ heading: str = ANSIColors.BOLD_BLUE
+ summary_long_option: str = ANSIColors.CYAN
+ summary_short_option: str = ANSIColors.GREEN
+ summary_label: str = ANSIColors.YELLOW
+ summary_action: str = ANSIColors.GREEN
+ long_option: str = ANSIColors.BOLD_CYAN
+ short_option: str = ANSIColors.BOLD_GREEN
+ label: str = ANSIColors.BOLD_YELLOW
+ action: str = ANSIColors.BOLD_GREEN
+ reset: str = ANSIColors.RESET
+
+
+@dataclass(frozen=True)
+class Syntax(ThemeSection):
+ prompt: str = ANSIColors.BOLD_MAGENTA
+ keyword: str = ANSIColors.BOLD_BLUE
+ builtin: str = ANSIColors.CYAN
+ comment: str = ANSIColors.RED
+ string: str = ANSIColors.GREEN
+ number: str = ANSIColors.YELLOW
+ op: str = ANSIColors.RESET
+ definition: str = ANSIColors.BOLD
+ soft_keyword: str = ANSIColors.BOLD_BLUE
+ reset: str = ANSIColors.RESET
+
+
+@dataclass(frozen=True)
+class Traceback(ThemeSection):
+ type: str = ANSIColors.BOLD_MAGENTA
+ message: str = ANSIColors.MAGENTA
+ filename: str = ANSIColors.MAGENTA
+ line_no: str = ANSIColors.MAGENTA
+ frame: str = ANSIColors.MAGENTA
+ error_highlight: str = ANSIColors.BOLD_RED
+ error_range: str = ANSIColors.RED
+ reset: str = ANSIColors.RESET
+
+
+@dataclass(frozen=True)
+class Unittest(ThemeSection):
+ passed: str = ANSIColors.GREEN
+ warn: str = ANSIColors.YELLOW
+ fail: str = ANSIColors.RED
+ fail_info: str = ANSIColors.BOLD_RED
+ reset: str = ANSIColors.RESET
+
+
+@dataclass(frozen=True)
+class Theme:
+ """A suite of themes for all sections of Python.
+
+ When adding a new one, remember to also modify `copy_with` and `no_colors`
+ below.
+ """
+ argparse: Argparse = field(default_factory=Argparse)
+ syntax: Syntax = field(default_factory=Syntax)
+ traceback: Traceback = field(default_factory=Traceback)
+ unittest: Unittest = field(default_factory=Unittest)
+
+ def copy_with(
+ self,
+ *,
+ argparse: Argparse | None = None,
+ syntax: Syntax | None = None,
+ traceback: Traceback | None = None,
+ unittest: Unittest | None = None,
+ ) -> Self:
+ """Return a new Theme based on this instance with some sections replaced.
+
+ Themes are immutable to protect against accidental modifications that
+ could lead to invalid terminal states.
+ """
+ return type(self)(
+ argparse=argparse or self.argparse,
+ syntax=syntax or self.syntax,
+ traceback=traceback or self.traceback,
+ unittest=unittest or self.unittest,
+ )
+
+ @classmethod
+ def no_colors(cls) -> Self:
+ """Return a new Theme where colors in all sections are empty strings.
+
+ This allows writing user code as if colors are always used. The color
+ fields will be ANSI color code strings when colorization is desired
+ and possible, and empty strings otherwise.
+ """
+ return cls(
+ argparse=Argparse.no_colors(),
+ syntax=Syntax.no_colors(),
+ traceback=Traceback.no_colors(),
+ unittest=Unittest.no_colors(),
+ )
+
+
def get_colors(
colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None
) -> ANSIColors:
@@ -138,26 +307,40 @@ def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
return hasattr(file, "isatty") and file.isatty()
-def set_theme(t: dict[ColorTag, str] | None = None) -> None:
- global theme
+default_theme = Theme()
+theme_no_color = default_theme.no_colors()
+
+
+def get_theme(
+ *,
+ tty_file: IO[str] | IO[bytes] | None = None,
+ force_color: bool = False,
+ force_no_color: bool = False,
+) -> Theme:
+ """Returns the currently set theme, potentially in a zero-color variant.
+
+ In cases where colorizing is not possible (see `can_colorize`), the returned
+ theme contains all empty strings in all color definitions.
+ See `Theme.no_colors()` for more information.
+
+ It is recommended not to cache the result of this function for extended
+ periods of time because the user might influence theme selection by
+ the interactive shell, a debugger, or application-specific code. The
+ environment (including environment variable state and console configuration
+ on Windows) can also change in the course of the application life cycle.
+ """
+ if force_color or (not force_no_color and can_colorize(file=tty_file)):
+ return _theme
+ return theme_no_color
+
+
+def set_theme(t: Theme) -> None:
+ global _theme
- if t:
- theme = t
- return
+ if not isinstance(t, Theme):
+ raise ValueError(f"Expected Theme object, found {t}")
- colors = get_colors()
- theme = {
- "PROMPT": colors.BOLD_MAGENTA,
- "KEYWORD": colors.BOLD_BLUE,
- "BUILTIN": colors.CYAN,
- "COMMENT": colors.RED,
- "STRING": colors.GREEN,
- "NUMBER": colors.YELLOW,
- "OP": colors.RESET,
- "DEFINITION": colors.BOLD,
- "SOFT_KEYWORD": colors.BOLD_BLUE,
- "RESET": colors.RESET,
- }
+ _theme = t
-set_theme()
+set_theme(default_theme)
diff --git a/Lib/_compat_pickle.py b/Lib/_compat_pickle.py
index 439f8c02f4b..a9813264324 100644
--- a/Lib/_compat_pickle.py
+++ b/Lib/_compat_pickle.py
@@ -175,7 +175,6 @@ IMPORT_MAPPING.update({
'SimpleDialog': 'tkinter.simpledialog',
'DocXMLRPCServer': 'xmlrpc.server',
'SimpleHTTPServer': 'http.server',
- 'CGIHTTPServer': 'http.server',
# For compatibility with broken pickles saved in old Python 3 versions
'UserDict': 'collections',
'UserList': 'collections',
@@ -217,8 +216,6 @@ REVERSE_NAME_MAPPING.update({
('DocXMLRPCServer', 'DocCGIXMLRPCRequestHandler'),
('http.server', 'SimpleHTTPRequestHandler'):
('SimpleHTTPServer', 'SimpleHTTPRequestHandler'),
- ('http.server', 'CGIHTTPRequestHandler'):
- ('CGIHTTPServer', 'CGIHTTPRequestHandler'),
('_socket', 'socket'): ('socket', '_socketobject'),
})
diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py
index b9304ec3c03..f168d169a32 100644
--- a/Lib/_opcode_metadata.py
+++ b/Lib/_opcode_metadata.py
@@ -6,10 +6,6 @@ _specializations = {
"RESUME": [
"RESUME_CHECK",
],
- "LOAD_CONST": [
- "LOAD_CONST_MORTAL",
- "LOAD_CONST_IMMORTAL",
- ],
"TO_BOOL": [
"TO_BOOL_ALWAYS_TRUE",
"TO_BOOL_BOOL",
@@ -186,28 +182,26 @@ _specialized_opmap = {
'LOAD_ATTR_PROPERTY': 187,
'LOAD_ATTR_SLOT': 188,
'LOAD_ATTR_WITH_HINT': 189,
- 'LOAD_CONST_IMMORTAL': 190,
- 'LOAD_CONST_MORTAL': 191,
- 'LOAD_GLOBAL_BUILTIN': 192,
- 'LOAD_GLOBAL_MODULE': 193,
- 'LOAD_SUPER_ATTR_ATTR': 194,
- 'LOAD_SUPER_ATTR_METHOD': 195,
- 'RESUME_CHECK': 196,
- 'SEND_GEN': 197,
- 'STORE_ATTR_INSTANCE_VALUE': 198,
- 'STORE_ATTR_SLOT': 199,
- 'STORE_ATTR_WITH_HINT': 200,
- 'STORE_SUBSCR_DICT': 201,
- 'STORE_SUBSCR_LIST_INT': 202,
- 'TO_BOOL_ALWAYS_TRUE': 203,
- 'TO_BOOL_BOOL': 204,
- 'TO_BOOL_INT': 205,
- 'TO_BOOL_LIST': 206,
- 'TO_BOOL_NONE': 207,
- 'TO_BOOL_STR': 208,
- 'UNPACK_SEQUENCE_LIST': 209,
- 'UNPACK_SEQUENCE_TUPLE': 210,
- 'UNPACK_SEQUENCE_TWO_TUPLE': 211,
+ 'LOAD_GLOBAL_BUILTIN': 190,
+ 'LOAD_GLOBAL_MODULE': 191,
+ 'LOAD_SUPER_ATTR_ATTR': 192,
+ 'LOAD_SUPER_ATTR_METHOD': 193,
+ 'RESUME_CHECK': 194,
+ 'SEND_GEN': 195,
+ 'STORE_ATTR_INSTANCE_VALUE': 196,
+ 'STORE_ATTR_SLOT': 197,
+ 'STORE_ATTR_WITH_HINT': 198,
+ 'STORE_SUBSCR_DICT': 199,
+ 'STORE_SUBSCR_LIST_INT': 200,
+ 'TO_BOOL_ALWAYS_TRUE': 201,
+ 'TO_BOOL_BOOL': 202,
+ 'TO_BOOL_INT': 203,
+ 'TO_BOOL_LIST': 204,
+ 'TO_BOOL_NONE': 205,
+ 'TO_BOOL_STR': 206,
+ 'UNPACK_SEQUENCE_LIST': 207,
+ 'UNPACK_SEQUENCE_TUPLE': 208,
+ 'UNPACK_SEQUENCE_TWO_TUPLE': 209,
}
opmap = {
diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py
index e3db1b52b11..bc35823f701 100644
--- a/Lib/_pydatetime.py
+++ b/Lib/_pydatetime.py
@@ -467,6 +467,7 @@ def _parse_isoformat_time(tstr):
hour, minute, second, microsecond = time_comps
became_next_day = False
error_from_components = False
+ error_from_tz = None
if (hour == 24):
if all(time_comp == 0 for time_comp in time_comps[1:]):
hour = 0
@@ -500,14 +501,22 @@ def _parse_isoformat_time(tstr):
else:
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
- td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
- seconds=tz_comps[2], microseconds=tz_comps[3])
-
- tzi = timezone(tzsign * td)
+ try:
+ # This function is intended to validate datetimes, but because
+ # we restrict time zones to ±24h, it serves here as well.
+ _check_time_fields(hour=tz_comps[0], minute=tz_comps[1],
+ second=tz_comps[2], microsecond=tz_comps[3],
+ fold=0)
+ except ValueError as e:
+ error_from_tz = e
+ else:
+ td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
+ seconds=tz_comps[2], microseconds=tz_comps[3])
+ tzi = timezone(tzsign * td)
time_comps.append(tzi)
- return time_comps, became_next_day, error_from_components
+ return time_comps, became_next_day, error_from_components, error_from_tz
# tuple[int, int, int] -> tuple[int, int, int] version of date.fromisocalendar
def _isoweek_to_gregorian(year, week, day):
@@ -1127,8 +1136,8 @@ class date:
This is 'YYYY-MM-DD'.
References:
- - http://www.w3.org/TR/NOTE-datetime
- - http://www.cl.cam.ac.uk/~mgk25/iso-time.html
+ - https://www.w3.org/TR/NOTE-datetime
+ - https://www.cl.cam.ac.uk/~mgk25/iso-time.html
"""
return "%04d-%02d-%02d" % (self._year, self._month, self._day)
@@ -1262,7 +1271,7 @@ class date:
The first week is 1; Monday is 1 ... Sunday is 7.
ISO calendar algorithm taken from
- http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
+ https://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
(used with permission)
"""
year = self._year
@@ -1633,9 +1642,21 @@ class time:
time_string = time_string.removeprefix('T')
try:
- return cls(*_parse_isoformat_time(time_string)[0])
- except Exception:
- raise ValueError(f'Invalid isoformat string: {time_string!r}')
+ time_components, _, error_from_components, error_from_tz = (
+ _parse_isoformat_time(time_string)
+ )
+ except ValueError:
+ raise ValueError(
+ f'Invalid isoformat string: {time_string!r}') from None
+ else:
+ if error_from_tz:
+ raise error_from_tz
+ if error_from_components:
+ raise ValueError(
+ "Minute, second, and microsecond must be 0 when hour is 24"
+ )
+
+ return cls(*time_components)
def strftime(self, format):
"""Format using strftime(). The date part of the timestamp passed
@@ -1947,11 +1968,16 @@ class datetime(date):
if tstr:
try:
- time_components, became_next_day, error_from_components = _parse_isoformat_time(tstr)
+ (time_components,
+ became_next_day,
+ error_from_components,
+ error_from_tz) = _parse_isoformat_time(tstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
else:
+ if error_from_tz:
+ raise error_from_tz
if error_from_components:
raise ValueError("minute, second, and microsecond must be 0 when hour is 24")
@@ -2089,7 +2115,6 @@ class datetime(date):
else:
ts = (self - _EPOCH) // timedelta(seconds=1)
localtm = _time.localtime(ts)
- local = datetime(*localtm[:6])
# Extract TZ data
gmtoff = localtm.tm_gmtoff
zone = localtm.tm_zone
@@ -2139,7 +2164,7 @@ class datetime(date):
By default, the fractional part is omitted if self.microsecond == 0.
If self.tzinfo is not None, the UTC offset is also attached, giving
- giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'.
+ a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'.
Optional argument sep specifies the separator between date and
time, default 'T'.
diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py
index 46fa9ffcb1e..781b38ec26b 100644
--- a/Lib/_pydecimal.py
+++ b/Lib/_pydecimal.py
@@ -6120,9 +6120,9 @@ _parse_format_specifier_regex = re.compile(r"""\A
(?P<no_neg_0>z)?
(?P<alt>\#)?
(?P<zeropad>0)?
-(?P<minimumwidth>(?!0)\d+)?
+(?P<minimumwidth>\d+)?
(?P<thousands_sep>[,_])?
-(?:\.(?P<precision>0|(?!0)\d+))?
+(?:\.(?P<precision>\d+))?
(?P<type>[eEfFgGn%])?
\z
""", re.VERBOSE|re.DOTALL)
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index a870de5b532..fb2a6d049ca 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -407,6 +407,9 @@ class IOBase(metaclass=abc.ABCMeta):
if closed:
return
+ if dealloc_warn := getattr(self, "_dealloc_warn", None):
+ dealloc_warn(self)
+
# If close() fails, the caller logs the exception with
# sys.unraisablehook. close() must be called at the end at __del__().
self.close()
@@ -645,8 +648,6 @@ class RawIOBase(IOBase):
self._unsupported("write")
io.RawIOBase.register(RawIOBase)
-from _io import FileIO
-RawIOBase.register(FileIO)
class BufferedIOBase(IOBase):
@@ -853,6 +854,10 @@ class _BufferedIOMixin(BufferedIOBase):
else:
return "<{}.{} name={!r}>".format(modname, clsname, name)
+ def _dealloc_warn(self, source):
+ if dealloc_warn := getattr(self.raw, "_dealloc_warn", None):
+ dealloc_warn(source)
+
### Lower-level APIs ###
def fileno(self):
@@ -1563,7 +1568,8 @@ class FileIO(RawIOBase):
if not isinstance(fd, int):
raise TypeError('expected integer from opener')
if fd < 0:
- raise OSError('Negative file descriptor')
+ # bpo-27066: Raise a ValueError for bad value.
+ raise ValueError(f'opener returned {fd}')
owned_fd = fd
if not noinherit_flag:
os.set_inheritable(fd, False)
@@ -1600,12 +1606,11 @@ class FileIO(RawIOBase):
raise
self._fd = fd
- def __del__(self):
+ def _dealloc_warn(self, source):
if self._fd >= 0 and self._closefd and not self.closed:
import warnings
- warnings.warn('unclosed file %r' % (self,), ResourceWarning,
+ warnings.warn(f'unclosed file {source!r}', ResourceWarning,
stacklevel=2, source=self)
- self.close()
def __getstate__(self):
raise TypeError(f"cannot pickle {self.__class__.__name__!r} object")
@@ -1780,7 +1785,7 @@ class FileIO(RawIOBase):
if not self.closed:
self._stat_atopen = None
try:
- if self._closefd:
+ if self._closefd and self._fd >= 0:
os.close(self._fd)
finally:
super().close()
@@ -2689,6 +2694,10 @@ class TextIOWrapper(TextIOBase):
def newlines(self):
return self._decoder.newlines if self._decoder else None
+ def _dealloc_warn(self, source):
+ if dealloc_warn := getattr(self.buffer, "_dealloc_warn", None):
+ dealloc_warn(source)
+
class StringIO(TextIOWrapper):
"""Text I/O implementation using an in-memory buffer.
diff --git a/Lib/_pyrepl/_module_completer.py b/Lib/_pyrepl/_module_completer.py
index 347f05607c7..1e9462a4215 100644
--- a/Lib/_pyrepl/_module_completer.py
+++ b/Lib/_pyrepl/_module_completer.py
@@ -17,8 +17,8 @@ if TYPE_CHECKING:
def make_default_module_completer() -> ModuleCompleter:
- # Inside pyrepl, __package__ is set to '_pyrepl'
- return ModuleCompleter(namespace={'__package__': '_pyrepl'})
+ # Inside pyrepl, __package__ is set to None by default
+ return ModuleCompleter(namespace={'__package__': None})
class ModuleCompleter:
@@ -42,11 +42,11 @@ class ModuleCompleter:
self._global_cache: list[pkgutil.ModuleInfo] = []
self._curr_sys_path: list[str] = sys.path[:]
- def get_completions(self, line: str) -> list[str]:
+ def get_completions(self, line: str) -> list[str] | None:
"""Return the next possible import completions for 'line'."""
result = ImportParser(line).parse()
if not result:
- return []
+ return None
try:
return self.complete(*result)
except Exception:
@@ -81,8 +81,11 @@ class ModuleCompleter:
def _find_modules(self, path: str, prefix: str) -> list[str]:
if not path:
# Top-level import (e.g. `import foo<tab>`` or `from foo<tab>`)`
- return [name for _, name, _ in self.global_cache
- if name.startswith(prefix)]
+ builtin_modules = [name for name in sys.builtin_module_names
+ if self.is_suggestion_match(name, prefix)]
+ third_party_modules = [module.name for module in self.global_cache
+ if self.is_suggestion_match(module.name, prefix)]
+ return sorted(builtin_modules + third_party_modules)
if path.startswith('.'):
# Convert relative path to absolute path
@@ -97,7 +100,14 @@ class ModuleCompleter:
if mod_info.ispkg and mod_info.name == segment]
modules = self.iter_submodules(modules)
return [module.name for module in modules
- if module.name.startswith(prefix)]
+ if self.is_suggestion_match(module.name, prefix)]
+
+ def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
+ if prefix:
+ return module_name.startswith(prefix)
+ # For consistency with attribute completion, which
+ # does not suggest private attributes unless requested.
+ return not module_name.startswith("_")
def iter_submodules(self, parent_modules: list[pkgutil.ModuleInfo]) -> Iterator[pkgutil.ModuleInfo]:
"""Iterate over all submodules of the given parent modules."""
diff --git a/Lib/_pyrepl/base_eventqueue.py b/Lib/_pyrepl/base_eventqueue.py
index e018c4fc183..0589a0f437e 100644
--- a/Lib/_pyrepl/base_eventqueue.py
+++ b/Lib/_pyrepl/base_eventqueue.py
@@ -69,18 +69,14 @@ class BaseEventQueue:
trace('added event {event}', event=event)
self.events.append(event)
- def push(self, char: int | bytes | str) -> None:
+ def push(self, char: int | bytes) -> None:
"""
Processes a character by updating the buffer and handling special key mappings.
"""
+ assert isinstance(char, (int, bytes))
ord_char = char if isinstance(char, int) else ord(char)
- if ord_char > 255:
- assert isinstance(char, str)
- char = bytes(char.encode(self.encoding, "replace"))
- self.buf.extend(char)
- else:
- char = bytes(bytearray((ord_char,)))
- self.buf.append(ord_char)
+ char = ord_char.to_bytes()
+ self.buf.append(ord_char)
if char in self.keymap:
if self.keymap is self.compiled_keymap:
@@ -91,7 +87,7 @@ class BaseEventQueue:
if isinstance(k, dict):
self.keymap = k
else:
- self.insert(Event('key', k, self.flush_buf()))
+ self.insert(Event('key', k, bytes(self.flush_buf())))
self.keymap = self.compiled_keymap
elif self.buf and self.buf[0] == 27: # escape
@@ -100,7 +96,7 @@ class BaseEventQueue:
# the docstring in keymap.py
trace('unrecognized escape sequence, propagating...')
self.keymap = self.compiled_keymap
- self.insert(Event('key', '\033', bytearray(b'\033')))
+ self.insert(Event('key', '\033', b'\033'))
for _c in self.flush_buf()[1:]:
self.push(_c)
@@ -110,5 +106,5 @@ class BaseEventQueue:
except UnicodeError:
return
else:
- self.insert(Event('key', decoded, self.flush_buf()))
+ self.insert(Event('key', decoded, bytes(self.flush_buf())))
self.keymap = self.compiled_keymap
diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py
index 2054a8e400f..50c824995d8 100644
--- a/Lib/_pyrepl/commands.py
+++ b/Lib/_pyrepl/commands.py
@@ -370,6 +370,13 @@ class self_insert(EditCommand):
r = self.reader
text = self.event * r.get_arg()
r.insert(text)
+ if r.paste_mode:
+ data = ""
+ ev = r.console.getpending()
+ data += ev.data
+ if data:
+ r.insert(data)
+ r.last_refresh_cache.invalidated = True
class insert_nl(EditCommand):
@@ -439,7 +446,7 @@ class help(Command):
import _sitebuiltins
with self.reader.suspend():
- self.reader.msg = _sitebuiltins._Helper()() # type: ignore[assignment, call-arg]
+ self.reader.msg = _sitebuiltins._Helper()() # type: ignore[assignment]
class invalid_key(Command):
@@ -484,7 +491,6 @@ class perform_bracketed_paste(Command):
data = ""
start = time.time()
while done not in data:
- self.reader.console.wait(100)
ev = self.reader.console.getpending()
data += ev.data
trace(
diff --git a/Lib/_pyrepl/main.py b/Lib/_pyrepl/main.py
index a6f824dcc4a..447eb1e551e 100644
--- a/Lib/_pyrepl/main.py
+++ b/Lib/_pyrepl/main.py
@@ -1,6 +1,7 @@
import errno
import os
import sys
+import types
CAN_USE_PYREPL: bool
@@ -29,12 +30,10 @@ def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
print(FAIL_REASON, file=sys.stderr)
return sys._baserepl()
- if mainmodule:
- namespace = mainmodule.__dict__
- else:
- import __main__
- namespace = __main__.__dict__
- namespace.pop("__pyrepl_interactive_console", None)
+ if not mainmodule:
+ mainmodule = types.ModuleType("__main__")
+
+ namespace = mainmodule.__dict__
# sys._baserepl() above does this internally, we do it here
startup_path = os.getenv("PYTHONSTARTUP")
diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py
index 65c2230dfd6..0ebd9162eca 100644
--- a/Lib/_pyrepl/reader.py
+++ b/Lib/_pyrepl/reader.py
@@ -28,7 +28,7 @@ from contextlib import contextmanager
from dataclasses import dataclass, field, fields
from . import commands, console, input
-from .utils import wlen, unbracket, disp_str, gen_colors
+from .utils import wlen, unbracket, disp_str, gen_colors, THEME
from .trace import trace
@@ -491,11 +491,8 @@ class Reader:
prompt = self.ps1
if self.can_colorize:
- prompt = (
- f"{_colorize.theme["PROMPT"]}"
- f"{prompt}"
- f"{_colorize.theme["RESET"]}"
- )
+ t = THEME()
+ prompt = f"{t.prompt}{prompt}{t.reset}"
return prompt
def push_input_trans(self, itrans: input.KeymapTranslator) -> None:
diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py
index 560a9db1921..9560ae779ab 100644
--- a/Lib/_pyrepl/readline.py
+++ b/Lib/_pyrepl/readline.py
@@ -134,7 +134,8 @@ class ReadlineAlikeReader(historical_reader.HistoricalReader, CompletingReader):
return "".join(b[p + 1 : self.pos])
def get_completions(self, stem: str) -> list[str]:
- if module_completions := self.get_module_completions():
+ module_completions = self.get_module_completions()
+ if module_completions is not None:
return module_completions
if len(stem) == 0 and self.more_lines is not None:
b = self.buffer
@@ -165,7 +166,7 @@ class ReadlineAlikeReader(historical_reader.HistoricalReader, CompletingReader):
result.sort()
return result
- def get_module_completions(self) -> list[str]:
+ def get_module_completions(self) -> list[str] | None:
line = self.get_line()
return self.config.module_completer.get_completions(line)
@@ -606,6 +607,7 @@ def _setup(namespace: Mapping[str, Any]) -> None:
# set up namespace in rlcompleter, which requires it to be a bona fide dict
if not isinstance(namespace, dict):
namespace = dict(namespace)
+ _wrapper.config.module_completer = ModuleCompleter(namespace)
_wrapper.config.readline_completer = RLCompleter(namespace).complete
# this is not really what readline.c does. Better than nothing I guess
diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py
index e2274629b65..965b853c34b 100644
--- a/Lib/_pyrepl/simple_interact.py
+++ b/Lib/_pyrepl/simple_interact.py
@@ -31,6 +31,7 @@ import os
import sys
import code
import warnings
+import errno
from .readline import _get_reader, multiline_input, append_history_file
@@ -110,6 +111,10 @@ def run_multiline_interactive_console(
more_lines = functools.partial(_more_lines, console)
input_n = 0
+ _is_x_showrefcount_set = sys._xoptions.get("showrefcount")
+ _is_pydebug_build = hasattr(sys, "gettotalrefcount")
+ show_ref_count = _is_x_showrefcount_set and _is_pydebug_build
+
def maybe_run_command(statement: str) -> bool:
statement = statement.strip()
if statement in console.locals or statement not in REPL_COMMANDS:
@@ -149,6 +154,7 @@ def run_multiline_interactive_console(
append_history_file()
except (FileNotFoundError, PermissionError, OSError) as e:
warnings.warn(f"failed to open the history file for writing: {e}")
+
input_n += 1
except KeyboardInterrupt:
r = _get_reader()
@@ -162,3 +168,13 @@ def run_multiline_interactive_console(
except MemoryError:
console.write("\nMemoryError\n")
console.resetbuffer()
+ except SystemExit:
+ raise
+ except:
+ console.showtraceback()
+ console.resetbuffer()
+ if show_ref_count:
+ console.write(
+ f"[{sys.gettotalrefcount()} refs,"
+ f" {sys.getallocatedblocks()} blocks]\n"
+ )
diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py
index 07b160d2324..d21cdd9b076 100644
--- a/Lib/_pyrepl/unix_console.py
+++ b/Lib/_pyrepl/unix_console.py
@@ -29,6 +29,7 @@ import signal
import struct
import termios
import time
+import types
import platform
from fcntl import ioctl
@@ -39,6 +40,12 @@ from .trace import trace
from .unix_eventqueue import EventQueue
from .utils import wlen
+# declare posix optional to allow None assignment on other platforms
+posix: types.ModuleType | None
+try:
+ import posix
+except ImportError:
+ posix = None
TYPE_CHECKING = False
@@ -197,6 +204,12 @@ class UnixConsole(Console):
self.event_queue = EventQueue(self.input_fd, self.encoding)
self.cursor_visible = 1
+ signal.signal(signal.SIGCONT, self._sigcont_handler)
+
+ def _sigcont_handler(self, signum, frame):
+ self.restore()
+ self.prepare()
+
def __read(self, n: int) -> bytes:
return os.read(self.input_fd, n)
@@ -550,11 +563,9 @@ class UnixConsole(Console):
@property
def input_hook(self):
- try:
- import posix
- except ImportError:
- return None
- if posix._is_inputhook_installed():
+ # avoid inline imports here so the repl doesn't get flooded
+ # with import logging from -X importtime=2
+ if posix is not None and posix._is_inputhook_installed():
return posix._inputhook
def __enable_bracketed_paste(self) -> None:
diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py
index fe154aa59a0..e04fbdc6c8a 100644
--- a/Lib/_pyrepl/utils.py
+++ b/Lib/_pyrepl/utils.py
@@ -23,6 +23,11 @@ IDENTIFIERS_AFTER = {"def", "class"}
BUILTINS = {str(name) for name in dir(builtins) if not name.startswith('_')}
+def THEME(**kwargs):
+ # Not cached: the user can modify the theme inside the interactive session.
+ return _colorize.get_theme(**kwargs).syntax
+
+
class Span(NamedTuple):
"""Span indexing that's inclusive on both ends."""
@@ -36,15 +41,21 @@ class Span(NamedTuple):
@classmethod
def from_token(cls, token: TI, line_len: list[int]) -> Self:
+ end_offset = -1
+ if (token.type in {T.FSTRING_MIDDLE, T.TSTRING_MIDDLE}
+ and token.string.endswith(("{", "}"))):
+ # gh-134158: a visible trailing brace comes from a double brace in input
+ end_offset += 1
+
return cls(
line_len[token.start[0] - 1] + token.start[1],
- line_len[token.end[0] - 1] + token.end[1] - 1,
+ line_len[token.end[0] - 1] + token.end[1] + end_offset,
)
class ColorSpan(NamedTuple):
span: Span
- tag: _colorize.ColorTag
+ tag: str
@functools.cache
@@ -97,6 +108,8 @@ def gen_colors(buffer: str) -> Iterator[ColorSpan]:
for color in gen_colors_from_token_stream(gen, line_lengths):
yield color
last_emitted = color
+ except SyntaxError:
+ return
except tokenize.TokenError as te:
yield from recover_unterminated_string(
te, line_lengths, last_emitted, buffer
@@ -135,7 +148,7 @@ def recover_unterminated_string(
span = Span(start, end)
trace("yielding span {a} -> {b}", a=span.start, b=span.end)
- yield ColorSpan(span, "STRING")
+ yield ColorSpan(span, "string")
else:
trace(
"unhandled token error({buffer}) = {te}",
@@ -164,28 +177,28 @@ def gen_colors_from_token_stream(
| T.TSTRING_START | T.TSTRING_MIDDLE | T.TSTRING_END
):
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "STRING")
+ yield ColorSpan(span, "string")
case T.COMMENT:
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "COMMENT")
+ yield ColorSpan(span, "comment")
case T.NUMBER:
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "NUMBER")
+ yield ColorSpan(span, "number")
case T.OP:
if token.string in "([{":
bracket_level += 1
elif token.string in ")]}":
bracket_level -= 1
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "OP")
+ yield ColorSpan(span, "op")
case T.NAME:
if is_def_name:
is_def_name = False
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "DEFINITION")
+ yield ColorSpan(span, "definition")
elif keyword.iskeyword(token.string):
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "KEYWORD")
+ yield ColorSpan(span, "keyword")
if token.string in IDENTIFIERS_AFTER:
is_def_name = True
elif (
@@ -194,10 +207,10 @@ def gen_colors_from_token_stream(
and is_soft_keyword_used(prev_token, token, next_token)
):
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "SOFT_KEYWORD")
+ yield ColorSpan(span, "soft_keyword")
elif token.string in BUILTINS:
span = Span.from_token(token, line_lengths)
- yield ColorSpan(span, "BUILTIN")
+ yield ColorSpan(span, "builtin")
keyword_first_sets_match = {"False", "None", "True", "await", "lambda", "not"}
@@ -249,7 +262,10 @@ def is_soft_keyword_used(*tokens: TI | None) -> bool:
def disp_str(
- buffer: str, colors: list[ColorSpan] | None = None, start_index: int = 0
+ buffer: str,
+ colors: list[ColorSpan] | None = None,
+ start_index: int = 0,
+ force_color: bool = False,
) -> tuple[CharBuffer, CharWidths]:
r"""Decompose the input buffer into a printable variant with applied colors.
@@ -290,15 +306,16 @@ def disp_str(
# move past irrelevant spans
colors.pop(0)
+ theme = THEME(force_color=force_color)
pre_color = ""
post_color = ""
if colors and colors[0].span.start < start_index:
# looks like we're continuing a previous color (e.g. a multiline str)
- pre_color = _colorize.theme[colors[0].tag]
+ pre_color = theme[colors[0].tag]
for i, c in enumerate(buffer, start_index):
if colors and colors[0].span.start == i: # new color starts now
- pre_color = _colorize.theme[colors[0].tag]
+ pre_color = theme[colors[0].tag]
if c == "\x1a": # CTRL-Z on Windows
chars.append(c)
@@ -315,7 +332,7 @@ def disp_str(
char_widths.append(str_width(c))
if colors and colors[0].span.end == i: # current color ends now
- post_color = _colorize.theme["RESET"]
+ post_color = theme.reset
colors.pop(0)
chars[-1] = pre_color + chars[-1] + post_color
@@ -325,7 +342,7 @@ def disp_str(
if colors and colors[0].span.start < i and colors[0].span.end > i:
# even though the current color should be continued, reset it for now.
# the next call to `disp_str()` will revive it.
- chars[-1] += _colorize.theme["RESET"]
+ chars[-1] += theme.reset
return chars, char_widths
diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py
index 77985e59a93..c56dcd6d7dd 100644
--- a/Lib/_pyrepl/windows_console.py
+++ b/Lib/_pyrepl/windows_console.py
@@ -24,6 +24,7 @@ import os
import sys
import ctypes
+import types
from ctypes.wintypes import (
_COORD,
WORD,
@@ -58,6 +59,12 @@ except:
self.err = err
self.descr = descr
+# declare nt optional to allow None assignment on other platforms
+nt: types.ModuleType | None
+try:
+ import nt
+except ImportError:
+ nt = None
TYPE_CHECKING = False
@@ -121,9 +128,8 @@ class _error(Exception):
def _supports_vt():
try:
- import nt
return nt._supports_virtual_terminal()
- except (ImportError, AttributeError):
+ except AttributeError:
return False
class WindowsConsole(Console):
@@ -235,11 +241,9 @@ class WindowsConsole(Console):
@property
def input_hook(self):
- try:
- import nt
- except ImportError:
- return None
- if nt._is_inputhook_installed():
+ # avoid inline imports here so the repl doesn't get flooded
+ # with import logging from -X importtime=2
+ if nt is not None and nt._is_inputhook_installed():
return nt._inputhook
def __write_changed_line(
@@ -415,10 +419,7 @@ class WindowsConsole(Console):
return info.srWindow.Bottom # type: ignore[no-any-return]
- def _read_input(self, block: bool = True) -> INPUT_RECORD | None:
- if not block and not self.wait(timeout=0):
- return None
-
+ def _read_input(self) -> INPUT_RECORD | None:
rec = INPUT_RECORD()
read = DWORD()
if not ReadConsoleInput(InHandle, rec, 1, read):
@@ -427,14 +428,10 @@ class WindowsConsole(Console):
return rec
def _read_input_bulk(
- self, block: bool, n: int
+ self, n: int
) -> tuple[ctypes.Array[INPUT_RECORD], int]:
rec = (n * INPUT_RECORD)()
read = DWORD()
-
- if not block and not self.wait(timeout=0):
- return rec, 0
-
if not ReadConsoleInput(InHandle, rec, n, read):
raise WinError(GetLastError())
@@ -445,8 +442,11 @@ class WindowsConsole(Console):
and there is no event pending, otherwise waits for the
completion of an event."""
+ if not block and not self.wait(timeout=0):
+ return None
+
while self.event_queue.empty():
- rec = self._read_input(block)
+ rec = self._read_input()
if rec is None:
return None
@@ -464,7 +464,7 @@ class WindowsConsole(Console):
if key == "\r":
# Make enter unix-like
- return Event(evt="key", data="\n", raw=b"\n")
+ return Event(evt="key", data="\n")
elif key_event.wVirtualKeyCode == 8:
# Turn backspace directly into the command
key = "backspace"
@@ -476,24 +476,29 @@ class WindowsConsole(Console):
key = f"ctrl {key}"
elif key_event.dwControlKeyState & ALT_ACTIVE:
# queue the key, return the meta command
- self.event_queue.insert(Event(evt="key", data=key, raw=key))
+ self.event_queue.insert(Event(evt="key", data=key))
return Event(evt="key", data="\033") # keymap.py uses this for meta
- return Event(evt="key", data=key, raw=key)
+ return Event(evt="key", data=key)
if block:
continue
return None
elif self.__vt_support:
# If virtual terminal is enabled, scanning VT sequences
- self.event_queue.push(rec.Event.KeyEvent.uChar.UnicodeChar)
+ for char in raw_key.encode(self.event_queue.encoding, "replace"):
+ self.event_queue.push(char)
continue
if key_event.dwControlKeyState & ALT_ACTIVE:
- # queue the key, return the meta command
- self.event_queue.insert(Event(evt="key", data=key, raw=raw_key))
- return Event(evt="key", data="\033") # keymap.py uses this for meta
-
- return Event(evt="key", data=key, raw=raw_key)
+ # Do not swallow characters that have been entered via AltGr:
+ # Windows internally converts AltGr to CTRL+ALT, see
+ # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscanw
+ if not key_event.dwControlKeyState & CTRL_ACTIVE:
+ # queue the key, return the meta command
+ self.event_queue.insert(Event(evt="key", data=key))
+ return Event(evt="key", data="\033") # keymap.py uses this for meta
+
+ return Event(evt="key", data=key)
return self.event_queue.get()
def push_char(self, char: int | bytes) -> None:
@@ -542,12 +547,20 @@ class WindowsConsole(Console):
if e2:
e.data += e2.data
- recs, rec_count = self._read_input_bulk(False, 1024)
+ recs, rec_count = self._read_input_bulk(1024)
for i in range(rec_count):
rec = recs[i]
+ # In case of a legacy console, we do not only receive a keydown
+ # event, but also a keyup event - and for uppercase letters
+ # an additional SHIFT_PRESSED event.
if rec and rec.EventType == KEY_EVENT:
key_event = rec.Event.KeyEvent
+ if not key_event.bKeyDown:
+ continue
ch = key_event.uChar.UnicodeChar
+ if ch == "\x00":
+ # ignore SHIFT_PRESSED and special keys
+ continue
if ch == "\r":
ch += "\n"
e.data += ch
diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index aa63933a49d..cdc55e8daaf 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -14,6 +14,7 @@ import os
import time
import locale
import calendar
+import re
from re import compile as re_compile
from re import sub as re_sub
from re import IGNORECASE
@@ -41,6 +42,29 @@ def _findall(haystack, needle):
yield i
i += len(needle)
+def _fixmonths(months):
+ yield from months
+ # The lower case of 'İ' ('\u0130') is 'i\u0307'.
+ # The re module only supports 1-to-1 character matching in
+ # case-insensitive mode.
+ for s in months:
+ if 'i\u0307' in s:
+ yield s.replace('i\u0307', '\u0130')
+
+lzh_TW_alt_digits = (
+ # 〇:一:二:三:四:五:六:七:八:九
+ '\u3007', '\u4e00', '\u4e8c', '\u4e09', '\u56db',
+ '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d',
+ # 十:十一:十二:十三:十四:十五:十六:十七:十八:十九
+ '\u5341', '\u5341\u4e00', '\u5341\u4e8c', '\u5341\u4e09', '\u5341\u56db',
+ '\u5341\u4e94', '\u5341\u516d', '\u5341\u4e03', '\u5341\u516b', '\u5341\u4e5d',
+ # 廿:廿一:廿二:廿三:廿四:廿五:廿六:廿七:廿八:廿九
+ '\u5eff', '\u5eff\u4e00', '\u5eff\u4e8c', '\u5eff\u4e09', '\u5eff\u56db',
+ '\u5eff\u4e94', '\u5eff\u516d', '\u5eff\u4e03', '\u5eff\u516b', '\u5eff\u4e5d',
+ # 卅:卅一
+ '\u5345', '\u5345\u4e00')
+
+
class LocaleTime(object):
"""Stores and handles locale-specific information related to time.
@@ -84,6 +108,7 @@ class LocaleTime(object):
self.__calc_weekday()
self.__calc_month()
self.__calc_am_pm()
+ self.__calc_alt_digits()
self.__calc_timezone()
self.__calc_date_time()
if _getlang() != self.lang:
@@ -119,9 +144,43 @@ class LocaleTime(object):
am_pm.append(time.strftime("%p", time_tuple).lower().strip())
self.am_pm = am_pm
+ def __calc_alt_digits(self):
+ # Set self.LC_alt_digits by using time.strftime().
+
+ # The magic data should contain all decimal digits.
+ time_tuple = time.struct_time((1998, 1, 27, 10, 43, 56, 1, 27, 0))
+ s = time.strftime("%x%X", time_tuple)
+ if s.isascii():
+ # Fast path -- all digits are ASCII.
+ self.LC_alt_digits = ()
+ return
+
+ digits = ''.join(sorted(set(re.findall(r'\d', s))))
+ if len(digits) == 10 and ord(digits[-1]) == ord(digits[0]) + 9:
+ # All 10 decimal digits from the same set.
+ if digits.isascii():
+ # All digits are ASCII.
+ self.LC_alt_digits = ()
+ return
+
+ self.LC_alt_digits = [a + b for a in digits for b in digits]
+ # Test whether the numbers contain leading zero.
+ time_tuple2 = time.struct_time((2000, 1, 1, 1, 1, 1, 5, 1, 0))
+ if self.LC_alt_digits[1] not in time.strftime("%x %X", time_tuple2):
+ self.LC_alt_digits[:10] = digits
+ return
+
+ # Either non-Gregorian calendar or non-decimal numbers.
+ if {'\u4e00', '\u4e03', '\u4e5d', '\u5341', '\u5eff'}.issubset(s):
+ # lzh_TW
+ self.LC_alt_digits = lzh_TW_alt_digits
+ return
+
+ self.LC_alt_digits = None
+
def __calc_date_time(self):
- # Set self.date_time, self.date, & self.time by using
- # time.strftime().
+ # Set self.LC_date_time, self.LC_date, self.LC_time and
+ # self.LC_time_ampm by using time.strftime().
# Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
# overloaded numbers is minimized. The order in which searches for
@@ -129,26 +188,32 @@ class LocaleTime(object):
# possible ambiguity for what something represents.
time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0))
- replacement_pairs = [
+ replacement_pairs = []
+
+ # Non-ASCII digits
+ if self.LC_alt_digits or self.LC_alt_digits is None:
+ for n, d in [(19, '%OC'), (99, '%Oy'), (22, '%OH'),
+ (44, '%OM'), (55, '%OS'), (17, '%Od'),
+ (3, '%Om'), (2, '%Ow'), (10, '%OI')]:
+ if self.LC_alt_digits is None:
+ s = chr(0x660 + n // 10) + chr(0x660 + n % 10)
+ replacement_pairs.append((s, d))
+ if n < 10:
+ replacement_pairs.append((s[1], d))
+ elif len(self.LC_alt_digits) > n:
+ replacement_pairs.append((self.LC_alt_digits[n], d))
+ else:
+ replacement_pairs.append((time.strftime(d, time_tuple), d))
+ replacement_pairs += [
('1999', '%Y'), ('99', '%y'), ('22', '%H'),
('44', '%M'), ('55', '%S'), ('76', '%j'),
('17', '%d'), ('03', '%m'), ('3', '%m'),
# '3' needed for when no leading zero.
('2', '%w'), ('10', '%I'),
- # Non-ASCII digits
- ('\u0661\u0669\u0669\u0669', '%Y'),
- ('\u0669\u0669', '%Oy'),
- ('\u0662\u0662', '%OH'),
- ('\u0664\u0664', '%OM'),
- ('\u0665\u0665', '%OS'),
- ('\u0661\u0667', '%Od'),
- ('\u0660\u0663', '%Om'),
- ('\u0663', '%Om'),
- ('\u0662', '%Ow'),
- ('\u0661\u0660', '%OI'),
]
+
date_time = []
- for directive in ('%c', '%x', '%X'):
+ for directive in ('%c', '%x', '%X', '%r'):
current_format = time.strftime(directive, time_tuple).lower()
current_format = current_format.replace('%', '%%')
# The month and the day of the week formats are treated specially
@@ -172,9 +237,10 @@ class LocaleTime(object):
if tz:
current_format = current_format.replace(tz, "%Z")
# Transform all non-ASCII digits to digits in range U+0660 to U+0669.
- current_format = re_sub(r'\d(?<![0-9])',
- lambda m: chr(0x0660 + int(m[0])),
- current_format)
+ if not current_format.isascii() and self.LC_alt_digits is None:
+ current_format = re_sub(r'\d(?<![0-9])',
+ lambda m: chr(0x0660 + int(m[0])),
+ current_format)
for old, new in replacement_pairs:
current_format = current_format.replace(old, new)
# If %W is used, then Sunday, 2005-01-03 will fall on week 0 since
@@ -189,6 +255,7 @@ class LocaleTime(object):
self.LC_date_time = date_time[0]
self.LC_date = date_time[1]
self.LC_time = date_time[2]
+ self.LC_time_ampm = date_time[3]
def __find_month_format(self, directive):
"""Find the month format appropriate for the current locale.
@@ -213,7 +280,7 @@ class LocaleTime(object):
full_indices &= indices
indices = set(_findall(datetime, self.a_month[m]))
if abbr_indices is None:
- abbr_indices = indices
+ abbr_indices = set(indices)
else:
abbr_indices &= indices
if not full_indices and not abbr_indices:
@@ -241,7 +308,7 @@ class LocaleTime(object):
if self.f_weekday[wd] != self.a_weekday[wd]:
indices = set(_findall(datetime, self.a_weekday[wd]))
if abbr_indices is None:
- abbr_indices = indices
+ abbr_indices = set(indices)
else:
abbr_indices &= indices
if not full_indices and not abbr_indices:
@@ -288,8 +355,10 @@ class TimeRE(dict):
# The " [1-9]" part of the regex is to make %c from ANSI C work
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
'f': r"(?P<f>[0-9]{1,6})",
- 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
+ 'H': r"(?P<H>2[0-3]|[0-1]\d|\d| \d)",
+ 'k': r"(?P<H>2[0-3]|[0-1]\d|\d| \d)",
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9])",
+ 'l': r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9])",
'G': r"(?P<G>\d\d\d\d)",
'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
@@ -302,26 +371,59 @@ class TimeRE(dict):
# W is set below by using 'U'
'y': r"(?P<y>\d\d)",
'Y': r"(?P<Y>\d\d\d\d)",
- 'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
+ 'z': r"(?P<z>([+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?)|(?-i:Z))?",
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
- 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
- 'b': self.__seqToRE(self.locale_time.a_month[1:], 'b'),
+ 'B': self.__seqToRE(_fixmonths(self.locale_time.f_month[1:]), 'B'),
+ 'b': self.__seqToRE(_fixmonths(self.locale_time.a_month[1:]), 'b'),
'p': self.__seqToRE(self.locale_time.am_pm, 'p'),
'Z': self.__seqToRE((tz for tz_names in self.locale_time.timezone
for tz in tz_names),
'Z'),
'%': '%'}
- for d in 'dmyHIMS':
- mapping['O' + d] = r'(?P<%s>\d\d|\d| \d)' % d
- mapping['Ow'] = r'(?P<w>\d)'
+ if self.locale_time.LC_alt_digits is None:
+ for d in 'dmyCHIMS':
+ mapping['O' + d] = r'(?P<%s>\d\d|\d| \d)' % d
+ mapping['Ow'] = r'(?P<w>\d)'
+ else:
+ mapping.update({
+ 'Od': self.__seqToRE(self.locale_time.LC_alt_digits[1:32], 'd',
+ '3[0-1]|[1-2][0-9]|0[1-9]|[1-9]'),
+ 'Om': self.__seqToRE(self.locale_time.LC_alt_digits[1:13], 'm',
+ '1[0-2]|0[1-9]|[1-9]'),
+ 'Ow': self.__seqToRE(self.locale_time.LC_alt_digits[:7], 'w',
+ '[0-6]'),
+ 'Oy': self.__seqToRE(self.locale_time.LC_alt_digits, 'y',
+ '[0-9][0-9]'),
+ 'OC': self.__seqToRE(self.locale_time.LC_alt_digits, 'C',
+ '[0-9][0-9]'),
+ 'OH': self.__seqToRE(self.locale_time.LC_alt_digits[:24], 'H',
+ '2[0-3]|[0-1][0-9]|[0-9]'),
+ 'OI': self.__seqToRE(self.locale_time.LC_alt_digits[1:13], 'I',
+ '1[0-2]|0[1-9]|[1-9]'),
+ 'OM': self.__seqToRE(self.locale_time.LC_alt_digits[:60], 'M',
+ '[0-5][0-9]|[0-9]'),
+ 'OS': self.__seqToRE(self.locale_time.LC_alt_digits[:62], 'S',
+ '6[0-1]|[0-5][0-9]|[0-9]'),
+ })
+ mapping.update({
+ 'e': mapping['d'],
+ 'Oe': mapping['Od'],
+ 'P': mapping['p'],
+ 'Op': mapping['p'],
+ 'W': mapping['U'].replace('U', 'W'),
+ })
mapping['W'] = mapping['U'].replace('U', 'W')
+
base.__init__(mapping)
+ base.__setitem__('T', self.pattern('%H:%M:%S'))
+ base.__setitem__('R', self.pattern('%H:%M'))
+ base.__setitem__('r', self.pattern(self.locale_time.LC_time_ampm))
base.__setitem__('X', self.pattern(self.locale_time.LC_time))
base.__setitem__('x', self.pattern(self.locale_time.LC_date))
base.__setitem__('c', self.pattern(self.locale_time.LC_date_time))
- def __seqToRE(self, to_convert, directive):
+ def __seqToRE(self, to_convert, directive, altregex=None):
"""Convert a list to a regex string for matching a directive.
Want possible matching values to be from longest to shortest. This
@@ -337,8 +439,9 @@ class TimeRE(dict):
else:
return ''
regex = '|'.join(re_escape(stuff) for stuff in to_convert)
- regex = '(?P<%s>%s' % (directive, regex)
- return '%s)' % regex
+ if altregex is not None:
+ regex += '|' + altregex
+ return '(?P<%s>%s)' % (directive, regex)
def pattern(self, format):
"""Return regex pattern for the format string.
@@ -365,7 +468,7 @@ class TimeRE(dict):
nonlocal day_of_month_in_format
day_of_month_in_format = True
return self[format_char]
- format = re_sub(r'%([OE]?\\?.?)', repl, format)
+ format = re_sub(r'%[-_0^#]*[0-9]*([OE]?\\?.?)', repl, format)
if day_of_month_in_format and not year_in_format:
import warnings
warnings.warn("""\
@@ -467,6 +570,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
# values
weekday = julian = None
found_dict = found.groupdict()
+ if locale_time.LC_alt_digits:
+ def parse_int(s):
+ try:
+ return locale_time.LC_alt_digits.index(s)
+ except ValueError:
+ return int(s)
+ else:
+ parse_int = int
+
for group_key in found_dict.keys():
# Directives not explicitly handled below:
# c, x, X
@@ -474,30 +586,34 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
# U, W
# worthless without day of the week
if group_key == 'y':
- year = int(found_dict['y'])
- # Open Group specification for strptime() states that a %y
- #value in the range of [00, 68] is in the century 2000, while
- #[69,99] is in the century 1900
- if year <= 68:
- year += 2000
+ year = parse_int(found_dict['y'])
+ if 'C' in found_dict:
+ century = parse_int(found_dict['C'])
+ year += century * 100
else:
- year += 1900
+ # Open Group specification for strptime() states that a %y
+ #value in the range of [00, 68] is in the century 2000, while
+ #[69,99] is in the century 1900
+ if year <= 68:
+ year += 2000
+ else:
+ year += 1900
elif group_key == 'Y':
year = int(found_dict['Y'])
elif group_key == 'G':
iso_year = int(found_dict['G'])
elif group_key == 'm':
- month = int(found_dict['m'])
+ month = parse_int(found_dict['m'])
elif group_key == 'B':
month = locale_time.f_month.index(found_dict['B'].lower())
elif group_key == 'b':
month = locale_time.a_month.index(found_dict['b'].lower())
elif group_key == 'd':
- day = int(found_dict['d'])
+ day = parse_int(found_dict['d'])
elif group_key == 'H':
- hour = int(found_dict['H'])
+ hour = parse_int(found_dict['H'])
elif group_key == 'I':
- hour = int(found_dict['I'])
+ hour = parse_int(found_dict['I'])
ampm = found_dict.get('p', '').lower()
# If there was no AM/PM indicator, we'll treat this like AM
if ampm in ('', locale_time.am_pm[0]):
@@ -513,9 +629,9 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
if hour != 12:
hour += 12
elif group_key == 'M':
- minute = int(found_dict['M'])
+ minute = parse_int(found_dict['M'])
elif group_key == 'S':
- second = int(found_dict['S'])
+ second = parse_int(found_dict['S'])
elif group_key == 'f':
s = found_dict['f']
# Pad to always return microseconds.
@@ -548,27 +664,28 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
iso_week = int(found_dict['V'])
elif group_key == 'z':
z = found_dict['z']
- if z == 'Z':
- gmtoff = 0
- else:
- if z[3] == ':':
- z = z[:3] + z[4:]
- if len(z) > 5:
- if z[5] != ':':
- msg = f"Inconsistent use of : in {found_dict['z']}"
- raise ValueError(msg)
- z = z[:5] + z[6:]
- hours = int(z[1:3])
- minutes = int(z[3:5])
- seconds = int(z[5:7] or 0)
- gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
- gmtoff_remainder = z[8:]
- # Pad to always return microseconds.
- gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
- gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
- if z.startswith("-"):
- gmtoff = -gmtoff
- gmtoff_fraction = -gmtoff_fraction
+ if z:
+ if z == 'Z':
+ gmtoff = 0
+ else:
+ if z[3] == ':':
+ z = z[:3] + z[4:]
+ if len(z) > 5:
+ if z[5] != ':':
+ msg = f"Inconsistent use of : in {found_dict['z']}"
+ raise ValueError(msg)
+ z = z[:5] + z[6:]
+ hours = int(z[1:3])
+ minutes = int(z[3:5])
+ seconds = int(z[5:7] or 0)
+ gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
+ gmtoff_remainder = z[8:]
+ # Pad to always return microseconds.
+ gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
+ gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
+ if z.startswith("-"):
+ gmtoff = -gmtoff
+ gmtoff_fraction = -gmtoff_fraction
elif group_key == 'Z':
# Since -1 is default value only need to worry about setting tz if
# it can be something other than -1.
diff --git a/Lib/_threading_local.py b/Lib/_threading_local.py
index b006d76c4e2..0b9e5d3bbf6 100644
--- a/Lib/_threading_local.py
+++ b/Lib/_threading_local.py
@@ -4,128 +4,6 @@
class. Depending on the version of Python you're using, there may be a
faster one available. You should always import the `local` class from
`threading`.)
-
-Thread-local objects support the management of thread-local data.
-If you have data that you want to be local to a thread, simply create
-a thread-local object and use its attributes:
-
- >>> mydata = local()
- >>> mydata.number = 42
- >>> mydata.number
- 42
-
-You can also access the local-object's dictionary:
-
- >>> mydata.__dict__
- {'number': 42}
- >>> mydata.__dict__.setdefault('widgets', [])
- []
- >>> mydata.widgets
- []
-
-What's important about thread-local objects is that their data are
-local to a thread. If we access the data in a different thread:
-
- >>> log = []
- >>> def f():
- ... items = sorted(mydata.__dict__.items())
- ... log.append(items)
- ... mydata.number = 11
- ... log.append(mydata.number)
-
- >>> import threading
- >>> thread = threading.Thread(target=f)
- >>> thread.start()
- >>> thread.join()
- >>> log
- [[], 11]
-
-we get different data. Furthermore, changes made in the other thread
-don't affect data seen in this thread:
-
- >>> mydata.number
- 42
-
-Of course, values you get from a local object, including a __dict__
-attribute, are for whatever thread was current at the time the
-attribute was read. For that reason, you generally don't want to save
-these values across threads, as they apply only to the thread they
-came from.
-
-You can create custom local objects by subclassing the local class:
-
- >>> class MyLocal(local):
- ... number = 2
- ... def __init__(self, /, **kw):
- ... self.__dict__.update(kw)
- ... def squared(self):
- ... return self.number ** 2
-
-This can be useful to support default values, methods and
-initialization. Note that if you define an __init__ method, it will be
-called each time the local object is used in a separate thread. This
-is necessary to initialize each thread's dictionary.
-
-Now if we create a local object:
-
- >>> mydata = MyLocal(color='red')
-
-Now we have a default number:
-
- >>> mydata.number
- 2
-
-an initial color:
-
- >>> mydata.color
- 'red'
- >>> del mydata.color
-
-And a method that operates on the data:
-
- >>> mydata.squared()
- 4
-
-As before, we can access the data in a separate thread:
-
- >>> log = []
- >>> thread = threading.Thread(target=f)
- >>> thread.start()
- >>> thread.join()
- >>> log
- [[('color', 'red')], 11]
-
-without affecting this thread's data:
-
- >>> mydata.number
- 2
- >>> mydata.color
- Traceback (most recent call last):
- ...
- AttributeError: 'MyLocal' object has no attribute 'color'
-
-Note that subclasses can define slots, but they are not thread
-local. They are shared across threads:
-
- >>> class MyLocal(local):
- ... __slots__ = 'number'
-
- >>> mydata = MyLocal()
- >>> mydata.number = 42
- >>> mydata.color = 'red'
-
-So, the separate thread:
-
- >>> thread = threading.Thread(target=f)
- >>> thread.start()
- >>> thread.join()
-
-affects what we see:
-
- >>> mydata.number
- 11
-
->>> del mydata
"""
from weakref import ref
diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 971f636f971..c83a1573ccd 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -12,7 +12,7 @@ __all__ = [
"ForwardRef",
"call_annotate_function",
"call_evaluate_function",
- "get_annotate_function",
+ "get_annotate_from_class_namespace",
"get_annotations",
"annotations_to_string",
"type_repr",
@@ -27,6 +27,9 @@ class Format(enum.IntEnum):
_sentinel = object()
+# Following `NAME_ERROR_MSG` in `ceval_macros.h`:
+_NAME_ERROR_MSG = "name '{name:.200}' is not defined"
+
# Slots shared by ForwardRef and _Stringifier. The __forward__ names must be
# preserved for compatibility with the old typing.ForwardRef class. The remaining
@@ -38,6 +41,7 @@ _SLOTS = (
"__weakref__",
"__arg__",
"__globals__",
+ "__extra_names__",
"__code__",
"__ast_node__",
"__cell__",
@@ -82,6 +86,7 @@ class ForwardRef:
# is created through __class__ assignment on a _Stringifier object.
self.__globals__ = None
self.__cell__ = None
+ self.__extra_names__ = None
# These are initially None but serve as a cache and may be set to a non-None
# value later.
self.__code__ = None
@@ -90,11 +95,28 @@ class ForwardRef:
def __init_subclass__(cls, /, *args, **kwds):
raise TypeError("Cannot subclass ForwardRef")
- def evaluate(self, *, globals=None, locals=None, type_params=None, owner=None):
+ def evaluate(
+ self,
+ *,
+ globals=None,
+ locals=None,
+ type_params=None,
+ owner=None,
+ format=Format.VALUE,
+ ):
"""Evaluate the forward reference and return the value.
If the forward reference cannot be evaluated, raise an exception.
"""
+ match format:
+ case Format.STRING:
+ return self.__forward_arg__
+ case Format.VALUE:
+ is_forwardref_format = False
+ case Format.FORWARDREF:
+ is_forwardref_format = True
+ case _:
+ raise NotImplementedError(format)
if self.__cell__ is not None:
try:
return self.__cell__.cell_contents
@@ -151,21 +173,42 @@ class ForwardRef:
if not self.__forward_is_class__ or param_name not in globals:
globals[param_name] = param
locals.pop(param_name, None)
+ if self.__extra_names__:
+ locals = {**locals, **self.__extra_names__}
arg = self.__forward_arg__
if arg.isidentifier() and not keyword.iskeyword(arg):
if arg in locals:
- value = locals[arg]
+ return locals[arg]
elif arg in globals:
- value = globals[arg]
+ return globals[arg]
elif hasattr(builtins, arg):
return getattr(builtins, arg)
+ elif is_forwardref_format:
+ return self
else:
- raise NameError(arg)
+ raise NameError(_NAME_ERROR_MSG.format(name=arg), name=arg)
else:
code = self.__forward_code__
- value = eval(code, globals=globals, locals=locals)
- return value
+ try:
+ return eval(code, globals=globals, locals=locals)
+ except Exception:
+ if not is_forwardref_format:
+ raise
+ new_locals = _StringifierDict(
+ {**builtins.__dict__, **locals},
+ globals=globals,
+ owner=owner,
+ is_class=self.__forward_is_class__,
+ format=format,
+ )
+ try:
+ result = eval(code, globals=globals, locals=new_locals)
+ except Exception:
+ return self
+ else:
+ new_locals.transmogrify()
+ return result
def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard):
import typing
@@ -231,6 +274,10 @@ class ForwardRef:
and self.__forward_is_class__ == other.__forward_is_class__
and self.__cell__ == other.__cell__
and self.__owner__ == other.__owner__
+ and (
+ (tuple(sorted(self.__extra_names__.items())) if self.__extra_names__ else None) ==
+ (tuple(sorted(other.__extra_names__.items())) if other.__extra_names__ else None)
+ )
)
def __hash__(self):
@@ -241,6 +288,7 @@ class ForwardRef:
self.__forward_is_class__,
self.__cell__,
self.__owner__,
+ tuple(sorted(self.__extra_names__.items())) if self.__extra_names__ else None,
))
def __or__(self, other):
@@ -260,6 +308,9 @@ class ForwardRef:
return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
+_Template = type(t"")
+
+
class _Stringifier:
# Must match the slots on ForwardRef, so we can turn an instance of one into an
# instance of the other in place.
@@ -274,6 +325,7 @@ class _Stringifier:
cell=None,
*,
stringifier_dict,
+ extra_names=None,
):
# Either an AST node or a simple str (for the common case where a ForwardRef
# represent a single name).
@@ -285,6 +337,7 @@ class _Stringifier:
self.__code__ = None
self.__ast_node__ = node
self.__globals__ = globals
+ self.__extra_names__ = extra_names
self.__cell__ = cell
self.__owner__ = owner
self.__stringifier_dict__ = stringifier_dict
@@ -292,28 +345,65 @@ class _Stringifier:
def __convert_to_ast(self, other):
if isinstance(other, _Stringifier):
if isinstance(other.__ast_node__, str):
- return ast.Name(id=other.__ast_node__)
- return other.__ast_node__
- elif isinstance(other, slice):
+ return ast.Name(id=other.__ast_node__), other.__extra_names__
+ return other.__ast_node__, other.__extra_names__
+ elif type(other) is _Template:
+ return _template_to_ast(other), None
+ elif (
+ # In STRING format we don't bother with the create_unique_name() dance;
+ # it's better to emit the repr() of the object instead of an opaque name.
+ self.__stringifier_dict__.format == Format.STRING
+ or other is None
+ or type(other) in (str, int, float, bool, complex)
+ ):
+ return ast.Constant(value=other), None
+ elif type(other) is dict:
+ extra_names = {}
+ keys = []
+ values = []
+ for key, value in other.items():
+ new_key, new_extra_names = self.__convert_to_ast(key)
+ if new_extra_names is not None:
+ extra_names.update(new_extra_names)
+ keys.append(new_key)
+ new_value, new_extra_names = self.__convert_to_ast(value)
+ if new_extra_names is not None:
+ extra_names.update(new_extra_names)
+ values.append(new_value)
+ return ast.Dict(keys, values), extra_names
+ elif type(other) in (list, tuple, set):
+ extra_names = {}
+ elts = []
+ for elt in other:
+ new_elt, new_extra_names = self.__convert_to_ast(elt)
+ if new_extra_names is not None:
+ extra_names.update(new_extra_names)
+ elts.append(new_elt)
+ ast_class = {list: ast.List, tuple: ast.Tuple, set: ast.Set}[type(other)]
+ return ast_class(elts), extra_names
+ else:
+ name = self.__stringifier_dict__.create_unique_name()
+ return ast.Name(id=name), {name: other}
+
+ def __convert_to_ast_getitem(self, other):
+ if isinstance(other, slice):
+ extra_names = {}
+
+ def conv(obj):
+ if obj is None:
+ return None
+ new_obj, new_extra_names = self.__convert_to_ast(obj)
+ if new_extra_names is not None:
+ extra_names.update(new_extra_names)
+ return new_obj
+
return ast.Slice(
- lower=(
- self.__convert_to_ast(other.start)
- if other.start is not None
- else None
- ),
- upper=(
- self.__convert_to_ast(other.stop)
- if other.stop is not None
- else None
- ),
- step=(
- self.__convert_to_ast(other.step)
- if other.step is not None
- else None
- ),
- )
+ lower=conv(other.start),
+ upper=conv(other.stop),
+ step=conv(other.step),
+ ), extra_names
else:
- return ast.Constant(value=other)
+ return self.__convert_to_ast(other)
def __get_ast(self):
node = self.__ast_node__
@@ -321,13 +411,19 @@ class _Stringifier:
return ast.Name(id=node)
return node
- def __make_new(self, node):
+ def __make_new(self, node, extra_names=None):
+ new_extra_names = {}
+ if self.__extra_names__ is not None:
+ new_extra_names.update(self.__extra_names__)
+ if extra_names is not None:
+ new_extra_names.update(extra_names)
stringifier = _Stringifier(
node,
self.__globals__,
self.__owner__,
self.__forward_is_class__,
stringifier_dict=self.__stringifier_dict__,
+ extra_names=new_extra_names or None,
)
self.__stringifier_dict__.stringifiers.append(stringifier)
return stringifier
@@ -343,27 +439,37 @@ class _Stringifier:
if self.__ast_node__ == "__classdict__":
raise KeyError
if isinstance(other, tuple):
- elts = [self.__convert_to_ast(elt) for elt in other]
+ extra_names = {}
+ elts = []
+ for elt in other:
+ new_elt, new_extra_names = self.__convert_to_ast_getitem(elt)
+ if new_extra_names is not None:
+ extra_names.update(new_extra_names)
+ elts.append(new_elt)
other = ast.Tuple(elts)
else:
- other = self.__convert_to_ast(other)
+ other, extra_names = self.__convert_to_ast_getitem(other)
assert isinstance(other, ast.AST), repr(other)
- return self.__make_new(ast.Subscript(self.__get_ast(), other))
+ return self.__make_new(ast.Subscript(self.__get_ast(), other), extra_names)
def __getattr__(self, attr):
return self.__make_new(ast.Attribute(self.__get_ast(), attr))
def __call__(self, *args, **kwargs):
- return self.__make_new(
- ast.Call(
- self.__get_ast(),
- [self.__convert_to_ast(arg) for arg in args],
- [
- ast.keyword(key, self.__convert_to_ast(value))
- for key, value in kwargs.items()
- ],
- )
- )
+ extra_names = {}
+ ast_args = []
+ for arg in args:
+ new_arg, new_extra_names = self.__convert_to_ast(arg)
+ if new_extra_names is not None:
+ extra_names.update(new_extra_names)
+ ast_args.append(new_arg)
+ ast_kwargs = []
+ for key, value in kwargs.items():
+ new_value, new_extra_names = self.__convert_to_ast(value)
+ if new_extra_names is not None:
+ extra_names.update(new_extra_names)
+ ast_kwargs.append(ast.keyword(key, new_value))
+ return self.__make_new(ast.Call(self.__get_ast(), ast_args, ast_kwargs), extra_names)
def __iter__(self):
yield self.__make_new(ast.Starred(self.__get_ast()))
@@ -378,8 +484,9 @@ class _Stringifier:
def _make_binop(op: ast.AST):
def binop(self, other):
+ rhs, extra_names = self.__convert_to_ast(other)
return self.__make_new(
- ast.BinOp(self.__get_ast(), op, self.__convert_to_ast(other))
+ ast.BinOp(self.__get_ast(), op, rhs), extra_names
)
return binop
@@ -402,8 +509,9 @@ class _Stringifier:
def _make_rbinop(op: ast.AST):
def rbinop(self, other):
+ new_other, extra_names = self.__convert_to_ast(other)
return self.__make_new(
- ast.BinOp(self.__convert_to_ast(other), op, self.__get_ast())
+ ast.BinOp(new_other, op, self.__get_ast()), extra_names
)
return rbinop
@@ -426,12 +534,14 @@ class _Stringifier:
def _make_compare(op):
def compare(self, other):
+ rhs, extra_names = self.__convert_to_ast(other)
return self.__make_new(
ast.Compare(
left=self.__get_ast(),
ops=[op],
- comparators=[self.__convert_to_ast(other)],
- )
+ comparators=[rhs],
+ ),
+ extra_names,
)
return compare
@@ -458,14 +568,42 @@ class _Stringifier:
del _make_unary_op
+def _template_to_ast(template):
+ values = []
+ for part in template:
+ match part:
+ case str():
+ values.append(ast.Constant(value=part))
+ # Interpolation, but we don't want to import the string module
+ case _:
+ interp = ast.Interpolation(
+ str=part.expression,
+ value=ast.parse(part.expression),
+ conversion=(
+ ord(part.conversion)
+ if part.conversion is not None
+ else -1
+ ),
+ format_spec=(
+ ast.Constant(value=part.format_spec)
+ if part.format_spec != ""
+ else None
+ ),
+ )
+ values.append(interp)
+ return ast.TemplateStr(values=values)
+
+
class _StringifierDict(dict):
- def __init__(self, namespace, globals=None, owner=None, is_class=False):
+ def __init__(self, namespace, *, globals=None, owner=None, is_class=False, format):
super().__init__(namespace)
self.namespace = namespace
self.globals = globals
self.owner = owner
self.is_class = is_class
self.stringifiers = []
+ self.next_id = 1
+ self.format = format
def __missing__(self, key):
fwdref = _Stringifier(
@@ -478,6 +616,19 @@ class _StringifierDict(dict):
self.stringifiers.append(fwdref)
return fwdref
+ def transmogrify(self):
+ for obj in self.stringifiers:
+ obj.__class__ = ForwardRef
+ obj.__stringifier_dict__ = None # not needed for ForwardRef
+ if isinstance(obj.__ast_node__, str):
+ obj.__arg__ = obj.__ast_node__
+ obj.__ast_node__ = None
+
+ def create_unique_name(self):
+ name = f"__annotationlib_name_{self.next_id}__"
+ self.next_id += 1
+ return name
+
def call_evaluate_function(evaluate, format, *, owner=None):
"""Call an evaluate function. Evaluate functions are normally generated for
@@ -521,20 +672,11 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
# possibly constants if the annotate function uses them directly). We then
# convert each of those into a string to get an approximation of the
# original source.
- globals = _StringifierDict({})
- if annotate.__closure__:
- freevars = annotate.__code__.co_freevars
- new_closure = []
- for i, cell in enumerate(annotate.__closure__):
- if i < len(freevars):
- name = freevars[i]
- else:
- name = "__cell__"
- fwdref = _Stringifier(name, stringifier_dict=globals)
- new_closure.append(types.CellType(fwdref))
- closure = tuple(new_closure)
- else:
- closure = None
+ globals = _StringifierDict({}, format=format)
+ is_class = isinstance(owner, type)
+ closure = _build_closure(
+ annotate, owner, is_class, globals, allow_evaluation=False
+ )
func = types.FunctionType(
annotate.__code__,
globals,
@@ -544,9 +686,9 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
)
annos = func(Format.VALUE_WITH_FAKE_GLOBALS)
if _is_evaluate:
- return annos if isinstance(annos, str) else repr(annos)
+ return _stringify_single(annos)
return {
- key: val if isinstance(val, str) else repr(val)
+ key: _stringify_single(val)
for key, val in annos.items()
}
elif format == Format.FORWARDREF:
@@ -569,33 +711,43 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
# that returns a bool and an defined set of attributes.
namespace = {**annotate.__builtins__, **annotate.__globals__}
is_class = isinstance(owner, type)
- globals = _StringifierDict(namespace, annotate.__globals__, owner, is_class)
- if annotate.__closure__:
- freevars = annotate.__code__.co_freevars
- new_closure = []
- for i, cell in enumerate(annotate.__closure__):
- try:
- cell.cell_contents
- except ValueError:
- if i < len(freevars):
- name = freevars[i]
- else:
- name = "__cell__"
- fwdref = _Stringifier(
- name,
- cell=cell,
- owner=owner,
- globals=annotate.__globals__,
- is_class=is_class,
- stringifier_dict=globals,
- )
- globals.stringifiers.append(fwdref)
- new_closure.append(types.CellType(fwdref))
- else:
- new_closure.append(cell)
- closure = tuple(new_closure)
+ globals = _StringifierDict(
+ namespace,
+ globals=annotate.__globals__,
+ owner=owner,
+ is_class=is_class,
+ format=format,
+ )
+ closure = _build_closure(
+ annotate, owner, is_class, globals, allow_evaluation=True
+ )
+ func = types.FunctionType(
+ annotate.__code__,
+ globals,
+ closure=closure,
+ argdefs=annotate.__defaults__,
+ kwdefaults=annotate.__kwdefaults__,
+ )
+ try:
+ result = func(Format.VALUE_WITH_FAKE_GLOBALS)
+ except Exception:
+ pass
else:
- closure = None
+ globals.transmogrify()
+ return result
+
+ # Try again, but do not provide any globals. This allows us to return
+ # a value in certain cases where an exception gets raised during evaluation.
+ globals = _StringifierDict(
+ {},
+ globals=annotate.__globals__,
+ owner=owner,
+ is_class=is_class,
+ format=format,
+ )
+ closure = _build_closure(
+ annotate, owner, is_class, globals, allow_evaluation=False
+ )
func = types.FunctionType(
annotate.__code__,
globals,
@@ -604,13 +756,21 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
kwdefaults=annotate.__kwdefaults__,
)
result = func(Format.VALUE_WITH_FAKE_GLOBALS)
- for obj in globals.stringifiers:
- obj.__class__ = ForwardRef
- obj.__stringifier_dict__ = None # not needed for ForwardRef
- if isinstance(obj.__ast_node__, str):
- obj.__arg__ = obj.__ast_node__
- obj.__ast_node__ = None
- return result
+ globals.transmogrify()
+ if _is_evaluate:
+ if isinstance(result, ForwardRef):
+ return result.evaluate(format=Format.FORWARDREF)
+ else:
+ return result
+ else:
+ return {
+ key: (
+ val.evaluate(format=Format.FORWARDREF)
+ if isinstance(val, ForwardRef)
+ else val
+ )
+ for key, val in result.items()
+ }
elif format == Format.VALUE:
# Should be impossible because __annotate__ functions must not raise
# NotImplementedError for this format.
@@ -619,20 +779,61 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
raise ValueError(f"Invalid format: {format!r}")
-def get_annotate_function(obj):
- """Get the __annotate__ function for an object.
+def _build_closure(annotate, owner, is_class, stringifier_dict, *, allow_evaluation):
+ if not annotate.__closure__:
+ return None
+ freevars = annotate.__code__.co_freevars
+ new_closure = []
+ for i, cell in enumerate(annotate.__closure__):
+ if i < len(freevars):
+ name = freevars[i]
+ else:
+ name = "__cell__"
+ new_cell = None
+ if allow_evaluation:
+ try:
+ cell.cell_contents
+ except ValueError:
+ pass
+ else:
+ new_cell = cell
+ if new_cell is None:
+ fwdref = _Stringifier(
+ name,
+ cell=cell,
+ owner=owner,
+ globals=annotate.__globals__,
+ is_class=is_class,
+ stringifier_dict=stringifier_dict,
+ )
+ stringifier_dict.stringifiers.append(fwdref)
+ new_cell = types.CellType(fwdref)
+ new_closure.append(new_cell)
+ return tuple(new_closure)
+
+
+def _stringify_single(anno):
+ if anno is ...:
+ return "..."
+ # We have to handle str specially to support PEP 563 stringified annotations.
+ elif isinstance(anno, str):
+ return anno
+ elif isinstance(anno, _Template):
+ return ast.unparse(_template_to_ast(anno))
+ else:
+ return repr(anno)
+
- obj may be a function, class, or module, or a user-defined type with
- an `__annotate__` attribute.
+def get_annotate_from_class_namespace(obj):
+ """Retrieve the annotate function from a class namespace dictionary.
- Returns the __annotate__ function or None.
+ Return None if the namespace does not contain an annotate function.
+ This is useful in metaclass ``__new__`` methods to retrieve the annotate function.
"""
- if isinstance(obj, dict):
- try:
- return obj["__annotate__"]
- except KeyError:
- return obj.get("__annotate_func__", None)
- return getattr(obj, "__annotate__", None)
+ try:
+ return obj["__annotate__"]
+ except KeyError:
+ return obj.get("__annotate_func__", None)
def get_annotations(
@@ -703,7 +904,7 @@ def get_annotations(
# For FORWARDREF, we use __annotations__ if it exists
try:
ann = _get_dunder_annotations(obj)
- except NameError:
+ except Exception:
pass
else:
if ann is not None:
@@ -724,7 +925,7 @@ def get_annotations(
# But if we didn't get it, we use __annotations__ instead.
ann = _get_dunder_annotations(obj)
if ann is not None:
- return annotations_to_string(ann)
+ return annotations_to_string(ann)
case Format.VALUE_WITH_FAKE_GLOBALS:
raise ValueError("The VALUE_WITH_FAKE_GLOBALS format is for internal use only")
case _:
@@ -741,48 +942,49 @@ def get_annotations(
if not eval_str:
return dict(ann)
- if isinstance(obj, type):
- # class
- obj_globals = None
- module_name = getattr(obj, "__module__", None)
- if module_name:
- module = sys.modules.get(module_name, None)
- if module:
- obj_globals = getattr(module, "__dict__", None)
- obj_locals = dict(vars(obj))
- unwrap = obj
- elif isinstance(obj, types.ModuleType):
- # module
- obj_globals = getattr(obj, "__dict__")
- obj_locals = None
- unwrap = None
- elif callable(obj):
- # this includes types.Function, types.BuiltinFunctionType,
- # types.BuiltinMethodType, functools.partial, functools.singledispatch,
- # "class funclike" from Lib/test/test_inspect... on and on it goes.
- obj_globals = getattr(obj, "__globals__", None)
- obj_locals = None
- unwrap = obj
- else:
- obj_globals = obj_locals = unwrap = None
-
- if unwrap is not None:
- while True:
- if hasattr(unwrap, "__wrapped__"):
- unwrap = unwrap.__wrapped__
- continue
- if functools := sys.modules.get("functools"):
- if isinstance(unwrap, functools.partial):
- unwrap = unwrap.func
+ if globals is None or locals is None:
+ if isinstance(obj, type):
+ # class
+ obj_globals = None
+ module_name = getattr(obj, "__module__", None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ obj_globals = getattr(module, "__dict__", None)
+ obj_locals = dict(vars(obj))
+ unwrap = obj
+ elif isinstance(obj, types.ModuleType):
+ # module
+ obj_globals = getattr(obj, "__dict__")
+ obj_locals = None
+ unwrap = None
+ elif callable(obj):
+ # this includes types.Function, types.BuiltinFunctionType,
+ # types.BuiltinMethodType, functools.partial, functools.singledispatch,
+ # "class funclike" from Lib/test/test_inspect... on and on it goes.
+ obj_globals = getattr(obj, "__globals__", None)
+ obj_locals = None
+ unwrap = obj
+ else:
+ obj_globals = obj_locals = unwrap = None
+
+ if unwrap is not None:
+ while True:
+ if hasattr(unwrap, "__wrapped__"):
+ unwrap = unwrap.__wrapped__
continue
- break
- if hasattr(unwrap, "__globals__"):
- obj_globals = unwrap.__globals__
+ if functools := sys.modules.get("functools"):
+ if isinstance(unwrap, functools.partial):
+ unwrap = unwrap.func
+ continue
+ break
+ if hasattr(unwrap, "__globals__"):
+ obj_globals = unwrap.__globals__
- if globals is None:
- globals = obj_globals
- if locals is None:
- locals = obj_locals
+ if globals is None:
+ globals = obj_globals
+ if locals is None:
+ locals = obj_locals
# "Inject" type parameters into the local namespace
# (unless they are shadowed by assignments *in* the local namespace),
@@ -811,6 +1013,9 @@ def type_repr(value):
if value.__module__ == "builtins":
return value.__qualname__
return f"{value.__module__}.{value.__qualname__}"
+ elif isinstance(value, _Template):
+ tree = _template_to_ast(value)
+ return ast.unparse(tree)
if value is ...:
return "..."
return repr(value)
@@ -832,7 +1037,7 @@ def _get_and_call_annotate(obj, format):
May not return a fresh dictionary.
"""
- annotate = get_annotate_function(obj)
+ annotate = getattr(obj, "__annotate__", None)
if annotate is not None:
ann = call_annotate_function(annotate, format, owner=obj)
if not isinstance(ann, dict):
@@ -841,14 +1046,27 @@ def _get_and_call_annotate(obj, format):
return None
+_BASE_GET_ANNOTATIONS = type.__dict__["__annotations__"].__get__
+
+
def _get_dunder_annotations(obj):
"""Return the annotations for an object, checking that it is a dictionary.
Does not return a fresh dictionary.
"""
- ann = getattr(obj, "__annotations__", None)
- if ann is None:
- return None
+ # This special case is needed to support types defined under
+ # from __future__ import annotations, where accessing the __annotations__
+ # attribute directly might return annotations for the wrong class.
+ if isinstance(obj, type):
+ try:
+ ann = _BASE_GET_ANNOTATIONS(obj)
+ except AttributeError:
+ # For static types, the descriptor raises AttributeError.
+ return None
+ else:
+ ann = getattr(obj, "__annotations__", None)
+ if ann is None:
+ return None
if not isinstance(ann, dict):
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
diff --git a/Lib/argparse.py b/Lib/argparse.py
index c0dcd0bbff0..83258cf3e0f 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -167,7 +167,6 @@ class HelpFormatter(object):
indent_increment=2,
max_help_position=24,
width=None,
- prefix_chars='-',
color=False,
):
# default setting for width
@@ -176,16 +175,7 @@ class HelpFormatter(object):
width = shutil.get_terminal_size().columns
width -= 2
- from _colorize import ANSIColors, NoColors, can_colorize, decolor
-
- if color and can_colorize():
- self._ansi = ANSIColors()
- self._decolor = decolor
- else:
- self._ansi = NoColors
- self._decolor = lambda text: text
-
- self._prefix_chars = prefix_chars
+ self._set_color(color)
self._prog = prog
self._indent_increment = indent_increment
self._max_help_position = min(max_help_position,
@@ -202,9 +192,20 @@ class HelpFormatter(object):
self._whitespace_matcher = _re.compile(r'\s+', _re.ASCII)
self._long_break_matcher = _re.compile(r'\n\n\n+')
+ def _set_color(self, color):
+ from _colorize import can_colorize, decolor, get_theme
+
+ if color and can_colorize():
+ self._theme = get_theme(force_color=True).argparse
+ self._decolor = decolor
+ else:
+ self._theme = get_theme(force_no_color=True).argparse
+ self._decolor = lambda text: text
+
# ===============================
# Section and indentation methods
# ===============================
+
def _indent(self):
self._current_indent += self._indent_increment
self._level += 1
@@ -237,14 +238,12 @@ class HelpFormatter(object):
# add the heading if the section was non-empty
if self.heading is not SUPPRESS and self.heading is not None:
- bold_blue = self.formatter._ansi.BOLD_BLUE
- reset = self.formatter._ansi.RESET
-
current_indent = self.formatter._current_indent
heading_text = _('%(heading)s:') % dict(heading=self.heading)
+ t = self.formatter._theme
heading = (
f'{" " * current_indent}'
- f'{bold_blue}{heading_text}{reset}\n'
+ f'{t.heading}{heading_text}{t.reset}\n'
)
else:
heading = ''
@@ -258,6 +257,7 @@ class HelpFormatter(object):
# ========================
# Message building methods
# ========================
+
def start_section(self, heading):
self._indent()
section = self._Section(self, self._current_section, heading)
@@ -301,6 +301,7 @@ class HelpFormatter(object):
# =======================
# Help-formatting methods
# =======================
+
def format_help(self):
help = self._root_section.format_help()
if help:
@@ -314,10 +315,7 @@ class HelpFormatter(object):
if part and part is not SUPPRESS])
def _format_usage(self, usage, actions, groups, prefix):
- bold_blue = self._ansi.BOLD_BLUE
- bold_magenta = self._ansi.BOLD_MAGENTA
- magenta = self._ansi.MAGENTA
- reset = self._ansi.RESET
+ t = self._theme
if prefix is None:
prefix = _('usage: ')
@@ -325,15 +323,15 @@ class HelpFormatter(object):
# if usage is specified, use that
if usage is not None:
usage = (
- magenta
+ t.prog_extra
+ usage
- % {"prog": f"{bold_magenta}{self._prog}{reset}{magenta}"}
- + reset
+ % {"prog": f"{t.prog}{self._prog}{t.reset}{t.prog_extra}"}
+ + t.reset
)
# if no optionals or positionals are available, usage is just prog
elif usage is None and not actions:
- usage = f"{bold_magenta}{self._prog}{reset}"
+ usage = f"{t.prog}{self._prog}{t.reset}"
# if optionals and positionals are available, calculate usage
elif usage is None:
@@ -411,23 +409,16 @@ class HelpFormatter(object):
usage = '\n'.join(lines)
usage = usage.removeprefix(prog)
- usage = f"{bold_magenta}{prog}{reset}{usage}"
+ usage = f"{t.prog}{prog}{t.reset}{usage}"
# prefix with 'usage:'
- return f'{bold_blue}{prefix}{reset}{usage}\n\n'
+ return f'{t.usage}{prefix}{t.reset}{usage}\n\n'
def _format_actions_usage(self, actions, groups):
return ' '.join(self._get_actions_usage_parts(actions, groups))
def _is_long_option(self, string):
- return len(string) >= 2 and string[1] in self._prefix_chars
-
- def _is_short_option(self, string):
- return (
- not self._is_long_option(string)
- and len(string) >= 1
- and string[0] in self._prefix_chars
- )
+ return len(string) > 2
def _get_actions_usage_parts(self, actions, groups):
# find group indices and identify actions in groups
@@ -452,10 +443,7 @@ class HelpFormatter(object):
# collect all actions format strings
parts = []
- cyan = self._ansi.CYAN
- green = self._ansi.GREEN
- yellow = self._ansi.YELLOW
- reset = self._ansi.RESET
+ t = self._theme
for action in actions:
# suppressed arguments are marked with None
@@ -465,7 +453,11 @@ class HelpFormatter(object):
# produce all arg strings
elif not action.option_strings:
default = self._get_default_metavar_for_positional(action)
- part = green + self._format_args(action, default) + reset
+ part = (
+ t.summary_action
+ + self._format_args(action, default)
+ + t.reset
+ )
# if it's in a group, strip the outer []
if action in group_actions:
@@ -475,26 +467,26 @@ class HelpFormatter(object):
# produce the first way to invoke the option in brackets
else:
option_string = action.option_strings[0]
+ if self._is_long_option(option_string):
+ option_color = t.summary_long_option
+ else:
+ option_color = t.summary_short_option
# if the Optional doesn't take a value, format is:
# -s or --long
if action.nargs == 0:
part = action.format_usage()
- if self._is_long_option(part):
- part = f"{cyan}{part}{reset}"
- elif self._is_short_option(part):
- part = f"{green}{part}{reset}"
+ part = f"{option_color}{part}{t.reset}"
# if the Optional takes a value, format is:
# -s ARGS or --long ARGS
else:
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
- if self._is_long_option(option_string):
- option_string = f"{cyan}{option_string}"
- elif self._is_short_option(option_string):
- option_string = f"{green}{option_string}"
- part = f"{option_string} {yellow}{args_string}{reset}"
+ part = (
+ f"{option_color}{option_string} "
+ f"{t.summary_label}{args_string}{t.reset}"
+ )
# make it look optional if it's not required or in a group
if not action.required and action not in group_actions:
@@ -590,17 +582,14 @@ class HelpFormatter(object):
return self._join_parts(parts)
def _format_action_invocation(self, action):
- bold_green = self._ansi.BOLD_GREEN
- bold_cyan = self._ansi.BOLD_CYAN
- bold_yellow = self._ansi.BOLD_YELLOW
- reset = self._ansi.RESET
+ t = self._theme
if not action.option_strings:
default = self._get_default_metavar_for_positional(action)
return (
- bold_green
+ t.action
+ ' '.join(self._metavar_formatter(action, default)(1))
- + reset
+ + t.reset
)
else:
@@ -609,11 +598,9 @@ class HelpFormatter(object):
parts = []
for s in strings:
if self._is_long_option(s):
- parts.append(f"{bold_cyan}{s}{reset}")
- elif self._is_short_option(s):
- parts.append(f"{bold_green}{s}{reset}")
+ parts.append(f"{t.long_option}{s}{t.reset}")
else:
- parts.append(s)
+ parts.append(f"{t.short_option}{s}{t.reset}")
return parts
# if the Optional doesn't take a value, format is:
@@ -628,7 +615,7 @@ class HelpFormatter(object):
default = self._get_default_metavar_for_optional(action)
option_strings = color_option_strings(action.option_strings)
args_string = (
- f"{bold_yellow}{self._format_args(action, default)}{reset}"
+ f"{t.label}{self._format_args(action, default)}{t.reset}"
)
return ', '.join(option_strings) + ' ' + args_string
@@ -1483,6 +1470,7 @@ class _ActionsContainer(object):
# ====================
# Registration methods
# ====================
+
def register(self, registry_name, value, object):
registry = self._registries.setdefault(registry_name, {})
registry[value] = object
@@ -1493,6 +1481,7 @@ class _ActionsContainer(object):
# ==================================
# Namespace default accessor methods
# ==================================
+
def set_defaults(self, **kwargs):
self._defaults.update(kwargs)
@@ -1512,6 +1501,7 @@ class _ActionsContainer(object):
# =======================
# Adding argument actions
# =======================
+
def add_argument(self, *args, **kwargs):
"""
add_argument(dest, ..., name=value, ...)
@@ -1544,7 +1534,7 @@ class _ActionsContainer(object):
action_name = kwargs.get('action')
action_class = self._pop_action_class(kwargs)
if not callable(action_class):
- raise ValueError('unknown action {action_class!r}')
+ raise ValueError(f'unknown action {action_class!r}')
action = action_class(**kwargs)
# raise an error if action for positional argument does not
@@ -1937,6 +1927,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# =======================
# Pretty __repr__ methods
# =======================
+
def _get_kwargs(self):
names = [
'prog',
@@ -1951,6 +1942,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# ==================================
# Optional/Positional adding methods
# ==================================
+
def add_subparsers(self, **kwargs):
if self._subparsers is not None:
raise ValueError('cannot have multiple subparser arguments')
@@ -2004,6 +1996,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# =====================================
# Command line argument parsing methods
# =====================================
+
def parse_args(self, args=None, namespace=None):
args, argv = self.parse_known_args(args, namespace)
if argv:
@@ -2598,6 +2591,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# ========================
# Value conversion methods
# ========================
+
def _get_values(self, action, arg_strings):
# optional argument produces a default when not present
if not arg_strings and action.nargs == OPTIONAL:
@@ -2697,6 +2691,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# =======================
# Help-formatting methods
# =======================
+
def format_usage(self):
formatter = self._get_formatter()
formatter.add_usage(self.usage, self._actions,
@@ -2727,20 +2722,14 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
return formatter.format_help()
def _get_formatter(self):
- if isinstance(self.formatter_class, type) and issubclass(
- self.formatter_class, HelpFormatter
- ):
- return self.formatter_class(
- prog=self.prog,
- prefix_chars=self.prefix_chars,
- color=self.color,
- )
- else:
- return self.formatter_class(prog=self.prog)
+ formatter = self.formatter_class(prog=self.prog)
+ formatter._set_color(self.color)
+ return formatter
# =====================
# Help-printing methods
# =====================
+
def print_usage(self, file=None):
if file is None:
file = _sys.stdout
@@ -2762,6 +2751,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# ===============
# Exiting methods
# ===============
+
def exit(self, status=0, message=None):
if message:
self._print_message(message, _sys.stderr)
diff --git a/Lib/ast.py b/Lib/ast.py
index aa788e6eb62..6d3daf64f5c 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -147,18 +147,22 @@ def dump(
if value is None and getattr(cls, name, ...) is None:
keywords = True
continue
- if (
- not show_empty
- and (value is None or value == [])
- # Special cases:
- # `Constant(value=None)` and `MatchSingleton(value=None)`
- and not isinstance(node, (Constant, MatchSingleton))
- ):
- args_buffer.append(repr(value))
- continue
- elif not keywords:
- args.extend(args_buffer)
- args_buffer = []
+ if not show_empty:
+ if value == []:
+ field_type = cls._field_types.get(name, object)
+ if getattr(field_type, '__origin__', ...) is list:
+ if not keywords:
+ args_buffer.append(repr(value))
+ continue
+ elif isinstance(value, Load):
+ field_type = cls._field_types.get(name, object)
+ if field_type is expr_context:
+ if not keywords:
+ args_buffer.append(repr(value))
+ continue
+ if not keywords:
+ args.extend(args_buffer)
+ args_buffer = []
value, simple = _format(value, level)
allsimple = allsimple and simple
if keywords:
@@ -626,11 +630,11 @@ def unparse(ast_obj):
return unparser.visit(ast_obj)
-def main():
+def main(args=None):
import argparse
import sys
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument('infile', nargs='?', default='-',
help='the file to parse; defaults to stdin')
parser.add_argument('-m', '--mode', default='exec',
@@ -643,7 +647,16 @@ def main():
'column offsets')
parser.add_argument('-i', '--indent', type=int, default=3,
help='indentation of nodes (number of spaces)')
- args = parser.parse_args()
+ parser.add_argument('--feature-version',
+ type=str, default=None, metavar='VERSION',
+ help='Python version in the format 3.x '
+ '(for example, 3.10)')
+ parser.add_argument('-O', '--optimize',
+ type=int, default=-1, metavar='LEVEL',
+ help='optimization level for parser (default -1)')
+ parser.add_argument('--show-empty', default=False, action='store_true',
+ help='show empty lists and fields in dump output')
+ args = parser.parse_args(args)
if args.infile == '-':
name = '<stdin>'
@@ -652,8 +665,22 @@ def main():
name = args.infile
with open(args.infile, 'rb') as infile:
source = infile.read()
- tree = parse(source, name, args.mode, type_comments=args.no_type_comments)
- print(dump(tree, include_attributes=args.include_attributes, indent=args.indent))
+
+ # Process feature_version
+ feature_version = None
+ if args.feature_version:
+ try:
+ major, minor = map(int, args.feature_version.split('.', 1))
+ except ValueError:
+ parser.error('Invalid format for --feature-version; '
+ 'expected format 3.x (for example, 3.10)')
+
+ feature_version = (major, minor)
+
+ tree = parse(source, name, args.mode, type_comments=args.no_type_comments,
+ feature_version=feature_version, optimize=args.optimize)
+ print(dump(tree, include_attributes=args.include_attributes,
+ indent=args.indent, show_empty=args.show_empty))
if __name__ == '__main__':
main()
diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py
index 69f5a30cfe5..21ca5c5f62a 100644
--- a/Lib/asyncio/__main__.py
+++ b/Lib/asyncio/__main__.py
@@ -1,5 +1,7 @@
+import argparse
import ast
import asyncio
+import asyncio.tools
import concurrent.futures
import contextvars
import inspect
@@ -10,7 +12,7 @@ import threading
import types
import warnings
-from _colorize import can_colorize, ANSIColors # type: ignore[import-not-found]
+from _colorize import get_theme
from _pyrepl.console import InteractiveColoredConsole
from . import futures
@@ -101,8 +103,9 @@ class REPLThread(threading.Thread):
exec(startup_code, console.locals)
ps1 = getattr(sys, "ps1", ">>> ")
- if can_colorize() and CAN_USE_PYREPL:
- ps1 = f"{ANSIColors.BOLD_MAGENTA}{ps1}{ANSIColors.RESET}"
+ if CAN_USE_PYREPL:
+ theme = get_theme().syntax
+ ps1 = f"{theme.prompt}{ps1}{theme.reset}"
console.write(f"{ps1}import asyncio\n")
if CAN_USE_PYREPL:
@@ -140,6 +143,37 @@ class REPLThread(threading.Thread):
if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ prog="python3 -m asyncio",
+ description="Interactive asyncio shell and CLI tools",
+ color=True,
+ )
+ subparsers = parser.add_subparsers(help="sub-commands", dest="command")
+ ps = subparsers.add_parser(
+ "ps", help="Display a table of all pending tasks in a process"
+ )
+ ps.add_argument("pid", type=int, help="Process ID to inspect")
+ pstree = subparsers.add_parser(
+ "pstree", help="Display a tree of all pending tasks in a process"
+ )
+ pstree.add_argument("pid", type=int, help="Process ID to inspect")
+ args = parser.parse_args()
+ match args.command:
+ case "ps":
+ asyncio.tools.display_awaited_by_tasks_table(args.pid)
+ sys.exit(0)
+ case "pstree":
+ asyncio.tools.display_awaited_by_tasks_tree(args.pid)
+ sys.exit(0)
+ case None:
+ pass # continue to the interactive shell
+ case _:
+ # shouldn't happen as an invalid command-line wouldn't parse
+ # but let's keep it for the next person adding a command
+ print(f"error: unhandled command {args.command}", file=sys.stderr)
+ parser.print_usage(file=sys.stderr)
+ sys.exit(1)
+
sys.audit("cpython.run_stdin")
if os.getenv('PYTHON_BASIC_REPL'):
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 29b872ce00e..520d4b39854 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -459,7 +459,7 @@ class BaseEventLoop(events.AbstractEventLoop):
return futures.Future(loop=self)
def create_task(self, coro, **kwargs):
- """Schedule a coroutine object.
+ """Schedule or begin executing a coroutine object.
Return a task object.
"""
@@ -1016,38 +1016,43 @@ class BaseEventLoop(events.AbstractEventLoop):
family, type_, proto, _, address = addr_info
sock = None
try:
- sock = socket.socket(family=family, type=type_, proto=proto)
- sock.setblocking(False)
- if local_addr_infos is not None:
- for lfamily, _, _, _, laddr in local_addr_infos:
- # skip local addresses of different family
- if lfamily != family:
- continue
- try:
- sock.bind(laddr)
- break
- except OSError as exc:
- msg = (
- f'error while attempting to bind on '
- f'address {laddr!r}: {str(exc).lower()}'
- )
- exc = OSError(exc.errno, msg)
- my_exceptions.append(exc)
- else: # all bind attempts failed
- if my_exceptions:
- raise my_exceptions.pop()
- else:
- raise OSError(f"no matching local address with {family=} found")
- await self.sock_connect(sock, address)
- return sock
- except OSError as exc:
- my_exceptions.append(exc)
- if sock is not None:
- sock.close()
- raise
+ try:
+ sock = socket.socket(family=family, type=type_, proto=proto)
+ sock.setblocking(False)
+ if local_addr_infos is not None:
+ for lfamily, _, _, _, laddr in local_addr_infos:
+ # skip local addresses of different family
+ if lfamily != family:
+ continue
+ try:
+ sock.bind(laddr)
+ break
+ except OSError as exc:
+ msg = (
+ f'error while attempting to bind on '
+ f'address {laddr!r}: {str(exc).lower()}'
+ )
+ exc = OSError(exc.errno, msg)
+ my_exceptions.append(exc)
+ else: # all bind attempts failed
+ if my_exceptions:
+ raise my_exceptions.pop()
+ else:
+ raise OSError(f"no matching local address with {family=} found")
+ await self.sock_connect(sock, address)
+ return sock
+ except OSError as exc:
+ my_exceptions.append(exc)
+ raise
except:
if sock is not None:
- sock.close()
+ try:
+ sock.close()
+ except OSError:
+ # An error when closing a newly created socket is
+ # not important, but it can overwrite more important
+ # non-OSError error. So ignore it.
+ pass
raise
finally:
exceptions = my_exceptions = None
@@ -1161,7 +1166,7 @@ class BaseEventLoop(events.AbstractEventLoop):
raise ExceptionGroup("create_connection failed", exceptions)
if len(exceptions) == 1:
raise exceptions[0]
- else:
+ elif exceptions:
# If they all have the same str(), raise one.
model = str(exceptions[0])
if all(str(exc) == model for exc in exceptions):
@@ -1170,6 +1175,9 @@ class BaseEventLoop(events.AbstractEventLoop):
# the various error messages.
raise OSError('Multiple exceptions: {}'.format(
', '.join(str(exc) for exc in exceptions)))
+ else:
+ # No exceptions were collected, raise a timeout error
+ raise TimeoutError('create_connection failed')
finally:
exceptions = None
diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py
index 9c2ba679ce2..d40af422e61 100644
--- a/Lib/asyncio/base_subprocess.py
+++ b/Lib/asyncio/base_subprocess.py
@@ -104,7 +104,12 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
for proto in self._pipes.values():
if proto is None:
continue
- proto.pipe.close()
+ # See gh-114177
+ # skip closing the pipe if loop is already closed
+ # this can happen e.g. when loop is closed immediately after
+ # process is killed
+ if self._loop and not self._loop.is_closed():
+ proto.pipe.close()
if (self._proc is not None and
# has the child process finished?
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
index d1df6707302..6bd00a64478 100644
--- a/Lib/asyncio/futures.py
+++ b/Lib/asyncio/futures.py
@@ -351,22 +351,19 @@ def _set_concurrent_future_state(concurrent, source):
def _copy_future_state(source, dest):
"""Internal helper to copy state from another Future.
- The other Future may be a concurrent.futures.Future.
+ The other Future must be a concurrent.futures.Future.
"""
- assert source.done()
if dest.cancelled():
return
assert not dest.done()
- if source.cancelled():
+ done, cancelled, result, exception = source._get_snapshot()
+ assert done
+ if cancelled:
dest.cancel()
+ elif exception is not None:
+ dest.set_exception(_convert_future_exc(exception))
else:
- exception = source.exception()
- if exception is not None:
- dest.set_exception(_convert_future_exc(exception))
- else:
- result = source.result()
- dest.set_result(result)
-
+ dest.set_result(result)
def _chain_future(source, destination):
"""Chain two futures so that when one completes, so does the other.
diff --git a/Lib/asyncio/graph.py b/Lib/asyncio/graph.py
index d8df7c9919a..b5bfeb1630a 100644
--- a/Lib/asyncio/graph.py
+++ b/Lib/asyncio/graph.py
@@ -1,6 +1,7 @@
"""Introspection utils for tasks call graphs."""
import dataclasses
+import io
import sys
import types
@@ -16,9 +17,6 @@ __all__ = (
'FutureCallGraph',
)
-if False: # for type checkers
- from typing import TextIO
-
# Sadly, we can't re-use the traceback module's datastructures as those
# are tailored for error reporting, whereas we need to represent an
# async call graph.
@@ -270,7 +268,7 @@ def print_call_graph(
future: futures.Future | None = None,
/,
*,
- file: TextIO | None = None,
+ file: io.Writer[str] | None = None,
depth: int = 1,
limit: int | None = None,
) -> None:
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index 22147451fa7..6ad84044adf 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -173,7 +173,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
# listening socket has triggered an EVENT_READ. There may be multiple
# connections waiting for an .accept() so it is called in a loop.
# See https://bugs.python.org/issue27906 for more details.
- for _ in range(backlog):
+ for _ in range(backlog + 1):
try:
conn, addr = sock.accept()
if self._debug:
diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py
index 1633478d1c8..00e8f6d5d1a 100644
--- a/Lib/asyncio/taskgroups.py
+++ b/Lib/asyncio/taskgroups.py
@@ -179,7 +179,7 @@ class TaskGroup:
exc = None
- def create_task(self, coro, *, name=None, context=None):
+ def create_task(self, coro, **kwargs):
"""Create a new task in this group and return it.
Similar to `asyncio.create_task`.
@@ -193,10 +193,7 @@ class TaskGroup:
if self._aborting:
coro.close()
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
- if context is None:
- task = self._loop.create_task(coro, name=name)
- else:
- task = self._loop.create_task(coro, name=name, context=context)
+ task = self._loop.create_task(coro, **kwargs)
futures.future_add_to_awaited_by(task, self._parent_task)
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index 825e91f5594..fbd5c39a7c5 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -386,19 +386,13 @@ else:
Task = _CTask = _asyncio.Task
-def create_task(coro, *, name=None, context=None):
+def create_task(coro, **kwargs):
"""Schedule the execution of a coroutine object in a spawn task.
Return a Task object.
"""
loop = events.get_running_loop()
- if context is None:
- # Use legacy API if context is not needed
- task = loop.create_task(coro, name=name)
- else:
- task = loop.create_task(coro, name=name, context=context)
-
- return task
+ return loop.create_task(coro, **kwargs)
# wait() and as_completed() similar to those in PEP 3148.
@@ -914,6 +908,25 @@ def gather(*coros_or_futures, return_exceptions=False):
return outer
+def _log_on_exception(fut):
+ if fut.cancelled():
+ return
+
+ exc = fut.exception()
+ if exc is None:
+ return
+
+ context = {
+ 'message':
+ f'{exc.__class__.__name__} exception in shielded future',
+ 'exception': exc,
+ 'future': fut,
+ }
+ if fut._source_traceback:
+ context['source_traceback'] = fut._source_traceback
+ fut._loop.call_exception_handler(context)
+
+
def shield(arg):
"""Wait for a future, shielding it from cancellation.
@@ -959,14 +972,11 @@ def shield(arg):
else:
cur_task = None
- def _inner_done_callback(inner, cur_task=cur_task):
- if cur_task is not None:
- futures.future_discard_from_awaited_by(inner, cur_task)
+ def _clear_awaited_by_callback(inner):
+ futures.future_discard_from_awaited_by(inner, cur_task)
+ def _inner_done_callback(inner):
if outer.cancelled():
- if not inner.cancelled():
- # Mark inner's result as retrieved.
- inner.exception()
return
if inner.cancelled():
@@ -978,10 +988,16 @@ def shield(arg):
else:
outer.set_result(inner.result())
-
def _outer_done_callback(outer):
if not inner.done():
inner.remove_done_callback(_inner_done_callback)
+ # Keep only one callback to log on cancel
+ inner.remove_done_callback(_log_on_exception)
+ inner.add_done_callback(_log_on_exception)
+
+ if cur_task is not None:
+ inner.add_done_callback(_clear_awaited_by_callback)
+
inner.add_done_callback(_inner_done_callback)
outer.add_done_callback(_outer_done_callback)
@@ -1030,9 +1046,9 @@ def create_eager_task_factory(custom_task_constructor):
used. E.g. `loop.set_task_factory(asyncio.eager_task_factory)`.
"""
- def factory(loop, coro, *, name=None, context=None):
+ def factory(loop, coro, *, eager_start=True, **kwargs):
return custom_task_constructor(
- coro, loop=loop, name=name, context=context, eager_start=True)
+ coro, loop=loop, eager_start=eager_start, **kwargs)
return factory
diff --git a/Lib/asyncio/tools.py b/Lib/asyncio/tools.py
new file mode 100644
index 00000000000..2683f34cc71
--- /dev/null
+++ b/Lib/asyncio/tools.py
@@ -0,0 +1,260 @@
+"""Tools to analyze tasks running in asyncio programs."""
+
+from collections import defaultdict, namedtuple
+from itertools import count
+from enum import Enum
+import sys
+from _remote_debugging import RemoteUnwinder, FrameInfo
+
+class NodeType(Enum):
+ COROUTINE = 1
+ TASK = 2
+
+
+class CycleFoundException(Exception):
+ """Raised when there is a cycle when drawing the call tree."""
+ def __init__(
+ self,
+ cycles: list[list[int]],
+ id2name: dict[int, str],
+ ) -> None:
+ super().__init__(cycles, id2name)
+ self.cycles = cycles
+ self.id2name = id2name
+
+
+
+# ─── indexing helpers ───────────────────────────────────────────
+def _format_stack_entry(elem: str|FrameInfo) -> str:
+ if not isinstance(elem, str):
+ if elem.lineno == 0 and elem.filename == "":
+ return f"{elem.funcname}"
+ else:
+ return f"{elem.funcname} {elem.filename}:{elem.lineno}"
+ return elem
+
+
+def _index(result):
+ id2name, awaits, task_stacks = {}, [], {}
+ for awaited_info in result:
+ for task_info in awaited_info.awaited_by:
+ task_id = task_info.task_id
+ task_name = task_info.task_name
+ id2name[task_id] = task_name
+
+ # Store the internal coroutine stack for this task
+ if task_info.coroutine_stack:
+ for coro_info in task_info.coroutine_stack:
+ call_stack = coro_info.call_stack
+ internal_stack = [_format_stack_entry(frame) for frame in call_stack]
+ task_stacks[task_id] = internal_stack
+
+ # Add the awaited_by relationships (external dependencies)
+ if task_info.awaited_by:
+ for coro_info in task_info.awaited_by:
+ call_stack = coro_info.call_stack
+ parent_task_id = coro_info.task_name
+ stack = [_format_stack_entry(frame) for frame in call_stack]
+ awaits.append((parent_task_id, stack, task_id))
+ return id2name, awaits, task_stacks
+
+
+def _build_tree(id2name, awaits, task_stacks):
+ id2label = {(NodeType.TASK, tid): name for tid, name in id2name.items()}
+ children = defaultdict(list)
+ cor_nodes = defaultdict(dict) # Maps parent -> {frame_name: node_key}
+ next_cor_id = count(1)
+
+ def get_or_create_cor_node(parent, frame):
+ """Get existing coroutine node or create new one under parent"""
+ if frame in cor_nodes[parent]:
+ return cor_nodes[parent][frame]
+
+ node_key = (NodeType.COROUTINE, f"c{next(next_cor_id)}")
+ id2label[node_key] = frame
+ children[parent].append(node_key)
+ cor_nodes[parent][frame] = node_key
+ return node_key
+
+ # Build task dependency tree with coroutine frames
+ for parent_id, stack, child_id in awaits:
+ cur = (NodeType.TASK, parent_id)
+ for frame in reversed(stack):
+ cur = get_or_create_cor_node(cur, frame)
+
+ child_key = (NodeType.TASK, child_id)
+ if child_key not in children[cur]:
+ children[cur].append(child_key)
+
+ # Add coroutine stacks for leaf tasks
+ awaiting_tasks = {parent_id for parent_id, _, _ in awaits}
+ for task_id in id2name:
+ if task_id not in awaiting_tasks and task_id in task_stacks:
+ cur = (NodeType.TASK, task_id)
+ for frame in reversed(task_stacks[task_id]):
+ cur = get_or_create_cor_node(cur, frame)
+
+ return id2label, children
+
+
+def _roots(id2label, children):
+ all_children = {c for kids in children.values() for c in kids}
+ return [n for n in id2label if n not in all_children]
+
+# ─── detect cycles in the task-to-task graph ───────────────────────
+def _task_graph(awaits):
+ """Return {parent_task_id: {child_task_id, …}, …}."""
+ g = defaultdict(set)
+ for parent_id, _stack, child_id in awaits:
+ g[parent_id].add(child_id)
+ return g
+
+
+def _find_cycles(graph):
+ """
+ Depth-first search for back-edges.
+
+ Returns a list of cycles (each cycle is a list of task-ids) or an
+ empty list if the graph is acyclic.
+ """
+ WHITE, GREY, BLACK = 0, 1, 2
+ color = defaultdict(lambda: WHITE)
+ path, cycles = [], []
+
+ def dfs(v):
+ color[v] = GREY
+ path.append(v)
+ for w in graph.get(v, ()):
+ if color[w] == WHITE:
+ dfs(w)
+ elif color[w] == GREY: # back-edge → cycle!
+ i = path.index(w)
+ cycles.append(path[i:] + [w]) # make a copy
+ color[v] = BLACK
+ path.pop()
+
+ for v in list(graph):
+ if color[v] == WHITE:
+ dfs(v)
+ return cycles
+
+
+# ─── PRINT TREE FUNCTION ───────────────────────────────────────
+def get_all_awaited_by(pid):
+ unwinder = RemoteUnwinder(pid)
+ return unwinder.get_all_awaited_by()
+
+
+def build_async_tree(result, task_emoji="(T)", cor_emoji=""):
+ """
+ Build a list of strings for pretty-print an async call tree.
+
+ The call tree is produced by `get_all_async_stacks()`, prefixing tasks
+ with `task_emoji` and coroutine frames with `cor_emoji`.
+ """
+ id2name, awaits, task_stacks = _index(result)
+ g = _task_graph(awaits)
+ cycles = _find_cycles(g)
+ if cycles:
+ raise CycleFoundException(cycles, id2name)
+ labels, children = _build_tree(id2name, awaits, task_stacks)
+
+ def pretty(node):
+ flag = task_emoji if node[0] == NodeType.TASK else cor_emoji
+ return f"{flag} {labels[node]}"
+
+ def render(node, prefix="", last=True, buf=None):
+ if buf is None:
+ buf = []
+ buf.append(f"{prefix}{'└── ' if last else '├── '}{pretty(node)}")
+ new_pref = prefix + (" " if last else "│ ")
+ kids = children.get(node, [])
+ for i, kid in enumerate(kids):
+ render(kid, new_pref, i == len(kids) - 1, buf)
+ return buf
+
+ return [render(root) for root in _roots(labels, children)]
+
+
+def build_task_table(result):
+ id2name, _, _ = _index(result)
+ table = []
+
+ for awaited_info in result:
+ thread_id = awaited_info.thread_id
+ for task_info in awaited_info.awaited_by:
+ # Get task info
+ task_id = task_info.task_id
+ task_name = task_info.task_name
+
+ # Build coroutine stack string
+ frames = [frame for coro in task_info.coroutine_stack
+ for frame in coro.call_stack]
+ coro_stack = " -> ".join(_format_stack_entry(x).split(" ")[0]
+ for x in frames)
+
+ # Handle tasks with no awaiters
+ if not task_info.awaited_by:
+ table.append([thread_id, hex(task_id), task_name, coro_stack,
+ "", "", "0x0"])
+ continue
+
+ # Handle tasks with awaiters
+ for coro_info in task_info.awaited_by:
+ parent_id = coro_info.task_name
+ awaiter_frames = [_format_stack_entry(x).split(" ")[0]
+ for x in coro_info.call_stack]
+ awaiter_chain = " -> ".join(awaiter_frames)
+ awaiter_name = id2name.get(parent_id, "Unknown")
+ parent_id_str = (hex(parent_id) if isinstance(parent_id, int)
+ else str(parent_id))
+
+ table.append([thread_id, hex(task_id), task_name, coro_stack,
+ awaiter_chain, awaiter_name, parent_id_str])
+
+ return table
+
+def _print_cycle_exception(exception: CycleFoundException):
+ print("ERROR: await-graph contains cycles - cannot print a tree!", file=sys.stderr)
+ print("", file=sys.stderr)
+ for c in exception.cycles:
+ inames = " → ".join(exception.id2name.get(tid, hex(tid)) for tid in c)
+ print(f"cycle: {inames}", file=sys.stderr)
+
+
+def _get_awaited_by_tasks(pid: int) -> list:
+ try:
+ return get_all_awaited_by(pid)
+ except RuntimeError as e:
+ while e.__context__ is not None:
+ e = e.__context__
+ print(f"Error retrieving tasks: {e}")
+ sys.exit(1)
+
+
+def display_awaited_by_tasks_table(pid: int) -> None:
+ """Build and print a table of all pending tasks under `pid`."""
+
+ tasks = _get_awaited_by_tasks(pid)
+ table = build_task_table(tasks)
+ # Print the table in a simple tabular format
+ print(
+ f"{'tid':<10} {'task id':<20} {'task name':<20} {'coroutine stack':<50} {'awaiter chain':<50} {'awaiter name':<15} {'awaiter id':<15}"
+ )
+ print("-" * 180)
+ for row in table:
+ print(f"{row[0]:<10} {row[1]:<20} {row[2]:<20} {row[3]:<50} {row[4]:<50} {row[5]:<15} {row[6]:<15}")
+
+
+def display_awaited_by_tasks_tree(pid: int) -> None:
+ """Build and print a tree of all pending tasks under `pid`."""
+
+ tasks = _get_awaited_by_tasks(pid)
+ try:
+ result = build_async_tree(tasks)
+ except CycleFoundException as e:
+ _print_cycle_exception(e)
+ sys.exit(1)
+
+ for tree in result:
+ print("\n".join(tree))
diff --git a/Lib/calendar.py b/Lib/calendar.py
index 01a76ff8e78..3be1b50500e 100644
--- a/Lib/calendar.py
+++ b/Lib/calendar.py
@@ -565,7 +565,7 @@ class HTMLCalendar(Calendar):
Return a formatted year as a complete HTML page.
"""
if encoding is None:
- encoding = sys.getdefaultencoding()
+ encoding = 'utf-8'
v = []
a = v.append
a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
@@ -810,7 +810,7 @@ def timegm(tuple):
def main(args=None):
import argparse
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
textgroup = parser.add_argument_group('text only arguments')
htmlgroup = parser.add_argument_group('html only arguments')
textgroup.add_argument(
@@ -846,7 +846,7 @@ def main(args=None):
parser.add_argument(
"-e", "--encoding",
default=None,
- help="encoding to use for output"
+ help="encoding to use for output (default utf-8)"
)
parser.add_argument(
"-t", "--type",
@@ -890,7 +890,7 @@ def main(args=None):
cal.setfirstweekday(options.first_weekday)
encoding = options.encoding
if encoding is None:
- encoding = sys.getdefaultencoding()
+ encoding = 'utf-8'
optdict = dict(encoding=encoding, css=options.css)
write = sys.stdout.buffer.write
if options.year is None:
diff --git a/Lib/cmd.py b/Lib/cmd.py
index 438b88aa104..51495fb3216 100644
--- a/Lib/cmd.py
+++ b/Lib/cmd.py
@@ -273,7 +273,7 @@ class Cmd:
endidx = readline.get_endidx() - stripped
if begidx>0:
cmd, args, foo = self.parseline(line)
- if cmd == '':
+ if not cmd:
compfunc = self.completedefault
else:
try:
diff --git a/Lib/code.py b/Lib/code.py
index 41331dfd071..f7e275d8801 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -224,7 +224,7 @@ class InteractiveConsole(InteractiveInterpreter):
sys.ps1 = ">>> "
delete_ps1_after = True
try:
- _ps2 = sys.ps2
+ sys.ps2
delete_ps2_after = False
except AttributeError:
sys.ps2 = "... "
@@ -385,7 +385,7 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None, local_exit=Fa
if __name__ == "__main__":
import argparse
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument('-q', action='store_true',
help="don't print version and copyright messages")
args = parser.parse_args()
diff --git a/Lib/compileall.py b/Lib/compileall.py
index 47e2446356e..67fe370451e 100644
--- a/Lib/compileall.py
+++ b/Lib/compileall.py
@@ -317,7 +317,9 @@ def main():
import argparse
parser = argparse.ArgumentParser(
- description='Utilities to support installing Python libraries.')
+ description='Utilities to support installing Python libraries.',
+ color=True,
+ )
parser.add_argument('-l', action='store_const', const=0,
default=None, dest='maxlevels',
help="don't recurse into subdirectories")
diff --git a/Lib/compression/bz2/__init__.py b/Lib/compression/bz2.py
index 16815d6cd20..16815d6cd20 100644
--- a/Lib/compression/bz2/__init__.py
+++ b/Lib/compression/bz2.py
diff --git a/Lib/compression/gzip/__init__.py b/Lib/compression/gzip.py
index 552f48f948a..552f48f948a 100644
--- a/Lib/compression/gzip/__init__.py
+++ b/Lib/compression/gzip.py
diff --git a/Lib/compression/lzma/__init__.py b/Lib/compression/lzma.py
index b4bc7ccb1db..b4bc7ccb1db 100644
--- a/Lib/compression/lzma/__init__.py
+++ b/Lib/compression/lzma.py
diff --git a/Lib/compression/zlib/__init__.py b/Lib/compression/zlib.py
index 3aa7e2db90e..3aa7e2db90e 100644
--- a/Lib/compression/zlib/__init__.py
+++ b/Lib/compression/zlib.py
diff --git a/Lib/compression/zstd/__init__.py b/Lib/compression/zstd/__init__.py
new file mode 100644
index 00000000000..84b25914b0a
--- /dev/null
+++ b/Lib/compression/zstd/__init__.py
@@ -0,0 +1,242 @@
+"""Python bindings to the Zstandard (zstd) compression library (RFC-8878)."""
+
+__all__ = (
+ # compression.zstd
+ 'COMPRESSION_LEVEL_DEFAULT',
+ 'compress',
+ 'CompressionParameter',
+ 'decompress',
+ 'DecompressionParameter',
+ 'finalize_dict',
+ 'get_frame_info',
+ 'Strategy',
+ 'train_dict',
+
+ # compression.zstd._zstdfile
+ 'open',
+ 'ZstdFile',
+
+ # _zstd
+ 'get_frame_size',
+ 'zstd_version',
+ 'zstd_version_info',
+ 'ZstdCompressor',
+ 'ZstdDecompressor',
+ 'ZstdDict',
+ 'ZstdError',
+)
+
+import _zstd
+import enum
+from _zstd import (ZstdCompressor, ZstdDecompressor, ZstdDict, ZstdError,
+ get_frame_size, zstd_version)
+from compression.zstd._zstdfile import ZstdFile, open, _nbytes
+
+# zstd_version_number is (MAJOR * 100 * 100 + MINOR * 100 + RELEASE)
+zstd_version_info = (*divmod(_zstd.zstd_version_number // 100, 100),
+ _zstd.zstd_version_number % 100)
+"""Version number of the runtime zstd library as a tuple of integers."""
+
+COMPRESSION_LEVEL_DEFAULT = _zstd.ZSTD_CLEVEL_DEFAULT
+"""The default compression level for Zstandard, currently '3'."""
+
+
+class FrameInfo:
+ """Information about a Zstandard frame."""
+
+ __slots__ = 'decompressed_size', 'dictionary_id'
+
+ def __init__(self, decompressed_size, dictionary_id):
+ super().__setattr__('decompressed_size', decompressed_size)
+ super().__setattr__('dictionary_id', dictionary_id)
+
+ def __repr__(self):
+ return (f'FrameInfo(decompressed_size={self.decompressed_size}, '
+ f'dictionary_id={self.dictionary_id})')
+
+ def __setattr__(self, name, _):
+ raise AttributeError(f"can't set attribute {name!r}")
+
+
+def get_frame_info(frame_buffer):
+ """Get Zstandard frame information from a frame header.
+
+ *frame_buffer* is a bytes-like object. It should start from the beginning
+ of a frame, and needs to include at least the frame header (6 to 18 bytes).
+
+ The returned FrameInfo object has two attributes.
+ 'decompressed_size' is the size in bytes of the data in the frame when
+ decompressed, or None when the decompressed size is unknown.
+ 'dictionary_id' is an int in the range (0, 2**32). The special value 0
+ means that the dictionary ID was not recorded in the frame header,
+ the frame may or may not need a dictionary to be decoded,
+ and the ID of such a dictionary is not specified.
+ """
+ return FrameInfo(*_zstd.get_frame_info(frame_buffer))
+
+
+def train_dict(samples, dict_size):
+ """Return a ZstdDict representing a trained Zstandard dictionary.
+
+ *samples* is an iterable of samples, where a sample is a bytes-like
+ object representing a file.
+
+ *dict_size* is the dictionary's maximum size, in bytes.
+ """
+ if not isinstance(dict_size, int):
+ ds_cls = type(dict_size).__qualname__
+ raise TypeError(f'dict_size must be an int object, not {ds_cls!r}.')
+
+ samples = tuple(samples)
+ chunks = b''.join(samples)
+ chunk_sizes = tuple(_nbytes(sample) for sample in samples)
+ if not chunks:
+ raise ValueError("samples contained no data; can't train dictionary.")
+ dict_content = _zstd.train_dict(chunks, chunk_sizes, dict_size)
+ return ZstdDict(dict_content)
+
+
+def finalize_dict(zstd_dict, /, samples, dict_size, level):
+ """Return a ZstdDict representing a finalized Zstandard dictionary.
+
+ Given a custom content as a basis for dictionary, and a set of samples,
+ finalize *zstd_dict* by adding headers and statistics according to the
+ Zstandard dictionary format.
+
+ You may compose an effective dictionary content by hand, which is used as
+ basis dictionary, and use some samples to finalize a dictionary. The basis
+ dictionary may be a "raw content" dictionary. See *is_raw* in ZstdDict.
+
+ *samples* is an iterable of samples, where a sample is a bytes-like object
+ representing a file.
+ *dict_size* is the dictionary's maximum size, in bytes.
+ *level* is the expected compression level. The statistics for each
+ compression level differ, so tuning the dictionary to the compression level
+ can provide improvements.
+ """
+
+ if not isinstance(zstd_dict, ZstdDict):
+ raise TypeError('zstd_dict argument should be a ZstdDict object.')
+ if not isinstance(dict_size, int):
+ raise TypeError('dict_size argument should be an int object.')
+ if not isinstance(level, int):
+ raise TypeError('level argument should be an int object.')
+
+ samples = tuple(samples)
+ chunks = b''.join(samples)
+ chunk_sizes = tuple(_nbytes(sample) for sample in samples)
+ if not chunks:
+ raise ValueError("The samples are empty content, can't finalize the "
+ "dictionary.")
+ dict_content = _zstd.finalize_dict(zstd_dict.dict_content, chunks,
+ chunk_sizes, dict_size, level)
+ return ZstdDict(dict_content)
+
+
+def compress(data, level=None, options=None, zstd_dict=None):
+ """Return Zstandard compressed *data* as bytes.
+
+ *level* is an int specifying the compression level to use, defaulting to
+ COMPRESSION_LEVEL_DEFAULT ('3').
+ *options* is a dict object that contains advanced compression
+ parameters. See CompressionParameter for more on options.
+ *zstd_dict* is a ZstdDict object, a pre-trained Zstandard dictionary. See
+ the function train_dict for how to train a ZstdDict on sample data.
+
+ For incremental compression, use a ZstdCompressor instead.
+ """
+ comp = ZstdCompressor(level=level, options=options, zstd_dict=zstd_dict)
+ return comp.compress(data, mode=ZstdCompressor.FLUSH_FRAME)
+
+
+def decompress(data, zstd_dict=None, options=None):
+ """Decompress one or more frames of Zstandard compressed *data*.
+
+ *zstd_dict* is a ZstdDict object, a pre-trained Zstandard dictionary. See
+ the function train_dict for how to train a ZstdDict on sample data.
+ *options* is a dict object that contains advanced compression
+ parameters. See DecompressionParameter for more on options.
+
+ For incremental decompression, use a ZstdDecompressor instead.
+ """
+ results = []
+ while True:
+ decomp = ZstdDecompressor(options=options, zstd_dict=zstd_dict)
+ results.append(decomp.decompress(data))
+ if not decomp.eof:
+ raise ZstdError('Compressed data ended before the '
+ 'end-of-stream marker was reached')
+ data = decomp.unused_data
+ if not data:
+ break
+ return b''.join(results)
+
+
+class CompressionParameter(enum.IntEnum):
+ """Compression parameters."""
+
+ compression_level = _zstd.ZSTD_c_compressionLevel
+ window_log = _zstd.ZSTD_c_windowLog
+ hash_log = _zstd.ZSTD_c_hashLog
+ chain_log = _zstd.ZSTD_c_chainLog
+ search_log = _zstd.ZSTD_c_searchLog
+ min_match = _zstd.ZSTD_c_minMatch
+ target_length = _zstd.ZSTD_c_targetLength
+ strategy = _zstd.ZSTD_c_strategy
+
+ enable_long_distance_matching = _zstd.ZSTD_c_enableLongDistanceMatching
+ ldm_hash_log = _zstd.ZSTD_c_ldmHashLog
+ ldm_min_match = _zstd.ZSTD_c_ldmMinMatch
+ ldm_bucket_size_log = _zstd.ZSTD_c_ldmBucketSizeLog
+ ldm_hash_rate_log = _zstd.ZSTD_c_ldmHashRateLog
+
+ content_size_flag = _zstd.ZSTD_c_contentSizeFlag
+ checksum_flag = _zstd.ZSTD_c_checksumFlag
+ dict_id_flag = _zstd.ZSTD_c_dictIDFlag
+
+ nb_workers = _zstd.ZSTD_c_nbWorkers
+ job_size = _zstd.ZSTD_c_jobSize
+ overlap_log = _zstd.ZSTD_c_overlapLog
+
+ def bounds(self):
+ """Return the (lower, upper) int bounds of a compression parameter.
+
+ Both the lower and upper bounds are inclusive.
+ """
+ return _zstd.get_param_bounds(self.value, is_compress=True)
+
+
+class DecompressionParameter(enum.IntEnum):
+ """Decompression parameters."""
+
+ window_log_max = _zstd.ZSTD_d_windowLogMax
+
+ def bounds(self):
+ """Return the (lower, upper) int bounds of a decompression parameter.
+
+ Both the lower and upper bounds are inclusive.
+ """
+ return _zstd.get_param_bounds(self.value, is_compress=False)
+
+
+class Strategy(enum.IntEnum):
+ """Compression strategies, listed from fastest to strongest.
+
+ Note that new strategies might be added in the future.
+ Only the order (from fast to strong) is guaranteed,
+ the numeric value might change.
+ """
+
+ fast = _zstd.ZSTD_fast
+ dfast = _zstd.ZSTD_dfast
+ greedy = _zstd.ZSTD_greedy
+ lazy = _zstd.ZSTD_lazy
+ lazy2 = _zstd.ZSTD_lazy2
+ btlazy2 = _zstd.ZSTD_btlazy2
+ btopt = _zstd.ZSTD_btopt
+ btultra = _zstd.ZSTD_btultra
+ btultra2 = _zstd.ZSTD_btultra2
+
+
+# Check validity of the CompressionParameter & DecompressionParameter types
+_zstd.set_parameter_types(CompressionParameter, DecompressionParameter)
diff --git a/Lib/compression/zstd/_zstdfile.py b/Lib/compression/zstd/_zstdfile.py
new file mode 100644
index 00000000000..d709f5efc65
--- /dev/null
+++ b/Lib/compression/zstd/_zstdfile.py
@@ -0,0 +1,345 @@
+import io
+from os import PathLike
+from _zstd import ZstdCompressor, ZstdDecompressor, ZSTD_DStreamOutSize
+from compression._common import _streams
+
+__all__ = ('ZstdFile', 'open')
+
+_MODE_CLOSED = 0
+_MODE_READ = 1
+_MODE_WRITE = 2
+
+
+def _nbytes(dat, /):
+ if isinstance(dat, (bytes, bytearray)):
+ return len(dat)
+ with memoryview(dat) as mv:
+ return mv.nbytes
+
+
+class ZstdFile(_streams.BaseStream):
+ """A file-like object providing transparent Zstandard (de)compression.
+
+ A ZstdFile can act as a wrapper for an existing file object, or refer
+ directly to a named file on disk.
+
+ ZstdFile provides a *binary* file interface. Data is read and returned as
+ bytes, and may only be written to objects that support the Buffer Protocol.
+ """
+
+ FLUSH_BLOCK = ZstdCompressor.FLUSH_BLOCK
+ FLUSH_FRAME = ZstdCompressor.FLUSH_FRAME
+
+ def __init__(self, file, /, mode='r', *,
+ level=None, options=None, zstd_dict=None):
+ """Open a Zstandard compressed file in binary mode.
+
+ *file* can be either an file-like object, or a file name to open.
+
+ *mode* can be 'r' for reading (default), 'w' for (over)writing, 'x' for
+ creating exclusively, or 'a' for appending. These can equivalently be
+ given as 'rb', 'wb', 'xb' and 'ab' respectively.
+
+ *level* is an optional int specifying the compression level to use,
+ or COMPRESSION_LEVEL_DEFAULT if not given.
+
+ *options* is an optional dict for advanced compression parameters.
+ See CompressionParameter and DecompressionParameter for the possible
+ options.
+
+ *zstd_dict* is an optional ZstdDict object, a pre-trained Zstandard
+ dictionary. See train_dict() to train ZstdDict on sample data.
+ """
+ self._fp = None
+ self._close_fp = False
+ self._mode = _MODE_CLOSED
+ self._buffer = None
+
+ if not isinstance(mode, str):
+ raise ValueError('mode must be a str')
+ if options is not None and not isinstance(options, dict):
+ raise TypeError('options must be a dict or None')
+ mode = mode.removesuffix('b') # handle rb, wb, xb, ab
+ if mode == 'r':
+ if level is not None:
+ raise TypeError('level is illegal in read mode')
+ self._mode = _MODE_READ
+ elif mode in {'w', 'a', 'x'}:
+ if level is not None and not isinstance(level, int):
+ raise TypeError('level must be int or None')
+ self._mode = _MODE_WRITE
+ self._compressor = ZstdCompressor(level=level, options=options,
+ zstd_dict=zstd_dict)
+ self._pos = 0
+ else:
+ raise ValueError(f'Invalid mode: {mode!r}')
+
+ if isinstance(file, (str, bytes, PathLike)):
+ self._fp = io.open(file, f'{mode}b')
+ self._close_fp = True
+ elif ((mode == 'r' and hasattr(file, 'read'))
+ or (mode != 'r' and hasattr(file, 'write'))):
+ self._fp = file
+ else:
+ raise TypeError('file must be a file-like object '
+ 'or a str, bytes, or PathLike object')
+
+ if self._mode == _MODE_READ:
+ raw = _streams.DecompressReader(
+ self._fp,
+ ZstdDecompressor,
+ zstd_dict=zstd_dict,
+ options=options,
+ )
+ self._buffer = io.BufferedReader(raw)
+
+ def close(self):
+ """Flush and close the file.
+
+ May be called multiple times. Once the file has been closed,
+ any other operation on it will raise ValueError.
+ """
+ if self._fp is None:
+ return
+ try:
+ if self._mode == _MODE_READ:
+ if getattr(self, '_buffer', None):
+ self._buffer.close()
+ self._buffer = None
+ elif self._mode == _MODE_WRITE:
+ self.flush(self.FLUSH_FRAME)
+ self._compressor = None
+ finally:
+ self._mode = _MODE_CLOSED
+ try:
+ if self._close_fp:
+ self._fp.close()
+ finally:
+ self._fp = None
+ self._close_fp = False
+
+ def write(self, data, /):
+ """Write a bytes-like object *data* to the file.
+
+ Returns the number of uncompressed bytes written, which is
+ always the length of data in bytes. Note that due to buffering,
+ the file on disk may not reflect the data written until .flush()
+ or .close() is called.
+ """
+ self._check_can_write()
+
+ length = _nbytes(data)
+
+ compressed = self._compressor.compress(data)
+ self._fp.write(compressed)
+ self._pos += length
+ return length
+
+ def flush(self, mode=FLUSH_BLOCK):
+ """Flush remaining data to the underlying stream.
+
+ The mode argument can be FLUSH_BLOCK or FLUSH_FRAME. Abuse of this
+ method will reduce compression ratio, use it only when necessary.
+
+ If the program is interrupted afterwards, all data can be recovered.
+ To ensure saving to disk, also need to use os.fsync(fd).
+
+ This method does nothing in reading mode.
+ """
+ if self._mode == _MODE_READ:
+ return
+ self._check_not_closed()
+ if mode not in {self.FLUSH_BLOCK, self.FLUSH_FRAME}:
+ raise ValueError('Invalid mode argument, expected either '
+ 'ZstdFile.FLUSH_FRAME or '
+ 'ZstdFile.FLUSH_BLOCK')
+ if self._compressor.last_mode == mode:
+ return
+ # Flush zstd block/frame, and write.
+ data = self._compressor.flush(mode)
+ self._fp.write(data)
+ if hasattr(self._fp, 'flush'):
+ self._fp.flush()
+
+ def read(self, size=-1):
+ """Read up to size uncompressed bytes from the file.
+
+ If size is negative or omitted, read until EOF is reached.
+ Returns b'' if the file is already at EOF.
+ """
+ if size is None:
+ size = -1
+ self._check_can_read()
+ return self._buffer.read(size)
+
+ def read1(self, size=-1):
+ """Read up to size uncompressed bytes, while trying to avoid
+ making multiple reads from the underlying stream. Reads up to a
+ buffer's worth of data if size is negative.
+
+ Returns b'' if the file is at EOF.
+ """
+ self._check_can_read()
+ if size < 0:
+ # Note this should *not* be io.DEFAULT_BUFFER_SIZE.
+ # ZSTD_DStreamOutSize is the minimum amount to read guaranteeing
+ # a full block is read.
+ size = ZSTD_DStreamOutSize
+ return self._buffer.read1(size)
+
+ def readinto(self, b):
+ """Read bytes into b.
+
+ Returns the number of bytes read (0 for EOF).
+ """
+ self._check_can_read()
+ return self._buffer.readinto(b)
+
+ def readinto1(self, b):
+ """Read bytes into b, while trying to avoid making multiple reads
+ from the underlying stream.
+
+ Returns the number of bytes read (0 for EOF).
+ """
+ self._check_can_read()
+ return self._buffer.readinto1(b)
+
+ def readline(self, size=-1):
+ """Read a line of uncompressed bytes from the file.
+
+ The terminating newline (if present) is retained. If size is
+ non-negative, no more than size bytes will be read (in which
+ case the line may be incomplete). Returns b'' if already at EOF.
+ """
+ self._check_can_read()
+ return self._buffer.readline(size)
+
+ def seek(self, offset, whence=io.SEEK_SET):
+ """Change the file position.
+
+ The new position is specified by offset, relative to the
+ position indicated by whence. Possible values for whence are:
+
+ 0: start of stream (default): offset must not be negative
+ 1: current stream position
+ 2: end of stream; offset must not be positive
+
+ Returns the new file position.
+
+ Note that seeking is emulated, so depending on the arguments,
+ this operation may be extremely slow.
+ """
+ self._check_can_read()
+
+ # BufferedReader.seek() checks seekable
+ return self._buffer.seek(offset, whence)
+
+ def peek(self, size=-1):
+ """Return buffered data without advancing the file position.
+
+ Always returns at least one byte of data, unless at EOF.
+ The exact number of bytes returned is unspecified.
+ """
+ # Relies on the undocumented fact that BufferedReader.peek() always
+ # returns at least one byte (except at EOF)
+ self._check_can_read()
+ return self._buffer.peek(size)
+
+ def __next__(self):
+ if ret := self._buffer.readline():
+ return ret
+ raise StopIteration
+
+ def tell(self):
+ """Return the current file position."""
+ self._check_not_closed()
+ if self._mode == _MODE_READ:
+ return self._buffer.tell()
+ elif self._mode == _MODE_WRITE:
+ return self._pos
+
+ def fileno(self):
+ """Return the file descriptor for the underlying file."""
+ self._check_not_closed()
+ return self._fp.fileno()
+
+ @property
+ def name(self):
+ self._check_not_closed()
+ return self._fp.name
+
+ @property
+ def mode(self):
+ return 'wb' if self._mode == _MODE_WRITE else 'rb'
+
+ @property
+ def closed(self):
+ """True if this file is closed."""
+ return self._mode == _MODE_CLOSED
+
+ def seekable(self):
+ """Return whether the file supports seeking."""
+ return self.readable() and self._buffer.seekable()
+
+ def readable(self):
+ """Return whether the file was opened for reading."""
+ self._check_not_closed()
+ return self._mode == _MODE_READ
+
+ def writable(self):
+ """Return whether the file was opened for writing."""
+ self._check_not_closed()
+ return self._mode == _MODE_WRITE
+
+
+def open(file, /, mode='rb', *, level=None, options=None, zstd_dict=None,
+ encoding=None, errors=None, newline=None):
+ """Open a Zstandard compressed file in binary or text mode.
+
+ file can be either a file name (given as a str, bytes, or PathLike object),
+ in which case the named file is opened, or it can be an existing file object
+ to read from or write to.
+
+ The mode parameter can be 'r', 'rb' (default), 'w', 'wb', 'x', 'xb', 'a',
+ 'ab' for binary mode, or 'rt', 'wt', 'xt', 'at' for text mode.
+
+ The level, options, and zstd_dict parameters specify the settings the same
+ as ZstdFile.
+
+ When using read mode (decompression), the options parameter is a dict
+ representing advanced decompression options. The level parameter is not
+ supported in this case. When using write mode (compression), only one of
+ level, an int representing the compression level, or options, a dict
+ representing advanced compression options, may be passed. In both modes,
+ zstd_dict is a ZstdDict instance containing a trained Zstandard dictionary.
+
+ For binary mode, this function is equivalent to the ZstdFile constructor:
+ ZstdFile(filename, mode, ...). In this case, the encoding, errors and
+ newline parameters must not be provided.
+
+ For text mode, an ZstdFile object is created, and wrapped in an
+ io.TextIOWrapper instance with the specified encoding, error handling
+ behavior, and line ending(s).
+ """
+
+ text_mode = 't' in mode
+ mode = mode.replace('t', '')
+
+ if text_mode:
+ if 'b' in mode:
+ raise ValueError(f'Invalid mode: {mode!r}')
+ else:
+ if encoding is not None:
+ raise ValueError('Argument "encoding" not supported in binary mode')
+ if errors is not None:
+ raise ValueError('Argument "errors" not supported in binary mode')
+ if newline is not None:
+ raise ValueError('Argument "newline" not supported in binary mode')
+
+ binary_file = ZstdFile(file, mode, level=level, options=options,
+ zstd_dict=zstd_dict)
+
+ if text_mode:
+ return io.TextIOWrapper(binary_file, encoding, errors, newline)
+ else:
+ return binary_file
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
index d98b1ebdd58..f506ce68aea 100644
--- a/Lib/concurrent/futures/_base.py
+++ b/Lib/concurrent/futures/_base.py
@@ -558,6 +558,33 @@ class Future(object):
self._condition.notify_all()
self._invoke_callbacks()
+ def _get_snapshot(self):
+ """Get a snapshot of the future's current state.
+
+ This method atomically retrieves the state in one lock acquisition,
+ which is significantly faster than multiple method calls.
+
+ Returns:
+ Tuple of (done, cancelled, result, exception)
+ - done: True if the future is done (cancelled or finished)
+ - cancelled: True if the future was cancelled
+ - result: The result if available and not cancelled
+ - exception: The exception if available and not cancelled
+ """
+ # Fast path: check if already finished without lock
+ if self._state == FINISHED:
+ return True, False, self._result, self._exception
+
+ # Need lock for other states since they can change
+ with self._condition:
+ # We have to check the state again after acquiring the lock
+ # because it may have changed in the meantime.
+ if self._state == FINISHED:
+ return True, False, self._result, self._exception
+ if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED}:
+ return True, True, None, None
+ return False, False, None, None
+
__class_getitem__ = classmethod(types.GenericAlias)
class Executor(object):
diff --git a/Lib/concurrent/futures/interpreter.py b/Lib/concurrent/futures/interpreter.py
index d17688dc9d7..cbb60ce80c1 100644
--- a/Lib/concurrent/futures/interpreter.py
+++ b/Lib/concurrent/futures/interpreter.py
@@ -1,69 +1,39 @@
"""Implements InterpreterPoolExecutor."""
-import contextlib
-import pickle
+from concurrent import interpreters
+import sys
import textwrap
from . import thread as _thread
-import _interpreters
-import _interpqueues
+import traceback
-class ExecutionFailed(_interpreters.InterpreterError):
- """An unhandled exception happened during execution."""
-
- def __init__(self, excinfo):
- msg = excinfo.formatted
- if not msg:
- if excinfo.type and excinfo.msg:
- msg = f'{excinfo.type.__name__}: {excinfo.msg}'
- else:
- msg = excinfo.type.__name__ or excinfo.msg
- super().__init__(msg)
- self.excinfo = excinfo
-
- def __str__(self):
+def do_call(results, func, args, kwargs):
+ try:
+ return func(*args, **kwargs)
+ except BaseException as exc:
+ # Send the captured exception out on the results queue,
+ # but still leave it unhandled for the interpreter to handle.
try:
- formatted = self.excinfo.errdisplay
- except Exception:
- return super().__str__()
- else:
- return textwrap.dedent(f"""
-{super().__str__()}
-
-Uncaught in the interpreter:
-
-{formatted}
- """.strip())
-
-
-UNBOUND = 2 # error; this should not happen.
+ results.put(exc)
+ except interpreters.NotShareableError:
+ # The exception is not shareable.
+ print('exception is not shareable:', file=sys.stderr)
+ traceback.print_exception(exc)
+ results.put(None)
+ raise # re-raise
class WorkerContext(_thread.WorkerContext):
@classmethod
- def prepare(cls, initializer, initargs, shared):
+ def prepare(cls, initializer, initargs):
def resolve_task(fn, args, kwargs):
if isinstance(fn, str):
# XXX Circle back to this later.
raise TypeError('scripts not supported')
- if args or kwargs:
- raise ValueError(f'a script does not take args or kwargs, got {args!r} and {kwargs!r}')
- data = textwrap.dedent(fn)
- kind = 'script'
- # Make sure the script compiles.
- # Ideally we wouldn't throw away the resulting code
- # object. However, there isn't much to be done until
- # code objects are shareable and/or we do a better job
- # of supporting code objects in _interpreters.exec().
- compile(data, '<string>', 'exec')
else:
- # Functions defined in the __main__ module can't be pickled,
- # so they can't be used here. In the future, we could possibly
- # borrow from multiprocessing to work around this.
- data = pickle.dumps((fn, args, kwargs))
- kind = 'function'
- return (data, kind)
+ task = (fn, args, kwargs)
+ return task
if initializer is not None:
try:
@@ -75,73 +45,24 @@ class WorkerContext(_thread.WorkerContext):
else:
initdata = None
def create_context():
- return cls(initdata, shared)
+ return cls(initdata)
return create_context, resolve_task
- @classmethod
- @contextlib.contextmanager
- def _capture_exc(cls, resultsid):
- try:
- yield
- except BaseException as exc:
- # Send the captured exception out on the results queue,
- # but still leave it unhandled for the interpreter to handle.
- err = pickle.dumps(exc)
- _interpqueues.put(resultsid, (None, err), 1, UNBOUND)
- raise # re-raise
-
- @classmethod
- def _send_script_result(cls, resultsid):
- _interpqueues.put(resultsid, (None, None), 0, UNBOUND)
-
- @classmethod
- def _call(cls, func, args, kwargs, resultsid):
- with cls._capture_exc(resultsid):
- res = func(*args or (), **kwargs or {})
- # Send the result back.
- try:
- _interpqueues.put(resultsid, (res, None), 0, UNBOUND)
- except _interpreters.NotShareableError:
- res = pickle.dumps(res)
- _interpqueues.put(resultsid, (res, None), 1, UNBOUND)
-
- @classmethod
- def _call_pickled(cls, pickled, resultsid):
- with cls._capture_exc(resultsid):
- fn, args, kwargs = pickle.loads(pickled)
- cls._call(fn, args, kwargs, resultsid)
-
- def __init__(self, initdata, shared=None):
+ def __init__(self, initdata):
self.initdata = initdata
- self.shared = dict(shared) if shared else None
- self.interpid = None
- self.resultsid = None
+ self.interp = None
+ self.results = None
def __del__(self):
- if self.interpid is not None:
+ if self.interp is not None:
self.finalize()
- def _exec(self, script):
- assert self.interpid is not None
- excinfo = _interpreters.exec(self.interpid, script, restrict=True)
- if excinfo is not None:
- raise ExecutionFailed(excinfo)
-
def initialize(self):
- assert self.interpid is None, self.interpid
- self.interpid = _interpreters.create(reqrefs=True)
+ assert self.interp is None, self.interp
+ self.interp = interpreters.create()
try:
- _interpreters.incref(self.interpid)
-
maxsize = 0
- fmt = 0
- self.resultsid = _interpqueues.create(maxsize, fmt, UNBOUND)
-
- self._exec(f'from {__name__} import WorkerContext')
-
- if self.shared:
- _interpreters.set___main___attrs(
- self.interpid, self.shared, restrict=True)
+ self.results = interpreters.create_queue(maxsize)
if self.initdata:
self.run(self.initdata)
@@ -150,64 +71,25 @@ class WorkerContext(_thread.WorkerContext):
raise # re-raise
def finalize(self):
- interpid = self.interpid
- resultsid = self.resultsid
- self.resultsid = None
- self.interpid = None
- if resultsid is not None:
- try:
- _interpqueues.destroy(resultsid)
- except _interpqueues.QueueNotFoundError:
- pass
- if interpid is not None:
- try:
- _interpreters.decref(interpid)
- except _interpreters.InterpreterNotFoundError:
- pass
+ interp = self.interp
+ results = self.results
+ self.results = None
+ self.interp = None
+ if results is not None:
+ del results
+ if interp is not None:
+ interp.close()
def run(self, task):
- data, kind = task
- if kind == 'script':
- raise NotImplementedError('script kind disabled')
- script = f"""
-with WorkerContext._capture_exc({self.resultsid}):
-{textwrap.indent(data, ' ')}
-WorkerContext._send_script_result({self.resultsid})"""
- elif kind == 'function':
- script = f'WorkerContext._call_pickled({data!r}, {self.resultsid})'
- else:
- raise NotImplementedError(kind)
-
try:
- self._exec(script)
- except ExecutionFailed as exc:
- exc_wrapper = exc
- else:
- exc_wrapper = None
-
- # Return the result, or raise the exception.
- while True:
- try:
- obj = _interpqueues.get(self.resultsid)
- except _interpqueues.QueueNotFoundError:
+ return self.interp.call(do_call, self.results, *task)
+ except interpreters.ExecutionFailed as wrapper:
+ # Wait for the exception data to show up.
+ exc = self.results.get()
+ if exc is None:
+ # The exception must have been not shareable.
raise # re-raise
- except _interpqueues.QueueError:
- continue
- except ModuleNotFoundError:
- # interpreters.queues doesn't exist, which means
- # QueueEmpty doesn't. Act as though it does.
- continue
- else:
- break
- (res, excdata), pickled, unboundop = obj
- assert unboundop is None, unboundop
- if excdata is not None:
- assert res is None, res
- assert pickled
- assert exc_wrapper is not None
- exc = pickle.loads(excdata)
- raise exc from exc_wrapper
- return pickle.loads(res) if pickled else res
+ raise exc from wrapper
class BrokenInterpreterPool(_thread.BrokenThreadPool):
@@ -221,11 +103,11 @@ class InterpreterPoolExecutor(_thread.ThreadPoolExecutor):
BROKEN = BrokenInterpreterPool
@classmethod
- def prepare_context(cls, initializer, initargs, shared):
- return WorkerContext.prepare(initializer, initargs, shared)
+ def prepare_context(cls, initializer, initargs):
+ return WorkerContext.prepare(initializer, initargs)
def __init__(self, max_workers=None, thread_name_prefix='',
- initializer=None, initargs=(), shared=None):
+ initializer=None, initargs=()):
"""Initializes a new InterpreterPoolExecutor instance.
Args:
@@ -235,8 +117,6 @@ class InterpreterPoolExecutor(_thread.ThreadPoolExecutor):
initializer: A callable or script used to initialize
each worker interpreter.
initargs: A tuple of arguments to pass to the initializer.
- shared: A mapping of shareabled objects to be inserted into
- each worker interpreter.
"""
super().__init__(max_workers, thread_name_prefix,
- initializer, initargs, shared=shared)
+ initializer, initargs)
diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py
index 76b7b2abe83..a14650bf5fa 100644
--- a/Lib/concurrent/futures/process.py
+++ b/Lib/concurrent/futures/process.py
@@ -755,6 +755,11 @@ class ProcessPoolExecutor(_base.Executor):
self._executor_manager_thread_wakeup
def _adjust_process_count(self):
+ # gh-132969: avoid error when state is reset and executor is still running,
+ # which will happen when shutdown(wait=False) is called.
+ if self._processes is None:
+ return
+
# if there's an idle process, we don't need to spawn a new one.
if self._idle_worker_semaphore.acquire(blocking=False):
return
diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/concurrent/interpreters/__init__.py
index e067f259364..0fd661249a2 100644
--- a/Lib/test/support/interpreters/__init__.py
+++ b/Lib/concurrent/interpreters/__init__.py
@@ -9,6 +9,10 @@ from _interpreters import (
InterpreterError, InterpreterNotFoundError, NotShareableError,
is_shareable,
)
+from ._queues import (
+ create as create_queue,
+ Queue, QueueEmpty, QueueFull,
+)
__all__ = [
@@ -20,21 +24,6 @@ __all__ = [
]
-_queuemod = None
-
-def __getattr__(name):
- if name in ('Queue', 'QueueEmpty', 'QueueFull', 'create_queue'):
- global create_queue, Queue, QueueEmpty, QueueFull
- ns = globals()
- from .queues import (
- create as create_queue,
- Queue, QueueEmpty, QueueFull,
- )
- return ns[name]
- else:
- raise AttributeError(name)
-
-
_EXEC_FAILURE_STR = """
{superstr}
@@ -226,33 +215,32 @@ class Interpreter:
if excinfo is not None:
raise ExecutionFailed(excinfo)
- def call(self, callable, /):
- """Call the object in the interpreter with given args/kwargs.
+ def _call(self, callable, args, kwargs):
+ res, excinfo = _interpreters.call(self._id, callable, args, kwargs, restrict=True)
+ if excinfo is not None:
+ raise ExecutionFailed(excinfo)
+ return res
- Only functions that take no arguments and have no closure
- are supported.
+ def call(self, callable, /, *args, **kwargs):
+ """Call the object in the interpreter with given args/kwargs.
- The return value is discarded.
+ Nearly all callables, args, kwargs, and return values are
+ supported. All "shareable" objects are supported, as are
+ "stateless" functions (meaning non-closures that do not use
+ any globals). This method will fall back to pickle.
If the callable raises an exception then the error display
- (including full traceback) is send back between the interpreters
+ (including full traceback) is sent back between the interpreters
and an ExecutionFailed exception is raised, much like what
happens with Interpreter.exec().
"""
- # XXX Support args and kwargs.
- # XXX Support arbitrary callables.
- # XXX Support returning the return value (e.g. via pickle).
- excinfo = _interpreters.call(self._id, callable, restrict=True)
- if excinfo is not None:
- raise ExecutionFailed(excinfo)
+ return self._call(callable, args, kwargs)
- def call_in_thread(self, callable, /):
+ def call_in_thread(self, callable, /, *args, **kwargs):
"""Return a new thread that calls the object in the interpreter.
The return value and any raised exception are discarded.
"""
- def task():
- self.call(callable)
- t = threading.Thread(target=task)
+ t = threading.Thread(target=self._call, args=(callable, args, kwargs))
t.start()
return t
diff --git a/Lib/test/support/interpreters/_crossinterp.py b/Lib/concurrent/interpreters/_crossinterp.py
index 544e197ba4c..f47eb693ac8 100644
--- a/Lib/test/support/interpreters/_crossinterp.py
+++ b/Lib/concurrent/interpreters/_crossinterp.py
@@ -61,7 +61,7 @@ class UnboundItem:
def __repr__(self):
return f'{self._MODULE}.{self._NAME}'
-# return f'interpreters.queues.UNBOUND'
+# return f'interpreters._queues.UNBOUND'
UNBOUND = object.__new__(UnboundItem)
diff --git a/Lib/test/support/interpreters/queues.py b/Lib/concurrent/interpreters/_queues.py
index deb8e8613af..99987f2f692 100644
--- a/Lib/test/support/interpreters/queues.py
+++ b/Lib/concurrent/interpreters/_queues.py
@@ -1,6 +1,5 @@
"""Cross-interpreter Queues High Level Module."""
-import pickle
import queue
import time
import weakref
@@ -63,29 +62,34 @@ def _resolve_unbound(flag):
return resolved
-def create(maxsize=0, *, syncobj=False, unbounditems=UNBOUND):
+def create(maxsize=0, *, unbounditems=UNBOUND):
"""Return a new cross-interpreter queue.
The queue may be used to pass data safely between interpreters.
- "syncobj" sets the default for Queue.put()
- and Queue.put_nowait().
-
- "unbounditems" likewise sets the default. See Queue.put() for
+ "unbounditems" sets the default for Queue.put(); see that method for
supported values. The default value is UNBOUND, which replaces
the unbound item.
"""
- fmt = _SHARED_ONLY if syncobj else _PICKLED
unbound = _serialize_unbound(unbounditems)
unboundop, = unbound
- qid = _queues.create(maxsize, fmt, unboundop)
- return Queue(qid, _fmt=fmt, _unbound=unbound)
+ qid = _queues.create(maxsize, unboundop, -1)
+ self = Queue(qid)
+ self._set_unbound(unboundop, unbounditems)
+ return self
def list_all():
"""Return a list of all open queues."""
- return [Queue(qid, _fmt=fmt, _unbound=(unboundop,))
- for qid, fmt, unboundop in _queues.list_all()]
+ queues = []
+ for qid, unboundop, _ in _queues.list_all():
+ self = Queue(qid)
+ if not hasattr(self, '_unbound'):
+ self._set_unbound(unboundop)
+ else:
+ assert self._unbound[0] == unboundop
+ queues.append(self)
+ return queues
_known_queues = weakref.WeakValueDictionary()
@@ -93,28 +97,17 @@ _known_queues = weakref.WeakValueDictionary()
class Queue:
"""A cross-interpreter queue."""
- def __new__(cls, id, /, *, _fmt=None, _unbound=None):
+ def __new__(cls, id, /):
# There is only one instance for any given ID.
if isinstance(id, int):
id = int(id)
else:
raise TypeError(f'id must be an int, got {id!r}')
- if _fmt is None:
- if _unbound is None:
- _fmt, op = _queues.get_queue_defaults(id)
- _unbound = (op,)
- else:
- _fmt, _ = _queues.get_queue_defaults(id)
- elif _unbound is None:
- _, op = _queues.get_queue_defaults(id)
- _unbound = (op,)
try:
self = _known_queues[id]
except KeyError:
self = super().__new__(cls)
self._id = id
- self._fmt = _fmt
- self._unbound = _unbound
_known_queues[id] = self
_queues.bind(id)
return self
@@ -143,11 +136,28 @@ class Queue:
def __getstate__(self):
return None
+ def _set_unbound(self, op, items=None):
+ assert not hasattr(self, '_unbound')
+ if items is None:
+ items = _resolve_unbound(op)
+ unbound = (op, items)
+ self._unbound = unbound
+ return unbound
+
@property
def id(self):
return self._id
@property
+ def unbounditems(self):
+ try:
+ _, items = self._unbound
+ except AttributeError:
+ op, _ = _queues.get_queue_defaults(self._id)
+ _, items = self._set_unbound(op)
+ return items
+
+ @property
def maxsize(self):
try:
return self._maxsize
@@ -165,77 +175,56 @@ class Queue:
return _queues.get_count(self._id)
def put(self, obj, timeout=None, *,
- syncobj=None,
- unbound=None,
+ unbounditems=None,
_delay=10 / 1000, # 10 milliseconds
):
"""Add the object to the queue.
This blocks while the queue is full.
- If "syncobj" is None (the default) then it uses the
- queue's default, set with create_queue().
-
- If "syncobj" is false then all objects are supported,
- at the expense of worse performance.
-
- If "syncobj" is true then the object must be "shareable".
- Examples of "shareable" objects include the builtin singletons,
- str, and memoryview. One benefit is that such objects are
- passed through the queue efficiently.
-
- The key difference, though, is conceptual: the corresponding
- object returned from Queue.get() will be strictly equivalent
- to the given obj. In other words, the two objects will be
- effectively indistinguishable from each other, even if the
- object is mutable. The received object may actually be the
- same object, or a copy (immutable values only), or a proxy.
- Regardless, the received object should be treated as though
- the original has been shared directly, whether or not it
- actually is. That's a slightly different and stronger promise
- than just (initial) equality, which is all "syncobj=False"
- can promise.
-
- "unbound" controls the behavior of Queue.get() for the given
+ For most objects, the object received through Queue.get() will
+ be a new one, equivalent to the original and not sharing any
+ actual underlying data. The notable exceptions include
+ cross-interpreter types (like Queue) and memoryview, where the
+ underlying data is actually shared. Furthermore, some types
+ can be sent through a queue more efficiently than others. This
+ group includes various immutable types like int, str, bytes, and
+ tuple (if the items are likewise efficiently shareable). See interpreters.is_shareable().
+
+ "unbounditems" controls the behavior of Queue.get() for the given
object if the current interpreter (calling put()) is later
destroyed.
- If "unbound" is None (the default) then it uses the
+ If "unbounditems" is None (the default) then it uses the
queue's default, set with create_queue(),
which is usually UNBOUND.
- If "unbound" is UNBOUND_ERROR then get() will raise an
+ If "unbounditems" is UNBOUND_ERROR then get() will raise an
ItemInterpreterDestroyed exception if the original interpreter
has been destroyed. This does not otherwise affect the queue;
the next call to put() will work like normal, returning the next
item in the queue.
- If "unbound" is UNBOUND_REMOVE then the item will be removed
+ If "unbounditems" is UNBOUND_REMOVE then the item will be removed
from the queue as soon as the original interpreter is destroyed.
Be aware that this will introduce an imbalance between put()
and get() calls.
- If "unbound" is UNBOUND then it is returned by get() in place
+ If "unbounditems" is UNBOUND then it is returned by get() in place
of the unbound item.
"""
- if syncobj is None:
- fmt = self._fmt
- else:
- fmt = _SHARED_ONLY if syncobj else _PICKLED
- if unbound is None:
- unboundop, = self._unbound
+ if unbounditems is None:
+ unboundop = -1
else:
- unboundop, = _serialize_unbound(unbound)
+ unboundop, = _serialize_unbound(unbounditems)
if timeout is not None:
timeout = int(timeout)
if timeout < 0:
raise ValueError(f'timeout value must be non-negative')
end = time.time() + timeout
- if fmt is _PICKLED:
- obj = pickle.dumps(obj)
while True:
try:
- _queues.put(self._id, obj, fmt, unboundop)
+ _queues.put(self._id, obj, unboundop)
except QueueFull as exc:
if timeout is not None and time.time() >= end:
raise # re-raise
@@ -243,18 +232,12 @@ class Queue:
else:
break
- def put_nowait(self, obj, *, syncobj=None, unbound=None):
- if syncobj is None:
- fmt = self._fmt
+ def put_nowait(self, obj, *, unbounditems=None):
+ if unbounditems is None:
+ unboundop = -1
else:
- fmt = _SHARED_ONLY if syncobj else _PICKLED
- if unbound is None:
- unboundop, = self._unbound
- else:
- unboundop, = _serialize_unbound(unbound)
- if fmt is _PICKLED:
- obj = pickle.dumps(obj)
- _queues.put(self._id, obj, fmt, unboundop)
+ unboundop, = _serialize_unbound(unbounditems)
+ _queues.put(self._id, obj, unboundop)
def get(self, timeout=None, *,
_delay=10 / 1000, # 10 milliseconds
@@ -265,7 +248,7 @@ class Queue:
If the next item's original interpreter has been destroyed
then the "next object" is determined by the value of the
- "unbound" argument to put().
+ "unbounditems" argument to put().
"""
if timeout is not None:
timeout = int(timeout)
@@ -274,7 +257,7 @@ class Queue:
end = time.time() + timeout
while True:
try:
- obj, fmt, unboundop = _queues.get(self._id)
+ obj, unboundop = _queues.get(self._id)
except QueueEmpty as exc:
if timeout is not None and time.time() >= end:
raise # re-raise
@@ -284,10 +267,6 @@ class Queue:
if unboundop is not None:
assert obj is None, repr(obj)
return _resolve_unbound(unboundop)
- if fmt == _PICKLED:
- obj = pickle.loads(obj)
- else:
- assert fmt == _SHARED_ONLY
return obj
def get_nowait(self):
@@ -297,16 +276,12 @@ class Queue:
is the same as get().
"""
try:
- obj, fmt, unboundop = _queues.get(self._id)
+ obj, unboundop = _queues.get(self._id)
except QueueEmpty as exc:
raise # re-raise
if unboundop is not None:
assert obj is None, repr(obj)
return _resolve_unbound(unboundop)
- if fmt == _PICKLED:
- obj = pickle.loads(obj)
- else:
- assert fmt == _SHARED_ONLY
return obj
diff --git a/Lib/configparser.py b/Lib/configparser.py
index 239fda60a02..18af1eadaad 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -1218,11 +1218,14 @@ class RawConfigParser(MutableMapping):
def _validate_key_contents(self, key):
"""Raises an InvalidWriteError for any keys containing
- delimiters or that match the section header pattern"""
+ delimiters or that begins with the section header pattern"""
if re.match(self.SECTCRE, key):
- raise InvalidWriteError("Cannot write keys matching section pattern")
- if any(delim in key for delim in self._delimiters):
- raise InvalidWriteError("Cannot write key that contains delimiters")
+ raise InvalidWriteError(
+ f"Cannot write key {key}; begins with section pattern")
+ for delim in self._delimiters:
+ if delim in key:
+ raise InvalidWriteError(
+ f"Cannot write key {key}; contains delimiter {delim}")
def _validate_value_types(self, *, section="", option="", value=""):
"""Raises a TypeError for illegal non-string values.
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index 823a3692fd1..d6d07a13f75 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -379,12 +379,6 @@ def create_unicode_buffer(init, size=None):
return buf
raise TypeError(init)
-
-def SetPointerType(pointer, cls):
- import warnings
- warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
- pointer.set_type(cls)
-
def ARRAY(typ, len):
return typ * len
diff --git a/Lib/ctypes/_layout.py b/Lib/ctypes/_layout.py
index 0719e72cfed..2048ccb6a1c 100644
--- a/Lib/ctypes/_layout.py
+++ b/Lib/ctypes/_layout.py
@@ -5,6 +5,7 @@ may change at any time.
"""
import sys
+import warnings
from _ctypes import CField, buffer_info
import ctypes
@@ -66,9 +67,26 @@ def get_layout(cls, input_fields, is_struct, base):
# For clarity, variables that count bits have `bit` in their names.
+ pack = getattr(cls, '_pack_', None)
+
layout = getattr(cls, '_layout_', None)
if layout is None:
- if sys.platform == 'win32' or getattr(cls, '_pack_', None):
+ if sys.platform == 'win32':
+ gcc_layout = False
+ elif pack:
+ if is_struct:
+ base_type_name = 'Structure'
+ else:
+ base_type_name = 'Union'
+ warnings._deprecated(
+ '_pack_ without _layout_',
+ f"Due to '_pack_', the '{cls.__name__}' {base_type_name} will "
+ + "use memory layout compatible with MSVC (Windows). "
+ + "If this is intended, set _layout_ to 'ms'. "
+ + "The implicit default is deprecated and slated to become "
+ + "an error in Python {remove}.",
+ remove=(3, 19),
+ )
gcc_layout = False
else:
gcc_layout = True
@@ -95,7 +113,6 @@ def get_layout(cls, input_fields, is_struct, base):
else:
big_endian = sys.byteorder == 'big'
- pack = getattr(cls, '_pack_', None)
if pack is not None:
try:
pack = int(pack)
diff --git a/Lib/curses/__init__.py b/Lib/curses/__init__.py
index 6165fe6c987..605d5fcbec5 100644
--- a/Lib/curses/__init__.py
+++ b/Lib/curses/__init__.py
@@ -30,9 +30,8 @@ def initscr():
fd=_sys.__stdout__.fileno())
stdscr = _curses.initscr()
for key, value in _curses.__dict__.items():
- if key[0:4] == 'ACS_' or key in ('LINES', 'COLS'):
+ if key.startswith('ACS_') or key in ('LINES', 'COLS'):
setattr(curses, key, value)
-
return stdscr
# This is a similar wrapper for start_color(), which adds the COLORS and
@@ -41,12 +40,9 @@ def initscr():
def start_color():
import _curses, curses
- retval = _curses.start_color()
- if hasattr(_curses, 'COLORS'):
- curses.COLORS = _curses.COLORS
- if hasattr(_curses, 'COLOR_PAIRS'):
- curses.COLOR_PAIRS = _curses.COLOR_PAIRS
- return retval
+ _curses.start_color()
+ curses.COLORS = _curses.COLORS
+ curses.COLOR_PAIRS = _curses.COLOR_PAIRS
# Import Python has_key() implementation if _curses doesn't contain has_key()
@@ -85,10 +81,11 @@ def wrapper(func, /, *args, **kwds):
# Start color, too. Harmless if the terminal doesn't have
# color; user can test with has_color() later on. The try/catch
# works around a minor bit of over-conscientiousness in the curses
- # module -- the error return from C start_color() is ignorable.
+ # module -- the error return from C start_color() is ignorable,
+ # unless they are raised by the interpreter due to other issues.
try:
start_color()
- except:
+ except _curses.error:
pass
return func(stdscr, *args, **kwds)
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 0f7dc9ae6b8..86d29df0639 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -244,6 +244,10 @@ _ATOMIC_TYPES = frozenset({
property,
})
+# Any marker is used in `make_dataclass` to mark unannotated fields as `Any`
+# without importing `typing` module.
+_ANY_MARKER = object()
+
class InitVar:
__slots__ = ('type', )
@@ -1591,7 +1595,7 @@ def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
for item in fields:
if isinstance(item, str):
name = item
- tp = 'typing.Any'
+ tp = _ANY_MARKER
elif len(item) == 2:
name, tp, = item
elif len(item) == 3:
@@ -1610,15 +1614,49 @@ def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
seen.add(name)
annotations[name] = tp
+ # We initially block the VALUE format, because inside dataclass() we'll
+ # call get_annotations(), which will try the VALUE format first. If we don't
+ # block, that means we'd always end up eagerly importing typing here, which
+ # is what we're trying to avoid.
+ value_blocked = True
+
+ def annotate_method(format):
+ def get_any():
+ match format:
+ case annotationlib.Format.STRING:
+ return 'typing.Any'
+ case annotationlib.Format.FORWARDREF:
+ typing = sys.modules.get("typing")
+ if typing is None:
+ return annotationlib.ForwardRef("Any", module="typing")
+ else:
+ return typing.Any
+ case annotationlib.Format.VALUE:
+ if value_blocked:
+ raise NotImplementedError
+ from typing import Any
+ return Any
+ case _:
+ raise NotImplementedError
+ annos = {
+ ann: get_any() if t is _ANY_MARKER else t
+ for ann, t in annotations.items()
+ }
+ if format == annotationlib.Format.STRING:
+ return annotationlib.annotations_to_string(annos)
+ else:
+ return annos
+
# Update 'ns' with the user-supplied namespace plus our calculated values.
def exec_body_callback(ns):
ns.update(namespace)
ns.update(defaults)
- ns['__annotations__'] = annotations
# We use `types.new_class()` instead of simply `type()` to allow dynamic creation
# of generic dataclasses.
cls = types.new_class(cls_name, bases, {}, exec_body_callback)
+ # For now, set annotations including the _ANY_MARKER.
+ cls.__annotate__ = annotate_method
# For pickling to work, the __module__ variable needs to be set to the frame
# where the dataclass is created.
@@ -1634,10 +1672,13 @@ def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
cls.__module__ = module
# Apply the normal provided decorator.
- return decorator(cls, init=init, repr=repr, eq=eq, order=order,
- unsafe_hash=unsafe_hash, frozen=frozen,
- match_args=match_args, kw_only=kw_only, slots=slots,
- weakref_slot=weakref_slot)
+ cls = decorator(cls, init=init, repr=repr, eq=eq, order=order,
+ unsafe_hash=unsafe_hash, frozen=frozen,
+ match_args=match_args, kw_only=kw_only, slots=slots,
+ weakref_slot=weakref_slot)
+ # Now that the class is ready, allow the VALUE format.
+ value_blocked = False
+ return cls
def replace(obj, /, **changes):
diff --git a/Lib/dbm/dumb.py b/Lib/dbm/dumb.py
index def120ffc37..1bc239a84ff 100644
--- a/Lib/dbm/dumb.py
+++ b/Lib/dbm/dumb.py
@@ -9,7 +9,7 @@ XXX TO DO:
- seems to contain a bug when updating...
- reclaim free space (currently, space once occupied by deleted or expanded
-items is never reused)
+items is not reused exept if .reorganize() is called)
- support concurrent access (currently, if two processes take turns making
updates, they can mess up the index)
@@ -17,8 +17,6 @@ updates, they can mess up the index)
- support efficient access to large databases (currently, the whole index
is read when the database is opened, and some updates rewrite the whole index)
-- support opening for read-only (flag = 'm')
-
"""
import ast as _ast
@@ -289,6 +287,34 @@ class _Database(collections.abc.MutableMapping):
def __exit__(self, *args):
self.close()
+ def reorganize(self):
+ if self._readonly:
+ raise error('The database is opened for reading only')
+ self._verify_open()
+ # Ensure all changes are committed before reorganizing.
+ self._commit()
+ # Open file in r+ to allow changing in-place.
+ with _io.open(self._datfile, 'rb+') as f:
+ reorganize_pos = 0
+
+ # Iterate over existing keys, sorted by starting byte.
+ for key in sorted(self._index, key = lambda k: self._index[k][0]):
+ pos, siz = self._index[key]
+ f.seek(pos)
+ val = f.read(siz)
+
+ f.seek(reorganize_pos)
+ f.write(val)
+ self._index[key] = (reorganize_pos, siz)
+
+ blocks_occupied = (siz + _BLOCKSIZE - 1) // _BLOCKSIZE
+ reorganize_pos += blocks_occupied * _BLOCKSIZE
+
+ f.truncate(reorganize_pos)
+ # Commit changes to index, which were not in-place.
+ self._commit()
+
+
def open(file, flag='c', mode=0o666):
"""Open the database file, filename, and return corresponding object.
diff --git a/Lib/dbm/sqlite3.py b/Lib/dbm/sqlite3.py
index 7e0ae2a29e3..b296a1bcd1b 100644
--- a/Lib/dbm/sqlite3.py
+++ b/Lib/dbm/sqlite3.py
@@ -15,6 +15,7 @@ LOOKUP_KEY = "SELECT value FROM Dict WHERE key = CAST(? AS BLOB)"
STORE_KV = "REPLACE INTO Dict (key, value) VALUES (CAST(? AS BLOB), CAST(? AS BLOB))"
DELETE_KEY = "DELETE FROM Dict WHERE key = CAST(? AS BLOB)"
ITER_KEYS = "SELECT key FROM Dict"
+REORGANIZE = "VACUUM"
class error(OSError):
@@ -122,6 +123,9 @@ class _Database(MutableMapping):
def __exit__(self, *args):
self.close()
+ def reorganize(self):
+ self._execute(REORGANIZE)
+
def open(filename, /, flag="r", mode=0o666):
"""Open a dbm.sqlite3 database and return the dbm object.
diff --git a/Lib/difflib.py b/Lib/difflib.py
index f1f4e62514a..487936dbf47 100644
--- a/Lib/difflib.py
+++ b/Lib/difflib.py
@@ -78,8 +78,8 @@ class SequenceMatcher:
sequences. As a rule of thumb, a .ratio() value over 0.6 means the
sequences are close matches:
- >>> print(round(s.ratio(), 3))
- 0.866
+ >>> print(round(s.ratio(), 2))
+ 0.87
>>>
If you're only interested in where the sequences match,
@@ -1615,16 +1615,13 @@ def _mdiff(fromlines, tolines, context=None, linejunk=None,
_file_template = """
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<html>
-
+<!DOCTYPE html>
+<html lang="en">
<head>
- <meta http-equiv="Content-Type"
- content="text/html; charset=%(charset)s" />
- <title></title>
- <style type="text/css">%(styles)s
+ <meta charset="%(charset)s">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Diff comparison</title>
+ <style>%(styles)s
</style>
</head>
@@ -1636,13 +1633,36 @@ _file_template = """
_styles = """
:root {color-scheme: light dark}
- table.diff {font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; border:medium}
- .diff_header {background-color:#e0e0e0}
- td.diff_header {text-align:right}
- .diff_next {background-color:#c0c0c0}
+ table.diff {
+ font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
+ border: medium;
+ }
+ .diff_header {
+ background-color: #e0e0e0;
+ font-weight: bold;
+ }
+ td.diff_header {
+ text-align: right;
+ padding: 0 8px;
+ }
+ .diff_next {
+ background-color: #c0c0c0;
+ padding: 4px 0;
+ }
.diff_add {background-color:palegreen}
.diff_chg {background-color:#ffff77}
.diff_sub {background-color:#ffaaaa}
+ table.diff[summary="Legends"] {
+ margin-top: 20px;
+ border: 1px solid #ccc;
+ }
+ table.diff[summary="Legends"] th {
+ background-color: #e0e0e0;
+ padding: 4px 8px;
+ }
+ table.diff[summary="Legends"] td {
+ padding: 4px 8px;
+ }
@media (prefers-color-scheme: dark) {
.diff_header {background-color:#666}
@@ -1650,6 +1670,8 @@ _styles = """
.diff_add {background-color:darkgreen}
.diff_chg {background-color:#847415}
.diff_sub {background-color:darkred}
+ table.diff[summary="Legends"] {border-color:#555}
+ table.diff[summary="Legends"] th{background-color:#666}
}"""
_table_template = """
@@ -1692,7 +1714,7 @@ class HtmlDiff(object):
make_table -- generates HTML for a single side by side table
make_file -- generates complete HTML file with a single side by side table
- See tools/scripts/diff.py for an example usage of this class.
+ See Doc/includes/diff.py for an example usage of this class.
"""
_file_template = _file_template
diff --git a/Lib/dis.py b/Lib/dis.py
index cb6d077a391..d6d2c1386dd 100644
--- a/Lib/dis.py
+++ b/Lib/dis.py
@@ -1131,7 +1131,7 @@ class Bytecode:
def main(args=None):
import argparse
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument('-C', '--show-caches', action='store_true',
help='show inline caches')
parser.add_argument('-O', '--show-offsets', action='store_true',
diff --git a/Lib/doctest.py b/Lib/doctest.py
index e02e73ed722..c8c95ecbb27 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -101,6 +101,7 @@ import pdb
import re
import sys
import traceback
+import types
import unittest
from io import StringIO, IncrementalNewlineDecoder
from collections import namedtuple
@@ -385,7 +386,7 @@ class _OutputRedirectingPdb(pdb.Pdb):
self.__out = out
self.__debugger_used = False
# do not play signal games in the pdb
- pdb.Pdb.__init__(self, stdout=out, nosigint=True)
+ super().__init__(stdout=out, nosigint=True)
# still use input() to get user input
self.use_rawinput = 1
@@ -1278,6 +1279,11 @@ class DocTestRunner:
# Reporting methods
#/////////////////////////////////////////////////////////////////
+ def report_skip(self, out, test, example):
+ """
+ Report that the given example was skipped.
+ """
+
def report_start(self, out, test, example):
"""
Report that the test runner is about to process the given
@@ -1375,6 +1381,8 @@ class DocTestRunner:
# If 'SKIP' is set, then skip this example.
if self.optionflags & SKIP:
+ if not quiet:
+ self.report_skip(out, test, example)
skips += 1
continue
@@ -1395,11 +1403,11 @@ class DocTestRunner:
exec(compile(example.source, filename, "single",
compileflags, True), test.globs)
self.debugger.set_continue() # ==== Example Finished ====
- exception = None
+ exc_info = None
except KeyboardInterrupt:
raise
- except:
- exception = sys.exc_info()
+ except BaseException as exc:
+ exc_info = type(exc), exc, exc.__traceback__.tb_next
self.debugger.set_continue() # ==== Example Finished ====
got = self._fakeout.getvalue() # the actual output
@@ -1408,21 +1416,21 @@ class DocTestRunner:
# If the example executed without raising any exceptions,
# verify its output.
- if exception is None:
+ if exc_info is None:
if check(example.want, got, self.optionflags):
outcome = SUCCESS
# The example raised an exception: check if it was expected.
else:
- formatted_ex = traceback.format_exception_only(*exception[:2])
- if issubclass(exception[0], SyntaxError):
+ formatted_ex = traceback.format_exception_only(*exc_info[:2])
+ if issubclass(exc_info[0], SyntaxError):
# SyntaxError / IndentationError is special:
# we don't care about the carets / suggestions / etc
# We only care about the error message and notes.
# They start with `SyntaxError:` (or any other class name)
exception_line_prefixes = (
- f"{exception[0].__qualname__}:",
- f"{exception[0].__module__}.{exception[0].__qualname__}:",
+ f"{exc_info[0].__qualname__}:",
+ f"{exc_info[0].__module__}.{exc_info[0].__qualname__}:",
)
exc_msg_index = next(
index
@@ -1433,7 +1441,7 @@ class DocTestRunner:
exc_msg = "".join(formatted_ex)
if not quiet:
- got += _exception_traceback(exception)
+ got += _exception_traceback(exc_info)
# If `example.exc_msg` is None, then we weren't expecting
# an exception.
@@ -1462,7 +1470,7 @@ class DocTestRunner:
elif outcome is BOOM:
if not quiet:
self.report_unexpected_exception(out, test, example,
- exception)
+ exc_info)
failures += 1
else:
assert False, ("unknown outcome", outcome)
@@ -2272,12 +2280,63 @@ def set_unittest_reportflags(flags):
return old
+class _DocTestCaseRunner(DocTestRunner):
+
+ def __init__(self, *args, test_case, test_result, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._test_case = test_case
+ self._test_result = test_result
+ self._examplenum = 0
+
+ def _subTest(self):
+ subtest = unittest.case._SubTest(self._test_case, str(self._examplenum), {})
+ self._examplenum += 1
+ return subtest
+
+ def report_skip(self, out, test, example):
+ unittest.case._addSkip(self._test_result, self._subTest(), '')
+
+ def report_success(self, out, test, example, got):
+ self._test_result.addSubTest(self._test_case, self._subTest(), None)
+
+ def report_unexpected_exception(self, out, test, example, exc_info):
+ tb = self._add_traceback(exc_info[2], test, example)
+ exc_info = (*exc_info[:2], tb)
+ self._test_result.addSubTest(self._test_case, self._subTest(), exc_info)
+
+ def report_failure(self, out, test, example, got):
+ msg = ('Failed example:\n' + _indent(example.source) +
+ self._checker.output_difference(example, got, self.optionflags).rstrip('\n'))
+ exc = self._test_case.failureException(msg)
+ tb = self._add_traceback(None, test, example)
+ exc_info = (type(exc), exc, tb)
+ self._test_result.addSubTest(self._test_case, self._subTest(), exc_info)
+
+ def _add_traceback(self, traceback, test, example):
+ if test.lineno is None or example.lineno is None:
+ lineno = None
+ else:
+ lineno = test.lineno + example.lineno + 1
+ return types.SimpleNamespace(
+ tb_frame = types.SimpleNamespace(
+ f_globals=test.globs,
+ f_code=types.SimpleNamespace(
+ co_filename=test.filename,
+ co_name=test.name,
+ ),
+ ),
+ tb_next = traceback,
+ tb_lasti = -1,
+ tb_lineno = lineno,
+ )
+
+
class DocTestCase(unittest.TestCase):
def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
checker=None):
- unittest.TestCase.__init__(self)
+ super().__init__()
self._dt_optionflags = optionflags
self._dt_checker = checker
self._dt_test = test
@@ -2301,30 +2360,28 @@ class DocTestCase(unittest.TestCase):
test.globs.clear()
test.globs.update(self._dt_globs)
+ def run(self, result=None):
+ self._test_result = result
+ return super().run(result)
+
def runTest(self):
test = self._dt_test
- old = sys.stdout
- new = StringIO()
optionflags = self._dt_optionflags
+ result = self._test_result
if not (optionflags & REPORTING_FLAGS):
# The option flags don't include any reporting flags,
# so add the default reporting flags
optionflags |= _unittest_reportflags
+ if getattr(result, 'failfast', False):
+ optionflags |= FAIL_FAST
- runner = DocTestRunner(optionflags=optionflags,
- checker=self._dt_checker, verbose=False)
-
- try:
- runner.DIVIDER = "-"*70
- results = runner.run(test, out=new.write, clear_globs=False)
- if results.skipped == results.attempted:
- raise unittest.SkipTest("all examples were skipped")
- finally:
- sys.stdout = old
-
- if results.failed:
- raise self.failureException(self.format_failure(new.getvalue()))
+ runner = _DocTestCaseRunner(optionflags=optionflags,
+ checker=self._dt_checker, verbose=False,
+ test_case=self, test_result=result)
+ results = runner.run(test, clear_globs=False)
+ if results.skipped == results.attempted:
+ raise unittest.SkipTest("all examples were skipped")
def format_failure(self, err):
test = self._dt_test
@@ -2439,7 +2496,7 @@ class DocTestCase(unittest.TestCase):
class SkipDocTestCase(DocTestCase):
def __init__(self, module):
self.module = module
- DocTestCase.__init__(self, None)
+ super().__init__(None)
def setUp(self):
self.skipTest("DocTestSuite will not work with -O2 and above")
@@ -2870,7 +2927,7 @@ __test__ = {"_TestClass": _TestClass,
def _test():
import argparse
- parser = argparse.ArgumentParser(description="doctest runner")
+ parser = argparse.ArgumentParser(description="doctest runner", color=True)
parser.add_argument('-v', '--verbose', action='store_true', default=False,
help='print very verbose output for all tests')
parser.add_argument('-o', '--option', action='append',
diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py
index 9a51b943733..91243378dc0 100644
--- a/Lib/email/_header_value_parser.py
+++ b/Lib/email/_header_value_parser.py
@@ -1020,6 +1020,8 @@ def _get_ptext_to_endchars(value, endchars):
a flag that is True iff there were any quoted printables decoded.
"""
+ if not value:
+ return '', '', False
fragment, *remainder = _wsp_splitter(value, 1)
vchars = []
escape = False
@@ -1573,7 +1575,7 @@ def get_dtext(value):
def _check_for_early_dl_end(value, domain_literal):
if value:
return False
- domain_literal.append(errors.InvalidHeaderDefect(
+ domain_literal.defects.append(errors.InvalidHeaderDefect(
"end of input inside domain-literal"))
domain_literal.append(ValueTerminal(']', 'domain-literal-end'))
return True
@@ -1592,9 +1594,9 @@ def get_domain_literal(value):
raise errors.HeaderParseError("expected '[' at start of domain-literal "
"but found '{}'".format(value))
value = value[1:]
+ domain_literal.append(ValueTerminal('[', 'domain-literal-start'))
if _check_for_early_dl_end(value, domain_literal):
return domain_literal, value
- domain_literal.append(ValueTerminal('[', 'domain-literal-start'))
if value[0] in WSP:
token, value = get_fws(value)
domain_literal.append(token)
diff --git a/Lib/email/header.py b/Lib/email/header.py
index 113a81f4131..220a84a7454 100644
--- a/Lib/email/header.py
+++ b/Lib/email/header.py
@@ -59,16 +59,22 @@ _max_append = email.quoprimime._max_append
def decode_header(header):
"""Decode a message header value without converting charset.
- Returns a list of (string, charset) pairs containing each of the decoded
- parts of the header. Charset is None for non-encoded parts of the header,
- otherwise a lower-case string containing the name of the character set
- specified in the encoded string.
+ For historical reasons, this function may return either:
+
+ 1. A list of length 1 containing a pair (str, None).
+ 2. A list of (bytes, charset) pairs containing each of the decoded
+ parts of the header. Charset is None for non-encoded parts of the header,
+ otherwise a lower-case string containing the name of the character set
+ specified in the encoded string.
header may be a string that may or may not contain RFC2047 encoded words,
or it may be a Header object.
An email.errors.HeaderParseError may be raised when certain decoding error
occurs (e.g. a base64 decoding exception).
+
+ This function exists for backwards compatibility only. For new code, we
+ recommend using email.headerregistry.HeaderRegistry instead.
"""
# If it is a Header object, we can just return the encoded chunks.
if hasattr(header, '_chunks'):
@@ -161,6 +167,9 @@ def make_header(decoded_seq, maxlinelen=None, header_name=None,
This function takes one of those sequence of pairs and returns a Header
instance. Optional maxlinelen, header_name, and continuation_ws are as in
the Header constructor.
+
+ This function exists for backwards compatibility only, and is not
+ recommended for use in new code.
"""
h = Header(maxlinelen=maxlinelen, header_name=header_name,
continuation_ws=continuation_ws)
diff --git a/Lib/email/message.py b/Lib/email/message.py
index 87fcab68868..41fcc2b9778 100644
--- a/Lib/email/message.py
+++ b/Lib/email/message.py
@@ -564,7 +564,7 @@ class Message:
msg.add_header('content-disposition', 'attachment', filename='bud.gif')
msg.add_header('content-disposition', 'attachment',
- filename=('utf-8', '', Fußballer.ppt'))
+ filename=('utf-8', '', 'Fußballer.ppt'))
msg.add_header('content-disposition', 'attachment',
filename='Fußballer.ppt'))
"""
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
index 7eab74dc0db..3de1f0d24a1 100644
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -417,8 +417,14 @@ def decode_params(params):
for name, continuations in rfc2231_params.items():
value = []
extended = False
- # Sort by number
- continuations.sort()
+ # Sort by number, treating None as 0 if there is no 0,
+ # and ignore it if there is already a 0.
+ has_zero = any(x[0] == 0 for x in continuations)
+ if has_zero:
+ continuations = [x for x in continuations if x[0] is not None]
+ else:
+ continuations = [(x[0] or 0, x[1], x[2]) for x in continuations]
+ continuations.sort(key=lambda x: x[0])
# And now append all values in numerical order, converting
# %-encodings for the encoded segments. If any of the
# continuation names ends in a *, then the entire string, after
diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py
index a94bb270671..4ecb6b6e297 100644
--- a/Lib/encodings/aliases.py
+++ b/Lib/encodings/aliases.py
@@ -405,6 +405,8 @@ aliases = {
'iso_8859_8' : 'iso8859_8',
'iso_8859_8_1988' : 'iso8859_8',
'iso_ir_138' : 'iso8859_8',
+ 'iso_8859_8_i' : 'iso8859_8',
+ 'iso_8859_8_e' : 'iso8859_8',
# iso8859_9 codec
'csisolatin5' : 'iso8859_9',
diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py
index 60a8d5eb227..0c90b4c9fe1 100644
--- a/Lib/encodings/idna.py
+++ b/Lib/encodings/idna.py
@@ -316,7 +316,7 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
def _buffer_decode(self, input, errors, final):
if errors != 'strict':
- raise UnicodeError("Unsupported error handling: {errors}")
+ raise UnicodeError(f"Unsupported error handling: {errors}")
if not input:
return ("", 0)
diff --git a/Lib/encodings/palmos.py b/Lib/encodings/palmos.py
index c506d654523..df164ca5b95 100644
--- a/Lib/encodings/palmos.py
+++ b/Lib/encodings/palmos.py
@@ -201,7 +201,7 @@ decoding_table = (
'\u02dc' # 0x98 -> SMALL TILDE
'\u2122' # 0x99 -> TRADE MARK SIGN
'\u0161' # 0x9A -> LATIN SMALL LETTER S WITH CARON
- '\x9b' # 0x9B -> <control>
+ '\u203a' # 0x9B -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
'\u0153' # 0x9C -> LATIN SMALL LIGATURE OE
'\x9d' # 0x9D -> <control>
'\x9e' # 0x9E -> <control>
diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py
index 6fc9f39b24c..aa641e94a8b 100644
--- a/Lib/ensurepip/__init__.py
+++ b/Lib/ensurepip/__init__.py
@@ -205,7 +205,7 @@ def _uninstall_helper(*, verbosity=0):
def _main(argv=None):
import argparse
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument(
"--version",
action="version",
diff --git a/Lib/fractions.py b/Lib/fractions.py
index 8163e3bb594..a8c67068522 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -168,9 +168,9 @@ _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
# A '0' that's *not* followed by another digit is parsed as a minimum width
# rather than a zeropad flag.
(?P<zeropad>0(?=[0-9]))?
- (?P<minimumwidth>0|[1-9][0-9]*)?
+ (?P<minimumwidth>[0-9]+)?
(?P<thousands_sep>[,_])?
- (?:\.(?P<precision>0|[1-9][0-9]*))?
+ (?:\.(?P<precision>[0-9]+))?
(?P<presentation_type>[eEfFgG%])
""", re.DOTALL | re.VERBOSE).fullmatch
@@ -238,11 +238,6 @@ class Fraction(numbers.Rational):
self._denominator = 1
return self
- elif isinstance(numerator, numbers.Rational):
- self._numerator = numerator.numerator
- self._denominator = numerator.denominator
- return self
-
elif (isinstance(numerator, float) or
(not isinstance(numerator, type) and
hasattr(numerator, 'as_integer_ratio'))):
@@ -278,6 +273,11 @@ class Fraction(numbers.Rational):
if m.group('sign') == '-':
numerator = -numerator
+ elif isinstance(numerator, numbers.Rational):
+ self._numerator = numerator.numerator
+ self._denominator = numerator.denominator
+ return self
+
else:
raise TypeError("argument should be a string or a Rational "
"instance or have the as_integer_ratio() method")
@@ -504,6 +504,9 @@ class Fraction(numbers.Rational):
trim_point = not alternate_form
exponent_indicator = "E" if presentation_type in "EFG" else "e"
+ if align == '=' and fill == '0':
+ zeropad = True
+
# Round to get the digits we need, figure out where to place the point,
# and decide whether to use scientific notation. 'point_pos' is the
# relative to the _end_ of the digit string: that is, it's the number
diff --git a/Lib/functools.py b/Lib/functools.py
index 714070c6ac9..7f0eac3f650 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -323,6 +323,9 @@ def _partial_new(cls, func, /, *args, **keywords):
"or a descriptor")
if args and args[-1] is Placeholder:
raise TypeError("trailing Placeholders are not allowed")
+ for value in keywords.values():
+ if value is Placeholder:
+ raise TypeError("Placeholder cannot be passed as a keyword argument")
if isinstance(func, base_cls):
pto_phcount = func._phcount
tot_args = func.args
diff --git a/Lib/genericpath.py b/Lib/genericpath.py
index ba7b0a13c7f..9363f564aab 100644
--- a/Lib/genericpath.py
+++ b/Lib/genericpath.py
@@ -8,7 +8,7 @@ import stat
__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
'getsize', 'isdevdrive', 'isdir', 'isfile', 'isjunction', 'islink',
- 'lexists', 'samefile', 'sameopenfile', 'samestat']
+ 'lexists', 'samefile', 'sameopenfile', 'samestat', 'ALLOW_MISSING']
# Does a path exist?
@@ -189,3 +189,12 @@ def _check_arg_types(funcname, *args):
f'os.PathLike object, not {s.__class__.__name__!r}') from None
if hasstr and hasbytes:
raise TypeError("Can't mix strings and bytes in path components") from None
+
+# A singleton with a true boolean value.
+@object.__new__
+class ALLOW_MISSING:
+ """Special value for use in realpath()."""
+ def __repr__(self):
+ return 'os.path.ALLOW_MISSING'
+ def __reduce__(self):
+ return self.__class__.__name__
diff --git a/Lib/getpass.py b/Lib/getpass.py
index bd0097ced94..1dd40e25e09 100644
--- a/Lib/getpass.py
+++ b/Lib/getpass.py
@@ -1,6 +1,7 @@
"""Utilities to get a password and/or the current user name.
-getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
+getpass(prompt[, stream[, echo_char]]) - Prompt for a password, with echo
+turned off and optional keyboard feedback.
getuser() - Get the user name from the environment or password database.
GetPassWarning - This UserWarning is issued when getpass() cannot prevent
@@ -25,13 +26,15 @@ __all__ = ["getpass","getuser","GetPassWarning"]
class GetPassWarning(UserWarning): pass
-def unix_getpass(prompt='Password: ', stream=None):
+def unix_getpass(prompt='Password: ', stream=None, *, echo_char=None):
"""Prompt for a password, with echo turned off.
Args:
prompt: Written on stream to ask for the input. Default: 'Password: '
stream: A writable file object to display the prompt. Defaults to
the tty. If no tty is available defaults to sys.stderr.
+ echo_char: A string used to mask input (e.g., '*'). If None, input is
+ hidden.
Returns:
The seKr3t input.
Raises:
@@ -40,6 +43,8 @@ def unix_getpass(prompt='Password: ', stream=None):
Always restores terminal settings before returning.
"""
+ _check_echo_char(echo_char)
+
passwd = None
with contextlib.ExitStack() as stack:
try:
@@ -68,12 +73,16 @@ def unix_getpass(prompt='Password: ', stream=None):
old = termios.tcgetattr(fd) # a copy to save
new = old[:]
new[3] &= ~termios.ECHO # 3 == 'lflags'
+ if echo_char:
+ new[3] &= ~termios.ICANON
tcsetattr_flags = termios.TCSAFLUSH
if hasattr(termios, 'TCSASOFT'):
tcsetattr_flags |= termios.TCSASOFT
try:
termios.tcsetattr(fd, tcsetattr_flags, new)
- passwd = _raw_input(prompt, stream, input=input)
+ passwd = _raw_input(prompt, stream, input=input,
+ echo_char=echo_char)
+
finally:
termios.tcsetattr(fd, tcsetattr_flags, old)
stream.flush() # issue7208
@@ -93,10 +102,11 @@ def unix_getpass(prompt='Password: ', stream=None):
return passwd
-def win_getpass(prompt='Password: ', stream=None):
+def win_getpass(prompt='Password: ', stream=None, *, echo_char=None):
"""Prompt for password with echo off, using Windows getwch()."""
if sys.stdin is not sys.__stdin__:
return fallback_getpass(prompt, stream)
+ _check_echo_char(echo_char)
for c in prompt:
msvcrt.putwch(c)
@@ -108,25 +118,39 @@ def win_getpass(prompt='Password: ', stream=None):
if c == '\003':
raise KeyboardInterrupt
if c == '\b':
+ if echo_char and pw:
+ msvcrt.putwch('\b')
+ msvcrt.putwch(' ')
+ msvcrt.putwch('\b')
pw = pw[:-1]
else:
pw = pw + c
+ if echo_char:
+ msvcrt.putwch(echo_char)
msvcrt.putwch('\r')
msvcrt.putwch('\n')
return pw
-def fallback_getpass(prompt='Password: ', stream=None):
+def fallback_getpass(prompt='Password: ', stream=None, *, echo_char=None):
+ _check_echo_char(echo_char)
import warnings
warnings.warn("Can not control echo on the terminal.", GetPassWarning,
stacklevel=2)
if not stream:
stream = sys.stderr
print("Warning: Password input may be echoed.", file=stream)
- return _raw_input(prompt, stream)
+ return _raw_input(prompt, stream, echo_char=echo_char)
+
+def _check_echo_char(echo_char):
+ # ASCII excluding control characters
+ if echo_char and not (echo_char.isprintable() and echo_char.isascii()):
+ raise ValueError("'echo_char' must be a printable ASCII string, "
+ f"got: {echo_char!r}")
-def _raw_input(prompt="", stream=None, input=None):
+
+def _raw_input(prompt="", stream=None, input=None, echo_char=None):
# This doesn't save the string in the GNU readline history.
if not stream:
stream = sys.stderr
@@ -143,6 +167,8 @@ def _raw_input(prompt="", stream=None, input=None):
stream.write(prompt)
stream.flush()
# NOTE: The Python C API calls flockfile() (and unlock) during readline.
+ if echo_char:
+ return _readline_with_echo_char(stream, input, echo_char)
line = input.readline()
if not line:
raise EOFError
@@ -151,6 +177,35 @@ def _raw_input(prompt="", stream=None, input=None):
return line
+def _readline_with_echo_char(stream, input, echo_char):
+ passwd = ""
+ eof_pressed = False
+ while True:
+ char = input.read(1)
+ if char == '\n' or char == '\r':
+ break
+ elif char == '\x03':
+ raise KeyboardInterrupt
+ elif char == '\x7f' or char == '\b':
+ if passwd:
+ stream.write("\b \b")
+ stream.flush()
+ passwd = passwd[:-1]
+ elif char == '\x04':
+ if eof_pressed:
+ break
+ else:
+ eof_pressed = True
+ elif char == '\x00':
+ continue
+ else:
+ passwd += char
+ stream.write(echo_char)
+ stream.flush()
+ eof_pressed = False
+ return passwd
+
+
def getuser():
"""Get the username from the environment or password database.
diff --git a/Lib/glob.py b/Lib/glob.py
index 341524282ba..1e48fe43167 100644
--- a/Lib/glob.py
+++ b/Lib/glob.py
@@ -358,6 +358,12 @@ class _GlobberBase:
"""
raise NotImplementedError
+ @staticmethod
+ def stringify_path(path):
+ """Converts the path to a string object
+ """
+ raise NotImplementedError
+
# High-level methods
def compile(self, pat, altsep=None):
@@ -466,8 +472,9 @@ class _GlobberBase:
select_next = self.selector(parts)
def select_recursive(path, exists=False):
- match_pos = len(str(path))
- if match is None or match(str(path), match_pos):
+ path_str = self.stringify_path(path)
+ match_pos = len(path_str)
+ if match is None or match(path_str, match_pos):
yield from select_next(path, exists)
stack = [path]
while stack:
@@ -489,7 +496,7 @@ class _GlobberBase:
pass
if is_dir or not dir_only:
- entry_path_str = str(entry_path)
+ entry_path_str = self.stringify_path(entry_path)
if dir_only:
entry_path = self.concat_path(entry_path, self.sep)
if match is None or match(entry_path_str, match_pos):
@@ -529,19 +536,6 @@ class _StringGlobber(_GlobberBase):
entries = list(scandir_it)
return ((entry, entry.name, entry.path) for entry in entries)
-
-class _PathGlobber(_GlobberBase):
- """Provides shell-style pattern matching and globbing for pathlib paths.
- """
-
@staticmethod
- def lexists(path):
- return path.info.exists(follow_symlinks=False)
-
- @staticmethod
- def scandir(path):
- return ((child.info, child.name, child) for child in path.iterdir())
-
- @staticmethod
- def concat_path(path, text):
- return path.with_segments(str(path) + text)
+ def stringify_path(path):
+ return path # Already a string.
diff --git a/Lib/gzip.py b/Lib/gzip.py
index b7375b25473..c00f51858de 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -667,7 +667,9 @@ def main():
from argparse import ArgumentParser
parser = ArgumentParser(description=
"A simple command line interface for the gzip module: act like gzip, "
- "but do not delete the input file.")
+ "but do not delete the input file.",
+ color=True,
+ )
group = parser.add_mutually_exclusive_group()
group.add_argument('--fast', action='store_true', help='compress faster')
group.add_argument('--best', action='store_true', help='compress better')
diff --git a/Lib/hashlib.py b/Lib/hashlib.py
index abacac22ea0..0e9bd98aa1f 100644
--- a/Lib/hashlib.py
+++ b/Lib/hashlib.py
@@ -141,29 +141,29 @@ def __get_openssl_constructor(name):
return __get_builtin_constructor(name)
-def __py_new(name, data=b'', **kwargs):
+def __py_new(name, *args, **kwargs):
"""new(name, data=b'', **kwargs) - Return a new hashing object using the
named algorithm; optionally initialized with data (which must be
a bytes-like object).
"""
- return __get_builtin_constructor(name)(data, **kwargs)
+ return __get_builtin_constructor(name)(*args, **kwargs)
-def __hash_new(name, data=b'', **kwargs):
+def __hash_new(name, *args, **kwargs):
"""new(name, data=b'') - Return a new hashing object using the named algorithm;
optionally initialized with data (which must be a bytes-like object).
"""
if name in __block_openssl_constructor:
# Prefer our builtin blake2 implementation.
- return __get_builtin_constructor(name)(data, **kwargs)
+ return __get_builtin_constructor(name)(*args, **kwargs)
try:
- return _hashlib.new(name, data, **kwargs)
+ return _hashlib.new(name, *args, **kwargs)
except ValueError:
# If the _hashlib module (OpenSSL) doesn't support the named
# hash, try using our builtin implementations.
# This allows for SHA224/256 and SHA384/512 support even though
# the OpenSSL library prior to 0.9.8 doesn't provide them.
- return __get_builtin_constructor(name)(data)
+ return __get_builtin_constructor(name)(*args, **kwargs)
try:
diff --git a/Lib/heapq.py b/Lib/heapq.py
index 9649da251f2..6ceb211f1ca 100644
--- a/Lib/heapq.py
+++ b/Lib/heapq.py
@@ -178,7 +178,7 @@ def heapify(x):
for i in reversed(range(n//2)):
_siftup(x, i)
-def _heappop_max(heap):
+def heappop_max(heap):
"""Maxheap version of a heappop."""
lastelt = heap.pop() # raises appropriate IndexError if heap is empty
if heap:
@@ -188,19 +188,32 @@ def _heappop_max(heap):
return returnitem
return lastelt
-def _heapreplace_max(heap, item):
+def heapreplace_max(heap, item):
"""Maxheap version of a heappop followed by a heappush."""
returnitem = heap[0] # raises appropriate IndexError if heap is empty
heap[0] = item
_siftup_max(heap, 0)
return returnitem
-def _heapify_max(x):
+def heappush_max(heap, item):
+ """Maxheap version of a heappush."""
+ heap.append(item)
+ _siftdown_max(heap, 0, len(heap)-1)
+
+def heappushpop_max(heap, item):
+ """Maxheap fast version of a heappush followed by a heappop."""
+ if heap and item < heap[0]:
+ item, heap[0] = heap[0], item
+ _siftup_max(heap, 0)
+ return item
+
+def heapify_max(x):
"""Transform list into a maxheap, in-place, in O(len(x)) time."""
n = len(x)
for i in reversed(range(n//2)):
_siftup_max(x, i)
+
# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos
# is the index of a leaf with a possibly out-of-order value. Restore the
# heap invariant.
@@ -335,9 +348,9 @@ def merge(*iterables, key=None, reverse=False):
h_append = h.append
if reverse:
- _heapify = _heapify_max
- _heappop = _heappop_max
- _heapreplace = _heapreplace_max
+ _heapify = heapify_max
+ _heappop = heappop_max
+ _heapreplace = heapreplace_max
direction = -1
else:
_heapify = heapify
@@ -490,10 +503,10 @@ def nsmallest(n, iterable, key=None):
result = [(elem, i) for i, elem in zip(range(n), it)]
if not result:
return result
- _heapify_max(result)
+ heapify_max(result)
top = result[0][0]
order = n
- _heapreplace = _heapreplace_max
+ _heapreplace = heapreplace_max
for elem in it:
if elem < top:
_heapreplace(result, (elem, order))
@@ -507,10 +520,10 @@ def nsmallest(n, iterable, key=None):
result = [(key(elem), i, elem) for i, elem in zip(range(n), it)]
if not result:
return result
- _heapify_max(result)
+ heapify_max(result)
top = result[0][0]
order = n
- _heapreplace = _heapreplace_max
+ _heapreplace = heapreplace_max
for elem in it:
k = key(elem)
if k < top:
@@ -583,19 +596,13 @@ try:
from _heapq import *
except ImportError:
pass
-try:
- from _heapq import _heapreplace_max
-except ImportError:
- pass
-try:
- from _heapq import _heapify_max
-except ImportError:
- pass
-try:
- from _heapq import _heappop_max
-except ImportError:
- pass
+# For backwards compatibility
+_heappop_max = heappop_max
+_heapreplace_max = heapreplace_max
+_heappush_max = heappush_max
+_heappushpop_max = heappushpop_max
+_heapify_max = heapify_max
if __name__ == "__main__":
diff --git a/Lib/html/parser.py b/Lib/html/parser.py
index 13c95c34e50..cc15de07b5b 100644
--- a/Lib/html/parser.py
+++ b/Lib/html/parser.py
@@ -12,6 +12,7 @@ import re
import _markupbase
from html import unescape
+from html.entities import html5 as html5_entities
__all__ = ['HTMLParser']
@@ -23,20 +24,50 @@ incomplete = re.compile('&[a-zA-Z#]')
entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
+attr_charref = re.compile(r'&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*)[;=]?')
starttagopen = re.compile('<[a-zA-Z]')
+endtagopen = re.compile('</[a-zA-Z]')
piclose = re.compile('>')
commentclose = re.compile(r'--\s*>')
# Note:
-# 1) if you change tagfind/attrfind remember to update locatestarttagend too;
-# 2) if you change tagfind/attrfind and/or locatestarttagend the parser will
+# 1) if you change tagfind/attrfind remember to update locatetagend too;
+# 2) if you change tagfind/attrfind and/or locatetagend the parser will
# explode, so don't do it.
-# see http://www.w3.org/TR/html5/tokenization.html#tag-open-state
-# and http://www.w3.org/TR/html5/tokenization.html#tag-name-state
-tagfind_tolerant = re.compile(r'([a-zA-Z][^\t\n\r\f />\x00]*)(?:\s|/(?!>))*')
-attrfind_tolerant = re.compile(
- r'((?<=[\'"\s/])[^\s/>][^\s/=>]*)(\s*=+\s*'
- r'(\'[^\']*\'|"[^"]*"|(?![\'"])[^>\s]*))?(?:\s|/(?!>))*')
+# see the HTML5 specs section "13.2.5.6 Tag open state",
+# "13.2.5.8 Tag name state" and "13.2.5.33 Attribute name state".
+# https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
+# https://html.spec.whatwg.org/multipage/parsing.html#tag-name-state
+# https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state
+tagfind_tolerant = re.compile(r'([a-zA-Z][^\t\n\r\f />]*)(?:[\t\n\r\f ]|/(?!>))*')
+attrfind_tolerant = re.compile(r"""
+ (
+ (?<=['"\t\n\r\f /])[^\t\n\r\f />][^\t\n\r\f /=>]* # attribute name
+ )
+ (= # value indicator
+ ('[^']*' # LITA-enclosed value
+ |"[^"]*" # LIT-enclosed value
+ |(?!['"])[^>\t\n\r\f ]* # bare value
+ )
+ )?
+ (?:[\t\n\r\f ]|/(?!>))* # possibly followed by a space
+""", re.VERBOSE)
+locatetagend = re.compile(r"""
+ [a-zA-Z][^\t\n\r\f />]* # tag name
+ [\t\n\r\f /]* # optional whitespace before attribute name
+ (?:(?<=['"\t\n\r\f /])[^\t\n\r\f />][^\t\n\r\f /=>]* # attribute name
+ (?:= # value indicator
+ (?:'[^']*' # LITA-enclosed value
+ |"[^"]*" # LIT-enclosed value
+ |(?!['"])[^>\t\n\r\f ]* # bare value
+ )
+ )?
+ [\t\n\r\f /]* # possibly followed by a space
+ )*
+ >?
+""", re.VERBOSE)
+# The following variables are not used, but are temporarily left for
+# backward compatibility.
locatestarttagend_tolerant = re.compile(r"""
<[a-zA-Z][^\t\n\r\f />\x00]* # tag name
(?:[\s/]* # optional whitespace before attribute name
@@ -53,10 +84,24 @@ locatestarttagend_tolerant = re.compile(r"""
\s* # trailing whitespace
""", re.VERBOSE)
endendtag = re.compile('>')
-# the HTML 5 spec, section 8.1.2.2, doesn't allow spaces between
-# </ and the tag name, so maybe this should be fixed
endtagfind = re.compile(r'</\s*([a-zA-Z][-.a-zA-Z0-9:_]*)\s*>')
+# Character reference processing logic specific to attribute values
+# See: https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
+def _replace_attr_charref(match):
+ ref = match.group(0)
+ # Numeric / hex char refs must always be unescaped
+ if ref.startswith('&#'):
+ return unescape(ref)
+ # Named character / entity references must only be unescaped
+ # if they are an exact match, and they are not followed by an equals sign
+ if not ref.endswith('=') and ref[1:] in html5_entities:
+ return unescape(ref)
+ # Otherwise do not unescape
+ return ref
+
+def _unescape_attrvalue(s):
+ return attr_charref.sub(_replace_attr_charref, s)
class HTMLParser(_markupbase.ParserBase):
@@ -122,7 +167,8 @@ class HTMLParser(_markupbase.ParserBase):
def set_cdata_mode(self, elem):
self.cdata_elem = elem.lower()
- self.interesting = re.compile(r'</\s*%s\s*>' % self.cdata_elem, re.I)
+ self.interesting = re.compile(r'</%s(?=[\t\n\r\f />])' % self.cdata_elem,
+ re.IGNORECASE|re.ASCII)
def clear_cdata_mode(self):
self.interesting = interesting_normal
@@ -147,7 +193,7 @@ class HTMLParser(_markupbase.ParserBase):
# & near the end and see if it's followed by a space or ;.
amppos = rawdata.rfind('&', max(i, n-34))
if (amppos >= 0 and
- not re.compile(r'[\s;]').search(rawdata, amppos)):
+ not re.compile(r'[\t\n\r\f ;]').search(rawdata, amppos)):
break # wait till we get all the text
j = n
else:
@@ -177,7 +223,7 @@ class HTMLParser(_markupbase.ParserBase):
k = self.parse_pi(i)
elif startswith("<!", i):
k = self.parse_html_declaration(i)
- elif (i + 1) < n:
+ elif (i + 1) < n or end:
self.handle_data("<")
k = i + 1
else:
@@ -185,17 +231,35 @@ class HTMLParser(_markupbase.ParserBase):
if k < 0:
if not end:
break
- k = rawdata.find('>', i + 1)
- if k < 0:
- k = rawdata.find('<', i + 1)
- if k < 0:
- k = i + 1
- else:
- k += 1
- if self.convert_charrefs and not self.cdata_elem:
- self.handle_data(unescape(rawdata[i:k]))
+ if starttagopen.match(rawdata, i): # < + letter
+ pass
+ elif startswith("</", i):
+ if i + 2 == n:
+ self.handle_data("</")
+ elif endtagopen.match(rawdata, i): # </ + letter
+ pass
+ else:
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<!--", i):
+ j = n
+ for suffix in ("--!", "--", "-"):
+ if rawdata.endswith(suffix, i+4):
+ j -= len(suffix)
+ break
+ self.handle_comment(rawdata[i+4:j])
+ elif startswith("<![CDATA[", i):
+ self.unknown_decl(rawdata[i+3:])
+ elif rawdata[i:i+9].lower() == '<!doctype':
+ self.handle_decl(rawdata[i+2:])
+ elif startswith("<!", i):
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<?", i):
+ self.handle_pi(rawdata[i+2:])
else:
- self.handle_data(rawdata[i:k])
+ raise AssertionError("we should not get here!")
+ k = n
i = self.updatepos(i, k)
elif startswith("&#", i):
match = charref.match(rawdata, i)
@@ -242,7 +306,7 @@ class HTMLParser(_markupbase.ParserBase):
else:
assert 0, "interesting.search() lied"
# end while
- if end and i < n and not self.cdata_elem:
+ if end and i < n:
if self.convert_charrefs and not self.cdata_elem:
self.handle_data(unescape(rawdata[i:n]))
else:
@@ -260,7 +324,7 @@ class HTMLParser(_markupbase.ParserBase):
if rawdata[i:i+4] == '<!--':
# this case is actually already handled in goahead()
return self.parse_comment(i)
- elif rawdata[i:i+3] == '<![':
+ elif rawdata[i:i+9] == '<![CDATA[':
return self.parse_marked_section(i)
elif rawdata[i:i+9].lower() == '<!doctype':
# find the closing >
@@ -273,11 +337,11 @@ class HTMLParser(_markupbase.ParserBase):
return self.parse_bogus_comment(i)
# Internal -- parse bogus comment, return length or -1 if not terminated
- # see http://www.w3.org/TR/html5/tokenization.html#bogus-comment-state
+ # see https://html.spec.whatwg.org/multipage/parsing.html#bogus-comment-state
def parse_bogus_comment(self, i, report=1):
rawdata = self.rawdata
assert rawdata[i:i+2] in ('<!', '</'), ('unexpected call to '
- 'parse_comment()')
+ 'parse_bogus_comment()')
pos = rawdata.find('>', i+2)
if pos == -1:
return -1
@@ -299,6 +363,8 @@ class HTMLParser(_markupbase.ParserBase):
# Internal -- handle starttag, return end or -1 if not terminated
def parse_starttag(self, i):
+ # See the HTML5 specs section "13.2.5.8 Tag name state"
+ # https://html.spec.whatwg.org/multipage/parsing.html#tag-name-state
self.__starttag_text = None
endpos = self.check_for_whole_start_tag(i)
if endpos < 0:
@@ -323,7 +389,7 @@ class HTMLParser(_markupbase.ParserBase):
attrvalue[:1] == '"' == attrvalue[-1:]:
attrvalue = attrvalue[1:-1]
if attrvalue:
- attrvalue = unescape(attrvalue)
+ attrvalue = _unescape_attrvalue(attrvalue)
attrs.append((attrname.lower(), attrvalue))
k = m.end()
@@ -344,76 +410,42 @@ class HTMLParser(_markupbase.ParserBase):
# or -1 if incomplete.
def check_for_whole_start_tag(self, i):
rawdata = self.rawdata
- m = locatestarttagend_tolerant.match(rawdata, i)
- if m:
- j = m.end()
- next = rawdata[j:j+1]
- if next == ">":
- return j + 1
- if next == "/":
- if rawdata.startswith("/>", j):
- return j + 2
- if rawdata.startswith("/", j):
- # buffer boundary
- return -1
- # else bogus input
- if j > i:
- return j
- else:
- return i + 1
- if next == "":
- # end of input
- return -1
- if next in ("abcdefghijklmnopqrstuvwxyz=/"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
- # end of input in or before attribute value, or we have the
- # '/' from a '/>' ending
- return -1
- if j > i:
- return j
- else:
- return i + 1
- raise AssertionError("we should not get here!")
+ match = locatetagend.match(rawdata, i+1)
+ assert match
+ j = match.end()
+ if rawdata[j-1] != ">":
+ return -1
+ return j
# Internal -- parse endtag, return end or -1 if incomplete
def parse_endtag(self, i):
+ # See the HTML5 specs section "13.2.5.7 End tag open state"
+ # https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
rawdata = self.rawdata
assert rawdata[i:i+2] == "</", "unexpected call to parse_endtag"
- match = endendtag.search(rawdata, i+1) # >
- if not match:
+ if rawdata.find('>', i+2) < 0: # fast check
return -1
- gtpos = match.end()
- match = endtagfind.match(rawdata, i) # </ + tag + >
- if not match:
- if self.cdata_elem is not None:
- self.handle_data(rawdata[i:gtpos])
- return gtpos
- # find the name: w3.org/TR/html5/tokenization.html#tag-name-state
- namematch = tagfind_tolerant.match(rawdata, i+2)
- if not namematch:
- # w3.org/TR/html5/tokenization.html#end-tag-open-state
- if rawdata[i:i+3] == '</>':
- return i+3
- else:
- return self.parse_bogus_comment(i)
- tagname = namematch.group(1).lower()
- # consume and ignore other stuff between the name and the >
- # Note: this is not 100% correct, since we might have things like
- # </tag attr=">">, but looking for > after the name should cover
- # most of the cases and is much simpler
- gtpos = rawdata.find('>', namematch.end())
- self.handle_endtag(tagname)
- return gtpos+1
+ if not endtagopen.match(rawdata, i): # </ + letter
+ if rawdata[i+2:i+3] == '>': # </> is ignored
+ # "missing-end-tag-name" parser error
+ return i+3
+ else:
+ return self.parse_bogus_comment(i)
- elem = match.group(1).lower() # script or style
- if self.cdata_elem is not None:
- if elem != self.cdata_elem:
- self.handle_data(rawdata[i:gtpos])
- return gtpos
+ match = locatetagend.match(rawdata, i+2)
+ assert match
+ j = match.end()
+ if rawdata[j-1] != ">":
+ return -1
- self.handle_endtag(elem)
+ # find the name: "13.2.5.8 Tag name state"
+ # https://html.spec.whatwg.org/multipage/parsing.html#tag-name-state
+ match = tagfind_tolerant.match(rawdata, i+2)
+ assert match
+ tag = match.group(1).lower()
+ self.handle_endtag(tag)
self.clear_cdata_mode()
- return gtpos
+ return j
# Overridable -- finish processing of start+end tag: <tag.../>
def handle_startendtag(self, tag, attrs):
diff --git a/Lib/http/client.py b/Lib/http/client.py
index 33a858d34ae..e7a1c7bc3b2 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -181,11 +181,10 @@ def _strip_ipv6_iface(enc_name: bytes) -> bytes:
return enc_name
class HTTPMessage(email.message.Message):
- # XXX The only usage of this method is in
- # http.server.CGIHTTPRequestHandler. Maybe move the code there so
- # that it doesn't need to be part of the public API. The API has
- # never been defined so this could cause backwards compatibility
- # issues.
+
+ # The getallmatchingheaders() method was only used by the CGI handler
+ # that was removed in Python 3.15. However, since the public API was not
+ # properly defined, it will be kept for backwards compatibility reasons.
def getallmatchingheaders(self, name):
"""Find all header lines matching a given header name.
diff --git a/Lib/http/server.py b/Lib/http/server.py
index a2aad4c9be3..a2ffbe2e44d 100644
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -1,29 +1,10 @@
"""HTTP server classes.
Note: BaseHTTPRequestHandler doesn't implement any HTTP request; see
-SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST,
-and (deprecated) CGIHTTPRequestHandler for CGI scripts.
+SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST.
It does, however, optionally implement HTTP/1.1 persistent connections.
-Notes on CGIHTTPRequestHandler
-------------------------------
-
-This class is deprecated. It implements GET and POST requests to cgi-bin scripts.
-
-If the os.fork() function is not present (Windows), subprocess.Popen() is used,
-with slightly altered but never documented semantics. Use from a threaded
-process is likely to trigger a warning at os.fork() time.
-
-In all cases, the implementation is intentionally naive -- all
-requests are executed synchronously.
-
-SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
--- it may execute arbitrary Python code or external programs.
-
-Note that status code 200 is sent prior to execution of a CGI script, so
-scripts cannot send other status codes such as 302 (redirect).
-
XXX To do:
- log requests even later (to capture byte count)
@@ -86,10 +67,8 @@ __all__ = [
"HTTPServer", "ThreadingHTTPServer",
"HTTPSServer", "ThreadingHTTPSServer",
"BaseHTTPRequestHandler", "SimpleHTTPRequestHandler",
- "CGIHTTPRequestHandler",
]
-import copy
import datetime
import email.utils
import html
@@ -99,7 +78,6 @@ import itertools
import mimetypes
import os
import posixpath
-import select
import shutil
import socket
import socketserver
@@ -137,7 +115,7 @@ DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"
class HTTPServer(socketserver.TCPServer):
allow_reuse_address = True # Seems to make sense in testing environment
- allow_reuse_port = True
+ allow_reuse_port = False
def server_bind(self):
"""Override server_bind to store the server name."""
@@ -750,7 +728,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
f = None
if os.path.isdir(path):
parts = urllib.parse.urlsplit(self.path)
- if not parts.path.endswith('/'):
+ if not parts.path.endswith(('/', '%2f', '%2F')):
# redirect browser - doing basically what apache does
self.send_response(HTTPStatus.MOVED_PERMANENTLY)
new_parts = (parts[0], parts[1], parts[2] + '/',
@@ -840,11 +818,14 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
return None
list.sort(key=lambda a: a.lower())
r = []
+ displaypath = self.path
+ displaypath = displaypath.split('#', 1)[0]
+ displaypath = displaypath.split('?', 1)[0]
try:
- displaypath = urllib.parse.unquote(self.path,
+ displaypath = urllib.parse.unquote(displaypath,
errors='surrogatepass')
except UnicodeDecodeError:
- displaypath = urllib.parse.unquote(self.path)
+ displaypath = urllib.parse.unquote(displaypath)
displaypath = html.escape(displaypath, quote=False)
enc = sys.getfilesystemencoding()
title = f'Directory listing for {displaypath}'
@@ -890,14 +871,14 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
"""
# abandon query parameters
- path = path.split('?',1)[0]
- path = path.split('#',1)[0]
+ path = path.split('#', 1)[0]
+ path = path.split('?', 1)[0]
# Don't forget explicit trailing slash when normalizing. Issue17324
- trailing_slash = path.rstrip().endswith('/')
try:
path = urllib.parse.unquote(path, errors='surrogatepass')
except UnicodeDecodeError:
path = urllib.parse.unquote(path)
+ trailing_slash = path.endswith('/')
path = posixpath.normpath(path)
words = path.split('/')
words = filter(None, words)
@@ -953,56 +934,6 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
return 'application/octet-stream'
-# Utilities for CGIHTTPRequestHandler
-
-def _url_collapse_path(path):
- """
- Given a URL path, remove extra '/'s and '.' path elements and collapse
- any '..' references and returns a collapsed path.
-
- Implements something akin to RFC-2396 5.2 step 6 to parse relative paths.
- The utility of this function is limited to is_cgi method and helps
- preventing some security attacks.
-
- Returns: The reconstituted URL, which will always start with a '/'.
-
- Raises: IndexError if too many '..' occur within the path.
-
- """
- # Query component should not be involved.
- path, _, query = path.partition('?')
- path = urllib.parse.unquote(path)
-
- # Similar to os.path.split(os.path.normpath(path)) but specific to URL
- # path semantics rather than local operating system semantics.
- path_parts = path.split('/')
- head_parts = []
- for part in path_parts[:-1]:
- if part == '..':
- head_parts.pop() # IndexError if more '..' than prior parts
- elif part and part != '.':
- head_parts.append( part )
- if path_parts:
- tail_part = path_parts.pop()
- if tail_part:
- if tail_part == '..':
- head_parts.pop()
- tail_part = ''
- elif tail_part == '.':
- tail_part = ''
- else:
- tail_part = ''
-
- if query:
- tail_part = '?'.join((tail_part, query))
-
- splitpath = ('/' + '/'.join(head_parts), tail_part)
- collapsed_path = "/".join(splitpath)
-
- return collapsed_path
-
-
-
nobody = None
def nobody_uid():
@@ -1026,274 +957,6 @@ def executable(path):
return os.access(path, os.X_OK)
-class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
-
- """Complete HTTP server with GET, HEAD and POST commands.
-
- GET and HEAD also support running CGI scripts.
-
- The POST command is *only* implemented for CGI scripts.
-
- """
-
- def __init__(self, *args, **kwargs):
- import warnings
- warnings._deprecated("http.server.CGIHTTPRequestHandler",
- remove=(3, 15))
- super().__init__(*args, **kwargs)
-
- # Determine platform specifics
- have_fork = hasattr(os, 'fork')
-
- # Make rfile unbuffered -- we need to read one line and then pass
- # the rest to a subprocess, so we can't use buffered input.
- rbufsize = 0
-
- def do_POST(self):
- """Serve a POST request.
-
- This is only implemented for CGI scripts.
-
- """
-
- if self.is_cgi():
- self.run_cgi()
- else:
- self.send_error(
- HTTPStatus.NOT_IMPLEMENTED,
- "Can only POST to CGI scripts")
-
- def send_head(self):
- """Version of send_head that support CGI scripts"""
- if self.is_cgi():
- return self.run_cgi()
- else:
- return SimpleHTTPRequestHandler.send_head(self)
-
- def is_cgi(self):
- """Test whether self.path corresponds to a CGI script.
-
- Returns True and updates the cgi_info attribute to the tuple
- (dir, rest) if self.path requires running a CGI script.
- Returns False otherwise.
-
- If any exception is raised, the caller should assume that
- self.path was rejected as invalid and act accordingly.
-
- The default implementation tests whether the normalized url
- path begins with one of the strings in self.cgi_directories
- (and the next character is a '/' or the end of the string).
-
- """
- collapsed_path = _url_collapse_path(self.path)
- dir_sep = collapsed_path.find('/', 1)
- while dir_sep > 0 and not collapsed_path[:dir_sep] in self.cgi_directories:
- dir_sep = collapsed_path.find('/', dir_sep+1)
- if dir_sep > 0:
- head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:]
- self.cgi_info = head, tail
- return True
- return False
-
-
- cgi_directories = ['/cgi-bin', '/htbin']
-
- def is_executable(self, path):
- """Test whether argument path is an executable file."""
- return executable(path)
-
- def is_python(self, path):
- """Test whether argument path is a Python script."""
- head, tail = os.path.splitext(path)
- return tail.lower() in (".py", ".pyw")
-
- def run_cgi(self):
- """Execute a CGI script."""
- dir, rest = self.cgi_info
- path = dir + '/' + rest
- i = path.find('/', len(dir)+1)
- while i >= 0:
- nextdir = path[:i]
- nextrest = path[i+1:]
-
- scriptdir = self.translate_path(nextdir)
- if os.path.isdir(scriptdir):
- dir, rest = nextdir, nextrest
- i = path.find('/', len(dir)+1)
- else:
- break
-
- # find an explicit query string, if present.
- rest, _, query = rest.partition('?')
-
- # dissect the part after the directory name into a script name &
- # a possible additional path, to be stored in PATH_INFO.
- i = rest.find('/')
- if i >= 0:
- script, rest = rest[:i], rest[i:]
- else:
- script, rest = rest, ''
-
- scriptname = dir + '/' + script
- scriptfile = self.translate_path(scriptname)
- if not os.path.exists(scriptfile):
- self.send_error(
- HTTPStatus.NOT_FOUND,
- "No such CGI script (%r)" % scriptname)
- return
- if not os.path.isfile(scriptfile):
- self.send_error(
- HTTPStatus.FORBIDDEN,
- "CGI script is not a plain file (%r)" % scriptname)
- return
- ispy = self.is_python(scriptname)
- if self.have_fork or not ispy:
- if not self.is_executable(scriptfile):
- self.send_error(
- HTTPStatus.FORBIDDEN,
- "CGI script is not executable (%r)" % scriptname)
- return
-
- # Reference: https://www6.uniovi.es/~antonio/ncsa_httpd/cgi/env.html
- # XXX Much of the following could be prepared ahead of time!
- env = copy.deepcopy(os.environ)
- env['SERVER_SOFTWARE'] = self.version_string()
- env['SERVER_NAME'] = self.server.server_name
- env['GATEWAY_INTERFACE'] = 'CGI/1.1'
- env['SERVER_PROTOCOL'] = self.protocol_version
- env['SERVER_PORT'] = str(self.server.server_port)
- env['REQUEST_METHOD'] = self.command
- uqrest = urllib.parse.unquote(rest)
- env['PATH_INFO'] = uqrest
- env['PATH_TRANSLATED'] = self.translate_path(uqrest)
- env['SCRIPT_NAME'] = scriptname
- env['QUERY_STRING'] = query
- env['REMOTE_ADDR'] = self.client_address[0]
- authorization = self.headers.get("authorization")
- if authorization:
- authorization = authorization.split()
- if len(authorization) == 2:
- import base64, binascii
- env['AUTH_TYPE'] = authorization[0]
- if authorization[0].lower() == "basic":
- try:
- authorization = authorization[1].encode('ascii')
- authorization = base64.decodebytes(authorization).\
- decode('ascii')
- except (binascii.Error, UnicodeError):
- pass
- else:
- authorization = authorization.split(':')
- if len(authorization) == 2:
- env['REMOTE_USER'] = authorization[0]
- # XXX REMOTE_IDENT
- if self.headers.get('content-type') is None:
- env['CONTENT_TYPE'] = self.headers.get_content_type()
- else:
- env['CONTENT_TYPE'] = self.headers['content-type']
- length = self.headers.get('content-length')
- if length:
- env['CONTENT_LENGTH'] = length
- referer = self.headers.get('referer')
- if referer:
- env['HTTP_REFERER'] = referer
- accept = self.headers.get_all('accept', ())
- env['HTTP_ACCEPT'] = ','.join(accept)
- ua = self.headers.get('user-agent')
- if ua:
- env['HTTP_USER_AGENT'] = ua
- co = filter(None, self.headers.get_all('cookie', []))
- cookie_str = ', '.join(co)
- if cookie_str:
- env['HTTP_COOKIE'] = cookie_str
- # XXX Other HTTP_* headers
- # Since we're setting the env in the parent, provide empty
- # values to override previously set values
- for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
- 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
- env.setdefault(k, "")
-
- self.send_response(HTTPStatus.OK, "Script output follows")
- self.flush_headers()
-
- decoded_query = query.replace('+', ' ')
-
- if self.have_fork:
- # Unix -- fork as we should
- args = [script]
- if '=' not in decoded_query:
- args.append(decoded_query)
- nobody = nobody_uid()
- self.wfile.flush() # Always flush before forking
- pid = os.fork()
- if pid != 0:
- # Parent
- pid, sts = os.waitpid(pid, 0)
- # throw away additional data [see bug #427345]
- while select.select([self.rfile], [], [], 0)[0]:
- if not self.rfile.read(1):
- break
- exitcode = os.waitstatus_to_exitcode(sts)
- if exitcode:
- self.log_error(f"CGI script exit code {exitcode}")
- return
- # Child
- try:
- try:
- os.setuid(nobody)
- except OSError:
- pass
- os.dup2(self.rfile.fileno(), 0)
- os.dup2(self.wfile.fileno(), 1)
- os.execve(scriptfile, args, env)
- except:
- self.server.handle_error(self.request, self.client_address)
- os._exit(127)
-
- else:
- # Non-Unix -- use subprocess
- import subprocess
- cmdline = [scriptfile]
- if self.is_python(scriptfile):
- interp = sys.executable
- if interp.lower().endswith("w.exe"):
- # On Windows, use python.exe, not pythonw.exe
- interp = interp[:-5] + interp[-4:]
- cmdline = [interp, '-u'] + cmdline
- if '=' not in query:
- cmdline.append(query)
- self.log_message("command: %s", subprocess.list2cmdline(cmdline))
- try:
- nbytes = int(length)
- except (TypeError, ValueError):
- nbytes = 0
- p = subprocess.Popen(cmdline,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env = env
- )
- if self.command.lower() == "post" and nbytes > 0:
- data = self.rfile.read(nbytes)
- else:
- data = None
- # throw away additional data [see bug #427345]
- while select.select([self.rfile._sock], [], [], 0)[0]:
- if not self.rfile._sock.recv(1):
- break
- stdout, stderr = p.communicate(data)
- self.wfile.write(stdout)
- if stderr:
- self.log_error('%s', stderr)
- p.stderr.close()
- p.stdout.close()
- status = p.returncode
- if status:
- self.log_error("CGI script exit status %#x", status)
- else:
- self.log_message("CGI script exited OK")
-
-
def _get_best_family(*address):
infos = socket.getaddrinfo(
*address,
@@ -1317,8 +980,8 @@ def test(HandlerClass=BaseHTTPRequestHandler,
HandlerClass.protocol_version = protocol
if tls_cert:
- server = ThreadingHTTPSServer(addr, HandlerClass, certfile=tls_cert,
- keyfile=tls_key, password=tls_password)
+ server = ServerClass(addr, HandlerClass, certfile=tls_cert,
+ keyfile=tls_key, password=tls_password)
else:
server = ServerClass(addr, HandlerClass)
@@ -1336,13 +999,12 @@ def test(HandlerClass=BaseHTTPRequestHandler,
print("\nKeyboard interrupt received, exiting.")
sys.exit(0)
-if __name__ == '__main__':
+
+def _main(args=None):
import argparse
import contextlib
- parser = argparse.ArgumentParser()
- parser.add_argument('--cgi', action='store_true',
- help='run as CGI server')
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument('-b', '--bind', metavar='ADDRESS',
help='bind to this address '
'(default: all interfaces)')
@@ -1362,7 +1024,7 @@ if __name__ == '__main__':
parser.add_argument('port', default=8000, type=int, nargs='?',
help='bind to this port '
'(default: %(default)s)')
- args = parser.parse_args()
+ args = parser.parse_args(args)
if not args.tls_cert and args.tls_key:
parser.error("--tls-key requires --tls-cert to be set")
@@ -1378,13 +1040,8 @@ if __name__ == '__main__':
except OSError as e:
parser.error(f"Failed to read TLS password file: {e}")
- if args.cgi:
- handler_class = CGIHTTPRequestHandler
- else:
- handler_class = SimpleHTTPRequestHandler
-
# ensure dual-stack is not disabled; ref #38907
- class DualStackServer(ThreadingHTTPServer):
+ class DualStackServerMixin:
def server_bind(self):
# suppress exception when protocol is IPv4
@@ -1397,9 +1054,16 @@ if __name__ == '__main__':
self.RequestHandlerClass(request, client_address, self,
directory=args.directory)
+ class HTTPDualStackServer(DualStackServerMixin, ThreadingHTTPServer):
+ pass
+ class HTTPSDualStackServer(DualStackServerMixin, ThreadingHTTPSServer):
+ pass
+
+ ServerClass = HTTPSDualStackServer if args.tls_cert else HTTPDualStackServer
+
test(
- HandlerClass=handler_class,
- ServerClass=DualStackServer,
+ HandlerClass=SimpleHTTPRequestHandler,
+ ServerClass=ServerClass,
port=args.port,
bind=args.bind,
protocol=args.protocol,
@@ -1407,3 +1071,7 @@ if __name__ == '__main__':
tls_key=args.tls_key,
tls_password=tls_key_password,
)
+
+
+if __name__ == '__main__':
+ _main()
diff --git a/Lib/idlelib/NEWS2x.txt b/Lib/idlelib/NEWS2x.txt
index 6751ca5f111..3721193007e 100644
--- a/Lib/idlelib/NEWS2x.txt
+++ b/Lib/idlelib/NEWS2x.txt
@@ -1,6 +1,6 @@
What's New in IDLE 2.7? (Merged into 3.1 before 2.7 release.)
=======================
-*Release date: XX-XXX-2010*
+*Release date: 03-Jul-2010*
- idle.py modified and simplified to better support developing experimental
versions of IDLE which are not installed in the standard location.
diff --git a/Lib/idlelib/News3.txt b/Lib/idlelib/News3.txt
index 74d84b38931..30784578cc6 100644
--- a/Lib/idlelib/News3.txt
+++ b/Lib/idlelib/News3.txt
@@ -4,6 +4,13 @@ Released on 2025-10-07
=========================
+gh-112936: IDLE - Include Shell menu in single-process mode,
+though with Restart Shell and View Last Restart disabled.
+Patch by Zhikang Yan.
+
+gh-112938: IDLE - Fix uninteruptable hang when Shell gets
+rapid continuous output.
+
gh-127060: Set TERM environment variable to 'dumb' to not add ANSI escape
sequences for text color in tracebacks. IDLE does not understand them.
Patch by Victor Stinner.
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 4d2adb48570..e618ef07a90 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -435,7 +435,7 @@ class FontPage(Frame):
self.font_name.set(font.lower())
def set_samples(self, event=None):
- """Update update both screen samples with the font settings.
+ """Update both screen samples with the font settings.
Called on font initialization and change events.
Accesses font_name, font_size, and font_bold Variables.
diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py
index d90dbcd11f9..1fae1d4b0ad 100644
--- a/Lib/idlelib/debugger.py
+++ b/Lib/idlelib/debugger.py
@@ -1,6 +1,6 @@
"""Debug user code with a GUI interface to a subclass of bdb.Bdb.
-The Idb idb and Debugger gui instances each need a reference to each
+The Idb instance 'idb' and Debugger instance 'gui' need references to each
other or to an rpc proxy for each other.
If IDLE is started with '-n', so that user code and idb both run in the
diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py
index c76db20c587..17b498f63ba 100644
--- a/Lib/idlelib/editor.py
+++ b/Lib/idlelib/editor.py
@@ -1649,7 +1649,7 @@ class IndentSearcher:
self.finished = 1
def run(self):
- """Return 2 lines containing block opener and and indent.
+ """Return 2 lines containing block opener and indent.
Either the indent line or both may be None.
"""
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
index a7293774eec..b63ff9ec287 100644
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -337,7 +337,7 @@ _tree_widget_spec = {
'file': 'tree',
'kwds': {},
'msg': "The canvas is scrollable.\n"
- "Click on folders up to to the lowest level."
+ "Click on folders up to the lowest level."
}
_undo_delegator_spec = {
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 9592559ba6d..183e67fabf9 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -2074,13 +2074,11 @@ def _signature_is_functionlike(obj):
code = getattr(obj, '__code__', None)
defaults = getattr(obj, '__defaults__', _void) # Important to use _void ...
kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here
- annotations = getattr(obj, '__annotations__', None)
return (isinstance(code, types.CodeType) and
isinstance(name, str) and
(defaults is None or isinstance(defaults, tuple)) and
- (kwdefaults is None or isinstance(kwdefaults, dict)) and
- (isinstance(annotations, (dict)) or annotations is None) )
+ (kwdefaults is None or isinstance(kwdefaults, dict)))
def _signature_strip_non_python_syntax(signature):
@@ -3343,7 +3341,7 @@ def _main():
import argparse
import importlib
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument(
'object',
help="The object to be analysed. "
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 703fa289dda..8b60b9d5c9c 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -729,7 +729,7 @@ class _BaseNetwork(_IPAddressBase):
return NotImplemented
def __hash__(self):
- return hash(int(self.network_address) ^ int(self.netmask))
+ return hash((int(self.network_address), int(self.netmask)))
def __contains__(self, other):
# always false if one is v4 and the other is v6.
@@ -1660,8 +1660,18 @@ class _BaseV6:
"""
if not ip_str:
raise AddressValueError('Address cannot be empty')
-
- parts = ip_str.split(':')
+ if len(ip_str) > 45:
+ shorten = ip_str
+ if len(shorten) > 100:
+ shorten = f'{ip_str[:45]}({len(ip_str)-90} chars elided){ip_str[-45:]}'
+ raise AddressValueError(f"At most 45 characters expected in "
+ f"{shorten!r}")
+
+ # We want to allow more parts than the max to be 'split'
+ # to preserve the correct error message when there are
+ # too many parts combined with '::'
+ _max_parts = cls._HEXTET_COUNT + 1
+ parts = ip_str.split(':', maxsplit=_max_parts)
# An IPv6 address needs at least 2 colons (3 parts).
_min_parts = 3
@@ -1681,7 +1691,6 @@ class _BaseV6:
# An IPv6 address can't have more than 8 colons (9 parts).
# The extra colon comes from using the "::" notation for a single
# leading or trailing zero part.
- _max_parts = cls._HEXTET_COUNT + 1
if len(parts) > _max_parts:
msg = "At most %d colons permitted in %r" % (_max_parts-1, ip_str)
raise AddressValueError(msg)
diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py
index 016638549aa..bc446e0f377 100644
--- a/Lib/json/encoder.py
+++ b/Lib/json/encoder.py
@@ -348,7 +348,6 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
_current_indent_level += 1
newline_indent = '\n' + _indent * _current_indent_level
item_separator = _item_separator + newline_indent
- yield newline_indent
else:
newline_indent = None
item_separator = _item_separator
@@ -381,6 +380,8 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
f'not {key.__class__.__name__}')
if first:
first = False
+ if newline_indent is not None:
+ yield newline_indent
else:
yield item_separator
yield _encoder(key)
@@ -413,7 +414,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
except BaseException as exc:
exc.add_note(f'when serializing {type(dct).__name__} item {key!r}')
raise
- if newline_indent is not None:
+ if not first and newline_indent is not None:
_current_indent_level -= 1
yield '\n' + _indent * _current_indent_level
yield '}'
diff --git a/Lib/json/tool.py b/Lib/json/tool.py
index 585583da860..1967817add8 100644
--- a/Lib/json/tool.py
+++ b/Lib/json/tool.py
@@ -7,7 +7,7 @@ import argparse
import json
import re
import sys
-from _colorize import ANSIColors, can_colorize
+from _colorize import get_theme, can_colorize
# The string we are colorizing is valid JSON,
@@ -17,34 +17,34 @@ from _colorize import ANSIColors, can_colorize
_color_pattern = re.compile(r'''
(?P<key>"(\\.|[^"\\])*")(?=:) |
(?P<string>"(\\.|[^"\\])*") |
+ (?P<number>NaN|-?Infinity|[0-9\-+.Ee]+) |
(?P<boolean>true|false) |
(?P<null>null)
''', re.VERBOSE)
-
-_colors = {
- 'key': ANSIColors.INTENSE_BLUE,
- 'string': ANSIColors.BOLD_GREEN,
- 'boolean': ANSIColors.BOLD_CYAN,
- 'null': ANSIColors.BOLD_CYAN,
+_group_to_theme_color = {
+ "key": "definition",
+ "string": "string",
+ "number": "number",
+ "boolean": "keyword",
+ "null": "keyword",
}
-def _replace_match_callback(match):
- for key, color in _colors.items():
- if m := match.group(key):
- return f"{color}{m}{ANSIColors.RESET}"
- return match.group()
-
+def _colorize_json(json_str, theme):
+ def _replace_match_callback(match):
+ for group, color in _group_to_theme_color.items():
+ if m := match.group(group):
+ return f"{theme[color]}{m}{theme.reset}"
+ return match.group()
-def _colorize_json(json_str):
return re.sub(_color_pattern, _replace_match_callback, json_str)
def main():
description = ('A simple command line interface for json module '
'to validate and pretty-print JSON objects.')
- parser = argparse.ArgumentParser(description=description)
+ parser = argparse.ArgumentParser(description=description, color=True)
parser.add_argument('infile', nargs='?',
help='a JSON file to be validated or pretty-printed',
default='-')
@@ -100,13 +100,16 @@ def main():
else:
outfile = open(options.outfile, 'w', encoding='utf-8')
with outfile:
- for obj in objs:
- if can_colorize(file=outfile):
+ if can_colorize(file=outfile):
+ t = get_theme(tty_file=outfile).syntax
+ for obj in objs:
json_str = json.dumps(obj, **dump_args)
- outfile.write(_colorize_json(json_str))
- else:
+ outfile.write(_colorize_json(json_str, t))
+ outfile.write('\n')
+ else:
+ for obj in objs:
json.dump(obj, outfile, **dump_args)
- outfile.write('\n')
+ outfile.write('\n')
except ValueError as e:
raise SystemExit(e)
diff --git a/Lib/linecache.py b/Lib/linecache.py
index 87d7d6fda65..ef73d1aa997 100644
--- a/Lib/linecache.py
+++ b/Lib/linecache.py
@@ -33,10 +33,9 @@ def getlines(filename, module_globals=None):
"""Get the lines for a Python source file from the cache.
Update the cache if it doesn't contain an entry for this file already."""
- if filename in cache:
- entry = cache[filename]
- if len(entry) != 1:
- return cache[filename][2]
+ entry = cache.get(filename, None)
+ if entry is not None and len(entry) != 1:
+ return entry[2]
try:
return updatecache(filename, module_globals)
@@ -56,10 +55,9 @@ def _make_key(code):
def _getlines_from_code(code):
code_id = _make_key(code)
- if code_id in _interactive_cache:
- entry = _interactive_cache[code_id]
- if len(entry) != 1:
- return _interactive_cache[code_id][2]
+ entry = _interactive_cache.get(code_id, None)
+ if entry is not None and len(entry) != 1:
+ return entry[2]
return []
@@ -84,12 +82,8 @@ def checkcache(filename=None):
filenames = [filename]
for filename in filenames:
- try:
- entry = cache[filename]
- except KeyError:
- continue
-
- if len(entry) == 1:
+ entry = cache.get(filename, None)
+ if entry is None or len(entry) == 1:
# lazy cache entry, leave it lazy.
continue
size, mtime, lines, fullname = entry
@@ -125,9 +119,7 @@ def updatecache(filename, module_globals=None):
# These import can fail if the interpreter is shutting down
return []
- if filename in cache:
- if len(cache[filename]) != 1:
- cache.pop(filename, None)
+ entry = cache.pop(filename, None)
if _source_unavailable(filename):
return []
@@ -146,9 +138,12 @@ def updatecache(filename, module_globals=None):
# Realise a lazy loader based lookup if there is one
# otherwise try to lookup right now.
- if lazycache(filename, module_globals):
+ lazy_entry = entry if entry is not None and len(entry) == 1 else None
+ if lazy_entry is None:
+ lazy_entry = _make_lazycache_entry(filename, module_globals)
+ if lazy_entry is not None:
try:
- data = cache[filename][0]()
+ data = lazy_entry[0]()
except (ImportError, OSError):
pass
else:
@@ -156,13 +151,14 @@ def updatecache(filename, module_globals=None):
# No luck, the PEP302 loader cannot find the source
# for this module.
return []
- cache[filename] = (
+ entry = (
len(data),
None,
[line + '\n' for line in data.splitlines()],
fullname
)
- return cache[filename][2]
+ cache[filename] = entry
+ return entry[2]
# Try looking through the module search path, which is only useful
# when handling a relative filename.
@@ -211,13 +207,20 @@ def lazycache(filename, module_globals):
get_source method must be found, the filename must be a cacheable
filename, and the filename must not be already cached.
"""
- if filename in cache:
- if len(cache[filename]) == 1:
- return True
- else:
- return False
+ entry = cache.get(filename, None)
+ if entry is not None:
+ return len(entry) == 1
+
+ lazy_entry = _make_lazycache_entry(filename, module_globals)
+ if lazy_entry is not None:
+ cache[filename] = lazy_entry
+ return True
+ return False
+
+
+def _make_lazycache_entry(filename, module_globals):
if not filename or (filename.startswith('<') and filename.endswith('>')):
- return False
+ return None
# Try for a __loader__, if available
if module_globals and '__name__' in module_globals:
spec = module_globals.get('__spec__')
@@ -230,9 +233,10 @@ def lazycache(filename, module_globals):
if name and get_source:
def get_lines(name=name, *args, **kwargs):
return get_source(name, *args, **kwargs)
- cache[filename] = (get_lines,)
- return True
- return False
+ return (get_lines,)
+ return None
+
+
def _register_code(code, string, name):
entry = (len(string),
@@ -245,4 +249,5 @@ def _register_code(code, string, name):
for const in code.co_consts:
if isinstance(const, type(code)):
stack.append(const)
- _interactive_cache[_make_key(code)] = entry
+ key = _make_key(code)
+ _interactive_cache[key] = entry
diff --git a/Lib/locale.py b/Lib/locale.py
index 2feb10e59c9..dfedc6386cb 100644
--- a/Lib/locale.py
+++ b/Lib/locale.py
@@ -883,6 +883,10 @@ del k, v
# updated 'sr@latn' -> 'sr_CS.UTF-8@latin' to 'sr_RS.UTF-8@latin'
# removed 'univ'
# removed 'universal'
+#
+# SS 2025-06-10:
+# Remove 'c.utf8' -> 'en_US.UTF-8' because 'en_US.UTF-8' does not exist
+# on all platforms.
locale_alias = {
'a3': 'az_AZ.KOI8-C',
@@ -962,7 +966,6 @@ locale_alias = {
'c.ascii': 'C',
'c.en': 'C',
'c.iso88591': 'en_US.ISO8859-1',
- 'c.utf8': 'en_US.UTF-8',
'c_c': 'C',
'c_c.c': 'C',
'ca': 'ca_ES.ISO8859-1',
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index aa9b79d8cab..c5860d53b1b 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -591,6 +591,7 @@ class Formatter(object):
%(threadName)s Thread name (if available)
%(taskName)s Task name (if available)
%(process)d Process ID (if available)
+ %(processName)s Process name (if available)
%(message)s The result of record.getMessage(), computed just as
the record is emitted
"""
@@ -2045,6 +2046,15 @@ def basicConfig(**kwargs):
created FileHandler, causing it to be used when the file is
opened in text mode. If not specified, the default value is
`backslashreplace`.
+ formatter If specified, set this formatter instance for all involved
+ handlers.
+ If not specified, the default is to create and use an instance of
+ `logging.Formatter` based on arguments 'format', 'datefmt' and
+ 'style'.
+ When 'formatter' is specified together with any of the three
+ arguments 'format', 'datefmt' and 'style', a `ValueError`
+ is raised to signal that these arguments would lose meaning
+ otherwise.
Note that you could specify a stream created using open(filename, mode)
rather than passing the filename and mode in. However, it should be
@@ -2067,6 +2077,9 @@ def basicConfig(**kwargs):
.. versionchanged:: 3.9
Added the ``encoding`` and ``errors`` parameters.
+
+ .. versionchanged:: 3.15
+ Added the ``formatter`` parameter.
"""
# Add thread safety in case someone mistakenly calls
# basicConfig() from multiple threads
@@ -2102,13 +2115,19 @@ def basicConfig(**kwargs):
stream = kwargs.pop("stream", None)
h = StreamHandler(stream)
handlers = [h]
- dfs = kwargs.pop("datefmt", None)
- style = kwargs.pop("style", '%')
- if style not in _STYLES:
- raise ValueError('Style must be one of: %s' % ','.join(
- _STYLES.keys()))
- fs = kwargs.pop("format", _STYLES[style][1])
- fmt = Formatter(fs, dfs, style)
+ fmt = kwargs.pop("formatter", None)
+ if fmt is None:
+ dfs = kwargs.pop("datefmt", None)
+ style = kwargs.pop("style", '%')
+ if style not in _STYLES:
+ raise ValueError('Style must be one of: %s' % ','.join(
+ _STYLES.keys()))
+ fs = kwargs.pop("format", _STYLES[style][1])
+ fmt = Formatter(fs, dfs, style)
+ else:
+ for forbidden_key in ("datefmt", "format", "style"):
+ if forbidden_key in kwargs:
+ raise ValueError(f"{forbidden_key!r} should not be specified together with 'formatter'")
for h in handlers:
if h.formatter is None:
h.setFormatter(fmt)
diff --git a/Lib/logging/config.py b/Lib/logging/config.py
index c994349fd6e..3d9aa00fa52 100644
--- a/Lib/logging/config.py
+++ b/Lib/logging/config.py
@@ -1018,7 +1018,7 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
"""
allow_reuse_address = True
- allow_reuse_port = True
+ allow_reuse_port = False
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
handler=None, ready=None, verify=None):
diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py
index b5a1b8da263..33e86d51a0f 100644
--- a/Lib/mimetypes.py
+++ b/Lib/mimetypes.py
@@ -698,7 +698,9 @@ _default_mime_types()
def _parse_args(args):
from argparse import ArgumentParser
- parser = ArgumentParser(description='map filename extensions to MIME types')
+ parser = ArgumentParser(
+ description='map filename extensions to MIME types', color=True
+ )
parser.add_argument(
'-e', '--extension',
action='store_true',
diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py
index 5f288a8d393..fc00d286126 100644
--- a/Lib/multiprocessing/connection.py
+++ b/Lib/multiprocessing/connection.py
@@ -76,7 +76,7 @@ def arbitrary_address(family):
if family == 'AF_INET':
return ('localhost', 0)
elif family == 'AF_UNIX':
- return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
+ return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
elif family == 'AF_PIPE':
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
(os.getpid(), next(_mmap_counter)), dir="")
diff --git a/Lib/multiprocessing/context.py b/Lib/multiprocessing/context.py
index d0a3ad00e53..051d567d457 100644
--- a/Lib/multiprocessing/context.py
+++ b/Lib/multiprocessing/context.py
@@ -145,7 +145,7 @@ class BaseContext(object):
'''Check whether this is a fake forked process in a frozen executable.
If so then run code specified by commandline and exit.
'''
- if sys.platform == 'win32' and getattr(sys, 'frozen', False):
+ if self.get_start_method() == 'spawn' and getattr(sys, 'frozen', False):
from .spawn import freeze_support
freeze_support()
diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py
index 681af2610e9..c91891ff162 100644
--- a/Lib/multiprocessing/forkserver.py
+++ b/Lib/multiprocessing/forkserver.py
@@ -222,6 +222,10 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
except ImportError:
pass
+ # gh-135335: flush stdout/stderr in case any of the preloaded modules
+ # wrote to them, otherwise children might inherit buffered data
+ util._flush_std_streams()
+
util._close_stdin()
sig_r, sig_w = os.pipe()
diff --git a/Lib/multiprocessing/sharedctypes.py b/Lib/multiprocessing/sharedctypes.py
index 6071707027b..eee1172e6e9 100644
--- a/Lib/multiprocessing/sharedctypes.py
+++ b/Lib/multiprocessing/sharedctypes.py
@@ -37,7 +37,12 @@ typecode_to_type = {
#
def _new_value(type_):
- size = ctypes.sizeof(type_)
+ try:
+ size = ctypes.sizeof(type_)
+ except TypeError as e:
+ raise TypeError("bad typecode (must be a ctypes type or one of "
+ "c, b, B, u, h, H, i, I, l, L, q, Q, f or d)") from e
+
wrapper = heap.BufferWrapper(size)
return rebuild_ctype(type_, wrapper, None)
diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
index b7192042b9c..a1a537dd48d 100644
--- a/Lib/multiprocessing/util.py
+++ b/Lib/multiprocessing/util.py
@@ -19,7 +19,7 @@ from subprocess import _args_from_interpreter_flags # noqa: F401
from . import process
__all__ = [
- 'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
+ 'sub_debug', 'debug', 'info', 'sub_warning', 'warn', 'get_logger',
'log_to_stderr', 'get_temp_dir', 'register_after_fork',
'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
@@ -34,6 +34,7 @@ SUBDEBUG = 5
DEBUG = 10
INFO = 20
SUBWARNING = 25
+WARNING = 30
LOGGER_NAME = 'multiprocessing'
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'
@@ -53,6 +54,10 @@ def info(msg, *args):
if _logger:
_logger.log(INFO, msg, *args, stacklevel=2)
+def warn(msg, *args):
+ if _logger:
+ _logger.log(WARNING, msg, *args, stacklevel=2)
+
def sub_warning(msg, *args):
if _logger:
_logger.log(SUBWARNING, msg, *args, stacklevel=2)
@@ -121,6 +126,21 @@ abstract_sockets_supported = _platform_supports_abstract_sockets()
# Function returning a temp directory which will be removed on exit
#
+# Maximum length of a socket file path is usually between 92 and 108 [1],
+# but Linux is known to use a size of 108 [2]. BSD-based systems usually
+# use a size of 104 or 108 and Windows does not create AF_UNIX sockets.
+#
+# [1]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html
+# [2]: https://man7.org/linux/man-pages/man7/unix.7.html.
+
+if sys.platform == 'linux':
+ _SUN_PATH_MAX = 108
+elif sys.platform.startswith(('openbsd', 'freebsd')):
+ _SUN_PATH_MAX = 104
+else:
+ # On Windows platforms, we do not create AF_UNIX sockets.
+ _SUN_PATH_MAX = None if os.name == 'nt' else 92
+
def _remove_temp_dir(rmtree, tempdir):
rmtree(tempdir)
@@ -130,12 +150,67 @@ def _remove_temp_dir(rmtree, tempdir):
if current_process is not None:
current_process._config['tempdir'] = None
+def _get_base_temp_dir(tempfile):
+ """Get a temporary directory where socket files will be created.
+
+ To prevent additional imports, pass a pre-imported 'tempfile' module.
+ """
+ if os.name == 'nt':
+ return None
+ # Most of the time, the default temporary directory is /tmp. Thus,
+ # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do
+ # not have a path length exceeding SUN_PATH_MAX.
+ #
+ # If users specify their own temporary directory, we may be unable
+ # to create those files. Therefore, we fall back to the system-wide
+ # temporary directory /tmp, assumed to exist on POSIX systems.
+ #
+ # See https://github.com/python/cpython/issues/132124.
+ base_tempdir = tempfile.gettempdir()
+ # Files created in a temporary directory are suffixed by a string
+ # generated by tempfile._RandomNameSequence, which, by design,
+ # is 8 characters long.
+ #
+ # Thus, the length of socket filename will be:
+ #
+ # len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX')
+ sun_path_len = len(base_tempdir) + 14 + 14
+ if sun_path_len <= _SUN_PATH_MAX:
+ return base_tempdir
+ # Fallback to the default system-wide temporary directory.
+ # This ignores user-defined environment variables.
+ #
+ # On POSIX systems, /tmp MUST be writable by any application [1].
+ # We however emit a warning if this is not the case to prevent
+ # obscure errors later in the execution.
+ #
+ # On some legacy systems, /var/tmp and /usr/tmp can be present
+ # and will be used instead.
+ #
+ # [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html
+ dirlist = ['/tmp', '/var/tmp', '/usr/tmp']
+ try:
+ base_system_tempdir = tempfile._get_default_tempdir(dirlist)
+ except FileNotFoundError:
+ warn("Process-wide temporary directory %s will not be usable for "
+ "creating socket files and no usable system-wide temporary "
+ "directory was found in %s", base_tempdir, dirlist)
+ # At this point, the system-wide temporary directory is not usable
+ # but we may assume that the user-defined one is, even if we will
+ # not be able to write socket files out there.
+ return base_tempdir
+ warn("Ignoring user-defined temporary directory: %s", base_tempdir)
+ # at most max(map(len, dirlist)) + 14 + 14 = 36 characters
+ assert len(base_system_tempdir) + 14 + 14 <= _SUN_PATH_MAX
+ return base_system_tempdir
+
def get_temp_dir():
# get name of a temp directory which will be automatically cleaned up
tempdir = process.current_process()._config.get('tempdir')
if tempdir is None:
import shutil, tempfile
- tempdir = tempfile.mkdtemp(prefix='pymp-')
+ base_tempdir = _get_base_temp_dir(tempfile)
+ tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
info('created temp directory %s', tempdir)
# keep a strong reference to shutil.rmtree(), since the finalizer
# can be called late during Python shutdown
diff --git a/Lib/netrc.py b/Lib/netrc.py
index b285fd8e357..2f502c1d533 100644
--- a/Lib/netrc.py
+++ b/Lib/netrc.py
@@ -7,6 +7,19 @@ import os, stat
__all__ = ["netrc", "NetrcParseError"]
+def _can_security_check():
+ # On WASI, getuid() is indicated as a stub but it may also be missing.
+ return os.name == 'posix' and hasattr(os, 'getuid')
+
+
+def _getpwuid(uid):
+ try:
+ import pwd
+ return pwd.getpwuid(uid)[0]
+ except (ImportError, LookupError):
+ return f'uid {uid}'
+
+
class NetrcParseError(Exception):
"""Exception raised on syntax errors in the .netrc file."""
def __init__(self, msg, filename=None, lineno=None):
@@ -142,21 +155,15 @@ class netrc:
self._security_check(fp, default_netrc, self.hosts[entryname][0])
def _security_check(self, fp, default_netrc, login):
- if os.name == 'posix' and default_netrc and login != "anonymous":
+ if _can_security_check() and default_netrc and login != "anonymous":
prop = os.fstat(fp.fileno())
- if prop.st_uid != os.getuid():
- import pwd
- try:
- fowner = pwd.getpwuid(prop.st_uid)[0]
- except KeyError:
- fowner = 'uid %s' % prop.st_uid
- try:
- user = pwd.getpwuid(os.getuid())[0]
- except KeyError:
- user = 'uid %s' % os.getuid()
+ current_user_id = os.getuid()
+ if prop.st_uid != current_user_id:
+ fowner = _getpwuid(prop.st_uid)
+ user = _getpwuid(current_user_id)
raise NetrcParseError(
- (f"~/.netrc file owner ({fowner}, {user}) does not match"
- " current user"))
+ f"~/.netrc file owner ({fowner}) does not match"
+ f" current user ({user})")
if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
raise NetrcParseError(
"~/.netrc access too permissive: access"
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index 5481bb8888e..9cdc16480f9 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -29,7 +29,7 @@ __all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext"
"abspath","curdir","pardir","sep","pathsep","defpath","altsep",
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction",
- "isdevdrive"]
+ "isdevdrive", "ALLOW_MISSING"]
def _get_bothseps(path):
if isinstance(path, bytes):
@@ -601,9 +601,10 @@ try:
from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink
except ImportError:
# realpath is a no-op on systems without _getfinalpathname support.
- realpath = abspath
+ def realpath(path, *, strict=False):
+ return abspath(path)
else:
- def _readlink_deep(path):
+ def _readlink_deep(path, ignored_error=OSError):
# These error codes indicate that we should stop reading links and
# return the path we currently have.
# 1: ERROR_INVALID_FUNCTION
@@ -636,7 +637,7 @@ else:
path = old_path
break
path = normpath(join(dirname(old_path), path))
- except OSError as ex:
+ except ignored_error as ex:
if ex.winerror in allowed_winerror:
break
raise
@@ -645,7 +646,7 @@ else:
break
return path
- def _getfinalpathname_nonstrict(path):
+ def _getfinalpathname_nonstrict(path, ignored_error=OSError):
# These error codes indicate that we should stop resolving the path
# and return the value we currently have.
# 1: ERROR_INVALID_FUNCTION
@@ -661,9 +662,10 @@ else:
# 87: ERROR_INVALID_PARAMETER
# 123: ERROR_INVALID_NAME
# 161: ERROR_BAD_PATHNAME
+ # 1005: ERROR_UNRECOGNIZED_VOLUME
# 1920: ERROR_CANT_ACCESS_FILE
# 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)
- allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1920, 1921
+ allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1005, 1920, 1921
# Non-strict algorithm is to find as much of the target directory
# as we can and join the rest.
@@ -672,17 +674,18 @@ else:
try:
path = _getfinalpathname(path)
return join(path, tail) if tail else path
- except OSError as ex:
+ except ignored_error as ex:
if ex.winerror not in allowed_winerror:
raise
try:
# The OS could not resolve this path fully, so we attempt
# to follow the link ourselves. If we succeed, join the tail
# and return.
- new_path = _readlink_deep(path)
+ new_path = _readlink_deep(path,
+ ignored_error=ignored_error)
if new_path != path:
return join(new_path, tail) if tail else new_path
- except OSError:
+ except ignored_error:
# If we fail to readlink(), let's keep traversing
pass
# If we get these errors, try to get the real name of the file without accessing it.
@@ -690,7 +693,7 @@ else:
try:
name = _findfirstfile(path)
path, _ = split(path)
- except OSError:
+ except ignored_error:
path, name = split(path)
else:
path, name = split(path)
@@ -720,6 +723,15 @@ else:
if normcase(path) == devnull:
return '\\\\.\\NUL'
had_prefix = path.startswith(prefix)
+
+ if strict is ALLOW_MISSING:
+ ignored_error = FileNotFoundError
+ strict = True
+ elif strict:
+ ignored_error = ()
+ else:
+ ignored_error = OSError
+
if not had_prefix and not isabs(path):
path = join(cwd, path)
try:
@@ -727,17 +739,16 @@ else:
initial_winerror = 0
except ValueError as ex:
# gh-106242: Raised for embedded null characters
- # In strict mode, we convert into an OSError.
+ # In strict modes, we convert into an OSError.
# Non-strict mode returns the path as-is, since we've already
# made it absolute.
if strict:
raise OSError(str(ex)) from None
path = normpath(path)
- except OSError as ex:
- if strict:
- raise
+ except ignored_error as ex:
initial_winerror = ex.winerror
- path = _getfinalpathname_nonstrict(path)
+ path = _getfinalpathname_nonstrict(path,
+ ignored_error=ignored_error)
# The path returned by _getfinalpathname will always start with \\?\ -
# strip off that prefix unless it was already provided on the original
# path.
diff --git a/Lib/os.py b/Lib/os.py
index 266e40b56f6..12926c832f5 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -10,7 +10,7 @@ This exports:
- os.extsep is the extension separator (always '.')
- os.altsep is the alternate pathname separator (None or '/')
- os.pathsep is the component separator used in $PATH etc
- - os.linesep is the line separator in text files ('\r' or '\n' or '\r\n')
+ - os.linesep is the line separator in text files ('\n' or '\r\n')
- os.defpath is the default search path for executables
- os.devnull is the file path of the null device ('/dev/null', etc.)
@@ -118,6 +118,7 @@ if _exists("_have_functions"):
_add("HAVE_FCHMODAT", "chmod")
_add("HAVE_FCHOWNAT", "chown")
_add("HAVE_FSTATAT", "stat")
+ _add("HAVE_LSTAT", "lstat")
_add("HAVE_FUTIMESAT", "utime")
_add("HAVE_LINKAT", "link")
_add("HAVE_MKDIRAT", "mkdir")
diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py
index 12cf9f579cb..2dc1f7f7126 100644
--- a/Lib/pathlib/__init__.py
+++ b/Lib/pathlib/__init__.py
@@ -28,8 +28,9 @@ except ImportError:
from pathlib._os import (
PathInfo, DirEntryInfo,
+ magic_open, vfspath,
ensure_different_files, ensure_distinct_paths,
- copyfile2, copyfileobj, magic_open, copy_info,
+ copyfile2, copyfileobj, copy_info,
)
@@ -1164,12 +1165,12 @@ class Path(PurePath):
# os.symlink() incorrectly creates a file-symlink on Windows. Avoid
# this by passing *target_is_dir* to os.symlink() on Windows.
def _copy_from_symlink(self, source, preserve_metadata=False):
- os.symlink(str(source.readlink()), self, source.info.is_dir())
+ os.symlink(vfspath(source.readlink()), self, source.info.is_dir())
if preserve_metadata:
copy_info(source.info, self, follow_symlinks=False)
else:
def _copy_from_symlink(self, source, preserve_metadata=False):
- os.symlink(str(source.readlink()), self)
+ os.symlink(vfspath(source.readlink()), self)
if preserve_metadata:
copy_info(source.info, self, follow_symlinks=False)
diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py
index 039836941dd..62a4adb555e 100644
--- a/Lib/pathlib/_os.py
+++ b/Lib/pathlib/_os.py
@@ -210,6 +210,26 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None,
raise TypeError(f"{cls.__name__} can't be opened with mode {mode!r}")
+def vfspath(path):
+ """
+ Return the string representation of a virtual path object.
+ """
+ try:
+ return os.fsdecode(path)
+ except TypeError:
+ pass
+
+ path_type = type(path)
+ try:
+ return path_type.__vfspath__(path)
+ except AttributeError:
+ if hasattr(path_type, '__vfspath__'):
+ raise
+
+ raise TypeError("expected str, bytes, os.PathLike or JoinablePath "
+ "object, not " + path_type.__name__)
+
+
def ensure_distinct_paths(source, target):
"""
Raise OSError(EINVAL) if the other path is within this path.
@@ -225,8 +245,8 @@ def ensure_distinct_paths(source, target):
err = OSError(EINVAL, "Source path is a parent of target path")
else:
return
- err.filename = str(source)
- err.filename2 = str(target)
+ err.filename = vfspath(source)
+ err.filename2 = vfspath(target)
raise err
@@ -247,8 +267,8 @@ def ensure_different_files(source, target):
except (OSError, ValueError):
return
err = OSError(EINVAL, "Source and target are the same file")
- err.filename = str(source)
- err.filename2 = str(target)
+ err.filename = vfspath(source)
+ err.filename2 = vfspath(target)
raise err
diff --git a/Lib/pathlib/types.py b/Lib/pathlib/types.py
index d8f5c34a1a7..42b80221608 100644
--- a/Lib/pathlib/types.py
+++ b/Lib/pathlib/types.py
@@ -11,9 +11,10 @@ Protocols for supporting classes in pathlib.
from abc import ABC, abstractmethod
-from glob import _PathGlobber
+from glob import _GlobberBase
from io import text_encoding
-from pathlib._os import magic_open, ensure_distinct_paths, ensure_different_files, copyfileobj
+from pathlib._os import (magic_open, vfspath, ensure_distinct_paths,
+ ensure_different_files, copyfileobj)
from pathlib import PurePath, Path
from typing import Optional, Protocol, runtime_checkable
@@ -60,6 +61,25 @@ class PathInfo(Protocol):
def is_symlink(self) -> bool: ...
+class _PathGlobber(_GlobberBase):
+ """Provides shell-style pattern matching and globbing for ReadablePath.
+ """
+
+ @staticmethod
+ def lexists(path):
+ return path.info.exists(follow_symlinks=False)
+
+ @staticmethod
+ def scandir(path):
+ return ((child.info, child.name, child) for child in path.iterdir())
+
+ @staticmethod
+ def concat_path(path, text):
+ return path.with_segments(vfspath(path) + text)
+
+ stringify_path = staticmethod(vfspath)
+
+
class _JoinablePath(ABC):
"""Abstract base class for pure path objects.
@@ -86,20 +106,19 @@ class _JoinablePath(ABC):
raise NotImplementedError
@abstractmethod
- def __str__(self):
- """Return the string representation of the path, suitable for
- passing to system calls."""
+ def __vfspath__(self):
+ """Return the string representation of the path."""
raise NotImplementedError
@property
def anchor(self):
"""The concatenation of the drive and root, or ''."""
- return _explode_path(str(self), self.parser.split)[0]
+ return _explode_path(vfspath(self), self.parser.split)[0]
@property
def name(self):
"""The final path component, if any."""
- return self.parser.split(str(self))[1]
+ return self.parser.split(vfspath(self))[1]
@property
def suffix(self):
@@ -135,7 +154,7 @@ class _JoinablePath(ABC):
split = self.parser.split
if split(name)[0]:
raise ValueError(f"Invalid name {name!r}")
- path = str(self)
+ path = vfspath(self)
path = path.removesuffix(split(path)[1]) + name
return self.with_segments(path)
@@ -168,7 +187,7 @@ class _JoinablePath(ABC):
def parts(self):
"""An object providing sequence-like access to the
components in the filesystem path."""
- anchor, parts = _explode_path(str(self), self.parser.split)
+ anchor, parts = _explode_path(vfspath(self), self.parser.split)
if anchor:
parts.append(anchor)
return tuple(reversed(parts))
@@ -179,24 +198,24 @@ class _JoinablePath(ABC):
paths) or a totally different path (if one of the arguments is
anchored).
"""
- return self.with_segments(str(self), *pathsegments)
+ return self.with_segments(vfspath(self), *pathsegments)
def __truediv__(self, key):
try:
- return self.with_segments(str(self), key)
+ return self.with_segments(vfspath(self), key)
except TypeError:
return NotImplemented
def __rtruediv__(self, key):
try:
- return self.with_segments(key, str(self))
+ return self.with_segments(key, vfspath(self))
except TypeError:
return NotImplemented
@property
def parent(self):
"""The logical parent of the path."""
- path = str(self)
+ path = vfspath(self)
parent = self.parser.split(path)[0]
if path != parent:
return self.with_segments(parent)
@@ -206,7 +225,7 @@ class _JoinablePath(ABC):
def parents(self):
"""A sequence of this path's logical parents."""
split = self.parser.split
- path = str(self)
+ path = vfspath(self)
parent = split(path)[0]
parents = []
while path != parent:
@@ -223,7 +242,7 @@ class _JoinablePath(ABC):
case_sensitive = self.parser.normcase('Aa') == 'Aa'
globber = _PathGlobber(self.parser.sep, case_sensitive, recursive=True)
match = globber.compile(pattern, altsep=self.parser.altsep)
- return match(str(self)) is not None
+ return match(vfspath(self)) is not None
class _ReadablePath(_JoinablePath):
@@ -412,7 +431,7 @@ class _WritablePath(_JoinablePath):
while stack:
src, dst = stack.pop()
if not follow_symlinks and src.info.is_symlink():
- dst.symlink_to(str(src.readlink()), src.info.is_dir())
+ dst.symlink_to(vfspath(src.readlink()), src.info.is_dir())
elif src.info.is_dir():
children = src.iterdir()
dst.mkdir()
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 343cf4404d7..fc83728fb6d 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -75,8 +75,10 @@ import dis
import code
import glob
import json
+import stat
import token
import types
+import atexit
import codeop
import pprint
import signal
@@ -92,10 +94,12 @@ import tokenize
import itertools
import traceback
import linecache
+import selectors
+import threading
import _colorize
+import _pyrepl.utils
-from contextlib import closing
-from contextlib import contextmanager
+from contextlib import ExitStack, closing, contextmanager
from rlcompleter import Completer
from types import CodeType
from warnings import deprecated
@@ -339,7 +343,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
_last_pdb_instance = None
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
- nosigint=False, readrc=True, mode=None, backend=None):
+ nosigint=False, readrc=True, mode=None, backend=None, colorize=False):
bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else get_default_backend())
cmd.Cmd.__init__(self, completekey, stdin, stdout)
sys.audit("pdb.Pdb")
@@ -352,6 +356,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self._wait_for_mainpyfile = False
self.tb_lineno = {}
self.mode = mode
+ self.colorize = colorize and _colorize.can_colorize(file=stdout or sys.stdout)
# Try to load readline if it exists
try:
import readline
@@ -743,12 +748,34 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.message(repr(obj))
@contextmanager
- def _enable_multiline_completion(self):
+ def _enable_multiline_input(self):
+ try:
+ import readline
+ except ImportError:
+ yield
+ return
+
+ def input_auto_indent():
+ last_index = readline.get_current_history_length()
+ last_line = readline.get_history_item(last_index)
+ if last_line:
+ if last_line.isspace():
+ # If the last line is empty, we don't need to indent
+ return
+
+ last_line = last_line.rstrip('\r\n')
+ indent = len(last_line) - len(last_line.lstrip())
+ if last_line.endswith(":"):
+ indent += 4
+ readline.insert_text(' ' * indent)
+
completenames = self.completenames
try:
self.completenames = self.complete_multiline_names
+ readline.set_startup_hook(input_auto_indent)
yield
finally:
+ readline.set_startup_hook()
self.completenames = completenames
return
@@ -857,7 +884,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
try:
if (code := codeop.compile_command(line + '\n', '<stdin>', 'single')) is None:
# Multi-line mode
- with self._enable_multiline_completion():
+ with self._enable_multiline_input():
buffer = line
continue_prompt = "... "
while (code := codeop.compile_command(buffer, '<stdin>', 'single')) is None:
@@ -879,7 +906,11 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return None, None, False
else:
line = line.rstrip('\r\n')
- buffer += '\n' + line
+ if line.isspace():
+ # empty line, just continue
+ buffer += '\n'
+ else:
+ buffer += '\n' + line
self.lastcmd = buffer
except SyntaxError as e:
# Maybe it's an await expression/statement
@@ -1036,6 +1067,13 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return True
return False
+ def _colorize_code(self, code):
+ if self.colorize:
+ colors = list(_pyrepl.utils.gen_colors(code))
+ chars, _ = _pyrepl.utils.disp_str(code, colors=colors, force_color=True)
+ code = "".join(chars)
+ return code
+
# interface abstraction functions
def message(self, msg, end='\n'):
@@ -2166,6 +2204,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
s += '->'
elif lineno == exc_lineno:
s += '>>'
+ if self.colorize:
+ line = self._colorize_code(line)
self.message(s + '\t' + line.rstrip())
def do_whatis(self, arg):
@@ -2365,8 +2405,14 @@ class Pdb(bdb.Bdb, cmd.Cmd):
prefix = '> '
else:
prefix = ' '
- self.message(prefix +
- self.format_stack_entry(frame_lineno, prompt_prefix))
+ stack_entry = self.format_stack_entry(frame_lineno, prompt_prefix)
+ if self.colorize:
+ lines = stack_entry.split(prompt_prefix, 1)
+ if len(lines) > 1:
+ # We have some code to display
+ lines[1] = self._colorize_code(lines[1])
+ stack_entry = prompt_prefix.join(lines)
+ self.message(prefix + stack_entry)
# Provide help
@@ -2604,7 +2650,7 @@ def set_trace(*, header=None, commands=None):
if Pdb._last_pdb_instance is not None:
pdb = Pdb._last_pdb_instance
else:
- pdb = Pdb(mode='inline', backend='monitoring')
+ pdb = Pdb(mode='inline', backend='monitoring', colorize=True)
if header is not None:
pdb.message(header)
pdb.set_trace(sys._getframe().f_back, commands=commands)
@@ -2619,7 +2665,7 @@ async def set_trace_async(*, header=None, commands=None):
if Pdb._last_pdb_instance is not None:
pdb = Pdb._last_pdb_instance
else:
- pdb = Pdb(mode='inline', backend='monitoring')
+ pdb = Pdb(mode='inline', backend='monitoring', colorize=True)
if header is not None:
pdb.message(header)
await pdb.set_trace_async(sys._getframe().f_back, commands=commands)
@@ -2627,13 +2673,26 @@ async def set_trace_async(*, header=None, commands=None):
# Remote PDB
class _PdbServer(Pdb):
- def __init__(self, sockfile, owns_sockfile=True, **kwargs):
+ def __init__(
+ self,
+ sockfile,
+ signal_server=None,
+ owns_sockfile=True,
+ colorize=False,
+ **kwargs,
+ ):
self._owns_sockfile = owns_sockfile
self._interact_state = None
self._sockfile = sockfile
self._command_name_cache = []
self._write_failed = False
- super().__init__(**kwargs)
+ if signal_server:
+ # Only started by the top level _PdbServer, not recursive ones.
+ self._start_signal_listener(signal_server)
+ # Override the `colorize` attribute set by the parent constructor,
+ # because it checks the server's stdout, rather than the client's.
+ super().__init__(colorize=False, **kwargs)
+ self.colorize = colorize
@staticmethod
def protocol_version():
@@ -2688,15 +2747,49 @@ class _PdbServer(Pdb):
f"PDB message doesn't follow the schema! {msg}"
)
+ @classmethod
+ def _start_signal_listener(cls, address):
+ def listener(sock):
+ with closing(sock):
+ # Check if the interpreter is finalizing every quarter of a second.
+ # Clean up and exit if so.
+ sock.settimeout(0.25)
+ sock.shutdown(socket.SHUT_WR)
+ while not shut_down.is_set():
+ try:
+ data = sock.recv(1024)
+ except socket.timeout:
+ continue
+ if data == b"":
+ return # EOF
+ signal.raise_signal(signal.SIGINT)
+
+ def stop_thread():
+ shut_down.set()
+ thread.join()
+
+ # Use a daemon thread so that we don't detach until after all non-daemon
+ # threads are done. Use an atexit handler to stop gracefully at that point,
+ # so that our thread is stopped before the interpreter is torn down.
+ shut_down = threading.Event()
+ thread = threading.Thread(
+ target=listener,
+ args=[socket.create_connection(address, timeout=5)],
+ daemon=True,
+ )
+ atexit.register(stop_thread)
+ thread.start()
+
def _send(self, **kwargs):
self._ensure_valid_message(kwargs)
json_payload = json.dumps(kwargs)
try:
self._sockfile.write(json_payload.encode() + b"\n")
self._sockfile.flush()
- except OSError:
- # This means that the client has abruptly disconnected, but we'll
- # handle that the next time we try to read from the client instead
+ except (OSError, ValueError):
+ # We get an OSError if the network connection has dropped, and a
+ # ValueError if detach() if the sockfile has been closed. We'll
+ # handle this the next time we try to read from the client instead
# of trying to handle it from everywhere _send() may be called.
# Track this with a flag rather than assuming readline() will ever
# return an empty string because the socket may be half-closed.
@@ -2887,7 +2980,11 @@ class _PdbServer(Pdb):
@typing.override
def _create_recursive_debugger(self):
- return _PdbServer(self._sockfile, owns_sockfile=False)
+ return _PdbServer(
+ self._sockfile,
+ owns_sockfile=False,
+ colorize=self.colorize,
+ )
@typing.override
def _prompt_for_confirmation(self, prompt, default):
@@ -2924,15 +3021,21 @@ class _PdbServer(Pdb):
class _PdbClient:
- def __init__(self, pid, sockfile, interrupt_script):
+ def __init__(self, pid, server_socket, interrupt_sock):
self.pid = pid
- self.sockfile = sockfile
- self.interrupt_script = interrupt_script
+ self.read_buf = b""
+ self.signal_read = None
+ self.signal_write = None
+ self.sigint_received = False
+ self.raise_on_sigint = False
+ self.server_socket = server_socket
+ self.interrupt_sock = interrupt_sock
self.pdb_instance = Pdb()
self.pdb_commands = set()
self.completion_matches = []
self.state = "dumb"
self.write_failed = False
+ self.multiline_block = False
def _ensure_valid_message(self, msg):
# Ensure the message conforms to our protocol.
@@ -2968,8 +3071,7 @@ class _PdbClient:
self._ensure_valid_message(kwargs)
json_payload = json.dumps(kwargs)
try:
- self.sockfile.write(json_payload.encode() + b"\n")
- self.sockfile.flush()
+ self.server_socket.sendall(json_payload.encode() + b"\n")
except OSError:
# This means that the client has abruptly disconnected, but we'll
# handle that the next time we try to read from the client instead
@@ -2978,9 +3080,44 @@ class _PdbClient:
# return an empty string because the socket may be half-closed.
self.write_failed = True
- def read_command(self, prompt):
- reply = input(prompt)
+ def _readline(self):
+ if self.sigint_received:
+ # There's a pending unhandled SIGINT. Handle it now.
+ self.sigint_received = False
+ raise KeyboardInterrupt
+ # Wait for either a SIGINT or a line or EOF from the PDB server.
+ selector = selectors.DefaultSelector()
+ selector.register(self.signal_read, selectors.EVENT_READ)
+ selector.register(self.server_socket, selectors.EVENT_READ)
+
+ while b"\n" not in self.read_buf:
+ for key, _ in selector.select():
+ if key.fileobj == self.signal_read:
+ self.signal_read.recv(1024)
+ if self.sigint_received:
+ # If not, we're reading wakeup events for sigints that
+ # we've previously handled, and can ignore them.
+ self.sigint_received = False
+ raise KeyboardInterrupt
+ elif key.fileobj == self.server_socket:
+ data = self.server_socket.recv(16 * 1024)
+ self.read_buf += data
+ if not data and b"\n" not in self.read_buf:
+ # EOF without a full final line. Drop the partial line.
+ self.read_buf = b""
+ return b""
+
+ ret, sep, self.read_buf = self.read_buf.partition(b"\n")
+ return ret + sep
+
+ def read_input(self, prompt, multiline_block):
+ self.multiline_block = multiline_block
+ with self._sigint_raises_keyboard_interrupt():
+ return input(prompt)
+
+ def read_command(self, prompt):
+ reply = self.read_input(prompt, multiline_block=False)
if self.state == "dumb":
# No logic applied whatsoever, just pass the raw reply back.
return reply
@@ -3003,9 +3140,9 @@ class _PdbClient:
return prefix + reply
# Otherwise, valid first line of a multi-line statement
- continue_prompt = "...".ljust(len(prompt))
+ more_prompt = "...".ljust(len(prompt))
while codeop.compile_command(reply, "<stdin>", "single") is None:
- reply += "\n" + input(continue_prompt)
+ reply += "\n" + self.read_input(more_prompt, multiline_block=True)
return prefix + reply
@@ -3030,11 +3167,70 @@ class _PdbClient:
finally:
readline.set_completer(old_completer)
+ @contextmanager
+ def _sigint_handler(self):
+ # Signal handling strategy:
+ # - When we call input() we want a SIGINT to raise KeyboardInterrupt
+ # - Otherwise we want to write to the wakeup FD and set a flag.
+ # We'll break out of select() when the wakeup FD is written to,
+ # and we'll check the flag whenever we're about to accept input.
+ def handler(signum, frame):
+ self.sigint_received = True
+ if self.raise_on_sigint:
+ # One-shot; don't raise again until the flag is set again.
+ self.raise_on_sigint = False
+ self.sigint_received = False
+ raise KeyboardInterrupt
+
+ sentinel = object()
+ old_handler = sentinel
+ old_wakeup_fd = sentinel
+
+ self.signal_read, self.signal_write = socket.socketpair()
+ with (closing(self.signal_read), closing(self.signal_write)):
+ self.signal_read.setblocking(False)
+ self.signal_write.setblocking(False)
+
+ try:
+ old_handler = signal.signal(signal.SIGINT, handler)
+
+ try:
+ old_wakeup_fd = signal.set_wakeup_fd(
+ self.signal_write.fileno(),
+ warn_on_full_buffer=False,
+ )
+ yield
+ finally:
+ # Restore the old wakeup fd if we installed a new one
+ if old_wakeup_fd is not sentinel:
+ signal.set_wakeup_fd(old_wakeup_fd)
+ finally:
+ self.signal_read = self.signal_write = None
+ if old_handler is not sentinel:
+ # Restore the old handler if we installed a new one
+ signal.signal(signal.SIGINT, old_handler)
+
+ @contextmanager
+ def _sigint_raises_keyboard_interrupt(self):
+ if self.sigint_received:
+ # There's a pending unhandled SIGINT. Handle it now.
+ self.sigint_received = False
+ raise KeyboardInterrupt
+
+ try:
+ self.raise_on_sigint = True
+ yield
+ finally:
+ self.raise_on_sigint = False
+
def cmdloop(self):
- with self.readline_completion(self.complete):
+ with (
+ self._sigint_handler(),
+ self.readline_completion(self.complete),
+ ):
while not self.write_failed:
try:
- if not (payload_bytes := self.sockfile.readline()):
+ if not (payload_bytes := self._readline()):
break
except KeyboardInterrupt:
self.send_interrupt()
@@ -3052,11 +3248,17 @@ class _PdbClient:
self.process_payload(payload)
def send_interrupt(self):
- print(
- "\n*** Program will stop at the next bytecode instruction."
- " (Use 'cont' to resume)."
- )
- sys.remote_exec(self.pid, self.interrupt_script)
+ if self.interrupt_sock is not None:
+ # Write to a socket that the PDB server listens on. This triggers
+ # the remote to raise a SIGINT for itself. We do this because
+ # Windows doesn't allow triggering SIGINT remotely.
+ # See https://stackoverflow.com/a/35792192 for many more details.
+ self.interrupt_sock.sendall(signal.SIGINT.to_bytes())
+ else:
+ # On Unix we can just send a SIGINT to the remote process.
+ # This is preferable to using the signal thread approach that we
+ # use on Windows because it can interrupt IO in the main thread.
+ os.kill(self.pid, signal.SIGINT)
def process_payload(self, payload):
match payload:
@@ -3105,9 +3307,13 @@ class _PdbClient:
origline = readline.get_line_buffer()
line = origline.lstrip()
- stripped = len(origline) - len(line)
- begidx = readline.get_begidx() - stripped
- endidx = readline.get_endidx() - stripped
+ if self.multiline_block:
+ # We're completing a line contained in a multi-line block.
+ # Force the remote to treat it as a Python expression.
+ line = "! " + line
+ offset = len(origline) - len(line)
+ begidx = readline.get_begidx() - offset
+ endidx = readline.get_endidx() - offset
msg = {
"complete": {
@@ -3122,7 +3328,7 @@ class _PdbClient:
if self.write_failed:
return None
- payload = self.sockfile.readline()
+ payload = self._readline()
if not payload:
return None
@@ -3139,11 +3345,31 @@ class _PdbClient:
return None
-def _connect(host, port, frame, commands, version):
+def _connect(
+ *,
+ host,
+ port,
+ frame,
+ commands,
+ version,
+ signal_raising_thread,
+ colorize,
+):
with closing(socket.create_connection((host, port))) as conn:
sockfile = conn.makefile("rwb")
- remote_pdb = _PdbServer(sockfile)
+ # The client requests this thread on Windows but not on Unix.
+ # Most tests don't request this thread, to keep them simpler.
+ if signal_raising_thread:
+ signal_server = (host, port)
+ else:
+ signal_server = None
+
+ remote_pdb = _PdbServer(
+ sockfile,
+ signal_server=signal_server,
+ colorize=colorize,
+ )
weakref.finalize(remote_pdb, sockfile.close)
if Pdb._last_pdb_instance is not None:
@@ -3158,49 +3384,57 @@ def _connect(host, port, frame, commands, version):
f"\nLocal pdb module's protocol version: {attach_ver}"
)
else:
- remote_pdb.rcLines.extend(commands.splitlines())
- remote_pdb.set_trace(frame=frame)
+ remote_pdb.set_trace(frame=frame, commands=commands.splitlines())
def attach(pid, commands=()):
"""Attach to a running process with the given PID."""
- with closing(socket.create_server(("localhost", 0))) as server:
+ with ExitStack() as stack:
+ server = stack.enter_context(
+ closing(socket.create_server(("localhost", 0)))
+ )
port = server.getsockname()[1]
- with tempfile.NamedTemporaryFile("w", delete_on_close=False) as connect_script:
- connect_script.write(
- textwrap.dedent(
- f"""
- import pdb, sys
- pdb._connect(
- host="localhost",
- port={port},
- frame=sys._getframe(1),
- commands={json.dumps("\n".join(commands))},
- version={_PdbServer.protocol_version()},
- )
- """
+ connect_script = stack.enter_context(
+ tempfile.NamedTemporaryFile("w", delete_on_close=False)
+ )
+
+ use_signal_thread = sys.platform == "win32"
+ colorize = _colorize.can_colorize()
+
+ connect_script.write(
+ textwrap.dedent(
+ f"""
+ import pdb, sys
+ pdb._connect(
+ host="localhost",
+ port={port},
+ frame=sys._getframe(1),
+ commands={json.dumps("\n".join(commands))},
+ version={_PdbServer.protocol_version()},
+ signal_raising_thread={use_signal_thread!r},
+ colorize={colorize!r},
)
+ """
)
- connect_script.close()
- sys.remote_exec(pid, connect_script.name)
-
- # TODO Add a timeout? Or don't bother since the user can ^C?
- client_sock, _ = server.accept()
-
- with closing(client_sock):
- sockfile = client_sock.makefile("rwb")
-
- with closing(sockfile):
- with tempfile.NamedTemporaryFile("w", delete_on_close=False) as interrupt_script:
- interrupt_script.write(
- 'import pdb, sys\n'
- 'if inst := pdb.Pdb._last_pdb_instance:\n'
- ' inst.set_trace(sys._getframe(1))\n'
- )
- interrupt_script.close()
+ )
+ connect_script.close()
+ orig_mode = os.stat(connect_script.name).st_mode
+ os.chmod(connect_script.name, orig_mode | stat.S_IROTH | stat.S_IRGRP)
+ sys.remote_exec(pid, connect_script.name)
+
+ # TODO Add a timeout? Or don't bother since the user can ^C?
+ client_sock, _ = server.accept()
+ stack.enter_context(closing(client_sock))
+
+ if use_signal_thread:
+ interrupt_sock, _ = server.accept()
+ stack.enter_context(closing(interrupt_sock))
+ interrupt_sock.setblocking(False)
+ else:
+ interrupt_sock = None
- _PdbClient(pid, sockfile, interrupt_script.name).cmdloop()
+ _PdbClient(pid, client_sock, interrupt_sock).cmdloop()
# Post-Mortem interface
@@ -3258,7 +3492,8 @@ def help():
_usage = """\
Debug the Python program given by pyfile. Alternatively,
an executable module or package to debug can be specified using
-the -m switch.
+the -m switch. You can also attach to a running Python process
+using the -p option with its PID.
Initial commands are read from .pdbrc files in your home directory
and in the current directory, if they exist. Commands supplied with
@@ -3272,10 +3507,13 @@ To let the script run up to a given line X in the debugged file, use
def main():
import argparse
- parser = argparse.ArgumentParser(usage="%(prog)s [-h] [-c command] (-m module | -p pid | pyfile) [args ...]",
- description=_usage,
- formatter_class=argparse.RawDescriptionHelpFormatter,
- allow_abbrev=False)
+ parser = argparse.ArgumentParser(
+ usage="%(prog)s [-h] [-c command] (-m module | -p pid | pyfile) [args ...]",
+ description=_usage,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ allow_abbrev=False,
+ color=True,
+ )
# We need to maunally get the script from args, because the first positional
# arguments could be either the script we need to debug, or the argument
@@ -3338,7 +3576,7 @@ def main():
# modified by the script being debugged. It's a bad idea when it was
# changed by the user from the command line. There is a "restart" command
# which allows explicit specification of command line arguments.
- pdb = Pdb(mode='cli', backend='monitoring')
+ pdb = Pdb(mode='cli', backend='monitoring', colorize=True)
pdb.rcLines.extend(opts.commands)
while True:
try:
diff --git a/Lib/pickle.py b/Lib/pickle.py
index 4fa3632d1a7..beaefae0479 100644
--- a/Lib/pickle.py
+++ b/Lib/pickle.py
@@ -1911,7 +1911,9 @@ def _main(args=None):
import argparse
import pprint
parser = argparse.ArgumentParser(
- description='display contents of the pickle files')
+ description='display contents of the pickle files',
+ color=True,
+ )
parser.add_argument(
'pickle_file',
nargs='+', help='the pickle file')
diff --git a/Lib/pickletools.py b/Lib/pickletools.py
index 53f25ea4e46..bcddfb722bd 100644
--- a/Lib/pickletools.py
+++ b/Lib/pickletools.py
@@ -2842,7 +2842,9 @@ __test__ = {'disassembler_test': _dis_test,
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
- description='disassemble one or more pickle files')
+ description='disassemble one or more pickle files',
+ color=True,
+ )
parser.add_argument(
'pickle_file',
nargs='+', help='the pickle file')
diff --git a/Lib/platform.py b/Lib/platform.py
index 507552f360b..da15bb4717b 100644
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -29,7 +29,7 @@
#
# History:
#
-# <see CVS and SVN checkin messages for history>
+# <see checkin messages for history>
#
# 1.0.9 - added invalidate_caches() function to invalidate cached values
# 1.0.8 - changed Windows support to read version from kernel32.dll
@@ -110,7 +110,7 @@ __copyright__ = """
"""
-__version__ = '1.0.9'
+__version__ = '1.1.0'
import collections
import os
@@ -528,53 +528,6 @@ def ios_ver(system="", release="", model="", is_simulator=False):
return IOSVersionInfo(system, release, model, is_simulator)
-def _java_getprop(name, default):
- """This private helper is deprecated in 3.13 and will be removed in 3.15"""
- from java.lang import System
- try:
- value = System.getProperty(name)
- if value is None:
- return default
- return value
- except AttributeError:
- return default
-
-def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
-
- """ Version interface for Jython.
-
- Returns a tuple (release, vendor, vminfo, osinfo) with vminfo being
- a tuple (vm_name, vm_release, vm_vendor) and osinfo being a
- tuple (os_name, os_version, os_arch).
-
- Values which cannot be determined are set to the defaults
- given as parameters (which all default to '').
-
- """
- import warnings
- warnings._deprecated('java_ver', remove=(3, 15))
- # Import the needed APIs
- try:
- import java.lang # noqa: F401
- except ImportError:
- return release, vendor, vminfo, osinfo
-
- vendor = _java_getprop('java.vendor', vendor)
- release = _java_getprop('java.version', release)
- vm_name, vm_release, vm_vendor = vminfo
- vm_name = _java_getprop('java.vm.name', vm_name)
- vm_vendor = _java_getprop('java.vm.vendor', vm_vendor)
- vm_release = _java_getprop('java.vm.version', vm_release)
- vminfo = vm_name, vm_release, vm_vendor
- os_name, os_version, os_arch = osinfo
- os_arch = _java_getprop('java.os.arch', os_arch)
- os_name = _java_getprop('java.os.name', os_name)
- os_version = _java_getprop('java.os.version', os_version)
- osinfo = os_name, os_version, os_arch
-
- return release, vendor, vminfo, osinfo
-
-
AndroidVer = collections.namedtuple(
"AndroidVer", "release api_level manufacturer model device is_emulator")
@@ -659,6 +612,9 @@ def system_alias(system, release, version):
### Various internal helpers
+# Table for cleaning up characters in filenames.
+_SIMPLE_SUBSTITUTIONS = str.maketrans(r' /\:;"()', r'_-------')
+
def _platform(*args):
""" Helper to format the platform string in a filename
@@ -668,28 +624,13 @@ def _platform(*args):
platform = '-'.join(x.strip() for x in filter(len, args))
# Cleanup some possible filename obstacles...
- platform = platform.replace(' ', '_')
- platform = platform.replace('/', '-')
- platform = platform.replace('\\', '-')
- platform = platform.replace(':', '-')
- platform = platform.replace(';', '-')
- platform = platform.replace('"', '-')
- platform = platform.replace('(', '-')
- platform = platform.replace(')', '-')
+ platform = platform.translate(_SIMPLE_SUBSTITUTIONS)
# No need to report 'unknown' information...
platform = platform.replace('unknown', '')
# Fold '--'s and remove trailing '-'
- while True:
- cleaned = platform.replace('--', '-')
- if cleaned == platform:
- break
- platform = cleaned
- while platform and platform[-1] == '-':
- platform = platform[:-1]
-
- return platform
+ return re.sub(r'-{2,}', '-', platform).rstrip('-')
def _node(default=''):
@@ -1034,13 +975,6 @@ def uname():
version = '16bit'
system = 'Windows'
- elif system[:4] == 'java':
- release, vendor, vminfo, osinfo = java_ver()
- system = 'Java'
- version = ', '.join(vminfo)
- if not version:
- version = vendor
-
# System specific extensions
if system == 'OpenVMS':
# OpenVMS seems to have release and version mixed up
@@ -1198,7 +1132,7 @@ def _sys_version(sys_version=None):
# CPython
cpython_sys_version_parser = re.compile(
r'([\w.+]+)\s*' # "version<space>"
- r'(?:experimental free-threading build\s+)?' # "free-threading-build<space>"
+ r'(?:free-threading build\s+)?' # "free-threading-build<space>"
r'\(#?([^,]+)' # "(#buildno"
r'(?:,\s*([\w ]*)' # ", builddate"
r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>"
@@ -1370,15 +1304,6 @@ def platform(aliased=False, terse=False):
platform = _platform(system, release, machine, processor,
'with',
libcname+libcversion)
- elif system == 'Java':
- # Java platforms
- r, v, vminfo, (os_name, os_version, os_arch) = java_ver()
- if terse or not os_name:
- platform = _platform(system, release, version)
- else:
- platform = _platform(system, release, version,
- 'on',
- os_name, os_version, os_arch)
else:
# Generic handler
@@ -1467,7 +1392,7 @@ def invalidate_caches():
def _parse_args(args: list[str] | None):
import argparse
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument("args", nargs="*", choices=["nonaliased", "terse"])
parser.add_argument(
"--terse",
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index db72ded8826..d38f3bd5872 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -36,7 +36,7 @@ __all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext"
"samefile","sameopenfile","samestat",
"curdir","pardir","sep","pathsep","defpath","altsep","extsep",
"devnull","realpath","supports_unicode_filenames","relpath",
- "commonpath", "isjunction","isdevdrive"]
+ "commonpath", "isjunction","isdevdrive","ALLOW_MISSING"]
def _get_sep(path):
@@ -402,10 +402,18 @@ symbolic links encountered in the path."""
curdir = '.'
pardir = '..'
getcwd = os.getcwd
- return _realpath(filename, strict, sep, curdir, pardir, getcwd)
+ if strict is ALLOW_MISSING:
+ ignored_error = FileNotFoundError
+ strict = True
+ elif strict:
+ ignored_error = ()
+ else:
+ ignored_error = OSError
+
+ lstat = os.lstat
+ readlink = os.readlink
+ maxlinks = None
-def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
- getcwd=os.getcwd, lstat=os.lstat, readlink=os.readlink, maxlinks=None):
# The stack of unresolved path parts. When popped, a special value of None
# indicates that a symlink target has been resolved, and that the original
# symlink path can be retrieved by popping again. The [::-1] slice is a
@@ -477,27 +485,28 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
path = newpath
continue
target = readlink(newpath)
- except OSError:
- if strict:
- raise
- path = newpath
+ except ignored_error:
+ pass
+ else:
+ # Resolve the symbolic link
+ if target.startswith(sep):
+ # Symlink target is absolute; reset resolved path.
+ path = sep
+ if maxlinks is None:
+ # Mark this symlink as seen but not fully resolved.
+ seen[newpath] = None
+ # Push the symlink path onto the stack, and signal its specialness
+ # by also pushing None. When these entries are popped, we'll
+ # record the fully-resolved symlink target in the 'seen' mapping.
+ rest.append(newpath)
+ rest.append(None)
+ # Push the unresolved symlink target parts onto the stack.
+ target_parts = target.split(sep)[::-1]
+ rest.extend(target_parts)
+ part_count += len(target_parts)
continue
- # Resolve the symbolic link
- if target.startswith(sep):
- # Symlink target is absolute; reset resolved path.
- path = sep
- if maxlinks is None:
- # Mark this symlink as seen but not fully resolved.
- seen[newpath] = None
- # Push the symlink path onto the stack, and signal its specialness
- # by also pushing None. When these entries are popped, we'll
- # record the fully-resolved symlink target in the 'seen' mapping.
- rest.append(newpath)
- rest.append(None)
- # Push the unresolved symlink target parts onto the stack.
- target_parts = target.split(sep)[::-1]
- rest.extend(target_parts)
- part_count += len(target_parts)
+ # An error occurred and was ignored.
+ path = newpath
return path
diff --git a/Lib/pprint.py b/Lib/pprint.py
index dc0953cec67..92a2c543ac2 100644
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -248,6 +248,49 @@ class PrettyPrinter:
_dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict
+ def _pprint_dict_view(self, object, stream, indent, allowance, context, level):
+ """Pretty print dict views (keys, values, items)."""
+ if isinstance(object, self._dict_items_view):
+ key = _safe_tuple
+ else:
+ key = _safe_key
+ write = stream.write
+ write(object.__class__.__name__ + '([')
+ if self._indent_per_level > 1:
+ write((self._indent_per_level - 1) * ' ')
+ length = len(object)
+ if length:
+ if self._sort_dicts:
+ entries = sorted(object, key=key)
+ else:
+ entries = object
+ self._format_items(entries, stream, indent, allowance + 1,
+ context, level)
+ write('])')
+
+ def _pprint_mapping_abc_view(self, object, stream, indent, allowance, context, level):
+ """Pretty print mapping views from collections.abc."""
+ write = stream.write
+ write(object.__class__.__name__ + '(')
+ # Dispatch formatting to the view's _mapping
+ self._format(object._mapping, stream, indent, allowance, context, level)
+ write(')')
+
+ _dict_keys_view = type({}.keys())
+ _dispatch[_dict_keys_view.__repr__] = _pprint_dict_view
+
+ _dict_values_view = type({}.values())
+ _dispatch[_dict_values_view.__repr__] = _pprint_dict_view
+
+ _dict_items_view = type({}.items())
+ _dispatch[_dict_items_view.__repr__] = _pprint_dict_view
+
+ _dispatch[_collections.abc.MappingView.__repr__] = _pprint_mapping_abc_view
+
+ _view_reprs = {cls.__repr__ for cls in
+ (_dict_keys_view, _dict_values_view, _dict_items_view,
+ _collections.abc.MappingView)}
+
def _pprint_list(self, object, stream, indent, allowance, context, level):
stream.write('[')
self._format_items(object, stream, indent, allowance + 1,
@@ -644,6 +687,42 @@ class PrettyPrinter:
del context[objid]
return format % ", ".join(components), readable, recursive
+ if issubclass(typ, _collections.abc.MappingView) and r in self._view_reprs:
+ objid = id(object)
+ if maxlevels and level >= maxlevels:
+ return "{...}", False, objid in context
+ if objid in context:
+ return _recursion(object), False, True
+ key = _safe_key
+ if issubclass(typ, (self._dict_items_view, _collections.abc.ItemsView)):
+ key = _safe_tuple
+ if hasattr(object, "_mapping"):
+ # Dispatch formatting to the view's _mapping
+ mapping_repr, readable, recursive = self.format(
+ object._mapping, context, maxlevels, level)
+ return (typ.__name__ + '(%s)' % mapping_repr), readable, recursive
+ elif hasattr(typ, "_mapping"):
+ # We have a view that somehow has lost its type's _mapping, raise
+ # an error by calling repr() instead of failing cryptically later
+ return repr(object), True, False
+ if self._sort_dicts:
+ object = sorted(object, key=key)
+ context[objid] = 1
+ readable = True
+ recursive = False
+ components = []
+ append = components.append
+ level += 1
+ for val in object:
+ vrepr, vreadable, vrecur = self.format(
+ val, context, maxlevels, level)
+ append(vrepr)
+ readable = readable and vreadable
+ if vrecur:
+ recursive = True
+ del context[objid]
+ return typ.__name__ + '([%s])' % ", ".join(components), readable, recursive
+
rep = repr(object)
return rep, (rep and not rep.startswith('<')), False
diff --git a/Lib/py_compile.py b/Lib/py_compile.py
index 388614e51b1..43d8ec90ffb 100644
--- a/Lib/py_compile.py
+++ b/Lib/py_compile.py
@@ -177,7 +177,7 @@ def main():
import argparse
description = 'A simple command-line interface for py_compile module.'
- parser = argparse.ArgumentParser(description=description)
+ parser = argparse.ArgumentParser(description=description, color=True)
parser.add_argument(
'-q', '--quiet',
action='store_true',
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index def76d076a2..d508fb70ea4 100644
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -1812,7 +1812,6 @@ def writedocs(dir, pkgpath='', done=None):
def _introdoc():
- import textwrap
ver = '%d.%d' % sys.version_info[:2]
if os.environ.get('PYTHON_BASIC_REPL'):
pyrepl_keys = ''
@@ -2110,7 +2109,7 @@ has the same effect as typing a particular string at the help> prompt.
self.output.write(_introdoc())
def list(self, items, columns=4, width=80):
- items = list(sorted(items))
+ items = sorted(items)
colw = width // columns
rows = (len(items) + columns - 1) // columns
for row in range(rows):
@@ -2142,7 +2141,7 @@ to. Enter any symbol to get more help.
Here is a list of available topics. Enter any topic name to get more help.
''')
- self.list(self.topics.keys())
+ self.list(self.topics.keys(), columns=3)
def showtopic(self, topic, more_xrefs=''):
try:
@@ -2170,7 +2169,6 @@ module "pydoc_data.topics" could not be found.
if more_xrefs:
xrefs = (xrefs or '') + ' ' + more_xrefs
if xrefs:
- import textwrap
text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n'
wrapped_text = textwrap.wrap(text, 72)
doc += '\n%s\n' % '\n'.join(wrapped_text)
diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py
index 3f3d52dcc6b..5f7e14a79d3 100644
--- a/Lib/pydoc_data/topics.py
+++ b/Lib/pydoc_data/topics.py
@@ -1,4 +1,4 @@
-# Autogenerated by Sphinx on Tue Apr 8 14:20:44 2025
+# Autogenerated by Sphinx on Tue May 6 18:33:44 2025
# as part of the release process.
topics = {
@@ -1784,9 +1784,8 @@ Additional information on exceptions can be found in section
Exceptions, and information on using the "raise" statement to generate
exceptions may be found in section The raise statement.
-Changed in version 3.14.0a6 (unreleased): Support for optionally
-dropping grouping parentheses when using multiple exception types. See
-**PEP 758**.
+Changed in version 3.14: Support for optionally dropping grouping
+parentheses when using multiple exception types. See **PEP 758**.
"except" clause
@@ -1976,9 +1975,9 @@ Changed in version 3.8: Prior to Python 3.8, a "continue" statement
was illegal in the "finally" clause due to a problem with the
implementation.
-Changed in version 3.14.0a6 (unreleased): The compiler emits a
-"SyntaxWarning" when a "return", "break" or "continue" appears in a
-"finally" block (see **PEP 765**).
+Changed in version 3.14: The compiler emits a "SyntaxWarning" when a
+"return", "break" or "continue" appears in a "finally" block (see
+**PEP 765**).
The "with" statement
@@ -3933,6 +3932,19 @@ pdb.set_trace(*, header=None, commands=None)
Added in version 3.14: The *commands* argument.
+awaitable pdb.set_trace_async(*, header=None, commands=None)
+
+ async version of "set_trace()". This function should be used inside
+ an async function with "await".
+
+ async def f():
+ await pdb.set_trace_async()
+
+ "await" statements are supported if the debugger is invoked by this
+ function.
+
+ Added in version 3.14.
+
pdb.post_mortem(t=None)
Enter post-mortem debugging of the given exception or traceback
@@ -3970,7 +3982,7 @@ The "run*" functions and "set_trace()" are aliases for instantiating
the "Pdb" class and calling the method of the same name. If you want
to access further features, you have to do this yourself:
-class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False, readrc=True, mode=None, backend=None)
+class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False, readrc=True, mode=None, backend=None, colorize=False)
"Pdb" is the debugger class.
@@ -4001,6 +4013,10 @@ class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=Fa
See "set_default_backend()". Otherwise the supported backends are
"'settrace'" and "'monitoring'".
+ The *colorize* argument, if set to "True", will enable colorized
+ output in the debugger, if color is supported. This will highlight
+ source code displayed in pdb.
+
Example call to enable tracing with *skip*:
import pdb; pdb.Pdb(skip=['django.*']).set_trace()
@@ -4018,6 +4034,8 @@ class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=Fa
Added in version 3.14: Added the *backend* argument.
+ Added in version 3.14: Added the *colorize* argument.
+
Changed in version 3.14: Inline breakpoints like "breakpoint()" or
"pdb.set_trace()" will always stop the program at calling frame,
ignoring the *skip* pattern (if any).
@@ -4496,7 +4514,7 @@ exceptions [excnumber]
When using "pdb.pm()" or "Pdb.post_mortem(...)" with a chained
exception instead of a traceback, it allows the user to move
between the chained exceptions using "exceptions" command to list
- exceptions, and "exception <number>" to switch to that exception.
+ exceptions, and "exceptions <number>" to switch to that exception.
Example:
@@ -6752,8 +6770,8 @@ object.__or__(self, other)
called. The "__divmod__()" method should be the equivalent to
using "__floordiv__()" and "__mod__()"; it should not be related to
"__truediv__()". Note that "__pow__()" should be defined to accept
- an optional third argument if the ternary version of the built-in
- "pow()" function is to be supported.
+ an optional third argument if the three-argument version of the
+ built-in "pow()" function is to be supported.
If one of those methods does not support the operation with the
supplied arguments, it should return "NotImplemented".
@@ -6785,8 +6803,13 @@ object.__ror__(self, other)
called if "type(x).__sub__(x, y)" returns "NotImplemented" or
"type(y)" is a subclass of "type(x)". [5]
- Note that ternary "pow()" will not try calling "__rpow__()" (the
- coercion rules would become too complicated).
+ Note that "__rpow__()" should be defined to accept an optional
+ third argument if the three-argument version of the built-in
+ "pow()" function is to be supported.
+
+ Changed in version 3.14.0a7 (unreleased): Three-argument "pow()"
+ now try calling "__rpow__()" if necessary. Previously it was only
+ called in two-argument "pow()" and the binary power operator.
Note:
@@ -8785,8 +8808,8 @@ object.__or__(self, other)
called. The "__divmod__()" method should be the equivalent to
using "__floordiv__()" and "__mod__()"; it should not be related to
"__truediv__()". Note that "__pow__()" should be defined to accept
- an optional third argument if the ternary version of the built-in
- "pow()" function is to be supported.
+ an optional third argument if the three-argument version of the
+ built-in "pow()" function is to be supported.
If one of those methods does not support the operation with the
supplied arguments, it should return "NotImplemented".
@@ -8818,8 +8841,13 @@ object.__ror__(self, other)
called if "type(x).__sub__(x, y)" returns "NotImplemented" or
"type(y)" is a subclass of "type(x)". [5]
- Note that ternary "pow()" will not try calling "__rpow__()" (the
- coercion rules would become too complicated).
+ Note that "__rpow__()" should be defined to accept an optional
+ third argument if the three-argument version of the built-in
+ "pow()" function is to be supported.
+
+ Changed in version 3.14.0a7 (unreleased): Three-argument "pow()"
+ now try calling "__rpow__()" if necessary. Previously it was only
+ called in two-argument "pow()" and the binary power operator.
Note:
@@ -10100,9 +10128,8 @@ Additional information on exceptions can be found in section
Exceptions, and information on using the "raise" statement to generate
exceptions may be found in section The raise statement.
-Changed in version 3.14.0a6 (unreleased): Support for optionally
-dropping grouping parentheses when using multiple exception types. See
-**PEP 758**.
+Changed in version 3.14: Support for optionally dropping grouping
+parentheses when using multiple exception types. See **PEP 758**.
"except" clause
@@ -10292,9 +10319,9 @@ Changed in version 3.8: Prior to Python 3.8, a "continue" statement
was illegal in the "finally" clause due to a problem with the
implementation.
-Changed in version 3.14.0a6 (unreleased): The compiler emits a
-"SyntaxWarning" when a "return", "break" or "continue" appears in a
-"finally" block (see **PEP 765**).
+Changed in version 3.14: The compiler emits a "SyntaxWarning" when a
+"return", "break" or "continue" appears in a "finally" block (see
+**PEP 765**).
''',
'types': r'''The standard type hierarchy
***************************
@@ -11141,25 +11168,15 @@ Special attributes
| | collected during class body execution. See also: |
| | "__annotations__ attributes". For best practices |
| | on working with "__annotations__", please see |
-| | "annotationlib". Caution: Accessing the |
-| | "__annotations__" attribute of a class object |
-| | directly may yield incorrect results in the |
-| | presence of metaclasses. In addition, the |
-| | attribute may not exist for some classes. Use |
-| | "annotationlib.get_annotations()" to retrieve |
-| | class annotations safely. Changed in version |
-| | 3.14: Annotations are now lazily evaluated. See |
-| | **PEP 649**. |
+| | "annotationlib". Where possible, use |
+| | "annotationlib.get_annotations()" instead of |
+| | accessing this attribute directly. Changed in |
+| | version 3.14: Annotations are now lazily |
+| | evaluated. See **PEP 649**. |
+----------------------------------------------------+----------------------------------------------------+
| type.__annotate__() | The *annotate function* for this class, or "None" |
| | if the class has no annotations. See also: |
-| | "__annotate__ attributes". Caution: Accessing |
-| | the "__annotate__" attribute of a class object |
-| | directly may yield incorrect results in the |
-| | presence of metaclasses. Use |
-| | "annotationlib.get_annotate_function()" to |
-| | retrieve the annotate function safely. Added in |
-| | version 3.14. |
+| | "__annotate__ attributes". Added in version 3.14. |
+----------------------------------------------------+----------------------------------------------------+
| type.__type_params__ | A "tuple" containing the type parameters of a |
| | generic class. Added in version 3.12. |
@@ -11355,16 +11372,16 @@ the "**keywords" syntax to accept arbitrary keyword arguments; bit
Flags for details on the semantics of each flags that might be
present.
-Future feature declarations ("from __future__ import division") also
-use bits in "co_flags" to indicate whether a code object was compiled
-with a particular feature enabled: bit "0x2000" is set if the function
-was compiled with future division enabled; bits "0x10" and "0x1000"
-were used in earlier versions of Python.
+Future feature declarations (for example, "from __future__ import
+division") also use bits in "co_flags" to indicate whether a code
+object was compiled with a particular feature enabled. See
+"compiler_flag".
Other bits in "co_flags" are reserved for internal use.
-If a code object represents a function and has a docstring, the first
-item in "co_consts" is the docstring of the function.
+If a code object represents a function and has a docstring, the
+"CO_HAS_DOCSTRING" bit is set in "co_flags" and the first item in
+"co_consts" is the docstring of the function.
Methods on code objects
diff --git a/Lib/random.py b/Lib/random.py
index 5e5d0c4c694..86d562f0b8a 100644
--- a/Lib/random.py
+++ b/Lib/random.py
@@ -1011,7 +1011,7 @@ if hasattr(_os, "fork"):
def _parse_args(arg_list: list[str] | None):
import argparse
parser = argparse.ArgumentParser(
- formatter_class=argparse.RawTextHelpFormatter)
+ formatter_class=argparse.RawTextHelpFormatter, color=True)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-c", "--choice", nargs="+",
diff --git a/Lib/reprlib.py b/Lib/reprlib.py
index 19dbe3a07eb..ab18247682b 100644
--- a/Lib/reprlib.py
+++ b/Lib/reprlib.py
@@ -28,7 +28,7 @@ def recursive_repr(fillvalue='...'):
wrapper.__doc__ = getattr(user_function, '__doc__')
wrapper.__name__ = getattr(user_function, '__name__')
wrapper.__qualname__ = getattr(user_function, '__qualname__')
- wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
+ wrapper.__annotate__ = getattr(user_function, '__annotate__', None)
wrapper.__type_params__ = getattr(user_function, '__type_params__', ())
wrapper.__wrapped__ = user_function
return wrapper
@@ -181,7 +181,22 @@ class Repr:
return s
def repr_int(self, x, level):
- s = builtins.repr(x) # XXX Hope this isn't too slow...
+ try:
+ s = builtins.repr(x)
+ except ValueError as exc:
+ assert 'sys.set_int_max_str_digits()' in str(exc)
+ # Those imports must be deferred due to Python's build system
+ # where the reprlib module is imported before the math module.
+ import math, sys
+ # Integers with more than sys.get_int_max_str_digits() digits
+ # are rendered differently as their repr() raises a ValueError.
+ # See https://github.com/python/cpython/issues/135487.
+ k = 1 + int(math.log10(abs(x)))
+ # Note: math.log10(abs(x)) may be overestimated or underestimated,
+ # but for simplicity, we do not compute the exact number of digits.
+ max_digits = sys.get_int_max_str_digits()
+ return (f'<{x.__class__.__name__} instance with roughly {k} '
+ f'digits (limit at {max_digits}) at 0x{id(x):x}>')
if len(s) > self.maxlong:
i = max(0, (self.maxlong-3)//2)
j = max(0, self.maxlong-3-i)
diff --git a/Lib/shelve.py b/Lib/shelve.py
index 50584716e9e..b53dc8b7a8e 100644
--- a/Lib/shelve.py
+++ b/Lib/shelve.py
@@ -171,6 +171,11 @@ class Shelf(collections.abc.MutableMapping):
if hasattr(self.dict, 'sync'):
self.dict.sync()
+ def reorganize(self):
+ self.sync()
+ if hasattr(self.dict, 'reorganize'):
+ self.dict.reorganize()
+
class BsdDbShelf(Shelf):
"""Shelf implementation using the "BSD" db interface.
diff --git a/Lib/shutil.py b/Lib/shutil.py
index 510ae8c6f22..ca0a2ea2f7f 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -32,6 +32,13 @@ try:
except ImportError:
_LZMA_SUPPORTED = False
+try:
+ from compression import zstd
+ del zstd
+ _ZSTD_SUPPORTED = True
+except ImportError:
+ _ZSTD_SUPPORTED = False
+
_WINDOWS = os.name == 'nt'
posix = nt = None
if os.name == 'posix':
@@ -1006,6 +1013,8 @@ def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
tar_compression = 'bz2'
elif _LZMA_SUPPORTED and compress == 'xz':
tar_compression = 'xz'
+ elif _ZSTD_SUPPORTED and compress == 'zst':
+ tar_compression = 'zst'
else:
raise ValueError("bad value for 'compress', or compression format not "
"supported : {0}".format(compress))
@@ -1134,6 +1143,10 @@ if _LZMA_SUPPORTED:
_ARCHIVE_FORMATS['xztar'] = (_make_tarball, [('compress', 'xz')],
"xz'ed tar-file")
+if _ZSTD_SUPPORTED:
+ _ARCHIVE_FORMATS['zstdtar'] = (_make_tarball, [('compress', 'zst')],
+ "zstd'ed tar-file")
+
def get_archive_formats():
"""Returns a list of supported formats for archiving and unarchiving.
@@ -1174,7 +1187,7 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
'base_name' is the name of the file to create, minus any format-specific
extension; 'format' is the archive format: one of "zip", "tar", "gztar",
- "bztar", or "xztar". Or any other registered format.
+ "bztar", "zstdtar", or "xztar". Or any other registered format.
'root_dir' is a directory that will be the root directory of the
archive; ie. we typically chdir into 'root_dir' before creating the
@@ -1359,6 +1372,10 @@ if _LZMA_SUPPORTED:
_UNPACK_FORMATS['xztar'] = (['.tar.xz', '.txz'], _unpack_tarfile, [],
"xz'ed tar-file")
+if _ZSTD_SUPPORTED:
+ _UNPACK_FORMATS['zstdtar'] = (['.tar.zst', '.tzst'], _unpack_tarfile, [],
+ "zstd'ed tar-file")
+
def _find_unpack_format(filename):
for name, info in _UNPACK_FORMATS.items():
for extension in info[0]:
diff --git a/Lib/site.py b/Lib/site.py
index 5c38b1b17d5..f9327197159 100644
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -75,6 +75,7 @@ import builtins
import _sitebuiltins
import _io as io
import stat
+import errno
# Prefixes for site-packages; add additional prefixes like /usr/local here
PREFIXES = [sys.prefix, sys.exec_prefix]
@@ -578,10 +579,15 @@ def register_readline():
def write_history():
try:
readline_module.write_history_file(history)
- except (FileNotFoundError, PermissionError):
+ except FileNotFoundError, PermissionError:
# home directory does not exist or is not writable
# https://bugs.python.org/issue19891
pass
+ except OSError:
+ if errno.EROFS:
+ pass # gh-128066: read-only file system
+ else:
+ raise
atexit.register(write_history)
diff --git a/Lib/socketserver.py b/Lib/socketserver.py
index 35b2723de3b..93b0a23be27 100644
--- a/Lib/socketserver.py
+++ b/Lib/socketserver.py
@@ -441,7 +441,7 @@ class TCPServer(BaseServer):
socket_type = socket.SOCK_STREAM
- request_queue_size = 5
+ request_queue_size = getattr(socket, "SOMAXCONN", 5)
allow_reuse_address = False
diff --git a/Lib/sqlite3/__main__.py b/Lib/sqlite3/__main__.py
index 79a6209468d..35344ecceff 100644
--- a/Lib/sqlite3/__main__.py
+++ b/Lib/sqlite3/__main__.py
@@ -10,9 +10,12 @@ import sys
from argparse import ArgumentParser
from code import InteractiveConsole
from textwrap import dedent
+from _colorize import get_theme, theme_no_color
+from ._completer import completer
-def execute(c, sql, suppress_errors=True):
+
+def execute(c, sql, suppress_errors=True, theme=theme_no_color):
"""Helper that wraps execution of SQL code.
This is used both by the REPL and by direct execution from the CLI.
@@ -25,11 +28,15 @@ def execute(c, sql, suppress_errors=True):
for row in c.execute(sql):
print(row)
except sqlite3.Error as e:
+ t = theme.traceback
tp = type(e).__name__
try:
- print(f"{tp} ({e.sqlite_errorname}): {e}", file=sys.stderr)
+ tp += f" ({e.sqlite_errorname})"
except AttributeError:
- print(f"{tp}: {e}", file=sys.stderr)
+ pass
+ print(
+ f"{t.type}{tp}{t.reset}: {t.message}{e}{t.reset}", file=sys.stderr
+ )
if not suppress_errors:
sys.exit(1)
@@ -37,10 +44,11 @@ def execute(c, sql, suppress_errors=True):
class SqliteInteractiveConsole(InteractiveConsole):
"""A simple SQLite REPL."""
- def __init__(self, connection):
+ def __init__(self, connection, use_color=False):
super().__init__()
self._con = connection
self._cur = connection.cursor()
+ self._use_color = use_color
def runsource(self, source, filename="<input>", symbol="single"):
"""Override runsource, the core of the InteractiveConsole REPL.
@@ -48,23 +56,39 @@ class SqliteInteractiveConsole(InteractiveConsole):
Return True if more input is needed; buffering is done automatically.
Return False if input is a complete statement ready for execution.
"""
- match source:
- case ".version":
- print(f"{sqlite3.sqlite_version}")
- case ".help":
- print("Enter SQL code and press enter.")
- case ".quit":
- sys.exit(0)
- case _:
- if not sqlite3.complete_statement(source):
- return True
- execute(self._cur, source)
+ theme = get_theme(force_no_color=not self._use_color)
+
+ if not source or source.isspace():
+ return False
+ if source[0] == ".":
+ match source[1:].strip():
+ case "version":
+ print(sqlite3.sqlite_version)
+ case "help":
+ t = theme.syntax
+ print(f"Enter SQL code or one of the below commands, and press enter.\n\n"
+ f"{t.builtin}.version{t.reset} Print underlying SQLite library version\n"
+ f"{t.builtin}.help{t.reset} Print this help message\n"
+ f"{t.builtin}.quit{t.reset} Exit the CLI, equivalent to CTRL-D\n")
+ case "quit":
+ sys.exit(0)
+ case "":
+ pass
+ case _ as unknown:
+ t = theme.traceback
+ self.write(f'{t.type}Error{t.reset}: {t.message}unknown '
+ f'command: "{unknown}"{t.reset}\n')
+ else:
+ if not sqlite3.complete_statement(source):
+ return True
+ execute(self._cur, source, theme=theme)
return False
def main(*args):
parser = ArgumentParser(
description="Python sqlite3 CLI",
+ color=True,
)
parser.add_argument(
"filename", type=str, default=":memory:", nargs="?",
@@ -104,22 +128,23 @@ def main(*args):
Each command will be run using execute() on the cursor.
Type ".help" for more information; type ".quit" or {eofkey} to quit.
""").strip()
- sys.ps1 = "sqlite> "
- sys.ps2 = " ... "
+
+ theme = get_theme()
+ s = theme.syntax
+
+ sys.ps1 = f"{s.prompt}sqlite> {s.reset}"
+ sys.ps2 = f"{s.prompt} ... {s.reset}"
con = sqlite3.connect(args.filename, isolation_level=None)
try:
if args.sql:
# SQL statement provided on the command-line; execute it directly.
- execute(con, args.sql, suppress_errors=False)
+ execute(con, args.sql, suppress_errors=False, theme=theme)
else:
# No SQL provided; start the REPL.
- console = SqliteInteractiveConsole(con)
- try:
- import readline # noqa: F401
- except ImportError:
- pass
- console.interact(banner, exitmsg="")
+ with completer():
+ console = SqliteInteractiveConsole(con, use_color=True)
+ console.interact(banner, exitmsg="")
finally:
con.close()
diff --git a/Lib/sqlite3/_completer.py b/Lib/sqlite3/_completer.py
new file mode 100644
index 00000000000..f21ef69cad6
--- /dev/null
+++ b/Lib/sqlite3/_completer.py
@@ -0,0 +1,42 @@
+from contextlib import contextmanager
+
+try:
+ from _sqlite3 import SQLITE_KEYWORDS
+except ImportError:
+ SQLITE_KEYWORDS = ()
+
+_completion_matches = []
+
+
+def _complete(text, state):
+ global _completion_matches
+
+ if state == 0:
+ text_upper = text.upper()
+ _completion_matches = [c for c in SQLITE_KEYWORDS if c.startswith(text_upper)]
+ try:
+ return _completion_matches[state] + " "
+ except IndexError:
+ return None
+
+
+@contextmanager
+def completer():
+ try:
+ import readline
+ except ImportError:
+ yield
+ return
+
+ old_completer = readline.get_completer()
+ try:
+ readline.set_completer(_complete)
+ if readline.backend == "editline":
+ # libedit uses "^I" instead of "tab"
+ command_string = "bind ^I rl_complete"
+ else:
+ command_string = "tab: complete"
+ readline.parse_and_bind(command_string)
+ yield
+ finally:
+ readline.set_completer(old_completer)
diff --git a/Lib/sre_compile.py b/Lib/sre_compile.py
deleted file mode 100644
index f9da61e6487..00000000000
--- a/Lib/sre_compile.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-warnings.warn(f"module {__name__!r} is deprecated",
- DeprecationWarning,
- stacklevel=2)
-
-from re import _compiler as _
-globals().update({k: v for k, v in vars(_).items() if k[:2] != '__'})
diff --git a/Lib/sre_constants.py b/Lib/sre_constants.py
deleted file mode 100644
index fa09d044292..00000000000
--- a/Lib/sre_constants.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-warnings.warn(f"module {__name__!r} is deprecated",
- DeprecationWarning,
- stacklevel=2)
-
-from re import _constants as _
-globals().update({k: v for k, v in vars(_).items() if k[:2] != '__'})
diff --git a/Lib/sre_parse.py b/Lib/sre_parse.py
deleted file mode 100644
index 25a3f557d44..00000000000
--- a/Lib/sre_parse.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import warnings
-warnings.warn(f"module {__name__!r} is deprecated",
- DeprecationWarning,
- stacklevel=2)
-
-from re import _parser as _
-globals().update({k: v for k, v in vars(_).items() if k[:2] != '__'})
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 05df4ad7f0f..7e3c4cbd6bb 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -116,7 +116,7 @@ except ImportError:
from _ssl import (
HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_SSLv2, HAS_SSLv3, HAS_TLSv1,
- HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3, HAS_PSK, HAS_PHA
+ HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3, HAS_PSK, HAS_PSK_TLS13, HAS_PHA
)
from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION
diff --git a/Lib/string/__init__.py b/Lib/string/__init__.py
index eab5067c9b1..b788d7136f1 100644
--- a/Lib/string/__init__.py
+++ b/Lib/string/__init__.py
@@ -191,16 +191,14 @@ class Template:
########################################################################
-# the Formatter class
-# see PEP 3101 for details and purpose of this class
-
-# The hard parts are reused from the C implementation. They're exposed as "_"
-# prefixed methods of str.
-
+# The Formatter class (PEP 3101).
+#
# The overall parser is implemented in _string.formatter_parser.
-# The field name parser is implemented in _string.formatter_field_name_split
+# The field name parser is implemented in _string.formatter_field_name_split.
class Formatter:
+ """See PEP 3101 for details and purpose of this class."""
+
def format(self, format_string, /, *args, **kwargs):
return self.vformat(format_string, args, kwargs)
@@ -264,22 +262,18 @@ class Formatter:
return ''.join(result), auto_arg_index
-
def get_value(self, key, args, kwargs):
if isinstance(key, int):
return args[key]
else:
return kwargs[key]
-
def check_unused_args(self, used_args, args, kwargs):
pass
-
def format_field(self, value, format_spec):
return format(value, format_spec)
-
def convert_field(self, value, conversion):
# do any conversion on the resulting object
if conversion is None:
@@ -292,28 +286,26 @@ class Formatter:
return ascii(value)
raise ValueError("Unknown conversion specifier {0!s}".format(conversion))
-
- # returns an iterable that contains tuples of the form:
- # (literal_text, field_name, format_spec, conversion)
- # literal_text can be zero length
- # field_name can be None, in which case there's no
- # object to format and output
- # if field_name is not None, it is looked up, formatted
- # with format_spec and conversion and then used
def parse(self, format_string):
+ """
+ Return an iterable that contains tuples of the form
+ (literal_text, field_name, format_spec, conversion).
+
+ *field_name* can be None, in which case there's no object
+ to format and output; otherwise, it is looked up and
+ formatted with *format_spec* and *conversion*.
+ """
return _string.formatter_parser(format_string)
-
- # given a field_name, find the object it references.
- # field_name: the field being looked up, e.g. "0.name"
- # or "lookup[3]"
- # used_args: a set of which args have been used
- # args, kwargs: as passed in to vformat
def get_field(self, field_name, args, kwargs):
- first, rest = _string.formatter_field_name_split(field_name)
+ """Find the object referenced by a given field name.
+ The field name *field_name* can be for instance "0.name"
+ or "lookup[3]". The *args* and *kwargs* arguments are
+ passed to get_value().
+ """
+ first, rest = _string.formatter_field_name_split(field_name)
obj = self.get_value(first, args, kwargs)
-
# loop through the rest of the field_name, doing
# getattr or getitem as needed
for is_attr, i in rest:
@@ -321,5 +313,4 @@ class Formatter:
obj = getattr(obj, i)
else:
obj = obj[i]
-
return obj, first
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index da5f5729e09..54c2eb515b6 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1235,8 +1235,11 @@ class Popen:
finally:
self._communication_started = True
-
- sts = self.wait(timeout=self._remaining_time(endtime))
+ try:
+ sts = self.wait(timeout=self._remaining_time(endtime))
+ except TimeoutExpired as exc:
+ exc.timeout = timeout
+ raise
return (stdout, stderr)
@@ -2145,8 +2148,11 @@ class Popen:
selector.unregister(key.fileobj)
key.fileobj.close()
self._fileobj2output[key.fileobj].append(data)
-
- self.wait(timeout=self._remaining_time(endtime))
+ try:
+ self.wait(timeout=self._remaining_time(endtime))
+ except TimeoutExpired as exc:
+ exc.timeout = orig_timeout
+ raise
# All data exchanged. Translate lists into strings.
if stdout is not None:
diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py
index dad715eb087..49e0986517c 100644
--- a/Lib/sysconfig/__init__.py
+++ b/Lib/sysconfig/__init__.py
@@ -219,18 +219,7 @@ if os.name == 'nt':
if "_PYTHON_PROJECT_BASE" in os.environ:
_PROJECT_BASE = _safe_realpath(os.environ["_PYTHON_PROJECT_BASE"])
-def is_python_build(check_home=None):
- if check_home is not None:
- import warnings
- warnings.warn(
- (
- 'The check_home argument of sysconfig.is_python_build is '
- 'deprecated and its value is ignored. '
- 'It will be removed in Python 3.15.'
- ),
- DeprecationWarning,
- stacklevel=2,
- )
+def is_python_build():
for fn in ("Setup", "Setup.local"):
if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
return True
@@ -468,7 +457,7 @@ def get_config_h_filename():
"""Return the path of pyconfig.h."""
if _PYTHON_BUILD:
if os.name == "nt":
- inc_dir = os.path.dirname(sys._base_executable)
+ inc_dir = os.path.join(_PROJECT_BASE, 'PC')
else:
inc_dir = _PROJECT_BASE
else:
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index 82c5f6704cb..068aa13ed70 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -67,7 +67,7 @@ __all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError",
"DEFAULT_FORMAT", "open","fully_trusted_filter", "data_filter",
"tar_filter", "FilterError", "AbsoluteLinkError",
"OutsideDestinationError", "SpecialFileError", "AbsolutePathError",
- "LinkOutsideDestinationError"]
+ "LinkOutsideDestinationError", "LinkFallbackError"]
#---------------------------------------------------------
@@ -399,7 +399,17 @@ class _Stream:
self.exception = lzma.LZMAError
else:
self.cmp = lzma.LZMACompressor(preset=preset)
-
+ elif comptype == "zst":
+ try:
+ from compression import zstd
+ except ImportError:
+ raise CompressionError("compression.zstd module is not available") from None
+ if mode == "r":
+ self.dbuf = b""
+ self.cmp = zstd.ZstdDecompressor()
+ self.exception = zstd.ZstdError
+ else:
+ self.cmp = zstd.ZstdCompressor()
elif comptype != "tar":
raise CompressionError("unknown compression type %r" % comptype)
@@ -591,6 +601,8 @@ class _StreamProxy(object):
return "bz2"
elif self.buf.startswith((b"\x5d\x00\x00\x80", b"\xfd7zXZ")):
return "xz"
+ elif self.buf.startswith(b"\x28\xb5\x2f\xfd"):
+ return "zst"
else:
return "tar"
@@ -754,10 +766,22 @@ class LinkOutsideDestinationError(FilterError):
super().__init__(f'{tarinfo.name!r} would link to {path!r}, '
+ 'which is outside the destination')
+class LinkFallbackError(FilterError):
+ def __init__(self, tarinfo, path):
+ self.tarinfo = tarinfo
+ self._path = path
+ super().__init__(f'link {tarinfo.name!r} would be extracted as a '
+ + f'copy of {path!r}, which was rejected')
+
+# Errors caused by filters -- both "fatal" and "non-fatal" -- that
+# we consider to be issues with the argument, rather than a bug in the
+# filter function
+_FILTER_ERRORS = (FilterError, OSError, ExtractError)
+
def _get_filtered_attrs(member, dest_path, for_data=True):
new_attrs = {}
name = member.name
- dest_path = os.path.realpath(dest_path)
+ dest_path = os.path.realpath(dest_path, strict=os.path.ALLOW_MISSING)
# Strip leading / (tar's directory separator) from filenames.
# Include os.sep (target OS directory separator) as well.
if name.startswith(('/', os.sep)):
@@ -767,7 +791,8 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
# For example, 'C:/foo' on Windows.
raise AbsolutePathError(member)
# Ensure we stay in the destination
- target_path = os.path.realpath(os.path.join(dest_path, name))
+ target_path = os.path.realpath(os.path.join(dest_path, name),
+ strict=os.path.ALLOW_MISSING)
if os.path.commonpath([target_path, dest_path]) != dest_path:
raise OutsideDestinationError(member, target_path)
# Limit permissions (no high bits, and go-w)
@@ -805,6 +830,9 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
if member.islnk() or member.issym():
if os.path.isabs(member.linkname):
raise AbsoluteLinkError(member)
+ normalized = os.path.normpath(member.linkname)
+ if normalized != member.linkname:
+ new_attrs['linkname'] = normalized
if member.issym():
target_path = os.path.join(dest_path,
os.path.dirname(name),
@@ -812,7 +840,8 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
else:
target_path = os.path.join(dest_path,
member.linkname)
- target_path = os.path.realpath(target_path)
+ target_path = os.path.realpath(target_path,
+ strict=os.path.ALLOW_MISSING)
if os.path.commonpath([target_path, dest_path]) != dest_path:
raise LinkOutsideDestinationError(member, target_path)
return new_attrs
@@ -1817,11 +1846,13 @@ class TarFile(object):
'r:gz' open for reading with gzip compression
'r:bz2' open for reading with bzip2 compression
'r:xz' open for reading with lzma compression
+ 'r:zst' open for reading with zstd compression
'a' or 'a:' open for appending, creating the file if necessary
'w' or 'w:' open for writing without compression
'w:gz' open for writing with gzip compression
'w:bz2' open for writing with bzip2 compression
'w:xz' open for writing with lzma compression
+ 'w:zst' open for writing with zstd compression
'x' or 'x:' create a tarfile exclusively without compression, raise
an exception if the file is already created
@@ -1831,16 +1862,20 @@ class TarFile(object):
if the file is already created
'x:xz' create an lzma compressed tarfile, raise an exception
if the file is already created
+ 'x:zst' create a zstd compressed tarfile, raise an exception
+ if the file is already created
'r|*' open a stream of tar blocks with transparent compression
'r|' open an uncompressed stream of tar blocks for reading
'r|gz' open a gzip compressed stream of tar blocks
'r|bz2' open a bzip2 compressed stream of tar blocks
'r|xz' open an lzma compressed stream of tar blocks
+ 'r|zst' open a zstd compressed stream of tar blocks
'w|' open an uncompressed stream for writing
'w|gz' open a gzip compressed stream for writing
'w|bz2' open a bzip2 compressed stream for writing
'w|xz' open an lzma compressed stream for writing
+ 'w|zst' open a zstd compressed stream for writing
"""
if not name and not fileobj:
@@ -2006,12 +2041,48 @@ class TarFile(object):
t._extfileobj = False
return t
+ @classmethod
+ def zstopen(cls, name, mode="r", fileobj=None, level=None, options=None,
+ zstd_dict=None, **kwargs):
+ """Open zstd compressed tar archive name for reading or writing.
+ Appending is not allowed.
+ """
+ if mode not in ("r", "w", "x"):
+ raise ValueError("mode must be 'r', 'w' or 'x'")
+
+ try:
+ from compression.zstd import ZstdFile, ZstdError
+ except ImportError:
+ raise CompressionError("compression.zstd module is not available") from None
+
+ fileobj = ZstdFile(
+ fileobj or name,
+ mode,
+ level=level,
+ options=options,
+ zstd_dict=zstd_dict
+ )
+
+ try:
+ t = cls.taropen(name, mode, fileobj, **kwargs)
+ except (ZstdError, EOFError) as e:
+ fileobj.close()
+ if mode == 'r':
+ raise ReadError("not a zstd file") from e
+ raise
+ except Exception:
+ fileobj.close()
+ raise
+ t._extfileobj = False
+ return t
+
# All *open() methods are registered here.
OPEN_METH = {
"tar": "taropen", # uncompressed tar
"gz": "gzopen", # gzip compressed tar
"bz2": "bz2open", # bzip2 compressed tar
- "xz": "xzopen" # lzma compressed tar
+ "xz": "xzopen", # lzma compressed tar
+ "zst": "zstopen", # zstd compressed tar
}
#--------------------------------------------------------------------------
@@ -2332,30 +2403,58 @@ class TarFile(object):
members = self
for member in members:
- tarinfo = self._get_extract_tarinfo(member, filter_function, path)
+ tarinfo, unfiltered = self._get_extract_tarinfo(
+ member, filter_function, path)
if tarinfo is None:
continue
if tarinfo.isdir():
# For directories, delay setting attributes until later,
# since permissions can interfere with extraction and
# extracting contents can reset mtime.
- directories.append(tarinfo)
+ directories.append(unfiltered)
self._extract_one(tarinfo, path, set_attrs=not tarinfo.isdir(),
- numeric_owner=numeric_owner)
+ numeric_owner=numeric_owner,
+ filter_function=filter_function)
# Reverse sort directories.
directories.sort(key=lambda a: a.name, reverse=True)
+
# Set correct owner, mtime and filemode on directories.
- for tarinfo in directories:
- dirpath = os.path.join(path, tarinfo.name)
+ for unfiltered in directories:
try:
+ # Need to re-apply any filter, to take the *current* filesystem
+ # state into account.
+ try:
+ tarinfo = filter_function(unfiltered, path)
+ except _FILTER_ERRORS as exc:
+ self._log_no_directory_fixup(unfiltered, repr(exc))
+ continue
+ if tarinfo is None:
+ self._log_no_directory_fixup(unfiltered,
+ 'excluded by filter')
+ continue
+ dirpath = os.path.join(path, tarinfo.name)
+ try:
+ lstat = os.lstat(dirpath)
+ except FileNotFoundError:
+ self._log_no_directory_fixup(tarinfo, 'missing')
+ continue
+ if not stat.S_ISDIR(lstat.st_mode):
+ # This is no longer a directory; presumably a later
+ # member overwrote the entry.
+ self._log_no_directory_fixup(tarinfo, 'not a directory')
+ continue
self.chown(tarinfo, dirpath, numeric_owner=numeric_owner)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
except ExtractError as e:
self._handle_nonfatal_error(e)
+ def _log_no_directory_fixup(self, member, reason):
+ self._dbg(2, "tarfile: Not fixing up directory %r (%s)" %
+ (member.name, reason))
+
def extract(self, member, path="", set_attrs=True, *, numeric_owner=False,
filter=None):
"""Extract a member from the archive to the current working directory,
@@ -2371,42 +2470,57 @@ class TarFile(object):
String names of common filters are accepted.
"""
filter_function = self._get_filter_function(filter)
- tarinfo = self._get_extract_tarinfo(member, filter_function, path)
+ tarinfo, unfiltered = self._get_extract_tarinfo(
+ member, filter_function, path)
if tarinfo is not None:
self._extract_one(tarinfo, path, set_attrs, numeric_owner)
def _get_extract_tarinfo(self, member, filter_function, path):
- """Get filtered TarInfo (or None) from member, which might be a str"""
+ """Get (filtered, unfiltered) TarInfos from *member*
+
+ *member* might be a string.
+
+ Return (None, None) if not found.
+ """
+
if isinstance(member, str):
- tarinfo = self.getmember(member)
+ unfiltered = self.getmember(member)
else:
- tarinfo = member
+ unfiltered = member
- unfiltered = tarinfo
+ filtered = None
try:
- tarinfo = filter_function(tarinfo, path)
- except (OSError, FilterError) as e:
+ filtered = filter_function(unfiltered, path)
+ except (OSError, UnicodeEncodeError, FilterError) as e:
self._handle_fatal_error(e)
except ExtractError as e:
self._handle_nonfatal_error(e)
- if tarinfo is None:
+ if filtered is None:
self._dbg(2, "tarfile: Excluded %r" % unfiltered.name)
- return None
+ return None, None
+
# Prepare the link target for makelink().
- if tarinfo.islnk():
- tarinfo = copy.copy(tarinfo)
- tarinfo._link_target = os.path.join(path, tarinfo.linkname)
- return tarinfo
+ if filtered.islnk():
+ filtered = copy.copy(filtered)
+ filtered._link_target = os.path.join(path, filtered.linkname)
+ return filtered, unfiltered
- def _extract_one(self, tarinfo, path, set_attrs, numeric_owner):
- """Extract from filtered tarinfo to disk"""
+ def _extract_one(self, tarinfo, path, set_attrs, numeric_owner,
+ filter_function=None):
+ """Extract from filtered tarinfo to disk.
+
+ filter_function is only used when extracting a *different*
+ member (e.g. as fallback to creating a symlink)
+ """
self._check("r")
try:
self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
set_attrs=set_attrs,
- numeric_owner=numeric_owner)
- except OSError as e:
+ numeric_owner=numeric_owner,
+ filter_function=filter_function,
+ extraction_root=path)
+ except (OSError, UnicodeEncodeError) as e:
self._handle_fatal_error(e)
except ExtractError as e:
self._handle_nonfatal_error(e)
@@ -2463,9 +2577,13 @@ class TarFile(object):
return None
def _extract_member(self, tarinfo, targetpath, set_attrs=True,
- numeric_owner=False):
- """Extract the TarInfo object tarinfo to a physical
+ numeric_owner=False, *, filter_function=None,
+ extraction_root=None):
+ """Extract the filtered TarInfo object tarinfo to a physical
file called targetpath.
+
+ filter_function is only used when extracting a *different*
+ member (e.g. as fallback to creating a symlink)
"""
# Fetch the TarInfo object for the given name
# and build the destination pathname, replacing
@@ -2494,7 +2612,10 @@ class TarFile(object):
elif tarinfo.ischr() or tarinfo.isblk():
self.makedev(tarinfo, targetpath)
elif tarinfo.islnk() or tarinfo.issym():
- self.makelink(tarinfo, targetpath)
+ self.makelink_with_filter(
+ tarinfo, targetpath,
+ filter_function=filter_function,
+ extraction_root=extraction_root)
elif tarinfo.type not in SUPPORTED_TYPES:
self.makeunknown(tarinfo, targetpath)
else:
@@ -2577,10 +2698,18 @@ class TarFile(object):
os.makedev(tarinfo.devmajor, tarinfo.devminor))
def makelink(self, tarinfo, targetpath):
+ return self.makelink_with_filter(tarinfo, targetpath, None, None)
+
+ def makelink_with_filter(self, tarinfo, targetpath,
+ filter_function, extraction_root):
"""Make a (symbolic) link called targetpath. If it cannot be created
(platform limitation), we try to make a copy of the referenced file
instead of a link.
+
+ filter_function is only used when extracting a *different*
+ member (e.g. as fallback to creating a link).
"""
+ keyerror_to_extracterror = False
try:
# For systems that support symbolic and hard links.
if tarinfo.issym():
@@ -2588,18 +2717,38 @@ class TarFile(object):
# Avoid FileExistsError on following os.symlink.
os.unlink(targetpath)
os.symlink(tarinfo.linkname, targetpath)
+ return
else:
if os.path.exists(tarinfo._link_target):
os.link(tarinfo._link_target, targetpath)
- else:
- self._extract_member(self._find_link_target(tarinfo),
- targetpath)
+ return
except symlink_exception:
+ keyerror_to_extracterror = True
+
+ try:
+ unfiltered = self._find_link_target(tarinfo)
+ except KeyError:
+ if keyerror_to_extracterror:
+ raise ExtractError(
+ "unable to resolve link inside archive") from None
+ else:
+ raise
+
+ if filter_function is None:
+ filtered = unfiltered
+ else:
+ if extraction_root is None:
+ raise ExtractError(
+ "makelink_with_filter: if filter_function is not None, "
+ + "extraction_root must also not be None")
try:
- self._extract_member(self._find_link_target(tarinfo),
- targetpath)
- except KeyError:
- raise ExtractError("unable to resolve link inside archive") from None
+ filtered = filter_function(unfiltered, extraction_root)
+ except _FILTER_ERRORS as cause:
+ raise LinkFallbackError(tarinfo, unfiltered.name) from cause
+ if filtered is not None:
+ self._extract_member(filtered, targetpath,
+ filter_function=filter_function,
+ extraction_root=extraction_root)
def chown(self, tarinfo, targetpath, numeric_owner):
"""Set owner of targetpath according to tarinfo. If numeric_owner
@@ -2883,7 +3032,7 @@ def main():
import argparse
description = 'A simple command-line interface for tarfile module.'
- parser = argparse.ArgumentParser(description=description)
+ parser = argparse.ArgumentParser(description=description, color=True)
parser.add_argument('-v', '--verbose', action='store_true', default=False,
help='Verbose output')
parser.add_argument('--filter', metavar='<filtername>',
@@ -2963,6 +3112,9 @@ def main():
'.tbz': 'bz2',
'.tbz2': 'bz2',
'.tb2': 'bz2',
+ # zstd
+ '.zst': 'zst',
+ '.tzst': 'zst',
}
tar_mode = 'w:' + compressions[ext] if ext in compressions else 'w'
tar_files = args.create
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index cadb0bed3cc..5e3ccab5f48 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -180,7 +180,7 @@ def _candidate_tempdir_list():
return dirlist
-def _get_default_tempdir():
+def _get_default_tempdir(dirlist=None):
"""Calculate the default directory to use for temporary files.
This routine should be called exactly once.
@@ -190,7 +190,8 @@ def _get_default_tempdir():
service, the name of the test file must be randomized."""
namer = _RandomNameSequence()
- dirlist = _candidate_tempdir_list()
+ if dirlist is None:
+ dirlist = _candidate_tempdir_list()
for dir in dirlist:
if dir != _os.curdir:
diff --git a/Lib/test/.ruff.toml b/Lib/test/.ruff.toml
index a1eac32a83a..f1a967203ce 100644
--- a/Lib/test/.ruff.toml
+++ b/Lib/test/.ruff.toml
@@ -9,8 +9,9 @@ extend-exclude = [
"encoded_modules/module_iso_8859_1.py",
"encoded_modules/module_koi8_r.py",
# SyntaxError because of t-strings
- "test_tstring.py",
+ "test_annotationlib.py",
"test_string/test_templatelib.py",
+ "test_tstring.py",
# New grammar constructions may not yet be recognized by Ruff,
# and tests re-use the same names as only the grammar is being checked.
"test_grammar.py",
@@ -18,5 +19,12 @@ extend-exclude = [
[lint]
select = [
+ "F401", # Unused import
"F811", # Redefinition of unused variable (useful for finding test methods with the same name)
]
+
+[lint.per-file-ignores]
+"*/**/__main__.py" = ["F401"] # Unused import
+"test_import/*.py" = ["F401"] # Unused import
+"test_importlib/*.py" = ["F401"] # Unused import
+"typinganndata/partialexecution/*.py" = ["F401"] # Unused import
diff --git a/Lib/test/_code_definitions.py b/Lib/test/_code_definitions.py
index 06cf6a10231..70c44da2ec6 100644
--- a/Lib/test/_code_definitions.py
+++ b/Lib/test/_code_definitions.py
@@ -1,4 +1,32 @@
+def simple_script():
+ assert True
+
+
+def complex_script():
+ obj = 'a string'
+ pickle = __import__('pickle')
+ def spam_minimal():
+ pass
+ spam_minimal()
+ data = pickle.dumps(obj)
+ res = pickle.loads(data)
+ assert res == obj, (res, obj)
+
+
+def script_with_globals():
+ obj1, obj2 = spam(42)
+ assert obj1 == 42
+ assert obj2 is None
+
+
+def script_with_explicit_empty_return():
+ return None
+
+
+def script_with_return():
+ return True
+
def spam_minimal():
# no arg defaults or kwarg defaults
@@ -12,6 +40,70 @@ def spam_minimal():
return
+def spam_with_builtins():
+ x = 42
+ values = (42,)
+ checks = tuple(callable(v) for v in values)
+ res = callable(values), tuple(values), list(values), checks
+ print(res)
+
+
+def spam_with_globals_and_builtins():
+ func1 = spam
+ func2 = spam_minimal
+ funcs = (func1, func2)
+ checks = tuple(callable(f) for f in funcs)
+ res = callable(funcs), tuple(funcs), list(funcs), checks
+ print(res)
+
+
+def spam_with_global_and_attr_same_name():
+ try:
+ spam_minimal.spam_minimal
+ except AttributeError:
+ pass
+
+
+def spam_full_args(a, b, /, c, d, *args, e, f, **kwargs):
+ return (a, b, c, d, e, f, args, kwargs)
+
+
+def spam_full_args_with_defaults(a=-1, b=-2, /, c=-3, d=-4, *args,
+ e=-5, f=-6, **kwargs):
+ return (a, b, c, d, e, f, args, kwargs)
+
+
+def spam_args_attrs_and_builtins(a, b, /, c, d, *args, e, f, **kwargs):
+ if args.__len__() > 2:
+ return None
+ return a, b, c, d, e, f, args, kwargs
+
+
+def spam_returns_arg(x):
+ return x
+
+
+def spam_raises():
+ raise Exception('spam!')
+
+
+def spam_with_inner_not_closure():
+ def eggs():
+ pass
+ eggs()
+
+
+def spam_with_inner_closure():
+ x = 42
+ def eggs():
+ print(x)
+ eggs()
+
+
+def spam_annotated(a: int, b: str, c: object) -> tuple:
+ return a, b, c
+
+
def spam_full(a, b, /, c, d:int=1, *args, e, f:object=None, **kwargs) -> tuple:
# arg defaults, kwarg defaults
# annotations
@@ -97,7 +189,23 @@ ham_C_closure, *_ = eggs_closure_C(2)
TOP_FUNCTIONS = [
# shallow
+ simple_script,
+ complex_script,
+ script_with_globals,
+ script_with_explicit_empty_return,
+ script_with_return,
spam_minimal,
+ spam_with_builtins,
+ spam_with_globals_and_builtins,
+ spam_with_global_and_attr_same_name,
+ spam_full_args,
+ spam_full_args_with_defaults,
+ spam_args_attrs_and_builtins,
+ spam_returns_arg,
+ spam_raises,
+ spam_with_inner_not_closure,
+ spam_with_inner_closure,
+ spam_annotated,
spam_full,
spam,
# outer func
@@ -127,6 +235,58 @@ FUNCTIONS = [
*NESTED_FUNCTIONS,
]
+STATELESS_FUNCTIONS = [
+ simple_script,
+ complex_script,
+ script_with_explicit_empty_return,
+ script_with_return,
+ spam,
+ spam_minimal,
+ spam_with_builtins,
+ spam_full_args,
+ spam_args_attrs_and_builtins,
+ spam_returns_arg,
+ spam_raises,
+ spam_annotated,
+ spam_with_inner_not_closure,
+ spam_with_inner_closure,
+ spam_N,
+ spam_C,
+ spam_NN,
+ spam_NC,
+ spam_CN,
+ spam_CC,
+ eggs_nested,
+ eggs_nested_N,
+ ham_nested,
+ ham_C_nested
+]
+STATELESS_CODE = [
+ *STATELESS_FUNCTIONS,
+ script_with_globals,
+ spam_full_args_with_defaults,
+ spam_with_globals_and_builtins,
+ spam_with_global_and_attr_same_name,
+ spam_full,
+]
+
+PURE_SCRIPT_FUNCTIONS = [
+ simple_script,
+ complex_script,
+ script_with_explicit_empty_return,
+ spam_minimal,
+ spam_with_builtins,
+ spam_raises,
+ spam_with_inner_not_closure,
+ spam_with_inner_closure,
+]
+SCRIPT_FUNCTIONS = [
+ *PURE_SCRIPT_FUNCTIONS,
+ script_with_globals,
+ spam_with_globals_and_builtins,
+ spam_with_global_and_attr_same_name,
+]
+
# generators
diff --git a/Lib/test/_test_embed_structseq.py b/Lib/test/_test_embed_structseq.py
index 154662efce9..4cac84d7a46 100644
--- a/Lib/test/_test_embed_structseq.py
+++ b/Lib/test/_test_embed_structseq.py
@@ -11,7 +11,7 @@ class TestStructSeq(unittest.TestCase):
# ob_refcnt
self.assertGreaterEqual(sys.getrefcount(obj_type), 1)
# tp_base
- self.assertTrue(issubclass(obj_type, tuple))
+ self.assertIsSubclass(obj_type, tuple)
# tp_bases
self.assertEqual(obj_type.__bases__, (tuple,))
# tp_dict
diff --git a/Lib/test/_test_gc_fast_cycles.py b/Lib/test/_test_gc_fast_cycles.py
new file mode 100644
index 00000000000..4e2c7d72a02
--- /dev/null
+++ b/Lib/test/_test_gc_fast_cycles.py
@@ -0,0 +1,48 @@
+# Run by test_gc.
+from test import support
+import _testinternalcapi
+import gc
+import unittest
+
+class IncrementalGCTests(unittest.TestCase):
+
+ # Use small increments to emulate longer running process in a shorter time
+ @support.gc_threshold(200, 10)
+ def test_incremental_gc_handles_fast_cycle_creation(self):
+
+ class LinkedList:
+
+ #Use slots to reduce number of implicit objects
+ __slots__ = "next", "prev", "surprise"
+
+ def __init__(self, next=None, prev=None):
+ self.next = next
+ if next is not None:
+ next.prev = self
+ self.prev = prev
+ if prev is not None:
+ prev.next = self
+
+ def make_ll(depth):
+ head = LinkedList()
+ for i in range(depth):
+ head = LinkedList(head, head.prev)
+ return head
+
+ head = make_ll(1000)
+
+ assert(gc.isenabled())
+ olds = []
+ initial_heap_size = _testinternalcapi.get_tracked_heap_size()
+ for i in range(20_000):
+ newhead = make_ll(20)
+ newhead.surprise = head
+ olds.append(newhead)
+ if len(olds) == 20:
+ new_objects = _testinternalcapi.get_tracked_heap_size() - initial_heap_size
+ self.assertLess(new_objects, 27_000, f"Heap growing. Reached limit after {i} iterations")
+ del olds[:]
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index 4dc9a31d22f..a1259ff1d63 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -513,9 +513,14 @@ class _TestProcess(BaseTestCase):
time.sleep(100)
@classmethod
- def _sleep_no_int_handler(cls):
+ def _sleep_some_event(cls, event):
+ event.set()
+ time.sleep(100)
+
+ @classmethod
+ def _sleep_no_int_handler(cls, event):
signal.signal(signal.SIGINT, signal.SIG_DFL)
- cls._sleep_some()
+ cls._sleep_some_event(event)
@classmethod
def _test_sleep(cls, delay):
@@ -525,7 +530,10 @@ class _TestProcess(BaseTestCase):
if self.TYPE == 'threads':
self.skipTest('test not appropriate for {}'.format(self.TYPE))
- p = self.Process(target=target or self._sleep_some)
+ event = self.Event()
+ if not target:
+ target = self._sleep_some_event
+ p = self.Process(target=target, args=(event,))
p.daemon = True
p.start()
@@ -543,8 +551,11 @@ class _TestProcess(BaseTestCase):
self.assertTimingAlmostEqual(join.elapsed, 0.0)
self.assertEqual(p.is_alive(), True)
- # XXX maybe terminating too soon causes the problems on Gentoo...
- time.sleep(1)
+ timeout = support.SHORT_TIMEOUT
+ if not event.wait(timeout):
+ p.terminate()
+ p.join()
+ self.fail(f"event not signaled in {timeout} seconds")
meth(p)
@@ -2463,6 +2474,12 @@ class _TestValue(BaseTestCase):
self.assertNotHasAttr(arr5, 'get_lock')
self.assertNotHasAttr(arr5, 'get_obj')
+ @unittest.skipIf(c_int is None, "requires _ctypes")
+ def test_invalid_typecode(self):
+ with self.assertRaisesRegex(TypeError, 'bad typecode'):
+ self.Value('x', None)
+ with self.assertRaisesRegex(TypeError, 'bad typecode'):
+ self.RawValue('x', None)
class _TestArray(BaseTestCase):
@@ -2543,6 +2560,12 @@ class _TestArray(BaseTestCase):
self.assertNotHasAttr(arr5, 'get_lock')
self.assertNotHasAttr(arr5, 'get_obj')
+ @unittest.skipIf(c_int is None, "requires _ctypes")
+ def test_invalid_typecode(self):
+ with self.assertRaisesRegex(TypeError, 'bad typecode'):
+ self.Array('x', [])
+ with self.assertRaisesRegex(TypeError, 'bad typecode'):
+ self.RawArray('x', [])
#
#
#
@@ -6778,6 +6801,35 @@ class _TestSpawnedSysPath(BaseTestCase):
self.assertEqual(child_sys_path[1:], sys.path[1:])
self.assertIsNone(import_error, msg=f"child could not import {self._mod_name}")
+ def test_std_streams_flushed_after_preload(self):
+ # gh-135335: Check fork server flushes standard streams after
+ # preloading modules
+ if multiprocessing.get_start_method() != "forkserver":
+ self.skipTest("forkserver specific test")
+
+ # Create a test module in the temporary directory on the child's path
+ # TODO: This can all be simplified once gh-126631 is fixed and we can
+ # use __main__ instead of a module.
+ dirname = os.path.join(self._temp_dir, 'preloaded_module')
+ init_name = os.path.join(dirname, '__init__.py')
+ os.mkdir(dirname)
+ with open(init_name, "w") as f:
+ cmd = '''if 1:
+ import sys
+ print('stderr', end='', file=sys.stderr)
+ print('stdout', end='', file=sys.stdout)
+ '''
+ f.write(cmd)
+
+ name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py')
+ env = {'PYTHONPATH': self._temp_dir}
+ _, out, err = test.support.script_helper.assert_python_ok(name, **env)
+
+ # Check stderr first, as it is more likely to be useful to see in the
+ # event of a failure.
+ self.assertEqual(err.decode().rstrip(), 'stderr')
+ self.assertEqual(out.decode().rstrip(), 'stdout')
+
class MiscTestCase(unittest.TestCase):
def test__all__(self):
@@ -6821,6 +6873,28 @@ class MiscTestCase(unittest.TestCase):
self.assertEqual("332833500", out.decode('utf-8').strip())
self.assertFalse(err, msg=err.decode('utf-8'))
+ def test_forked_thread_not_started(self):
+ # gh-134381: Ensure that a thread that has not been started yet in
+ # the parent process can be started within a forked child process.
+
+ if multiprocessing.get_start_method() != "fork":
+ self.skipTest("fork specific test")
+
+ q = multiprocessing.Queue()
+ t = threading.Thread(target=lambda: q.put("done"), daemon=True)
+
+ def child():
+ t.start()
+ t.join()
+
+ p = multiprocessing.Process(target=child)
+ p.start()
+ p.join(support.SHORT_TIMEOUT)
+
+ self.assertEqual(p.exitcode, 0)
+ self.assertEqual(q.get_nowait(), "done")
+ close_queue(q)
+
#
# Mixins
diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py
index 08b638e4b8d..6884ac0dbe6 100644
--- a/Lib/test/audit-tests.py
+++ b/Lib/test/audit-tests.py
@@ -643,6 +643,34 @@ def test_assert_unicode():
else:
raise RuntimeError("Expected sys.audit(9) to fail.")
+def test_sys_remote_exec():
+ import tempfile
+
+ pid = os.getpid()
+ event_pid = -1
+ event_script_path = ""
+ remote_event_script_path = ""
+ def hook(event, args):
+ if event not in ["sys.remote_exec", "cpython.remote_debugger_script"]:
+ return
+ print(event, args)
+ match event:
+ case "sys.remote_exec":
+ nonlocal event_pid, event_script_path
+ event_pid = args[0]
+ event_script_path = args[1]
+ case "cpython.remote_debugger_script":
+ nonlocal remote_event_script_path
+ remote_event_script_path = args[0]
+
+ sys.addaudithook(hook)
+ with tempfile.NamedTemporaryFile(mode='w+', delete=True) as tmp_file:
+ tmp_file.write("a = 1+1\n")
+ tmp_file.flush()
+ sys.remote_exec(pid, tmp_file.name)
+ assertEqual(event_pid, pid)
+ assertEqual(event_script_path, tmp_file.name)
+ assertEqual(remote_event_script_path, tmp_file.name)
if __name__ == "__main__":
from test.support import suppress_msvcrt_asserts
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 55844ec35a9..93b3382b9c6 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -183,7 +183,7 @@ class TestTZInfo(unittest.TestCase):
def __init__(self, offset, name):
self.__offset = offset
self.__name = name
- self.assertTrue(issubclass(NotEnough, tzinfo))
+ self.assertIsSubclass(NotEnough, tzinfo)
ne = NotEnough(3, "NotByALongShot")
self.assertIsInstance(ne, tzinfo)
@@ -232,7 +232,7 @@ class TestTZInfo(unittest.TestCase):
self.assertIs(type(derived), otype)
self.assertEqual(derived.utcoffset(None), offset)
self.assertEqual(derived.tzname(None), oname)
- self.assertFalse(hasattr(derived, 'spam'))
+ self.assertNotHasAttr(derived, 'spam')
def test_issue23600(self):
DSTDIFF = DSTOFFSET = timedelta(hours=1)
@@ -773,6 +773,9 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
microseconds=999999)),
"999999999 days, 23:59:59.999999")
+ # test the Doc/library/datetime.rst recipe
+ eq(f'-({-td(hours=-1)!s})', "-(1:00:00)")
+
def test_repr(self):
name = 'datetime.' + self.theclass.__name__
self.assertEqual(repr(self.theclass(1)),
@@ -810,7 +813,7 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
# Verify td -> string -> td identity.
s = repr(td)
- self.assertTrue(s.startswith('datetime.'))
+ self.assertStartsWith(s, 'datetime.')
s = s[9:]
td2 = eval(s)
self.assertEqual(td, td2)
@@ -1228,7 +1231,7 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
self.theclass.today()):
# Verify dt -> string -> date identity.
s = repr(dt)
- self.assertTrue(s.startswith('datetime.'))
+ self.assertStartsWith(s, 'datetime.')
s = s[9:]
dt2 = eval(s)
self.assertEqual(dt, dt2)
@@ -2215,7 +2218,7 @@ class TestDateTime(TestDate):
self.theclass.now()):
# Verify dt -> string -> datetime identity.
s = repr(dt)
- self.assertTrue(s.startswith('datetime.'))
+ self.assertStartsWith(s, 'datetime.')
s = s[9:]
dt2 = eval(s)
self.assertEqual(dt, dt2)
@@ -2969,6 +2972,17 @@ class TestDateTime(TestDate):
with self._assertNotWarns(DeprecationWarning):
self.theclass.strptime('02-29,2024', '%m-%d,%Y')
+ def test_strptime_z_empty(self):
+ for directive in ('z',):
+ string = '2025-04-25 11:42:47'
+ format = f'%Y-%m-%d %H:%M:%S%{directive}'
+ target = self.theclass(2025, 4, 25, 11, 42, 47)
+ with self.subTest(string=string,
+ format=format,
+ target=target):
+ result = self.theclass.strptime(string, format)
+ self.assertEqual(result, target)
+
def test_more_timetuple(self):
# This tests fields beyond those tested by the TestDate.test_timetuple.
t = self.theclass(2004, 12, 31, 6, 22, 33)
@@ -3568,6 +3582,10 @@ class TestDateTime(TestDate):
'2009-04-19T12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
'2009-04-19T12:30:45.400 ', # Trailing space (gh-130959)
'2009-04-19T12:30:45. 400', # Space before fraction (gh-130959)
+ '2009-04-19T12:30:45+00:90:00', # Time zone field out from range
+ '2009-04-19T12:30:45+00:00:90', # Time zone field out from range
+ '2009-04-19T12:30:45-00:90:00', # Time zone field out from range
+ '2009-04-19T12:30:45-00:00:90', # Time zone field out from range
]
for bad_str in bad_strs:
@@ -3669,7 +3687,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
# Verify t -> string -> time identity.
s = repr(t)
- self.assertTrue(s.startswith('datetime.'))
+ self.assertStartsWith(s, 'datetime.')
s = s[9:]
t2 = eval(s)
self.assertEqual(t, t2)
@@ -4792,6 +4810,11 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
'12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
'12:30:45.400 ', # Trailing space (gh-130959)
'12:30:45. 400', # Space before fraction (gh-130959)
+ '24:00:00.000001', # Has non-zero microseconds on 24:00
+ '24:00:01.000000', # Has non-zero seconds on 24:00
+ '24:01:00.000000', # Has non-zero minutes on 24:00
+ '12:30:45+00:90:00', # Time zone field out from range
+ '12:30:45+00:00:90', # Time zone field out from range
]
for bad_str in bad_strs:
diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py
index 713cbedb299..a2d01b157ac 100644
--- a/Lib/test/libregrtest/main.py
+++ b/Lib/test/libregrtest/main.py
@@ -190,6 +190,12 @@ class Regrtest:
strip_py_suffix(tests)
+ exclude_tests = set()
+ if self.exclude:
+ for arg in self.cmdline_args:
+ exclude_tests.add(arg)
+ self.cmdline_args = []
+
if self.pgo:
# add default PGO tests if no tests are specified
setup_pgo_tests(self.cmdline_args, self.pgo_extended)
@@ -200,17 +206,15 @@ class Regrtest:
if self.tsan_parallel:
setup_tsan_parallel_tests(self.cmdline_args)
- exclude_tests = set()
- if self.exclude:
- for arg in self.cmdline_args:
- exclude_tests.add(arg)
- self.cmdline_args = []
-
alltests = findtests(testdir=self.test_dir,
exclude=exclude_tests)
if not self.fromfile:
selected = tests or self.cmdline_args
+ if exclude_tests:
+ # Support "--pgo/--tsan -x test_xxx" command
+ selected = [name for name in selected
+ if name not in exclude_tests]
if selected:
selected = split_test_packages(selected)
else:
@@ -543,8 +547,6 @@ class Regrtest:
self.first_runtests = runtests
self.logger.set_tests(runtests)
- setup_process()
-
if (runtests.hunt_refleak is not None) and (not self.num_workers):
# gh-109739: WindowsLoadTracker thread interferes with refleak check
use_load_tracker = False
@@ -721,10 +723,7 @@ class Regrtest:
self._execute_python(cmd, environ)
def _init(self):
- # Set sys.stdout encoder error handler to backslashreplace,
- # similar to sys.stderr error handler, to avoid UnicodeEncodeError
- # when printing a traceback or any other non-encodable character.
- sys.stdout.reconfigure(errors="backslashreplace")
+ setup_process()
if self.junit_filename and not os.path.isabs(self.junit_filename):
self.junit_filename = os.path.abspath(self.junit_filename)
diff --git a/Lib/test/libregrtest/setup.py b/Lib/test/libregrtest/setup.py
index c0346aa934d..9bfc414cd61 100644
--- a/Lib/test/libregrtest/setup.py
+++ b/Lib/test/libregrtest/setup.py
@@ -1,5 +1,6 @@
import faulthandler
import gc
+import io
import os
import random
import signal
@@ -40,7 +41,7 @@ def setup_process() -> None:
faulthandler.enable(all_threads=True, file=stderr_fd)
# Display the Python traceback on SIGALRM or SIGUSR1 signal
- signals = []
+ signals: list[signal.Signals] = []
if hasattr(signal, 'SIGALRM'):
signals.append(signal.SIGALRM)
if hasattr(signal, 'SIGUSR1'):
@@ -52,6 +53,14 @@ def setup_process() -> None:
support.record_original_stdout(sys.stdout)
+ # Set sys.stdout encoder error handler to backslashreplace,
+ # similar to sys.stderr error handler, to avoid UnicodeEncodeError
+ # when printing a traceback or any other non-encodable character.
+ #
+ # Use an assertion to fix mypy error.
+ assert isinstance(sys.stdout, io.TextIOWrapper)
+ sys.stdout.reconfigure(errors="backslashreplace")
+
# Some times __path__ and __file__ are not absolute (e.g. while running from
# Lib/) and, if we change the CWD to run the tests in a temporary dir, some
# imports might fail. This affects only the modules imported before os.chdir().
diff --git a/Lib/test/libregrtest/single.py b/Lib/test/libregrtest/single.py
index 57d7b649d2e..958a915626a 100644
--- a/Lib/test/libregrtest/single.py
+++ b/Lib/test/libregrtest/single.py
@@ -283,7 +283,7 @@ def _runtest(result: TestResult, runtests: RunTests) -> None:
try:
setup_tests(runtests)
- if output_on_failure:
+ if output_on_failure or runtests.pgo:
support.verbose = True
stream = io.StringIO()
diff --git a/Lib/test/libregrtest/tsan.py b/Lib/test/libregrtest/tsan.py
index d984a735bdf..3545c5f999f 100644
--- a/Lib/test/libregrtest/tsan.py
+++ b/Lib/test/libregrtest/tsan.py
@@ -8,7 +8,7 @@ TSAN_TESTS = [
'test_capi.test_pyatomic',
'test_code',
'test_ctypes',
- # 'test_concurrent_futures', # gh-130605: too many data races
+ 'test_concurrent_futures',
'test_enum',
'test_functools',
'test_httpservers',
diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py
index c4a1506c9a7..72b8ea89e62 100644
--- a/Lib/test/libregrtest/utils.py
+++ b/Lib/test/libregrtest/utils.py
@@ -31,7 +31,7 @@ WORKER_WORK_DIR_PREFIX = WORK_DIR_PREFIX + 'worker_'
EXIT_TIMEOUT = 120.0
-ALL_RESOURCES = ('audio', 'curses', 'largefile', 'network',
+ALL_RESOURCES = ('audio', 'console', 'curses', 'largefile', 'network',
'decimal', 'cpu', 'subprocess', 'urlfetch', 'gui', 'walltime')
# Other resources excluded from --use=all:
@@ -335,43 +335,11 @@ def get_build_info():
build.append('with_assert')
# --enable-experimental-jit
- tier2 = re.search('-D_Py_TIER2=([0-9]+)', cflags)
- if tier2:
- tier2 = int(tier2.group(1))
-
- if not sys.flags.ignore_environment:
- PYTHON_JIT = os.environ.get('PYTHON_JIT', None)
- if PYTHON_JIT:
- PYTHON_JIT = (PYTHON_JIT != '0')
- else:
- PYTHON_JIT = None
-
- if tier2 == 1: # =yes
- if PYTHON_JIT == False:
- jit = 'JIT=off'
- else:
- jit = 'JIT'
- elif tier2 == 3: # =yes-off
- if PYTHON_JIT:
- jit = 'JIT'
+ if sys._jit.is_available():
+ if sys._jit.is_enabled():
+ build.append("JIT")
else:
- jit = 'JIT=off'
- elif tier2 == 4: # =interpreter
- if PYTHON_JIT == False:
- jit = 'JIT-interpreter=off'
- else:
- jit = 'JIT-interpreter'
- elif tier2 == 6: # =interpreter-off (Secret option!)
- if PYTHON_JIT:
- jit = 'JIT-interpreter'
- else:
- jit = 'JIT-interpreter=off'
- elif '-D_Py_JIT' in cflags:
- jit = 'JIT'
- else:
- jit = None
- if jit:
- build.append(jit)
+ build.append("JIT (disabled)")
# --enable-framework=name
framework = sysconfig.get_config_var('PYTHONFRAMEWORK')
diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py
index 009e04e9c0b..691029a1a54 100644
--- a/Lib/test/lock_tests.py
+++ b/Lib/test/lock_tests.py
@@ -124,6 +124,11 @@ class BaseLockTests(BaseTestCase):
lock = self.locktype()
del lock
+ def test_constructor_noargs(self):
+ self.assertRaises(TypeError, self.locktype, 1)
+ self.assertRaises(TypeError, self.locktype, x=1)
+ self.assertRaises(TypeError, self.locktype, 1, x=2)
+
def test_repr(self):
lock = self.locktype()
self.assertRegex(repr(lock), "<unlocked .* object (.*)?at .*>")
@@ -332,6 +337,26 @@ class RLockTests(BaseLockTests):
"""
Tests for recursive locks.
"""
+ def test_repr_count(self):
+ # see gh-134322: check that count values are correct:
+ # when a rlock is just created,
+ # in a second thread when rlock is acquired in the main thread.
+ lock = self.locktype()
+ self.assertIn("count=0", repr(lock))
+ self.assertIn("<unlocked", repr(lock))
+ lock.acquire()
+ lock.acquire()
+ self.assertIn("count=2", repr(lock))
+ self.assertIn("<locked", repr(lock))
+
+ result = []
+ def call_repr():
+ result.append(repr(lock))
+ with Bunch(call_repr, 1):
+ pass
+ self.assertIn("count=2", result[0])
+ self.assertIn("<locked", result[0])
+
def test_reacquire(self):
lock = self.locktype()
lock.acquire()
@@ -365,6 +390,24 @@ class RLockTests(BaseLockTests):
lock.release()
self.assertFalse(lock.locked())
+ def test_locked_with_2threads(self):
+ # see gh-134323: check that a rlock which
+ # is acquired in a different thread,
+ # is still locked in the main thread.
+ result = []
+ rlock = self.locktype()
+ self.assertFalse(rlock.locked())
+ def acquire():
+ result.append(rlock.locked())
+ rlock.acquire()
+ result.append(rlock.locked())
+
+ with Bunch(acquire, 1):
+ pass
+ self.assertTrue(rlock.locked())
+ self.assertFalse(result[0])
+ self.assertTrue(result[1])
+
def test_release_save_unacquired(self):
# Cannot _release_save an unacquired lock
lock = self.locktype()
diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py
index 9d38da5a86e..20306e1526d 100644
--- a/Lib/test/mapping_tests.py
+++ b/Lib/test/mapping_tests.py
@@ -70,8 +70,8 @@ class BasicTestMappingProtocol(unittest.TestCase):
if not d: self.fail("Full mapping must compare to True")
# keys(), items(), iterkeys() ...
def check_iterandlist(iter, lst, ref):
- self.assertTrue(hasattr(iter, '__next__'))
- self.assertTrue(hasattr(iter, '__iter__'))
+ self.assertHasAttr(iter, '__next__')
+ self.assertHasAttr(iter, '__iter__')
x = list(iter)
self.assertTrue(set(x)==set(lst)==set(ref))
check_iterandlist(iter(d.keys()), list(d.keys()),
diff --git a/Lib/test/mp_preload_flush.py b/Lib/test/mp_preload_flush.py
new file mode 100644
index 00000000000..3501554d366
--- /dev/null
+++ b/Lib/test/mp_preload_flush.py
@@ -0,0 +1,15 @@
+import multiprocessing
+import sys
+
+modname = 'preloaded_module'
+if __name__ == '__main__':
+ if modname in sys.modules:
+ raise AssertionError(f'{modname!r} is not in sys.modules')
+ multiprocessing.set_start_method('forkserver')
+ multiprocessing.set_forkserver_preload([modname])
+ for _ in range(2):
+ p = multiprocessing.Process()
+ p.start()
+ p.join()
+elif modname not in sys.modules:
+ raise AssertionError(f'{modname!r} is not in sys.modules')
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py
index bdc7ef62943..9a3a26a8400 100644
--- a/Lib/test/pickletester.py
+++ b/Lib/test/pickletester.py
@@ -1100,6 +1100,11 @@ class AbstractUnpickleTests:
self.check_unpickling_error((pickle.UnpicklingError, OverflowError),
dumped)
+ def test_large_binstring(self):
+ errmsg = 'BINSTRING pickle has negative byte count'
+ with self.assertRaisesRegex(pickle.UnpicklingError, errmsg):
+ self.loads(b'T\0\0\0\x80')
+
def test_get(self):
pickled = b'((lp100000\ng100000\nt.'
unpickled = self.loads(pickled)
@@ -2272,7 +2277,11 @@ class AbstractPicklingErrorTests:
def test_nested_lookup_error(self):
# Nested name does not exist
- obj = REX('AbstractPickleTests.spam')
+ global TestGlobal
+ class TestGlobal:
+ class A:
+ pass
+ obj = REX('TestGlobal.A.B.C')
obj.__module__ = __name__
for proto in protocols:
with self.subTest(proto=proto):
@@ -2280,9 +2289,9 @@ class AbstractPicklingErrorTests:
self.dumps(obj, proto)
self.assertEqual(str(cm.exception),
f"Can't pickle {obj!r}: "
- f"it's not found as {__name__}.AbstractPickleTests.spam")
+ f"it's not found as {__name__}.TestGlobal.A.B.C")
self.assertEqual(str(cm.exception.__context__),
- "type object 'AbstractPickleTests' has no attribute 'spam'")
+ "type object 'A' has no attribute 'B'")
obj.__module__ = None
for proto in protocols:
@@ -2290,21 +2299,25 @@ class AbstractPicklingErrorTests:
with self.assertRaises(pickle.PicklingError) as cm:
self.dumps(obj, proto)
self.assertEqual(str(cm.exception),
- f"Can't pickle {obj!r}: it's not found as __main__.AbstractPickleTests.spam")
+ f"Can't pickle {obj!r}: "
+ f"it's not found as __main__.TestGlobal.A.B.C")
self.assertEqual(str(cm.exception.__context__),
- "module '__main__' has no attribute 'AbstractPickleTests'")
+ "module '__main__' has no attribute 'TestGlobal'")
def test_wrong_object_lookup_error(self):
# Name is bound to different object
- obj = REX('AbstractPickleTests')
+ global TestGlobal
+ class TestGlobal:
+ pass
+ obj = REX('TestGlobal')
obj.__module__ = __name__
- AbstractPickleTests.ham = []
for proto in protocols:
with self.subTest(proto=proto):
with self.assertRaises(pickle.PicklingError) as cm:
self.dumps(obj, proto)
self.assertEqual(str(cm.exception),
- f"Can't pickle {obj!r}: it's not the same object as {__name__}.AbstractPickleTests")
+ f"Can't pickle {obj!r}: "
+ f"it's not the same object as {__name__}.TestGlobal")
self.assertIsNone(cm.exception.__context__)
obj.__module__ = None
@@ -2313,9 +2326,10 @@ class AbstractPicklingErrorTests:
with self.assertRaises(pickle.PicklingError) as cm:
self.dumps(obj, proto)
self.assertEqual(str(cm.exception),
- f"Can't pickle {obj!r}: it's not found as __main__.AbstractPickleTests")
+ f"Can't pickle {obj!r}: "
+ f"it's not found as __main__.TestGlobal")
self.assertEqual(str(cm.exception.__context__),
- "module '__main__' has no attribute 'AbstractPickleTests'")
+ "module '__main__' has no attribute 'TestGlobal'")
def test_local_lookup_error(self):
# Test that whichmodule() errors out cleanly when looking up
@@ -3059,7 +3073,7 @@ class AbstractPickleTests:
pickled = self.dumps(None, proto)
if proto >= 2:
proto_header = pickle.PROTO + bytes([proto])
- self.assertTrue(pickled.startswith(proto_header))
+ self.assertStartsWith(pickled, proto_header)
else:
self.assertEqual(count_opcode(pickle.PROTO, pickled), 0)
@@ -4998,7 +5012,7 @@ class AbstractDispatchTableTests:
p = self.pickler_class(f, 0)
with self.assertRaises(AttributeError):
p.dispatch_table
- self.assertFalse(hasattr(p, 'dispatch_table'))
+ self.assertNotHasAttr(p, 'dispatch_table')
def test_class_dispatch_table(self):
# A dispatch_table attribute can be specified class-wide
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index 682815c3fdd..80a262c18a5 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -658,6 +658,16 @@ def collect_zlib(info_add):
copy_attributes(info_add, zlib, 'zlib.%s', attributes)
+def collect_zstd(info_add):
+ try:
+ import _zstd
+ except ImportError:
+ return
+
+ attributes = ('zstd_version',)
+ copy_attributes(info_add, _zstd, 'zstd.%s', attributes)
+
+
def collect_expat(info_add):
try:
from xml.parsers import expat
@@ -910,10 +920,17 @@ def collect_windows(info_add):
try:
import _winapi
- dll_path = _winapi.GetModuleFileName(sys.dllhandle)
- info_add('windows.dll_path', dll_path)
- except (ImportError, AttributeError):
+ except ImportError:
pass
+ else:
+ try:
+ dll_path = _winapi.GetModuleFileName(sys.dllhandle)
+ info_add('windows.dll_path', dll_path)
+ except AttributeError:
+ pass
+
+ call_func(info_add, 'windows.ansi_code_page', _winapi, 'GetACP')
+ call_func(info_add, 'windows.oem_code_page', _winapi, 'GetOEMCP')
# windows.version_caption: "wmic os get Caption,Version /value" command
import subprocess
@@ -1051,6 +1068,7 @@ def collect_info(info):
collect_tkinter,
collect_windows,
collect_zlib,
+ collect_zstd,
collect_libregrtest_utils,
# Collecting from tests should be last as they have side effects.
diff --git a/Lib/test/subprocessdata/fd_status.py b/Lib/test/subprocessdata/fd_status.py
index d12bd95abee..90e785981ae 100644
--- a/Lib/test/subprocessdata/fd_status.py
+++ b/Lib/test/subprocessdata/fd_status.py
@@ -2,7 +2,7 @@
file descriptors on stdout.
Usage:
-fd_stats.py: check all file descriptors
+fd_status.py: check all file descriptors (up to 255)
fd_status.py fd1 fd2 ...: check only specified file descriptors
"""
@@ -18,7 +18,7 @@ if __name__ == "__main__":
_MAXFD = os.sysconf("SC_OPEN_MAX")
except:
_MAXFD = 256
- test_fds = range(0, _MAXFD)
+ test_fds = range(0, min(_MAXFD, 256))
else:
test_fds = map(int, sys.argv[1:])
for fd in test_fds:
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 24984ad81ff..fd39d3f7c95 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -33,7 +33,7 @@ __all__ = [
"is_resource_enabled", "requires", "requires_freebsd_version",
"requires_gil_enabled", "requires_linux_version", "requires_mac_ver",
"check_syntax_error",
- "requires_gzip", "requires_bz2", "requires_lzma",
+ "requires_gzip", "requires_bz2", "requires_lzma", "requires_zstd",
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
"requires_IEEE_754", "requires_zlib",
"has_fork_support", "requires_fork",
@@ -46,6 +46,7 @@ __all__ = [
# sys
"MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
"is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
+ "support_remote_exec_only",
# os
"get_pagesize",
# network
@@ -527,6 +528,13 @@ def requires_lzma(reason='requires lzma'):
lzma = None
return unittest.skipUnless(lzma, reason)
+def requires_zstd(reason='requires zstd'):
+ try:
+ from compression import zstd
+ except ImportError:
+ zstd = None
+ return unittest.skipUnless(zstd, reason)
+
def has_no_debug_ranges():
try:
import _testcapi
@@ -689,9 +697,11 @@ def sortdict(dict):
return "{%s}" % withcommas
-def run_code(code: str) -> dict[str, object]:
+def run_code(code: str, extra_names: dict[str, object] | None = None) -> dict[str, object]:
"""Run a piece of code after dedenting it, and return its global namespace."""
ns = {}
+ if extra_names:
+ ns.update(extra_names)
exec(textwrap.dedent(code), ns)
return ns
@@ -936,6 +946,31 @@ def check_sizeof(test, o, size):
% (type(o), result, size)
test.assertEqual(result, size, msg)
+def subTests(arg_names, arg_values, /, *, _do_cleanups=False):
+ """Run multiple subtests with different parameters.
+ """
+ single_param = False
+ if isinstance(arg_names, str):
+ arg_names = arg_names.replace(',',' ').split()
+ if len(arg_names) == 1:
+ single_param = True
+ arg_values = tuple(arg_values)
+ def decorator(func):
+ if isinstance(func, type):
+ raise TypeError('subTests() can only decorate methods, not classes')
+ @functools.wraps(func)
+ def wrapper(self, /, *args, **kwargs):
+ for values in arg_values:
+ if single_param:
+ values = (values,)
+ subtest_kwargs = dict(zip(arg_names, values))
+ with self.subTest(**subtest_kwargs):
+ func(self, *args, **kwargs, **subtest_kwargs)
+ if _do_cleanups:
+ self.doCleanups()
+ return wrapper
+ return decorator
+
#=======================================================================
# Decorator/context manager for running a code in a different locale,
# correctly resetting it afterwards.
@@ -1075,7 +1110,7 @@ def set_memlimit(limit: str) -> None:
global real_max_memuse
memlimit = _parse_memlimit(limit)
if memlimit < _2G - 1:
- raise ValueError('Memory limit {limit!r} too low to be useful')
+ raise ValueError(f'Memory limit {limit!r} too low to be useful')
real_max_memuse = memlimit
memlimit = min(memlimit, MAX_Py_ssize_t)
@@ -1092,7 +1127,6 @@ class _MemoryWatchdog:
self.started = False
def start(self):
- import warnings
try:
f = open(self.procfile, 'r')
except OSError as e:
@@ -2299,6 +2333,7 @@ def check_disallow_instantiation(testcase, tp, *args, **kwds):
qualname = f"{name}"
msg = f"cannot create '{re.escape(qualname)}' instances"
testcase.assertRaisesRegex(TypeError, msg, tp, *args, **kwds)
+ testcase.assertRaisesRegex(TypeError, msg, tp.__new__, tp, *args, **kwds)
def get_recursion_depth():
"""Get the recursion depth of the caller function.
@@ -2350,7 +2385,7 @@ def infinite_recursion(max_depth=None):
# very deep recursion.
max_depth = 20_000
elif max_depth < 3:
- raise ValueError("max_depth must be at least 3, got {max_depth}")
+ raise ValueError(f"max_depth must be at least 3, got {max_depth}")
depth = get_recursion_depth()
depth = max(depth - 1, 1) # Ignore infinite_recursion() frame.
limit = depth + max_depth
@@ -2648,13 +2683,9 @@ skip_on_s390x = unittest.skipIf(is_s390x, 'skipped on s390x')
Py_TRACE_REFS = hasattr(sys, 'getobjects')
-try:
- from _testinternalcapi import jit_enabled
-except ImportError:
- requires_jit_enabled = requires_jit_disabled = unittest.skip("requires _testinternalcapi")
-else:
- requires_jit_enabled = unittest.skipUnless(jit_enabled(), "requires JIT enabled")
- requires_jit_disabled = unittest.skipIf(jit_enabled(), "requires JIT disabled")
+_JIT_ENABLED = sys._jit.is_enabled()
+requires_jit_enabled = unittest.skipUnless(_JIT_ENABLED, "requires JIT enabled")
+requires_jit_disabled = unittest.skipIf(_JIT_ENABLED, "requires JIT disabled")
_BASE_COPY_SRC_DIR_IGNORED_NAMES = frozenset({
@@ -2723,7 +2754,7 @@ def iter_builtin_types():
# Fall back to making a best-effort guess.
if hasattr(object, '__flags__'):
# Look for any type object with the Py_TPFLAGS_STATIC_BUILTIN flag set.
- import datetime
+ import datetime # noqa: F401
seen = set()
for cls, subs in walk_class_hierarchy(object):
if cls in seen:
@@ -2855,36 +2886,59 @@ def iter_slot_wrappers(cls):
@contextlib.contextmanager
-def no_color():
+def force_color(color: bool):
import _colorize
from .os_helper import EnvironmentVarGuard
with (
- swap_attr(_colorize, "can_colorize", lambda file=None: False),
+ swap_attr(_colorize, "can_colorize", lambda file=None: color),
EnvironmentVarGuard() as env,
):
env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS")
- env.set("NO_COLOR", "1")
+ env.set("FORCE_COLOR" if color else "NO_COLOR", "1")
yield
+def force_colorized(func):
+ """Force the terminal to be colorized."""
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ with force_color(True):
+ return func(*args, **kwargs)
+ return wrapper
+
+
def force_not_colorized(func):
- """Force the terminal not to be colorized."""
+ """Force the terminal NOT to be colorized."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
- with no_color():
+ with force_color(False):
return func(*args, **kwargs)
return wrapper
+def force_colorized_test_class(cls):
+ """Force the terminal to be colorized for the entire test class."""
+ original_setUpClass = cls.setUpClass
+
+ @classmethod
+ @functools.wraps(cls.setUpClass)
+ def new_setUpClass(cls):
+ cls.enterClassContext(force_color(True))
+ original_setUpClass()
+
+ cls.setUpClass = new_setUpClass
+ return cls
+
+
def force_not_colorized_test_class(cls):
- """Force the terminal not to be colorized for the entire test class."""
+ """Force the terminal NOT to be colorized for the entire test class."""
original_setUpClass = cls.setUpClass
@classmethod
@functools.wraps(cls.setUpClass)
def new_setUpClass(cls):
- cls.enterClassContext(no_color())
+ cls.enterClassContext(force_color(False))
original_setUpClass()
cls.setUpClass = new_setUpClass
@@ -2901,12 +2955,6 @@ def make_clean_env() -> dict[str, str]:
return clean_env
-def initialized_with_pyrepl():
- """Detect whether PyREPL was used during Python initialization."""
- # If the main module has a __file__ attribute it's a Python module, which means PyREPL.
- return hasattr(sys.modules["__main__"], "__file__")
-
-
WINDOWS_STATUS = {
0xC0000005: "STATUS_ACCESS_VIOLATION",
0xC00000FD: "STATUS_STACK_OVERFLOW",
@@ -3023,6 +3071,27 @@ def is_libssl_fips_mode():
return False # more of a maybe, unless we add this to the _ssl module.
return get_fips_mode() != 0
+def _supports_remote_attaching():
+ PROCESS_VM_READV_SUPPORTED = False
+
+ try:
+ from _remote_debugging import PROCESS_VM_READV_SUPPORTED
+ except ImportError:
+ pass
+
+ return PROCESS_VM_READV_SUPPORTED
+
+def _support_remote_exec_only_impl():
+ if not sys.is_remote_debug_enabled():
+ return unittest.skip("Remote debugging is not enabled")
+ if sys.platform not in ("darwin", "linux", "win32"):
+ return unittest.skip("Test only runs on Linux, Windows and macOS")
+ if sys.platform == "linux" and not _supports_remote_attaching():
+ return unittest.skip("Test only runs on Linux with process_vm_readv support")
+ return _id
+
+def support_remote_exec_only(test):
+ return _support_remote_exec_only_impl()(test)
class EqualToForwardRef:
"""Helper to ease use of annotationlib.ForwardRef in tests.
diff --git a/Lib/test/support/interpreters/channels.py b/Lib/test/support/channels.py
index d2bd93d77f7..b2de24d9d3e 100644
--- a/Lib/test/support/interpreters/channels.py
+++ b/Lib/test/support/channels.py
@@ -2,14 +2,14 @@
import time
import _interpchannels as _channels
-from . import _crossinterp
+from concurrent.interpreters import _crossinterp
# aliases:
from _interpchannels import (
- ChannelError, ChannelNotFoundError, ChannelClosedError,
- ChannelEmptyError, ChannelNotEmptyError,
+ ChannelError, ChannelNotFoundError, ChannelClosedError, # noqa: F401
+ ChannelEmptyError, ChannelNotEmptyError, # noqa: F401
)
-from ._crossinterp import (
+from concurrent.interpreters._crossinterp import (
UNBOUND_ERROR, UNBOUND_REMOVE,
)
@@ -55,15 +55,23 @@ def create(*, unbounditems=UNBOUND):
"""
unbound = _serialize_unbound(unbounditems)
unboundop, = unbound
- cid = _channels.create(unboundop)
- recv, send = RecvChannel(cid), SendChannel(cid, _unbound=unbound)
+ cid = _channels.create(unboundop, -1)
+ recv, send = RecvChannel(cid), SendChannel(cid)
+ send._set_unbound(unboundop, unbounditems)
return recv, send
def list_all():
"""Return a list of (recv, send) for all open channels."""
- return [(RecvChannel(cid), SendChannel(cid, _unbound=unbound))
- for cid, unbound in _channels.list_all()]
+ channels = []
+ for cid, unboundop, _ in _channels.list_all():
+ chan = _, send = RecvChannel(cid), SendChannel(cid)
+ if not hasattr(send, '_unboundop'):
+ send._set_unbound(unboundop)
+ else:
+ assert send._unbound[0] == unboundop
+ channels.append(chan)
+ return channels
class _ChannelEnd:
@@ -175,16 +183,33 @@ class SendChannel(_ChannelEnd):
_end = 'send'
- def __new__(cls, cid, *, _unbound=None):
- if _unbound is None:
- try:
- op = _channels.get_channel_defaults(cid)
- _unbound = (op,)
- except ChannelNotFoundError:
- _unbound = _serialize_unbound(UNBOUND)
- self = super().__new__(cls, cid)
- self._unbound = _unbound
- return self
+# def __new__(cls, cid, *, _unbound=None):
+# if _unbound is None:
+# try:
+# op = _channels.get_channel_defaults(cid)
+# _unbound = (op,)
+# except ChannelNotFoundError:
+# _unbound = _serialize_unbound(UNBOUND)
+# self = super().__new__(cls, cid)
+# self._unbound = _unbound
+# return self
+
+ def _set_unbound(self, op, items=None):
+ assert not hasattr(self, '_unbound')
+ if items is None:
+ items = _resolve_unbound(op)
+ unbound = (op, items)
+ self._unbound = unbound
+ return unbound
+
+ @property
+ def unbounditems(self):
+ try:
+ _, items = self._unbound
+ except AttributeError:
+ op, _ = _channels.get_queue_defaults(self._id)
+ _, items = self._set_unbound(op)
+ return items
@property
def is_closed(self):
@@ -192,61 +217,61 @@ class SendChannel(_ChannelEnd):
return info.closed or info.closing
def send(self, obj, timeout=None, *,
- unbound=None,
+ unbounditems=None,
):
"""Send the object (i.e. its data) to the channel's receiving end.
This blocks until the object is received.
"""
- if unbound is None:
- unboundop, = self._unbound
+ if unbounditems is None:
+ unboundop = -1
else:
- unboundop, = _serialize_unbound(unbound)
+ unboundop, = _serialize_unbound(unbounditems)
_channels.send(self._id, obj, unboundop, timeout=timeout, blocking=True)
def send_nowait(self, obj, *,
- unbound=None,
+ unbounditems=None,
):
"""Send the object to the channel's receiving end.
If the object is immediately received then return True
(else False). Otherwise this is the same as send().
"""
- if unbound is None:
- unboundop, = self._unbound
+ if unbounditems is None:
+ unboundop = -1
else:
- unboundop, = _serialize_unbound(unbound)
+ unboundop, = _serialize_unbound(unbounditems)
# XXX Note that at the moment channel_send() only ever returns
# None. This should be fixed when channel_send_wait() is added.
# See bpo-32604 and gh-19829.
return _channels.send(self._id, obj, unboundop, blocking=False)
def send_buffer(self, obj, timeout=None, *,
- unbound=None,
+ unbounditems=None,
):
"""Send the object's buffer to the channel's receiving end.
This blocks until the object is received.
"""
- if unbound is None:
- unboundop, = self._unbound
+ if unbounditems is None:
+ unboundop = -1
else:
- unboundop, = _serialize_unbound(unbound)
+ unboundop, = _serialize_unbound(unbounditems)
_channels.send_buffer(self._id, obj, unboundop,
timeout=timeout, blocking=True)
def send_buffer_nowait(self, obj, *,
- unbound=None,
+ unbounditems=None,
):
"""Send the object's buffer to the channel's receiving end.
If the object is immediately received then return True
(else False). Otherwise this is the same as send().
"""
- if unbound is None:
- unboundop, = self._unbound
+ if unbounditems is None:
+ unboundop = -1
else:
- unboundop, = _serialize_unbound(unbound)
+ unboundop, = _serialize_unbound(unbounditems)
return _channels.send_buffer(self._id, obj, unboundop, blocking=False)
def close(self):
diff --git a/Lib/test/support/hashlib_helper.py b/Lib/test/support/hashlib_helper.py
index 5043f08dd93..7032257b068 100644
--- a/Lib/test/support/hashlib_helper.py
+++ b/Lib/test/support/hashlib_helper.py
@@ -23,6 +23,22 @@ def requires_builtin_hmac():
return unittest.skipIf(_hmac is None, "requires _hmac")
+def _missing_hash(digestname, implementation=None, *, exc=None):
+ parts = ["missing", implementation, f"hash algorithm: {digestname!r}"]
+ msg = " ".join(filter(None, parts))
+ raise unittest.SkipTest(msg) from exc
+
+
+def _openssl_availabillity(digestname, *, usedforsecurity):
+ try:
+ _hashlib.new(digestname, usedforsecurity=usedforsecurity)
+ except AttributeError:
+ assert _hashlib is None
+ _missing_hash(digestname, "OpenSSL")
+ except ValueError as exc:
+ _missing_hash(digestname, "OpenSSL", exc=exc)
+
+
def _decorate_func_or_class(func_or_class, decorator_func):
if not isinstance(func_or_class, type):
return decorator_func(func_or_class)
@@ -71,8 +87,7 @@ def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
try:
test_availability()
except ValueError as exc:
- msg = f"missing hash algorithm: {digestname!r}"
- raise unittest.SkipTest(msg) from exc
+ _missing_hash(digestname, exc=exc)
return func(*args, **kwargs)
return wrapper
@@ -87,14 +102,44 @@ def requires_openssl_hashdigest(digestname, *, usedforsecurity=True):
The hashing algorithm may be missing or blocked by a strict crypto policy.
"""
def decorator_func(func):
- @requires_hashlib()
+ @requires_hashlib() # avoid checking at each call
@functools.wraps(func)
def wrapper(*args, **kwargs):
+ _openssl_availabillity(digestname, usedforsecurity=usedforsecurity)
+ return func(*args, **kwargs)
+ return wrapper
+
+ def decorator(func_or_class):
+ return _decorate_func_or_class(func_or_class, decorator_func)
+ return decorator
+
+
+def find_openssl_hashdigest_constructor(digestname, *, usedforsecurity=True):
+ """Find the OpenSSL hash function constructor by its name."""
+ assert isinstance(digestname, str), digestname
+ _openssl_availabillity(digestname, usedforsecurity=usedforsecurity)
+ # This returns a function of the form _hashlib.openssl_<name> and
+ # not a lambda function as it is rejected by _hashlib.hmac_new().
+ return getattr(_hashlib, f"openssl_{digestname}")
+
+
+def requires_builtin_hashdigest(
+ module_name, digestname, *, usedforsecurity=True
+):
+ """Decorator raising SkipTest if a HACL* hashing algorithm is missing.
+
+ - The *module_name* is the C extension module name based on HACL*.
+ - The *digestname* is one of its member, e.g., 'md5'.
+ """
+ def decorator_func(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ module = import_module(module_name)
try:
- _hashlib.new(digestname, usedforsecurity=usedforsecurity)
- except ValueError:
- msg = f"missing OpenSSL hash algorithm: {digestname!r}"
- raise unittest.SkipTest(msg)
+ getattr(module, digestname)
+ except AttributeError:
+ fullname = f'{module_name}.{digestname}'
+ _missing_hash(fullname, implementation="HACL")
return func(*args, **kwargs)
return wrapper
@@ -103,6 +148,168 @@ def requires_openssl_hashdigest(digestname, *, usedforsecurity=True):
return decorator
+def find_builtin_hashdigest_constructor(
+ module_name, digestname, *, usedforsecurity=True
+):
+ """Find the HACL* hash function constructor.
+
+ - The *module_name* is the C extension module name based on HACL*.
+ - The *digestname* is one of its member, e.g., 'md5'.
+ """
+ module = import_module(module_name)
+ try:
+ constructor = getattr(module, digestname)
+ constructor(b'', usedforsecurity=usedforsecurity)
+ except (AttributeError, TypeError, ValueError):
+ _missing_hash(f'{module_name}.{digestname}', implementation="HACL")
+ return constructor
+
+
+class HashFunctionsTrait:
+ """Mixin trait class containing hash functions.
+
+ This class is assumed to have all unitest.TestCase methods but should
+ not directly inherit from it to prevent the test suite being run on it.
+
+ Subclasses should implement the hash functions by returning an object
+ that can be recognized as a valid digestmod parameter for both hashlib
+ and HMAC. In particular, it cannot be a lambda function as it will not
+ be recognized by hashlib (it will still be accepted by the pure Python
+ implementation of HMAC).
+ """
+
+ ALGORITHMS = [
+ 'md5', 'sha1',
+ 'sha224', 'sha256', 'sha384', 'sha512',
+ 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
+ ]
+
+ # Default 'usedforsecurity' to use when looking up a hash function.
+ usedforsecurity = True
+
+ def _find_constructor(self, name):
+ # By default, a missing algorithm skips the test that uses it.
+ self.assertIn(name, self.ALGORITHMS)
+ self.skipTest(f"missing hash function: {name}")
+
+ @property
+ def md5(self):
+ return self._find_constructor("md5")
+
+ @property
+ def sha1(self):
+ return self._find_constructor("sha1")
+
+ @property
+ def sha224(self):
+ return self._find_constructor("sha224")
+
+ @property
+ def sha256(self):
+ return self._find_constructor("sha256")
+
+ @property
+ def sha384(self):
+ return self._find_constructor("sha384")
+
+ @property
+ def sha512(self):
+ return self._find_constructor("sha512")
+
+ @property
+ def sha3_224(self):
+ return self._find_constructor("sha3_224")
+
+ @property
+ def sha3_256(self):
+ return self._find_constructor("sha3_256")
+
+ @property
+ def sha3_384(self):
+ return self._find_constructor("sha3_384")
+
+ @property
+ def sha3_512(self):
+ return self._find_constructor("sha3_512")
+
+
+class NamedHashFunctionsTrait(HashFunctionsTrait):
+ """Trait containing named hash functions.
+
+ Hash functions are available if and only if they are available in hashlib.
+ """
+
+ def _find_constructor(self, name):
+ self.assertIn(name, self.ALGORITHMS)
+ return name
+
+
+class OpenSSLHashFunctionsTrait(HashFunctionsTrait):
+ """Trait containing OpenSSL hash functions.
+
+ Hash functions are available if and only if they are available in _hashlib.
+ """
+
+ def _find_constructor(self, name):
+ self.assertIn(name, self.ALGORITHMS)
+ return find_openssl_hashdigest_constructor(
+ name, usedforsecurity=self.usedforsecurity
+ )
+
+
+class BuiltinHashFunctionsTrait(HashFunctionsTrait):
+ """Trait containing HACL* hash functions.
+
+ Hash functions are available if and only if they are available in C.
+ In particular, HACL* HMAC-MD5 may be available even though HACL* md5
+ is not since the former is unconditionally built.
+ """
+
+ def _find_constructor_in(self, module, name):
+ self.assertIn(name, self.ALGORITHMS)
+ return find_builtin_hashdigest_constructor(module, name)
+
+ @property
+ def md5(self):
+ return self._find_constructor_in("_md5", "md5")
+
+ @property
+ def sha1(self):
+ return self._find_constructor_in("_sha1", "sha1")
+
+ @property
+ def sha224(self):
+ return self._find_constructor_in("_sha2", "sha224")
+
+ @property
+ def sha256(self):
+ return self._find_constructor_in("_sha2", "sha256")
+
+ @property
+ def sha384(self):
+ return self._find_constructor_in("_sha2", "sha384")
+
+ @property
+ def sha512(self):
+ return self._find_constructor_in("_sha2", "sha512")
+
+ @property
+ def sha3_224(self):
+ return self._find_constructor_in("_sha3", "sha3_224")
+
+ @property
+ def sha3_256(self):
+ return self._find_constructor_in("_sha3","sha3_256")
+
+ @property
+ def sha3_384(self):
+ return self._find_constructor_in("_sha3","sha3_384")
+
+ @property
+ def sha3_512(self):
+ return self._find_constructor_in("_sha3","sha3_512")
+
+
def find_gil_minsize(modules_names, default=2048):
"""Get the largest GIL_MINSIZE value for the given cryptographic modules.
diff --git a/Lib/test/support/import_helper.py b/Lib/test/support/import_helper.py
index edb734d294f..0af63501f93 100644
--- a/Lib/test/support/import_helper.py
+++ b/Lib/test/support/import_helper.py
@@ -438,5 +438,5 @@ def ensure_module_imported(name, *, clearnone=True):
if sys.modules.get(name) is not None:
mod = sys.modules[name]
else:
- mod, _, _ = _force_import(name, False, True, clearnone)
+ mod, _, _ = _ensure_module(name, False, True, clearnone)
return mod
diff --git a/Lib/test/support/strace_helper.py b/Lib/test/support/strace_helper.py
index 798d6c68869..cf95f7bdc7d 100644
--- a/Lib/test/support/strace_helper.py
+++ b/Lib/test/support/strace_helper.py
@@ -38,7 +38,7 @@ class StraceResult:
This assumes the program under inspection doesn't print any non-utf8
strings which would mix into the strace output."""
- decoded_events = self.event_bytes.decode('utf-8')
+ decoded_events = self.event_bytes.decode('utf-8', 'surrogateescape')
matches = [
_syscall_regex.match(event)
for event in decoded_events.splitlines()
@@ -178,7 +178,10 @@ def get_syscalls(code, strace_flags, prelude="", cleanup="",
# Moderately expensive (spawns a subprocess), so share results when possible.
@cache
def _can_strace():
- res = strace_python("import sys; sys.exit(0)", [], check=False)
+ res = strace_python("import sys; sys.exit(0)",
+ # --trace option needs strace 5.5 (gh-133741)
+ ["--trace=%process"],
+ check=False)
if res.strace_returncode == 0 and res.python_returncode == 0:
assert res.events(), "Should have parsed multiple calls"
return True
diff --git a/Lib/test/support/warnings_helper.py b/Lib/test/support/warnings_helper.py
index a6e43dff200..5f6f14afd74 100644
--- a/Lib/test/support/warnings_helper.py
+++ b/Lib/test/support/warnings_helper.py
@@ -23,8 +23,7 @@ def check_syntax_warning(testcase, statement, errtext='',
testcase.assertEqual(len(warns), 1, warns)
warn, = warns
- testcase.assertTrue(issubclass(warn.category, SyntaxWarning),
- warn.category)
+ testcase.assertIsSubclass(warn.category, SyntaxWarning)
if errtext:
testcase.assertRegex(str(warn.message), errtext)
testcase.assertEqual(warn.filename, '<testcase>')
diff --git a/Lib/test/test__interpchannels.py b/Lib/test/test__interpchannels.py
index e4c1ad85451..858d31a73cf 100644
--- a/Lib/test/test__interpchannels.py
+++ b/Lib/test/test__interpchannels.py
@@ -9,7 +9,7 @@ import unittest
from test.support import import_helper, skip_if_sanitizer
_channels = import_helper.import_module('_interpchannels')
-from test.support.interpreters import _crossinterp
+from concurrent.interpreters import _crossinterp
from test.test__interpreters import (
_interpreters,
_run_output,
@@ -247,7 +247,7 @@ def _run_action(cid, action, end, state):
def clean_up_channels():
- for cid, _ in _channels.list_all():
+ for cid, _, _ in _channels.list_all():
try:
_channels.destroy(cid)
except _channels.ChannelNotFoundError:
@@ -373,11 +373,11 @@ class ChannelTests(TestBase):
self.assertIsInstance(cid, _channels.ChannelID)
def test_sequential_ids(self):
- before = [cid for cid, _ in _channels.list_all()]
+ before = [cid for cid, _, _ in _channels.list_all()]
id1 = _channels.create(REPLACE)
id2 = _channels.create(REPLACE)
id3 = _channels.create(REPLACE)
- after = [cid for cid, _ in _channels.list_all()]
+ after = [cid for cid, _, _ in _channels.list_all()]
self.assertEqual(id2, int(id1) + 1)
self.assertEqual(id3, int(id2) + 1)
diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py
index 0c43f46300f..a32d5d81d2b 100644
--- a/Lib/test/test__interpreters.py
+++ b/Lib/test/test__interpreters.py
@@ -474,15 +474,32 @@ class CommonTests(TestBase):
def test_signatures(self):
# See https://github.com/python/cpython/issues/126654
- msg = "expected 'shared' to be a dict"
+ msg = r'_interpreters.exec\(\) argument 3 must be dict, not int'
with self.assertRaisesRegex(TypeError, msg):
_interpreters.exec(self.id, 'a', 1)
with self.assertRaisesRegex(TypeError, msg):
_interpreters.exec(self.id, 'a', shared=1)
+ msg = r'_interpreters.run_string\(\) argument 3 must be dict, not int'
with self.assertRaisesRegex(TypeError, msg):
_interpreters.run_string(self.id, 'a', shared=1)
+ msg = r'_interpreters.run_func\(\) argument 3 must be dict, not int'
with self.assertRaisesRegex(TypeError, msg):
_interpreters.run_func(self.id, lambda: None, shared=1)
+ # See https://github.com/python/cpython/issues/135855
+ msg = r'_interpreters.set___main___attrs\(\) argument 2 must be dict, not int'
+ with self.assertRaisesRegex(TypeError, msg):
+ _interpreters.set___main___attrs(self.id, 1)
+
+ def test_invalid_shared_none(self):
+ msg = r'must be dict, not None'
+ with self.assertRaisesRegex(TypeError, msg):
+ _interpreters.exec(self.id, 'a', shared=None)
+ with self.assertRaisesRegex(TypeError, msg):
+ _interpreters.run_string(self.id, 'a', shared=None)
+ with self.assertRaisesRegex(TypeError, msg):
+ _interpreters.run_func(self.id, lambda: None, shared=None)
+ with self.assertRaisesRegex(TypeError, msg):
+ _interpreters.set___main___attrs(self.id, None)
def test_invalid_shared_encoding(self):
# See https://github.com/python/cpython/issues/127196
@@ -952,7 +969,8 @@ class RunFailedTests(TestBase):
""")
with self.subTest('script'):
- self.assert_run_failed(SyntaxError, script)
+ with self.assertRaises(SyntaxError):
+ _interpreters.run_string(self.id, script)
with self.subTest('module'):
modname = 'spam_spam_spam'
@@ -1019,12 +1037,19 @@ class RunFuncTests(TestBase):
with open(w, 'w', encoding="utf-8") as spipe:
with contextlib.redirect_stdout(spipe):
print('it worked!', end='')
+ failed = None
def f():
- _interpreters.set___main___attrs(self.id, dict(w=w))
- _interpreters.run_func(self.id, script)
+ nonlocal failed
+ try:
+ _interpreters.set___main___attrs(self.id, dict(w=w))
+ _interpreters.run_func(self.id, script)
+ except Exception as exc:
+ failed = exc
t = threading.Thread(target=f)
t.start()
t.join()
+ if failed:
+ raise Exception from failed
with open(r, encoding="utf-8") as outfile:
out = outfile.read()
@@ -1053,18 +1078,16 @@ class RunFuncTests(TestBase):
spam = True
def script():
assert spam
-
with self.assertRaises(ValueError):
_interpreters.run_func(self.id, script)
- # XXX This hasn't been fixed yet.
- @unittest.expectedFailure
def test_return_value(self):
def script():
return 'spam'
with self.assertRaises(ValueError):
_interpreters.run_func(self.id, script)
+# @unittest.skip("we're not quite there yet")
def test_args(self):
with self.subTest('args'):
def script(a, b=0):
diff --git a/Lib/test/test__osx_support.py b/Lib/test/test__osx_support.py
index 53aa26620a6..0813c4804c1 100644
--- a/Lib/test/test__osx_support.py
+++ b/Lib/test/test__osx_support.py
@@ -66,8 +66,8 @@ class Test_OSXSupport(unittest.TestCase):
'cc not found - check xcode-select')
def test__get_system_version(self):
- self.assertTrue(platform.mac_ver()[0].startswith(
- _osx_support._get_system_version()))
+ self.assertStartsWith(platform.mac_ver()[0],
+ _osx_support._get_system_version())
def test__remove_original_values(self):
config_vars = {
diff --git a/Lib/test/test_abstract_numbers.py b/Lib/test/test_abstract_numbers.py
index 72232b670cd..cf071d2c933 100644
--- a/Lib/test/test_abstract_numbers.py
+++ b/Lib/test/test_abstract_numbers.py
@@ -24,11 +24,11 @@ def concretize(cls):
class TestNumbers(unittest.TestCase):
def test_int(self):
- self.assertTrue(issubclass(int, Integral))
- self.assertTrue(issubclass(int, Rational))
- self.assertTrue(issubclass(int, Real))
- self.assertTrue(issubclass(int, Complex))
- self.assertTrue(issubclass(int, Number))
+ self.assertIsSubclass(int, Integral)
+ self.assertIsSubclass(int, Rational)
+ self.assertIsSubclass(int, Real)
+ self.assertIsSubclass(int, Complex)
+ self.assertIsSubclass(int, Number)
self.assertEqual(7, int(7).real)
self.assertEqual(0, int(7).imag)
@@ -38,11 +38,11 @@ class TestNumbers(unittest.TestCase):
self.assertEqual(1, int(7).denominator)
def test_float(self):
- self.assertFalse(issubclass(float, Integral))
- self.assertFalse(issubclass(float, Rational))
- self.assertTrue(issubclass(float, Real))
- self.assertTrue(issubclass(float, Complex))
- self.assertTrue(issubclass(float, Number))
+ self.assertNotIsSubclass(float, Integral)
+ self.assertNotIsSubclass(float, Rational)
+ self.assertIsSubclass(float, Real)
+ self.assertIsSubclass(float, Complex)
+ self.assertIsSubclass(float, Number)
self.assertEqual(7.3, float(7.3).real)
self.assertEqual(0, float(7.3).imag)
@@ -50,11 +50,11 @@ class TestNumbers(unittest.TestCase):
self.assertEqual(-7.3, float(-7.3).conjugate())
def test_complex(self):
- self.assertFalse(issubclass(complex, Integral))
- self.assertFalse(issubclass(complex, Rational))
- self.assertFalse(issubclass(complex, Real))
- self.assertTrue(issubclass(complex, Complex))
- self.assertTrue(issubclass(complex, Number))
+ self.assertNotIsSubclass(complex, Integral)
+ self.assertNotIsSubclass(complex, Rational)
+ self.assertNotIsSubclass(complex, Real)
+ self.assertIsSubclass(complex, Complex)
+ self.assertIsSubclass(complex, Number)
c1, c2 = complex(3, 2), complex(4,1)
# XXX: This is not ideal, but see the comment in math_trunc().
diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py
index be55f044b15..ae0e73f08c5 100644
--- a/Lib/test/test_annotationlib.py
+++ b/Lib/test/test_annotationlib.py
@@ -1,18 +1,19 @@
"""Tests for the annotations module."""
+import textwrap
import annotationlib
import builtins
import collections
import functools
import itertools
import pickle
+from string.templatelib import Template
import typing
import unittest
from annotationlib import (
Format,
ForwardRef,
get_annotations,
- get_annotate_function,
annotations_to_string,
type_repr,
)
@@ -121,6 +122,28 @@ class TestForwardRefFormat(unittest.TestCase):
self.assertIsInstance(gamma_anno, ForwardRef)
self.assertEqual(gamma_anno, support.EqualToForwardRef("some < obj", owner=f))
+ def test_partially_nonexistent_union(self):
+ # Test unions with '|' syntax equal unions with typing.Union[] with some forwardrefs
+ class UnionForwardrefs:
+ pipe: str | undefined
+ union: Union[str, undefined]
+
+ annos = get_annotations(UnionForwardrefs, format=Format.FORWARDREF)
+
+ pipe = annos["pipe"]
+ self.assertIsInstance(pipe, ForwardRef)
+ self.assertEqual(
+ pipe.evaluate(globals={"undefined": int}),
+ str | int,
+ )
+ union = annos["union"]
+ self.assertIsInstance(union, Union)
+ arg1, arg2 = typing.get_args(union)
+ self.assertIs(arg1, str)
+ self.assertEqual(
+ arg2, support.EqualToForwardRef("undefined", is_class=True, owner=UnionForwardrefs)
+ )
+
class TestStringFormat(unittest.TestCase):
def test_closure(self):
@@ -251,6 +274,126 @@ class TestStringFormat(unittest.TestCase):
},
)
+ def test_template_str(self):
+ def f(
+ x: t"{a}",
+ y: list[t"{a}"],
+ z: t"{a:b} {c!r} {d!s:t}",
+ a: t"a{b}c{d}e{f}g",
+ b: t"{a:{1}}",
+ c: t"{a | b * c}",
+ ): pass
+
+ annos = get_annotations(f, format=Format.STRING)
+ self.assertEqual(annos, {
+ "x": "t'{a}'",
+ "y": "list[t'{a}']",
+ "z": "t'{a:b} {c!r} {d!s:t}'",
+ "a": "t'a{b}c{d}e{f}g'",
+ # interpolations in the format spec are eagerly evaluated so we can't recover the source
+ "b": "t'{a:1}'",
+ "c": "t'{a | b * c}'",
+ })
+
+ def g(
+ x: t"{a}",
+ ): ...
+
+ annos = get_annotations(g, format=Format.FORWARDREF)
+ templ = annos["x"]
+ # Template and Interpolation don't have __eq__ so we have to compare manually
+ self.assertIsInstance(templ, Template)
+ self.assertEqual(templ.strings, ("", ""))
+ self.assertEqual(len(templ.interpolations), 1)
+ interp = templ.interpolations[0]
+ self.assertEqual(interp.value, support.EqualToForwardRef("a", owner=g))
+ self.assertEqual(interp.expression, "a")
+ self.assertIsNone(interp.conversion)
+ self.assertEqual(interp.format_spec, "")
+
+ def test_getitem(self):
+ def f(x: undef1[str, undef2]):
+ pass
+ anno = get_annotations(f, format=Format.STRING)
+ self.assertEqual(anno, {"x": "undef1[str, undef2]"})
+
+ anno = get_annotations(f, format=Format.FORWARDREF)
+ fwdref = anno["x"]
+ self.assertIsInstance(fwdref, ForwardRef)
+ self.assertEqual(
+ fwdref.evaluate(globals={"undef1": dict, "undef2": float}), dict[str, float]
+ )
+
+ def test_slice(self):
+ def f(x: a[b:c]):
+ pass
+ anno = get_annotations(f, format=Format.STRING)
+ self.assertEqual(anno, {"x": "a[b:c]"})
+
+ def f(x: a[b:c, d:e]):
+ pass
+ anno = get_annotations(f, format=Format.STRING)
+ self.assertEqual(anno, {"x": "a[b:c, d:e]"})
+
+ obj = slice(1, 1, 1)
+ def f(x: obj):
+ pass
+ anno = get_annotations(f, format=Format.STRING)
+ self.assertEqual(anno, {"x": "obj"})
+
+ def test_literals(self):
+ def f(
+ a: 1,
+ b: 1.0,
+ c: "hello",
+ d: b"hello",
+ e: True,
+ f: None,
+ g: ...,
+ h: 1j,
+ ):
+ pass
+
+ anno = get_annotations(f, format=Format.STRING)
+ self.assertEqual(
+ anno,
+ {
+ "a": "1",
+ "b": "1.0",
+ "c": 'hello',
+ "d": "b'hello'",
+ "e": "True",
+ "f": "None",
+ "g": "...",
+ "h": "1j",
+ },
+ )
+
+ def test_displays(self):
+ # Simple case first
+ def f(x: a[[int, str], float]):
+ pass
+ anno = get_annotations(f, format=Format.STRING)
+ self.assertEqual(anno, {"x": "a[[int, str], float]"})
+
+ def g(
+ w: a[[int, str], float],
+ x: a[{int}, 3],
+ y: a[{int: str}, 4],
+ z: a[(int, str), 5],
+ ):
+ pass
+ anno = get_annotations(g, format=Format.STRING)
+ self.assertEqual(
+ anno,
+ {
+ "w": "a[[int, str], float]",
+ "x": "a[{int}, 3]",
+ "y": "a[{int: str}, 4]",
+ "z": "a[(int, str), 5]",
+ },
+ )
+
def test_nested_expressions(self):
def f(
nested: list[Annotated[set[int], "set of ints", 4j]],
@@ -296,6 +439,17 @@ class TestStringFormat(unittest.TestCase):
with self.assertRaisesRegex(TypeError, format_msg):
get_annotations(f, format=Format.STRING)
+ def test_shenanigans(self):
+ # In cases like this we can't reconstruct the source; test that we do something
+ # halfway reasonable.
+ def f(x: x | (1).__class__, y: (1).__class__):
+ pass
+
+ self.assertEqual(
+ get_annotations(f, format=Format.STRING),
+ {"x": "x | <class 'int'>", "y": "<class 'int'>"},
+ )
+
class TestGetAnnotations(unittest.TestCase):
def test_builtin_type(self):
@@ -661,6 +815,70 @@ class TestGetAnnotations(unittest.TestCase):
{"x": int},
)
+ def test_stringized_annotation_permutations(self):
+ def define_class(name, has_future, has_annos, base_text, extra_names=None):
+ lines = []
+ if has_future:
+ lines.append("from __future__ import annotations")
+ lines.append(f"class {name}({base_text}):")
+ if has_annos:
+ lines.append(f" {name}_attr: int")
+ else:
+ lines.append(" pass")
+ code = "\n".join(lines)
+ ns = support.run_code(code, extra_names=extra_names)
+ return ns[name]
+
+ def check_annotations(cls, has_future, has_annos):
+ if has_annos:
+ if has_future:
+ anno = "int"
+ else:
+ anno = int
+ self.assertEqual(get_annotations(cls), {f"{cls.__name__}_attr": anno})
+ else:
+ self.assertEqual(get_annotations(cls), {})
+
+ for meta_future, base_future, child_future, meta_has_annos, base_has_annos, child_has_annos in itertools.product(
+ (False, True),
+ (False, True),
+ (False, True),
+ (False, True),
+ (False, True),
+ (False, True),
+ ):
+ with self.subTest(
+ meta_future=meta_future,
+ base_future=base_future,
+ child_future=child_future,
+ meta_has_annos=meta_has_annos,
+ base_has_annos=base_has_annos,
+ child_has_annos=child_has_annos,
+ ):
+ meta = define_class(
+ "Meta",
+ has_future=meta_future,
+ has_annos=meta_has_annos,
+ base_text="type",
+ )
+ base = define_class(
+ "Base",
+ has_future=base_future,
+ has_annos=base_has_annos,
+ base_text="metaclass=Meta",
+ extra_names={"Meta": meta},
+ )
+ child = define_class(
+ "Child",
+ has_future=child_future,
+ has_annos=child_has_annos,
+ base_text="Base",
+ extra_names={"Base": base},
+ )
+ check_annotations(meta, meta_future, meta_has_annos)
+ check_annotations(base, base_future, base_has_annos)
+ check_annotations(child, child_future, child_has_annos)
+
def test_modify_annotations(self):
def f(x: int):
pass
@@ -901,6 +1119,73 @@ class TestGetAnnotations(unittest.TestCase):
set(results.generic_func.__type_params__),
)
+ def test_partial_evaluation(self):
+ def f(
+ x: builtins.undef,
+ y: list[int],
+ z: 1 + int,
+ a: builtins.int,
+ b: [builtins.undef, builtins.int],
+ ):
+ pass
+
+ self.assertEqual(
+ get_annotations(f, format=Format.FORWARDREF),
+ {
+ "x": support.EqualToForwardRef("builtins.undef", owner=f),
+ "y": list[int],
+ "z": support.EqualToForwardRef("1 + int", owner=f),
+ "a": int,
+ "b": [
+ support.EqualToForwardRef("builtins.undef", owner=f),
+ # We can't resolve this because we have to evaluate the whole annotation
+ support.EqualToForwardRef("builtins.int", owner=f),
+ ],
+ },
+ )
+
+ self.assertEqual(
+ get_annotations(f, format=Format.STRING),
+ {
+ "x": "builtins.undef",
+ "y": "list[int]",
+ "z": "1 + int",
+ "a": "builtins.int",
+ "b": "[builtins.undef, builtins.int]",
+ },
+ )
+
+ def test_partial_evaluation_error(self):
+ def f(x: range[1]):
+ pass
+ with self.assertRaisesRegex(
+ TypeError, "type 'range' is not subscriptable"
+ ):
+ f.__annotations__
+
+ self.assertEqual(
+ get_annotations(f, format=Format.FORWARDREF),
+ {
+ "x": support.EqualToForwardRef("range[1]", owner=f),
+ },
+ )
+
+ def test_partial_evaluation_cell(self):
+ obj = object()
+
+ class RaisesAttributeError:
+ attriberr: obj.missing
+
+ anno = get_annotations(RaisesAttributeError, format=Format.FORWARDREF)
+ self.assertEqual(
+ anno,
+ {
+ "attriberr": support.EqualToForwardRef(
+ "obj.missing", is_class=True, owner=RaisesAttributeError
+ )
+ },
+ )
+
class TestCallEvaluateFunction(unittest.TestCase):
def test_evaluation(self):
@@ -933,13 +1218,13 @@ class MetaclassTests(unittest.TestCase):
b: float
self.assertEqual(get_annotations(Meta), {"a": int})
- self.assertEqual(get_annotate_function(Meta)(Format.VALUE), {"a": int})
+ self.assertEqual(Meta.__annotate__(Format.VALUE), {"a": int})
self.assertEqual(get_annotations(X), {})
- self.assertIs(get_annotate_function(X), None)
+ self.assertIs(X.__annotate__, None)
self.assertEqual(get_annotations(Y), {"b": float})
- self.assertEqual(get_annotate_function(Y)(Format.VALUE), {"b": float})
+ self.assertEqual(Y.__annotate__(Format.VALUE), {"b": float})
def test_unannotated_meta(self):
class Meta(type):
@@ -952,13 +1237,13 @@ class MetaclassTests(unittest.TestCase):
pass
self.assertEqual(get_annotations(Meta), {})
- self.assertIs(get_annotate_function(Meta), None)
+ self.assertIs(Meta.__annotate__, None)
self.assertEqual(get_annotations(Y), {})
- self.assertIs(get_annotate_function(Y), None)
+ self.assertIs(Y.__annotate__, None)
self.assertEqual(get_annotations(X), {"a": str})
- self.assertEqual(get_annotate_function(X)(Format.VALUE), {"a": str})
+ self.assertEqual(X.__annotate__(Format.VALUE), {"a": str})
def test_ordering(self):
# Based on a sample by David Ellis
@@ -996,7 +1281,7 @@ class MetaclassTests(unittest.TestCase):
for c in classes:
with self.subTest(c=c):
self.assertEqual(get_annotations(c), c.expected_annotations)
- annotate_func = get_annotate_function(c)
+ annotate_func = getattr(c, "__annotate__", None)
if c.expected_annotations:
self.assertEqual(
annotate_func(Format.VALUE), c.expected_annotations
@@ -1005,25 +1290,39 @@ class MetaclassTests(unittest.TestCase):
self.assertIs(annotate_func, None)
-class TestGetAnnotateFunction(unittest.TestCase):
- def test_static_class(self):
- self.assertIsNone(get_annotate_function(object))
- self.assertIsNone(get_annotate_function(int))
-
- def test_unannotated_class(self):
- class C:
- pass
+class TestGetAnnotateFromClassNamespace(unittest.TestCase):
+ def test_with_metaclass(self):
+ class Meta(type):
+ def __new__(mcls, name, bases, ns):
+ annotate = annotationlib.get_annotate_from_class_namespace(ns)
+ expected = ns["expected_annotate"]
+ with self.subTest(name=name):
+ if expected:
+ self.assertIsNotNone(annotate)
+ else:
+ self.assertIsNone(annotate)
+ return super().__new__(mcls, name, bases, ns)
+
+ class HasAnnotations(metaclass=Meta):
+ expected_annotate = True
+ a: int
- self.assertIsNone(get_annotate_function(C))
+ class NoAnnotations(metaclass=Meta):
+ expected_annotate = False
- D = type("D", (), {})
- self.assertIsNone(get_annotate_function(D))
+ class CustomAnnotate(metaclass=Meta):
+ expected_annotate = True
+ def __annotate__(format):
+ return {}
- def test_annotated_class(self):
- class C:
- a: int
+ code = """
+ from __future__ import annotations
- self.assertEqual(get_annotate_function(C)(Format.VALUE), {"a": int})
+ class HasFutureAnnotations(metaclass=Meta):
+ expected_annotate = False
+ a: int
+ """
+ exec(textwrap.dedent(code), {"Meta": Meta})
class TestTypeRepr(unittest.TestCase):
@@ -1240,6 +1539,38 @@ class TestForwardRefClass(unittest.TestCase):
with self.assertRaises(TypeError):
pickle.dumps(fr, proto)
+ def test_evaluate_string_format(self):
+ fr = ForwardRef("set[Any]")
+ self.assertEqual(fr.evaluate(format=Format.STRING), "set[Any]")
+
+ def test_evaluate_forwardref_format(self):
+ fr = ForwardRef("undef")
+ evaluated = fr.evaluate(format=Format.FORWARDREF)
+ self.assertIs(fr, evaluated)
+
+ fr = ForwardRef("set[undefined]")
+ evaluated = fr.evaluate(format=Format.FORWARDREF)
+ self.assertEqual(
+ evaluated,
+ set[support.EqualToForwardRef("undefined")],
+ )
+
+ fr = ForwardRef("a + b")
+ self.assertEqual(
+ fr.evaluate(format=Format.FORWARDREF),
+ support.EqualToForwardRef("a + b"),
+ )
+ self.assertEqual(
+ fr.evaluate(format=Format.FORWARDREF, locals={"a": 1, "b": 2}),
+ 3,
+ )
+
+ fr = ForwardRef('"a" + 1')
+ self.assertEqual(
+ fr.evaluate(format=Format.FORWARDREF),
+ support.EqualToForwardRef('"a" + 1'),
+ )
+
def test_evaluate_with_type_params(self):
class Gen[T]:
alias = int
@@ -1319,9 +1650,11 @@ class TestForwardRefClass(unittest.TestCase):
with support.swap_attr(builtins, "int", dict):
self.assertIs(ForwardRef("int").evaluate(), dict)
- with self.assertRaises(NameError):
+ with self.assertRaises(NameError, msg="name 'doesntexist' is not defined") as exc:
ForwardRef("doesntexist").evaluate()
+ self.assertEqual(exc.exception.name, "doesntexist")
+
def test_fwdref_invalid_syntax(self):
fr = ForwardRef("if")
with self.assertRaises(SyntaxError):
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index c5a1f31aa52..08ff41368d9 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -5469,11 +5469,60 @@ class TestHelpMetavarTypeFormatter(HelpTestCase):
version = ''
-class TestHelpUsageLongSubparserCommand(TestCase):
- """Test that subparser commands are formatted correctly in help"""
+class TestHelpCustomHelpFormatter(TestCase):
maxDiff = None
- def test_parent_help(self):
+ def test_custom_formatter_function(self):
+ def custom_formatter(prog):
+ return argparse.RawTextHelpFormatter(prog, indent_increment=5)
+
+ parser = argparse.ArgumentParser(
+ prog='PROG',
+ prefix_chars='-+',
+ formatter_class=custom_formatter
+ )
+ parser.add_argument('+f', '++foo', help="foo help")
+ parser.add_argument('spam', help="spam help")
+
+ parser_help = parser.format_help()
+ self.assertEqual(parser_help, textwrap.dedent('''\
+ usage: PROG [-h] [+f FOO] spam
+
+ positional arguments:
+ spam spam help
+
+ options:
+ -h, --help show this help message and exit
+ +f, ++foo FOO foo help
+ '''))
+
+ def test_custom_formatter_class(self):
+ class CustomFormatter(argparse.RawTextHelpFormatter):
+ def __init__(self, prog):
+ super().__init__(prog, indent_increment=5)
+
+ parser = argparse.ArgumentParser(
+ prog='PROG',
+ prefix_chars='-+',
+ formatter_class=CustomFormatter
+ )
+ parser.add_argument('+f', '++foo', help="foo help")
+ parser.add_argument('spam', help="spam help")
+
+ parser_help = parser.format_help()
+ self.assertEqual(parser_help, textwrap.dedent('''\
+ usage: PROG [-h] [+f FOO] spam
+
+ positional arguments:
+ spam spam help
+
+ options:
+ -h, --help show this help message and exit
+ +f, ++foo FOO foo help
+ '''))
+
+ def test_usage_long_subparser_command(self):
+ """Test that subparser commands are formatted correctly in help"""
def custom_formatter(prog):
return argparse.RawTextHelpFormatter(prog, max_help_position=50)
@@ -6756,7 +6805,7 @@ class TestImportStar(TestCase):
def test(self):
for name in argparse.__all__:
- self.assertTrue(hasattr(argparse, name))
+ self.assertHasAttr(argparse, name)
def test_all_exports_everything_but_modules(self):
items = [
@@ -6973,7 +7022,7 @@ class TestProgName(TestCase):
def check_usage(self, expected, *args, **kwargs):
res = script_helper.assert_python_ok('-Xutf8', *args, '-h', **kwargs)
- self.assertEqual(res.out.splitlines()[0].decode(),
+ self.assertEqual(os.fsdecode(res.out.splitlines()[0]),
f'usage: {expected} [-h]')
def test_script(self, compiled=False):
@@ -7053,12 +7102,13 @@ class TestTranslations(TestTranslationsBase):
class TestColorized(TestCase):
+ maxDiff = None
def setUp(self):
super().setUp()
# Ensure color even if ran with NO_COLOR=1
_colorize.can_colorize = lambda *args, **kwargs: True
- self.ansi = _colorize.ANSIColors()
+ self.theme = _colorize.get_theme(force_color=True).argparse
def test_argparse_color(self):
# Arrange: create a parser with a bit of everything
@@ -7120,13 +7170,17 @@ class TestColorized(TestCase):
sub2 = subparsers.add_parser("sub2", deprecated=True, help="sub2 help")
sub2.add_argument("--baz", choices=("X", "Y", "Z"), help="baz help")
- heading = self.ansi.BOLD_BLUE
- label, label_b = self.ansi.YELLOW, self.ansi.BOLD_YELLOW
- long, long_b = self.ansi.CYAN, self.ansi.BOLD_CYAN
- pos, pos_b = short, short_b = self.ansi.GREEN, self.ansi.BOLD_GREEN
- sub = self.ansi.BOLD_GREEN
- prog = self.ansi.BOLD_MAGENTA
- reset = self.ansi.RESET
+ prog = self.theme.prog
+ heading = self.theme.heading
+ long = self.theme.summary_long_option
+ short = self.theme.summary_short_option
+ label = self.theme.summary_label
+ pos = self.theme.summary_action
+ long_b = self.theme.long_option
+ short_b = self.theme.short_option
+ label_b = self.theme.label
+ pos_b = self.theme.action
+ reset = self.theme.reset
# Act
help_text = parser.format_help()
@@ -7171,9 +7225,9 @@ class TestColorized(TestCase):
{heading}subcommands:{reset}
valid subcommands
- {sub}{{sub1,sub2}}{reset} additional help
- {sub}sub1{reset} sub1 help
- {sub}sub2{reset} sub2 help
+ {pos_b}{{sub1,sub2}}{reset} additional help
+ {pos_b}sub1{reset} sub1 help
+ {pos_b}sub2{reset} sub2 help
"""
),
)
@@ -7187,10 +7241,10 @@ class TestColorized(TestCase):
prog="PROG",
usage="[prefix] %(prog)s [suffix]",
)
- heading = self.ansi.BOLD_BLUE
- prog = self.ansi.BOLD_MAGENTA
- reset = self.ansi.RESET
- usage = self.ansi.MAGENTA
+ heading = self.theme.heading
+ prog = self.theme.prog
+ reset = self.theme.reset
+ usage = self.theme.prog_extra
# Act
help_text = parser.format_help()
@@ -7207,6 +7261,79 @@ class TestColorized(TestCase):
),
)
+ def test_custom_formatter_function(self):
+ def custom_formatter(prog):
+ return argparse.RawTextHelpFormatter(prog, indent_increment=5)
+
+ parser = argparse.ArgumentParser(
+ prog="PROG",
+ prefix_chars="-+",
+ formatter_class=custom_formatter,
+ color=True,
+ )
+ parser.add_argument('+f', '++foo', help="foo help")
+ parser.add_argument('spam', help="spam help")
+
+ prog = self.theme.prog
+ heading = self.theme.heading
+ short = self.theme.summary_short_option
+ label = self.theme.summary_label
+ pos = self.theme.summary_action
+ long_b = self.theme.long_option
+ short_b = self.theme.short_option
+ label_b = self.theme.label
+ pos_b = self.theme.action
+ reset = self.theme.reset
+
+ parser_help = parser.format_help()
+ self.assertEqual(parser_help, textwrap.dedent(f'''\
+ {heading}usage: {reset}{prog}PROG{reset} [{short}-h{reset}] [{short}+f {label}FOO{reset}] {pos}spam{reset}
+
+ {heading}positional arguments:{reset}
+ {pos_b}spam{reset} spam help
+
+ {heading}options:{reset}
+ {short_b}-h{reset}, {long_b}--help{reset} show this help message and exit
+ {short_b}+f{reset}, {long_b}++foo{reset} {label_b}FOO{reset} foo help
+ '''))
+
+ def test_custom_formatter_class(self):
+ class CustomFormatter(argparse.RawTextHelpFormatter):
+ def __init__(self, prog):
+ super().__init__(prog, indent_increment=5)
+
+ parser = argparse.ArgumentParser(
+ prog="PROG",
+ prefix_chars="-+",
+ formatter_class=CustomFormatter,
+ color=True,
+ )
+ parser.add_argument('+f', '++foo', help="foo help")
+ parser.add_argument('spam', help="spam help")
+
+ prog = self.theme.prog
+ heading = self.theme.heading
+ short = self.theme.summary_short_option
+ label = self.theme.summary_label
+ pos = self.theme.summary_action
+ long_b = self.theme.long_option
+ short_b = self.theme.short_option
+ label_b = self.theme.label
+ pos_b = self.theme.action
+ reset = self.theme.reset
+
+ parser_help = parser.format_help()
+ self.assertEqual(parser_help, textwrap.dedent(f'''\
+ {heading}usage: {reset}{prog}PROG{reset} [{short}-h{reset}] [{short}+f {label}FOO{reset}] {pos}spam{reset}
+
+ {heading}positional arguments:{reset}
+ {pos_b}spam{reset} spam help
+
+ {heading}options:{reset}
+ {short_b}-h{reset}, {long_b}--help{reset} show this help message and exit
+ {short_b}+f{reset}, {long_b}++foo{reset} {label_b}FOO{reset} foo help
+ '''))
+
def tearDownModule():
# Remove global references to avoid looking like we have refleaks.
diff --git a/Lib/test/test_asdl_parser.py b/Lib/test/test_asdl_parser.py
index 2c198a6b8b2..b9df6568123 100644
--- a/Lib/test/test_asdl_parser.py
+++ b/Lib/test/test_asdl_parser.py
@@ -62,17 +62,17 @@ class TestAsdlParser(unittest.TestCase):
alias = self.types['alias']
self.assertEqual(
str(alias),
- 'Product([Field(identifier, name), Field(identifier, asname, opt=True)], '
+ 'Product([Field(identifier, name), Field(identifier, asname, quantifiers=[OPTIONAL])], '
'[Field(int, lineno), Field(int, col_offset), '
- 'Field(int, end_lineno, opt=True), Field(int, end_col_offset, opt=True)])')
+ 'Field(int, end_lineno, quantifiers=[OPTIONAL]), Field(int, end_col_offset, quantifiers=[OPTIONAL])])')
def test_attributes(self):
stmt = self.types['stmt']
self.assertEqual(len(stmt.attributes), 4)
self.assertEqual(repr(stmt.attributes[0]), 'Field(int, lineno)')
self.assertEqual(repr(stmt.attributes[1]), 'Field(int, col_offset)')
- self.assertEqual(repr(stmt.attributes[2]), 'Field(int, end_lineno, opt=True)')
- self.assertEqual(repr(stmt.attributes[3]), 'Field(int, end_col_offset, opt=True)')
+ self.assertEqual(repr(stmt.attributes[2]), 'Field(int, end_lineno, quantifiers=[OPTIONAL])')
+ self.assertEqual(repr(stmt.attributes[3]), 'Field(int, end_col_offset, quantifiers=[OPTIONAL])')
def test_constructor_fields(self):
ehandler = self.types['excepthandler']
diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py
index ae9db093d2e..cc46529c0ef 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -1,16 +1,20 @@
import _ast_unparse
import ast
import builtins
+import contextlib
import copy
import dis
import enum
+import itertools
import os
import re
import sys
+import tempfile
import textwrap
import types
import unittest
import weakref
+from io import StringIO
from pathlib import Path
from textwrap import dedent
try:
@@ -19,9 +23,10 @@ except ImportError:
_testinternalcapi = None
from test import support
-from test.support import os_helper, script_helper
+from test.support import os_helper
from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow
from test.support.ast_helper import ASTTestMixin
+from test.support.import_helper import ensure_lazy_imports
from test.test_ast.utils import to_tuple
from test.test_ast.snippets import (
eval_tests, eval_results, exec_tests, exec_results, single_tests, single_results
@@ -43,6 +48,12 @@ def ast_repr_update_snapshots() -> None:
AST_REPR_DATA_FILE.write_text("\n".join(data))
+class LazyImportTest(unittest.TestCase):
+ @support.cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("ast", {"contextlib", "enum", "inspect", "re", "collections", "argparse"})
+
+
class AST_Tests(unittest.TestCase):
maxDiff = None
@@ -264,12 +275,12 @@ class AST_Tests(unittest.TestCase):
self.assertEqual(alias.end_col_offset, 17)
def test_base_classes(self):
- self.assertTrue(issubclass(ast.For, ast.stmt))
- self.assertTrue(issubclass(ast.Name, ast.expr))
- self.assertTrue(issubclass(ast.stmt, ast.AST))
- self.assertTrue(issubclass(ast.expr, ast.AST))
- self.assertTrue(issubclass(ast.comprehension, ast.AST))
- self.assertTrue(issubclass(ast.Gt, ast.AST))
+ self.assertIsSubclass(ast.For, ast.stmt)
+ self.assertIsSubclass(ast.Name, ast.expr)
+ self.assertIsSubclass(ast.stmt, ast.AST)
+ self.assertIsSubclass(ast.expr, ast.AST)
+ self.assertIsSubclass(ast.comprehension, ast.AST)
+ self.assertIsSubclass(ast.Gt, ast.AST)
def test_field_attr_existence(self):
for name, item in ast.__dict__.items():
@@ -810,6 +821,17 @@ class AST_Tests(unittest.TestCase):
with self.assertRaisesRegex(ValueError, f"identifier field can't represent '{constant}' constant"):
compile(expr, "<test>", "eval")
+ def test_constant_as_unicode_name(self):
+ constants = [
+ ("True", b"Tru\xe1\xb5\x89"),
+ ("False", b"Fal\xc5\xbfe"),
+ ("None", b"N\xc2\xbane"),
+ ]
+ for constant in constants:
+ with self.assertRaisesRegex(ValueError,
+ f"identifier field can't represent '{constant[0]}' constant"):
+ ast.parse(constant[1], mode="eval")
+
def test_precedence_enum(self):
class _Precedence(enum.IntEnum):
"""Precedence table that originated from python grammar."""
@@ -1079,7 +1101,7 @@ class CopyTests(unittest.TestCase):
def test_replace_interface(self):
for klass in self.iter_ast_classes():
with self.subTest(klass=klass):
- self.assertTrue(hasattr(klass, '__replace__'))
+ self.assertHasAttr(klass, '__replace__')
fields = set(klass._fields)
with self.subTest(klass=klass, fields=fields):
@@ -1293,13 +1315,22 @@ class CopyTests(unittest.TestCase):
self.assertIs(repl.id, 'y')
self.assertIs(repl.ctx, context)
+ def test_replace_accept_missing_field_with_default(self):
+ node = ast.FunctionDef(name="foo", args=ast.arguments())
+ self.assertIs(node.returns, None)
+ self.assertEqual(node.decorator_list, [])
+ node2 = copy.replace(node, name="bar")
+ self.assertEqual(node2.name, "bar")
+ self.assertIs(node2.returns, None)
+ self.assertEqual(node2.decorator_list, [])
+
def test_replace_reject_known_custom_instance_fields_commits(self):
node = ast.parse('x').body[0].value
node.extra = extra = object() # add instance 'extra' field
context = node.ctx
# explicit rejection of known instance fields
- self.assertTrue(hasattr(node, 'extra'))
+ self.assertHasAttr(node, 'extra')
msg = "Name.__replace__ got an unexpected keyword argument 'extra'."
with self.assertRaisesRegex(TypeError, re.escape(msg)):
copy.replace(node, extra=1)
@@ -1341,17 +1372,17 @@ class ASTHelpers_Test(unittest.TestCase):
def test_dump(self):
node = ast.parse('spam(eggs, "and cheese")')
self.assertEqual(ast.dump(node),
- "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), "
- "args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')]))])"
+ "Module(body=[Expr(value=Call(func=Name(id='spam'), "
+ "args=[Name(id='eggs'), Constant(value='and cheese')]))])"
)
self.assertEqual(ast.dump(node, annotate_fields=False),
- "Module([Expr(Call(Name('spam', Load()), [Name('eggs', Load()), "
+ "Module([Expr(Call(Name('spam'), [Name('eggs'), "
"Constant('and cheese')]))])"
)
self.assertEqual(ast.dump(node, include_attributes=True),
- "Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load(), "
+ "Module(body=[Expr(value=Call(func=Name(id='spam', "
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=4), "
- "args=[Name(id='eggs', ctx=Load(), lineno=1, col_offset=5, "
+ "args=[Name(id='eggs', lineno=1, col_offset=5, "
"end_lineno=1, end_col_offset=9), Constant(value='and cheese', "
"lineno=1, col_offset=11, end_lineno=1, end_col_offset=23)], "
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=24), "
@@ -1365,18 +1396,18 @@ Module(
body=[
Expr(
value=Call(
- func=Name(id='spam', ctx=Load()),
+ func=Name(id='spam'),
args=[
- Name(id='eggs', ctx=Load()),
+ Name(id='eggs'),
Constant(value='and cheese')]))])""")
self.assertEqual(ast.dump(node, annotate_fields=False, indent='\t'), """\
Module(
\t[
\t\tExpr(
\t\t\tCall(
-\t\t\t\tName('spam', Load()),
+\t\t\t\tName('spam'),
\t\t\t\t[
-\t\t\t\t\tName('eggs', Load()),
+\t\t\t\t\tName('eggs'),
\t\t\t\t\tConstant('and cheese')]))])""")
self.assertEqual(ast.dump(node, include_attributes=True, indent=3), """\
Module(
@@ -1385,7 +1416,6 @@ Module(
value=Call(
func=Name(
id='spam',
- ctx=Load(),
lineno=1,
col_offset=0,
end_lineno=1,
@@ -1393,7 +1423,6 @@ Module(
args=[
Name(
id='eggs',
- ctx=Load(),
lineno=1,
col_offset=5,
end_lineno=1,
@@ -1423,23 +1452,23 @@ Module(
)
node = ast.Raise(exc=ast.Name(id='e', ctx=ast.Load()), lineno=3, col_offset=4)
self.assertEqual(ast.dump(node),
- "Raise(exc=Name(id='e', ctx=Load()))"
+ "Raise(exc=Name(id='e'))"
)
self.assertEqual(ast.dump(node, annotate_fields=False),
- "Raise(Name('e', Load()))"
+ "Raise(Name('e'))"
)
self.assertEqual(ast.dump(node, include_attributes=True),
- "Raise(exc=Name(id='e', ctx=Load()), lineno=3, col_offset=4)"
+ "Raise(exc=Name(id='e'), lineno=3, col_offset=4)"
)
self.assertEqual(ast.dump(node, annotate_fields=False, include_attributes=True),
- "Raise(Name('e', Load()), lineno=3, col_offset=4)"
+ "Raise(Name('e'), lineno=3, col_offset=4)"
)
node = ast.Raise(cause=ast.Name(id='e', ctx=ast.Load()))
self.assertEqual(ast.dump(node),
- "Raise(cause=Name(id='e', ctx=Load()))"
+ "Raise(cause=Name(id='e'))"
)
self.assertEqual(ast.dump(node, annotate_fields=False),
- "Raise(cause=Name('e', Load()))"
+ "Raise(cause=Name('e'))"
)
# Arguments:
node = ast.arguments(args=[ast.arg("x")])
@@ -1471,10 +1500,10 @@ Module(
[ast.Name('dataclass', ctx=ast.Load())],
)
self.assertEqual(ast.dump(node),
- "ClassDef(name='T', keywords=[keyword(arg='a', value=Constant(value=None))], decorator_list=[Name(id='dataclass', ctx=Load())])",
+ "ClassDef(name='T', keywords=[keyword(arg='a', value=Constant(value=None))], decorator_list=[Name(id='dataclass')])",
)
self.assertEqual(ast.dump(node, annotate_fields=False),
- "ClassDef('T', [], [keyword('a', Constant(None))], [], [Name('dataclass', Load())])",
+ "ClassDef('T', [], [keyword('a', Constant(None))], [], [Name('dataclass')])",
)
def test_dump_show_empty(self):
@@ -1502,7 +1531,7 @@ Module(
check_node(
# Corner case: there are no real `Name` instances with `id=''`:
ast.Name(id='', ctx=ast.Load()),
- empty="Name(id='', ctx=Load())",
+ empty="Name(id='')",
full="Name(id='', ctx=Load())",
)
@@ -1513,39 +1542,63 @@ Module(
)
check_node(
+ ast.MatchSingleton(value=[]),
+ empty="MatchSingleton(value=[])",
+ full="MatchSingleton(value=[])",
+ )
+
+ check_node(
ast.Constant(value=None),
empty="Constant(value=None)",
full="Constant(value=None)",
)
check_node(
+ ast.Constant(value=[]),
+ empty="Constant(value=[])",
+ full="Constant(value=[])",
+ )
+
+ check_node(
ast.Constant(value=''),
empty="Constant(value='')",
full="Constant(value='')",
)
+ check_node(
+ ast.Interpolation(value=ast.Constant(42), str=None, conversion=-1),
+ empty="Interpolation(value=Constant(value=42), str=None, conversion=-1)",
+ full="Interpolation(value=Constant(value=42), str=None, conversion=-1)",
+ )
+
+ check_node(
+ ast.Interpolation(value=ast.Constant(42), str=[], conversion=-1),
+ empty="Interpolation(value=Constant(value=42), str=[], conversion=-1)",
+ full="Interpolation(value=Constant(value=42), str=[], conversion=-1)",
+ )
+
check_text(
"def a(b: int = 0, *, c): ...",
- empty="Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load()))], kwonlyargs=[arg(arg='c')], kw_defaults=[None], defaults=[Constant(value=0)]), body=[Expr(value=Constant(value=Ellipsis))])])",
+ empty="Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int'))], kwonlyargs=[arg(arg='c')], kw_defaults=[None], defaults=[Constant(value=0)]), body=[Expr(value=Constant(value=Ellipsis))])])",
full="Module(body=[FunctionDef(name='a', args=arguments(posonlyargs=[], args=[arg(arg='b', annotation=Name(id='int', ctx=Load()))], kwonlyargs=[arg(arg='c')], kw_defaults=[None], defaults=[Constant(value=0)]), body=[Expr(value=Constant(value=Ellipsis))], decorator_list=[], type_params=[])], type_ignores=[])",
)
check_text(
"def a(b: int = 0, *, c): ...",
- empty="Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', ctx=Load(), lineno=1, col_offset=9, end_lineno=1, end_col_offset=12), lineno=1, col_offset=6, end_lineno=1, end_col_offset=12)], kwonlyargs=[arg(arg='c', lineno=1, col_offset=21, end_lineno=1, end_col_offset=22)], kw_defaults=[None], defaults=[Constant(value=0, lineno=1, col_offset=15, end_lineno=1, end_col_offset=16)]), body=[Expr(value=Constant(value=Ellipsis, lineno=1, col_offset=25, end_lineno=1, end_col_offset=28), lineno=1, col_offset=25, end_lineno=1, end_col_offset=28)], lineno=1, col_offset=0, end_lineno=1, end_col_offset=28)])",
+ empty="Module(body=[FunctionDef(name='a', args=arguments(args=[arg(arg='b', annotation=Name(id='int', lineno=1, col_offset=9, end_lineno=1, end_col_offset=12), lineno=1, col_offset=6, end_lineno=1, end_col_offset=12)], kwonlyargs=[arg(arg='c', lineno=1, col_offset=21, end_lineno=1, end_col_offset=22)], kw_defaults=[None], defaults=[Constant(value=0, lineno=1, col_offset=15, end_lineno=1, end_col_offset=16)]), body=[Expr(value=Constant(value=Ellipsis, lineno=1, col_offset=25, end_lineno=1, end_col_offset=28), lineno=1, col_offset=25, end_lineno=1, end_col_offset=28)], lineno=1, col_offset=0, end_lineno=1, end_col_offset=28)])",
full="Module(body=[FunctionDef(name='a', args=arguments(posonlyargs=[], args=[arg(arg='b', annotation=Name(id='int', ctx=Load(), lineno=1, col_offset=9, end_lineno=1, end_col_offset=12), lineno=1, col_offset=6, end_lineno=1, end_col_offset=12)], kwonlyargs=[arg(arg='c', lineno=1, col_offset=21, end_lineno=1, end_col_offset=22)], kw_defaults=[None], defaults=[Constant(value=0, lineno=1, col_offset=15, end_lineno=1, end_col_offset=16)]), body=[Expr(value=Constant(value=Ellipsis, lineno=1, col_offset=25, end_lineno=1, end_col_offset=28), lineno=1, col_offset=25, end_lineno=1, end_col_offset=28)], decorator_list=[], type_params=[], lineno=1, col_offset=0, end_lineno=1, end_col_offset=28)], type_ignores=[])",
include_attributes=True,
)
check_text(
'spam(eggs, "and cheese")',
- empty="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')]))])",
+ empty="Module(body=[Expr(value=Call(func=Name(id='spam'), args=[Name(id='eggs'), Constant(value='and cheese')]))])",
full="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load()), Constant(value='and cheese')], keywords=[]))], type_ignores=[])",
)
check_text(
'spam(eggs, text="and cheese")',
- empty="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load())], keywords=[keyword(arg='text', value=Constant(value='and cheese'))]))])",
+ empty="Module(body=[Expr(value=Call(func=Name(id='spam'), args=[Name(id='eggs')], keywords=[keyword(arg='text', value=Constant(value='and cheese'))]))])",
full="Module(body=[Expr(value=Call(func=Name(id='spam', ctx=Load()), args=[Name(id='eggs', ctx=Load())], keywords=[keyword(arg='text', value=Constant(value='and cheese'))]))], type_ignores=[])",
)
@@ -1579,12 +1632,12 @@ Module(
self.assertEqual(src, ast.fix_missing_locations(src))
self.maxDiff = None
self.assertEqual(ast.dump(src, include_attributes=True),
- "Module(body=[Expr(value=Call(func=Name(id='write', ctx=Load(), "
+ "Module(body=[Expr(value=Call(func=Name(id='write', "
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=5), "
"args=[Constant(value='spam', lineno=1, col_offset=6, end_lineno=1, "
"end_col_offset=12)], lineno=1, col_offset=0, end_lineno=1, "
"end_col_offset=13), lineno=1, col_offset=0, end_lineno=1, "
- "end_col_offset=13), Expr(value=Call(func=Name(id='spam', ctx=Load(), "
+ "end_col_offset=13), Expr(value=Call(func=Name(id='spam', "
"lineno=1, col_offset=0, end_lineno=1, end_col_offset=0), "
"args=[Constant(value='eggs', lineno=1, col_offset=0, end_lineno=1, "
"end_col_offset=0)], lineno=1, col_offset=0, end_lineno=1, "
@@ -3040,7 +3093,7 @@ class ASTConstructorTests(unittest.TestCase):
with self.assertWarnsRegex(DeprecationWarning,
r"FunctionDef\.__init__ missing 1 required positional argument: 'name'"):
node = ast.FunctionDef(args=args)
- self.assertFalse(hasattr(node, "name"))
+ self.assertNotHasAttr(node, "name")
self.assertEqual(node.decorator_list, [])
node = ast.FunctionDef(name='foo', args=args)
self.assertEqual(node.name, 'foo')
@@ -3232,23 +3285,263 @@ class ModuleStateTests(unittest.TestCase):
self.assertEqual(res, 0)
-class ASTMainTests(unittest.TestCase):
- # Tests `ast.main()` function.
+class CommandLineTests(unittest.TestCase):
+ def setUp(self):
+ self.filename = tempfile.mktemp()
+ self.addCleanup(os_helper.unlink, self.filename)
- def test_cli_file_input(self):
- code = "print(1, 2, 3)"
- expected = ast.dump(ast.parse(code), indent=3)
+ @staticmethod
+ def text_normalize(string):
+ return textwrap.dedent(string).strip()
+
+ def set_source(self, content):
+ Path(self.filename).write_text(self.text_normalize(content))
+
+ def invoke_ast(self, *flags):
+ stderr = StringIO()
+ stdout = StringIO()
+ with (
+ contextlib.redirect_stdout(stdout),
+ contextlib.redirect_stderr(stderr),
+ ):
+ ast.main(args=[*flags, self.filename])
+ self.assertEqual(stderr.getvalue(), '')
+ return stdout.getvalue().strip()
- with os_helper.temp_dir() as tmp_dir:
- filename = os.path.join(tmp_dir, "test_module.py")
- with open(filename, 'w', encoding='utf-8') as f:
- f.write(code)
- res, _ = script_helper.run_python_until_end("-m", "ast", filename)
+ def check_output(self, source, expect, *flags):
+ self.set_source(source)
+ res = self.invoke_ast(*flags)
+ expect = self.text_normalize(expect)
+ self.assertEqual(res, expect)
- self.assertEqual(res.err, b"")
- self.assertEqual(expected.splitlines(),
- res.out.decode("utf8").splitlines())
- self.assertEqual(res.rc, 0)
+ @support.requires_resource('cpu')
+ def test_invocation(self):
+ # test various combinations of parameters
+ base_flags = (
+ ('-m=exec', '--mode=exec'),
+ ('--no-type-comments', '--no-type-comments'),
+ ('-a', '--include-attributes'),
+ ('-i=4', '--indent=4'),
+ ('--feature-version=3.13', '--feature-version=3.13'),
+ ('-O=-1', '--optimize=-1'),
+ ('--show-empty', '--show-empty'),
+ )
+ self.set_source('''
+ print(1, 2, 3)
+ def f(x: int) -> int:
+ x -= 1
+ return x
+ ''')
+
+ for r in range(1, len(base_flags) + 1):
+ for choices in itertools.combinations(base_flags, r=r):
+ for args in itertools.product(*choices):
+ with self.subTest(flags=args):
+ self.invoke_ast(*args)
+
+ @support.force_not_colorized
+ def test_help_message(self):
+ for flag in ('-h', '--help', '--unknown'):
+ with self.subTest(flag=flag):
+ output = StringIO()
+ with self.assertRaises(SystemExit):
+ with contextlib.redirect_stderr(output):
+ ast.main(args=flag)
+ self.assertStartsWith(output.getvalue(), 'usage: ')
+
+ def test_exec_mode_flag(self):
+ # test 'python -m ast -m/--mode exec'
+ source = 'x: bool = 1 # type: ignore[assignment]'
+ expect = '''
+ Module(
+ body=[
+ AnnAssign(
+ target=Name(id='x', ctx=Store()),
+ annotation=Name(id='bool'),
+ value=Constant(value=1),
+ simple=1)],
+ type_ignores=[
+ TypeIgnore(lineno=1, tag='[assignment]')])
+ '''
+ for flag in ('-m=exec', '--mode=exec'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_single_mode_flag(self):
+ # test 'python -m ast -m/--mode single'
+ source = 'pass'
+ expect = '''
+ Interactive(
+ body=[
+ Pass()])
+ '''
+ for flag in ('-m=single', '--mode=single'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_eval_mode_flag(self):
+ # test 'python -m ast -m/--mode eval'
+ source = 'print(1, 2, 3)'
+ expect = '''
+ Expression(
+ body=Call(
+ func=Name(id='print'),
+ args=[
+ Constant(value=1),
+ Constant(value=2),
+ Constant(value=3)]))
+ '''
+ for flag in ('-m=eval', '--mode=eval'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_func_type_mode_flag(self):
+ # test 'python -m ast -m/--mode func_type'
+ source = '(int, str) -> list[int]'
+ expect = '''
+ FunctionType(
+ argtypes=[
+ Name(id='int'),
+ Name(id='str')],
+ returns=Subscript(
+ value=Name(id='list'),
+ slice=Name(id='int')))
+ '''
+ for flag in ('-m=func_type', '--mode=func_type'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_no_type_comments_flag(self):
+ # test 'python -m ast --no-type-comments'
+ source = 'x: bool = 1 # type: ignore[assignment]'
+ expect = '''
+ Module(
+ body=[
+ AnnAssign(
+ target=Name(id='x', ctx=Store()),
+ annotation=Name(id='bool'),
+ value=Constant(value=1),
+ simple=1)])
+ '''
+ self.check_output(source, expect, '--no-type-comments')
+
+ def test_include_attributes_flag(self):
+ # test 'python -m ast -a/--include-attributes'
+ source = 'pass'
+ expect = '''
+ Module(
+ body=[
+ Pass(
+ lineno=1,
+ col_offset=0,
+ end_lineno=1,
+ end_col_offset=4)])
+ '''
+ for flag in ('-a', '--include-attributes'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_indent_flag(self):
+ # test 'python -m ast -i/--indent 0'
+ source = 'pass'
+ expect = '''
+ Module(
+ body=[
+ Pass()])
+ '''
+ for flag in ('-i=0', '--indent=0'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_feature_version_flag(self):
+ # test 'python -m ast --feature-version 3.9/3.10'
+ source = '''
+ match x:
+ case 1:
+ pass
+ '''
+ expect = '''
+ Module(
+ body=[
+ Match(
+ subject=Name(id='x'),
+ cases=[
+ match_case(
+ pattern=MatchValue(
+ value=Constant(value=1)),
+ body=[
+ Pass()])])])
+ '''
+ self.check_output(source, expect, '--feature-version=3.10')
+ with self.assertRaises(SyntaxError):
+ self.invoke_ast('--feature-version=3.9')
+
+ def test_no_optimize_flag(self):
+ # test 'python -m ast -O/--optimize -1/0'
+ source = '''
+ match a:
+ case 1+2j:
+ pass
+ '''
+ expect = '''
+ Module(
+ body=[
+ Match(
+ subject=Name(id='a'),
+ cases=[
+ match_case(
+ pattern=MatchValue(
+ value=BinOp(
+ left=Constant(value=1),
+ op=Add(),
+ right=Constant(value=2j))),
+ body=[
+ Pass()])])])
+ '''
+ for flag in ('-O=-1', '--optimize=-1', '-O=0', '--optimize=0'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_optimize_flag(self):
+ # test 'python -m ast -O/--optimize 1/2'
+ source = '''
+ match a:
+ case 1+2j:
+ pass
+ '''
+ expect = '''
+ Module(
+ body=[
+ Match(
+ subject=Name(id='a'),
+ cases=[
+ match_case(
+ pattern=MatchValue(
+ value=Constant(value=(1+2j))),
+ body=[
+ Pass()])])])
+ '''
+ for flag in ('-O=1', '--optimize=1', '-O=2', '--optimize=2'):
+ with self.subTest(flag=flag):
+ self.check_output(source, expect, flag)
+
+ def test_show_empty_flag(self):
+ # test 'python -m ast --show-empty'
+ source = 'print(1, 2, 3)'
+ expect = '''
+ Module(
+ body=[
+ Expr(
+ value=Call(
+ func=Name(id='print', ctx=Load()),
+ args=[
+ Constant(value=1),
+ Constant(value=2),
+ Constant(value=3)],
+ keywords=[]))],
+ type_ignores=[])
+ '''
+ self.check_output(source, expect, '--show-empty')
class ASTOptimiziationTests(unittest.TestCase):
diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py
index 2c44647bf3e..636cb33dd98 100644
--- a/Lib/test/test_asyncgen.py
+++ b/Lib/test/test_asyncgen.py
@@ -2021,6 +2021,15 @@ class TestUnawaitedWarnings(unittest.TestCase):
g.athrow(RuntimeError)
gc_collect()
+ def test_athrow_throws_immediately(self):
+ async def gen():
+ yield 1
+
+ g = gen()
+ msg = "athrow expected at least 1 argument, got 0"
+ with self.assertRaisesRegex(TypeError, msg):
+ g.athrow()
+
def test_aclose(self):
async def gen():
yield 1
diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py
index 2ca5c4c6719..12179eb0c9e 100644
--- a/Lib/test/test_asyncio/test_base_events.py
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -24,6 +24,10 @@ import warnings
MOCK_ANY = mock.ANY
+class CustomError(Exception):
+ pass
+
+
def tearDownModule():
asyncio._set_event_loop_policy(None)
@@ -1190,6 +1194,36 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
self.loop.run_until_complete(coro)
self.assertTrue(sock.close.called)
+ @patch_socket
+ def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket):
+ # See gh-135836: Fix IndexError when Happy Eyeballs algorithm
+ # results in empty exceptions list
+
+ async def getaddrinfo(*args, **kw):
+ return [(socket.AF_INET, socket.SOCK_STREAM, 0, '', ('127.0.0.1', 80)),
+ (socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('::1', 80))]
+
+ def getaddrinfo_task(*args, **kwds):
+ return self.loop.create_task(getaddrinfo(*args, **kwds))
+
+ self.loop.getaddrinfo = getaddrinfo_task
+
+ # Mock staggered_race to return empty exceptions list
+ # This simulates the scenario where Happy Eyeballs algorithm
+ # cancels all attempts but doesn't properly collect exceptions
+ with mock.patch('asyncio.staggered.staggered_race') as mock_staggered:
+ # Return (None, []) - no winner, empty exceptions list
+ async def mock_race(coro_fns, delay, loop):
+ return None, []
+ mock_staggered.side_effect = mock_race
+
+ coro = self.loop.create_connection(
+ MyProto, 'example.com', 80, happy_eyeballs_delay=0.1)
+
+ # Should raise TimeoutError instead of IndexError
+ with self.assertRaisesRegex(TimeoutError, "create_connection failed"):
+ self.loop.run_until_complete(coro)
+
def test_create_connection_host_port_sock(self):
coro = self.loop.create_connection(
MyProto, 'example.com', 80, sock=object())
@@ -1296,6 +1330,31 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
self.assertEqual(len(cm.exception.exceptions), 1)
self.assertIsInstance(cm.exception.exceptions[0], OSError)
+ @patch_socket
+ def test_create_connection_connect_non_os_err_close_err(self, m_socket):
+ # Test the case when sock_connect() raises non-OSError exception
+ # and sock.close() raises OSError.
+ async def getaddrinfo(*args, **kw):
+ return [(2, 1, 6, '', ('107.6.106.82', 80))]
+
+ def getaddrinfo_task(*args, **kwds):
+ return self.loop.create_task(getaddrinfo(*args, **kwds))
+
+ self.loop.getaddrinfo = getaddrinfo_task
+ self.loop.sock_connect = mock.Mock()
+ self.loop.sock_connect.side_effect = CustomError
+ sock = mock.Mock()
+ m_socket.socket.return_value = sock
+ sock.close.side_effect = OSError
+
+ coro = self.loop.create_connection(MyProto, 'example.com', 80)
+ self.assertRaises(
+ CustomError, self.loop.run_until_complete, coro)
+
+ coro = self.loop.create_connection(MyProto, 'example.com', 80, all_errors=True)
+ self.assertRaises(
+ CustomError, self.loop.run_until_complete, coro)
+
def test_create_connection_multiple(self):
async def getaddrinfo(*args, **kw):
return [(2, 1, 6, '', ('0.0.0.1', 80)),
diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py b/Lib/test/test_asyncio/test_eager_task_factory.py
index a2fb1022ae4..9f3b6f9acef 100644
--- a/Lib/test/test_asyncio/test_eager_task_factory.py
+++ b/Lib/test/test_asyncio/test_eager_task_factory.py
@@ -263,6 +263,24 @@ class EagerTaskFactoryLoopTests:
self.run_coro(run())
+ def test_eager_start_false(self):
+ name = None
+
+ async def asyncfn():
+ nonlocal name
+ name = asyncio.current_task().get_name()
+
+ async def main():
+ t = asyncio.get_running_loop().create_task(
+ asyncfn(), eager_start=False, name="example"
+ )
+ self.assertFalse(t.done())
+ self.assertIsNone(name)
+ await t
+ self.assertEqual(name, "example")
+
+ self.run_coro(main())
+
class PyEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase):
Task = tasks._PyTask
@@ -505,5 +523,24 @@ class EagerCTaskTests(BaseEagerTaskFactoryTests, test_utils.TestCase):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
return super().tearDown()
+
+class DefaultTaskFactoryEagerStart(test_utils.TestCase):
+ def test_eager_start_true_with_default_factory(self):
+ name = None
+
+ async def asyncfn():
+ nonlocal name
+ name = asyncio.current_task().get_name()
+
+ async def main():
+ t = asyncio.get_running_loop().create_task(
+ asyncfn(), eager_start=True, name="example"
+ )
+ self.assertTrue(t.done())
+ self.assertEqual(name, "example")
+ await t
+
+ asyncio.run(main(), loop_factory=asyncio.EventLoop)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py
index 8b51522278a..39bef465bdb 100644
--- a/Lib/test/test_asyncio/test_futures.py
+++ b/Lib/test/test_asyncio/test_futures.py
@@ -413,7 +413,7 @@ class BaseFutureTests:
def test_copy_state(self):
from asyncio.futures import _copy_future_state
- f = self._new_future(loop=self.loop)
+ f = concurrent.futures.Future()
f.set_result(10)
newf = self._new_future(loop=self.loop)
@@ -421,7 +421,7 @@ class BaseFutureTests:
self.assertTrue(newf.done())
self.assertEqual(newf.result(), 10)
- f_exception = self._new_future(loop=self.loop)
+ f_exception = concurrent.futures.Future()
f_exception.set_exception(RuntimeError())
newf_exception = self._new_future(loop=self.loop)
@@ -429,7 +429,7 @@ class BaseFutureTests:
self.assertTrue(newf_exception.done())
self.assertRaises(RuntimeError, newf_exception.result)
- f_cancelled = self._new_future(loop=self.loop)
+ f_cancelled = concurrent.futures.Future()
f_cancelled.cancel()
newf_cancelled = self._new_future(loop=self.loop)
@@ -441,7 +441,7 @@ class BaseFutureTests:
except BaseException as e:
f_exc = e
- f_conexc = self._new_future(loop=self.loop)
+ f_conexc = concurrent.futures.Future()
f_conexc.set_exception(f_exc)
newf_conexc = self._new_future(loop=self.loop)
@@ -454,6 +454,56 @@ class BaseFutureTests:
newf_tb = ''.join(traceback.format_tb(newf_exc.__traceback__))
self.assertEqual(newf_tb.count('raise concurrent.futures.InvalidStateError'), 1)
+ def test_copy_state_from_concurrent_futures(self):
+ """Test _copy_future_state from concurrent.futures.Future.
+
+ This tests the optimized path using _get_snapshot when available.
+ """
+ from asyncio.futures import _copy_future_state
+
+ # Test with a result
+ f_concurrent = concurrent.futures.Future()
+ f_concurrent.set_result(42)
+ f_asyncio = self._new_future(loop=self.loop)
+ _copy_future_state(f_concurrent, f_asyncio)
+ self.assertTrue(f_asyncio.done())
+ self.assertEqual(f_asyncio.result(), 42)
+
+ # Test with an exception
+ f_concurrent_exc = concurrent.futures.Future()
+ f_concurrent_exc.set_exception(ValueError("test exception"))
+ f_asyncio_exc = self._new_future(loop=self.loop)
+ _copy_future_state(f_concurrent_exc, f_asyncio_exc)
+ self.assertTrue(f_asyncio_exc.done())
+ with self.assertRaises(ValueError) as cm:
+ f_asyncio_exc.result()
+ self.assertEqual(str(cm.exception), "test exception")
+
+ # Test with cancelled state
+ f_concurrent_cancelled = concurrent.futures.Future()
+ f_concurrent_cancelled.cancel()
+ f_asyncio_cancelled = self._new_future(loop=self.loop)
+ _copy_future_state(f_concurrent_cancelled, f_asyncio_cancelled)
+ self.assertTrue(f_asyncio_cancelled.cancelled())
+
+ # Test that destination already cancelled prevents copy
+ f_concurrent_result = concurrent.futures.Future()
+ f_concurrent_result.set_result(10)
+ f_asyncio_precancelled = self._new_future(loop=self.loop)
+ f_asyncio_precancelled.cancel()
+ _copy_future_state(f_concurrent_result, f_asyncio_precancelled)
+ self.assertTrue(f_asyncio_precancelled.cancelled())
+
+ # Test exception type conversion
+ f_concurrent_invalid = concurrent.futures.Future()
+ f_concurrent_invalid.set_exception(concurrent.futures.InvalidStateError("invalid"))
+ f_asyncio_invalid = self._new_future(loop=self.loop)
+ _copy_future_state(f_concurrent_invalid, f_asyncio_invalid)
+ self.assertTrue(f_asyncio_invalid.done())
+ with self.assertRaises(asyncio.exceptions.InvalidStateError) as cm:
+ f_asyncio_invalid.result()
+ self.assertEqual(str(cm.exception), "invalid")
+
def test_iter(self):
fut = self._new_future(loop=self.loop)
diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py
index de81936b745..aab6a779170 100644
--- a/Lib/test/test_asyncio/test_selector_events.py
+++ b/Lib/test/test_asyncio/test_selector_events.py
@@ -347,6 +347,18 @@ class BaseSelectorEventLoopTests(test_utils.TestCase):
selectors.EVENT_WRITE)])
self.loop._remove_writer.assert_called_with(1)
+ def test_accept_connection_zero_one(self):
+ for backlog in [0, 1]:
+ sock = mock.Mock()
+ sock.accept.return_value = (mock.Mock(), mock.Mock())
+ with self.subTest(backlog):
+ mock_obj = mock.patch.object
+ with mock_obj(self.loop, '_accept_connection2') as accept2_mock:
+ self.loop._accept_connection(
+ mock.Mock(), sock, backlog=backlog)
+ self.loop.run_until_complete(asyncio.sleep(0))
+ self.assertEqual(sock.accept.call_count, backlog + 1)
+
def test_accept_connection_multiple(self):
sock = mock.Mock()
sock.accept.return_value = (mock.Mock(), mock.Mock())
@@ -362,7 +374,7 @@ class BaseSelectorEventLoopTests(test_utils.TestCase):
self.loop._accept_connection(
mock.Mock(), sock, backlog=backlog)
self.loop.run_until_complete(asyncio.sleep(0))
- self.assertEqual(sock.accept.call_count, backlog)
+ self.assertEqual(sock.accept.call_count, backlog + 1)
def test_accept_connection_skip_connectionabortederror(self):
sock = mock.Mock()
@@ -388,7 +400,7 @@ class BaseSelectorEventLoopTests(test_utils.TestCase):
# as in test_accept_connection_multiple avoid task pending
# warnings by using asyncio.sleep(0)
self.loop.run_until_complete(asyncio.sleep(0))
- self.assertEqual(sock.accept.call_count, backlog)
+ self.assertEqual(sock.accept.call_count, backlog + 1)
class SelectorTransportTests(test_utils.TestCase):
diff --git a/Lib/test/test_asyncio/test_ssl.py b/Lib/test/test_asyncio/test_ssl.py
index 986ecc2c5a9..3a7185cd897 100644
--- a/Lib/test/test_asyncio/test_ssl.py
+++ b/Lib/test/test_asyncio/test_ssl.py
@@ -195,9 +195,10 @@ class TestSSL(test_utils.TestCase):
except (BrokenPipeError, ConnectionError):
pass
- def test_create_server_ssl_1(self):
+ @support.bigmemtest(size=25, memuse=90*2**20, dry_run=False)
+ def test_create_server_ssl_1(self, size):
CNT = 0 # number of clients that were successful
- TOTAL_CNT = 25 # total number of clients that test will create
+ TOTAL_CNT = size # total number of clients that test will create
TIMEOUT = support.LONG_TIMEOUT # timeout for this test
A_DATA = b'A' * 1024 * BUF_MULTIPLIER
@@ -1038,9 +1039,10 @@ class TestSSL(test_utils.TestCase):
self.loop.run_until_complete(run_main())
- def test_create_server_ssl_over_ssl(self):
+ @support.bigmemtest(size=25, memuse=90*2**20, dry_run=False)
+ def test_create_server_ssl_over_ssl(self, size):
CNT = 0 # number of clients that were successful
- TOTAL_CNT = 25 # total number of clients that test will create
+ TOTAL_CNT = size # total number of clients that test will create
TIMEOUT = support.LONG_TIMEOUT # timeout for this test
A_DATA = b'A' * 1024 * BUF_MULTIPLIER
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
index 8d7f1733454..f6f976f213a 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -89,8 +89,8 @@ class BaseTaskTests:
Future = None
all_tasks = None
- def new_task(self, loop, coro, name='TestTask', context=None):
- return self.__class__.Task(coro, loop=loop, name=name, context=context)
+ def new_task(self, loop, coro, name='TestTask', context=None, eager_start=None):
+ return self.__class__.Task(coro, loop=loop, name=name, context=context, eager_start=eager_start)
def new_future(self, loop):
return self.__class__.Future(loop=loop)
@@ -2116,6 +2116,46 @@ class BaseTaskTests:
self.assertTrue(outer.cancelled())
self.assertEqual(0, 0 if outer._callbacks is None else len(outer._callbacks))
+ def test_shield_cancel_outer_result(self):
+ mock_handler = mock.Mock()
+ self.loop.set_exception_handler(mock_handler)
+ inner = self.new_future(self.loop)
+ outer = asyncio.shield(inner)
+ test_utils.run_briefly(self.loop)
+ outer.cancel()
+ test_utils.run_briefly(self.loop)
+ inner.set_result(1)
+ test_utils.run_briefly(self.loop)
+ mock_handler.assert_not_called()
+
+ def test_shield_cancel_outer_exception(self):
+ mock_handler = mock.Mock()
+ self.loop.set_exception_handler(mock_handler)
+ inner = self.new_future(self.loop)
+ outer = asyncio.shield(inner)
+ test_utils.run_briefly(self.loop)
+ outer.cancel()
+ test_utils.run_briefly(self.loop)
+ inner.set_exception(Exception('foo'))
+ test_utils.run_briefly(self.loop)
+ mock_handler.assert_called_once()
+
+ def test_shield_duplicate_log_once(self):
+ mock_handler = mock.Mock()
+ self.loop.set_exception_handler(mock_handler)
+ inner = self.new_future(self.loop)
+ outer = asyncio.shield(inner)
+ test_utils.run_briefly(self.loop)
+ outer.cancel()
+ test_utils.run_briefly(self.loop)
+ outer = asyncio.shield(inner)
+ test_utils.run_briefly(self.loop)
+ outer.cancel()
+ test_utils.run_briefly(self.loop)
+ inner.set_exception(Exception('foo'))
+ test_utils.run_briefly(self.loop)
+ mock_handler.assert_called_once()
+
def test_shield_shortcut(self):
fut = self.new_future(self.loop)
fut.set_result(42)
@@ -2686,6 +2726,35 @@ class BaseTaskTests:
self.assertEqual([None, 1, 2], ret)
+ def test_eager_start_true(self):
+ name = None
+
+ async def asyncfn():
+ nonlocal name
+ name = self.current_task().get_name()
+
+ async def main():
+ t = self.new_task(coro=asyncfn(), loop=asyncio.get_running_loop(), eager_start=True, name="example")
+ self.assertTrue(t.done())
+ self.assertEqual(name, "example")
+ await t
+
+ def test_eager_start_false(self):
+ name = None
+
+ async def asyncfn():
+ nonlocal name
+ name = self.current_task().get_name()
+
+ async def main():
+ t = self.new_task(coro=asyncfn(), loop=asyncio.get_running_loop(), eager_start=False, name="example")
+ self.assertFalse(t.done())
+ self.assertIsNone(name)
+ await t
+ self.assertEqual(name, "example")
+
+ asyncio.run(main(), loop_factory=asyncio.EventLoop)
+
def test_get_coro(self):
loop = asyncio.new_event_loop()
coro = coroutine_function()
diff --git a/Lib/test/test_asyncio/test_tools.py b/Lib/test/test_asyncio/test_tools.py
new file mode 100644
index 00000000000..34e94830204
--- /dev/null
+++ b/Lib/test/test_asyncio/test_tools.py
@@ -0,0 +1,1706 @@
+import unittest
+
+from asyncio import tools
+
+from collections import namedtuple
+
+FrameInfo = namedtuple('FrameInfo', ['funcname', 'filename', 'lineno'])
+CoroInfo = namedtuple('CoroInfo', ['call_stack', 'task_name'])
+TaskInfo = namedtuple('TaskInfo', ['task_id', 'task_name', 'coroutine_stack', 'awaited_by'])
+AwaitedInfo = namedtuple('AwaitedInfo', ['thread_id', 'awaited_by'])
+
+
+# mock output of get_all_awaited_by function.
+TEST_INPUTS_TREE = [
+ [
+ # test case containing a task called timer being awaited in two
+ # different subtasks part of a TaskGroup (root1 and root2) which call
+ # awaiter functions.
+ (
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="timer",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "/path/to/app.py", 130),
+ FrameInfo("awaiter2", "/path/to/app.py", 120),
+ FrameInfo("awaiter", "/path/to/app.py", 110)
+ ],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiterB3", "/path/to/app.py", 190),
+ FrameInfo("awaiterB2", "/path/to/app.py", 180),
+ FrameInfo("awaiterB", "/path/to/app.py", 170)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiterB3", "/path/to/app.py", 190),
+ FrameInfo("awaiterB2", "/path/to/app.py", 180),
+ FrameInfo("awaiterB", "/path/to/app.py", 170)
+ ],
+ task_name=6
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "/path/to/app.py", 130),
+ FrameInfo("awaiter2", "/path/to/app.py", 120),
+ FrameInfo("awaiter", "/path/to/app.py", 110)
+ ],
+ task_name=7
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="root1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=9,
+ task_name="root2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="child1_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="child2_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="child1_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="child2_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ),
+ (
+ [
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " └── __aexit__",
+ " └── _aexit",
+ " ├── (T) root1",
+ " │ └── bloch",
+ " │ └── blocho_caller",
+ " │ └── __aexit__",
+ " │ └── _aexit",
+ " │ ├── (T) child1_1",
+ " │ │ └── awaiter /path/to/app.py:110",
+ " │ │ └── awaiter2 /path/to/app.py:120",
+ " │ │ └── awaiter3 /path/to/app.py:130",
+ " │ │ └── (T) timer",
+ " │ └── (T) child2_1",
+ " │ └── awaiterB /path/to/app.py:170",
+ " │ └── awaiterB2 /path/to/app.py:180",
+ " │ └── awaiterB3 /path/to/app.py:190",
+ " │ └── (T) timer",
+ " └── (T) root2",
+ " └── bloch",
+ " └── blocho_caller",
+ " └── __aexit__",
+ " └── _aexit",
+ " ├── (T) child1_2",
+ " │ └── awaiter /path/to/app.py:110",
+ " │ └── awaiter2 /path/to/app.py:120",
+ " │ └── awaiter3 /path/to/app.py:130",
+ " │ └── (T) timer",
+ " └── (T) child2_2",
+ " └── awaiterB /path/to/app.py:170",
+ " └── awaiterB2 /path/to/app.py:180",
+ " └── awaiterB3 /path/to/app.py:190",
+ " └── (T) timer",
+ ]
+ ]
+ ),
+ ],
+ [
+ # test case containing two roots
+ (
+ AwaitedInfo(
+ thread_id=9,
+ awaited_by=[
+ TaskInfo(
+ task_id=5,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-6",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-7",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="Task-8",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(
+ thread_id=10,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=11, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ),
+ (
+ [
+ [
+ "└── (T) Task-5",
+ " └── main2",
+ " ├── (T) Task-6",
+ " ├── (T) Task-7",
+ " └── (T) Task-8",
+ ],
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " ├── (T) Task-2",
+ " ├── (T) Task-3",
+ " └── (T) Task-4",
+ ],
+ ]
+ ),
+ ],
+ [
+ # test case containing two roots, one of them without subtasks
+ (
+ [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ ),
+ AwaitedInfo(
+ thread_id=3,
+ awaited_by=[
+ TaskInfo(
+ task_id=4,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=8, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ]
+ ),
+ (
+ [
+ ["└── (T) Task-5"],
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " ├── (T) Task-2",
+ " ├── (T) Task-3",
+ " └── (T) Task-4",
+ ],
+ ]
+ ),
+ ],
+]
+
+TEST_INPUTS_CYCLES_TREE = [
+ [
+ # this test case contains a cycle: two tasks awaiting each other.
+ (
+ [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="a",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter2", "", 0)],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="b",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ]
+ ),
+ ([[4, 3, 4]]),
+ ],
+ [
+ # this test case contains two cycles
+ (
+ [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="B",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_c", "", 0)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_a", "", 0)
+ ],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="C",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0)
+ ],
+ task_name=6
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ]
+ ),
+ ([[4, 3, 4], [4, 6, 5, 4]]),
+ ],
+]
+
+TEST_INPUTS_TABLE = [
+ [
+ # test case containing a task called timer being awaited in two
+ # different subtasks part of a TaskGroup (root1 and root2) which call
+ # awaiter functions.
+ (
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="timer",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "", 0),
+ FrameInfo("awaiter2", "", 0),
+ FrameInfo("awaiter", "", 0)
+ ],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter1_3", "", 0),
+ FrameInfo("awaiter1_2", "", 0),
+ FrameInfo("awaiter1", "", 0)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter1_3", "", 0),
+ FrameInfo("awaiter1_2", "", 0),
+ FrameInfo("awaiter1", "", 0)
+ ],
+ task_name=6
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "", 0),
+ FrameInfo("awaiter2", "", 0),
+ FrameInfo("awaiter", "", 0)
+ ],
+ task_name=7
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="root1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=9,
+ task_name="root2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="child1_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="child2_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="child1_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="child2_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ),
+ (
+ [
+ [1, "0x2", "Task-1", "", "", "", "0x0"],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "",
+ "awaiter3 -> awaiter2 -> awaiter",
+ "child1_1",
+ "0x4",
+ ],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "",
+ "awaiter1_3 -> awaiter1_2 -> awaiter1",
+ "child2_2",
+ "0x5",
+ ],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "",
+ "awaiter1_3 -> awaiter1_2 -> awaiter1",
+ "child2_1",
+ "0x6",
+ ],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "",
+ "awaiter3 -> awaiter2 -> awaiter",
+ "child1_2",
+ "0x7",
+ ],
+ [
+ 1,
+ "0x8",
+ "root1",
+ "",
+ "_aexit -> __aexit__ -> main",
+ "Task-1",
+ "0x2",
+ ],
+ [
+ 1,
+ "0x9",
+ "root2",
+ "",
+ "_aexit -> __aexit__ -> main",
+ "Task-1",
+ "0x2",
+ ],
+ [
+ 1,
+ "0x4",
+ "child1_1",
+ "",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root1",
+ "0x8",
+ ],
+ [
+ 1,
+ "0x6",
+ "child2_1",
+ "",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root1",
+ "0x8",
+ ],
+ [
+ 1,
+ "0x7",
+ "child1_2",
+ "",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root2",
+ "0x9",
+ ],
+ [
+ 1,
+ "0x5",
+ "child2_2",
+ "",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root2",
+ "0x9",
+ ],
+ ]
+ ),
+ ],
+ [
+ # test case containing two roots
+ (
+ AwaitedInfo(
+ thread_id=9,
+ awaited_by=[
+ TaskInfo(
+ task_id=5,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-6",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-7",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="Task-8",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(
+ thread_id=10,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=11, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ),
+ (
+ [
+ [9, "0x5", "Task-5", "", "", "", "0x0"],
+ [9, "0x6", "Task-6", "", "main2", "Task-5", "0x5"],
+ [9, "0x7", "Task-7", "", "main2", "Task-5", "0x5"],
+ [9, "0x8", "Task-8", "", "main2", "Task-5", "0x5"],
+ [10, "0x1", "Task-1", "", "", "", "0x0"],
+ [10, "0x2", "Task-2", "", "main", "Task-1", "0x1"],
+ [10, "0x3", "Task-3", "", "main", "Task-1", "0x1"],
+ [10, "0x4", "Task-4", "", "main", "Task-1", "0x1"],
+ ]
+ ),
+ ],
+ [
+ # test case containing two roots, one of them without subtasks
+ (
+ [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ ),
+ AwaitedInfo(
+ thread_id=3,
+ awaited_by=[
+ TaskInfo(
+ task_id=4,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=8, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ]
+ ),
+ (
+ [
+ [1, "0x2", "Task-5", "", "", "", "0x0"],
+ [3, "0x4", "Task-1", "", "", "", "0x0"],
+ [3, "0x5", "Task-2", "", "main", "Task-1", "0x4"],
+ [3, "0x6", "Task-3", "", "main", "Task-1", "0x4"],
+ [3, "0x7", "Task-4", "", "main", "Task-1", "0x4"],
+ ]
+ ),
+ ],
+ # CASES WITH CYCLES
+ [
+ # this test case contains a cycle: two tasks awaiting each other.
+ (
+ [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="a",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter2", "", 0)],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="b",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ]
+ ),
+ (
+ [
+ [1, "0x2", "Task-1", "", "", "", "0x0"],
+ [1, "0x3", "a", "", "awaiter2", "b", "0x4"],
+ [1, "0x3", "a", "", "main", "Task-1", "0x2"],
+ [1, "0x4", "b", "", "awaiter", "a", "0x3"],
+ ]
+ ),
+ ],
+ [
+ # this test case contains two cycles
+ (
+ [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="B",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_c", "", 0)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_a", "", 0)
+ ],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="C",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0)
+ ],
+ task_name=6
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=0, awaited_by=[])
+ ]
+ ),
+ (
+ [
+ [1, "0x2", "Task-1", "", "", "", "0x0"],
+ [
+ 1,
+ "0x3",
+ "A",
+ "",
+ "nested -> nested -> task_b",
+ "B",
+ "0x4",
+ ],
+ [
+ 1,
+ "0x4",
+ "B",
+ "",
+ "nested -> nested -> task_c",
+ "C",
+ "0x5",
+ ],
+ [
+ 1,
+ "0x4",
+ "B",
+ "",
+ "nested -> nested -> task_a",
+ "A",
+ "0x3",
+ ],
+ [
+ 1,
+ "0x5",
+ "C",
+ "",
+ "nested -> nested",
+ "Task-2",
+ "0x6",
+ ],
+ [
+ 1,
+ "0x6",
+ "Task-2",
+ "",
+ "nested -> nested -> task_b",
+ "B",
+ "0x4",
+ ],
+ ]
+ ),
+ ],
+]
+
+
+class TestAsyncioToolsTree(unittest.TestCase):
+ def test_asyncio_utils(self):
+ for input_, tree in TEST_INPUTS_TREE:
+ with self.subTest(input_):
+ result = tools.build_async_tree(input_)
+ self.assertEqual(result, tree)
+
+ def test_asyncio_utils_cycles(self):
+ for input_, cycles in TEST_INPUTS_CYCLES_TREE:
+ with self.subTest(input_):
+ try:
+ tools.build_async_tree(input_)
+ except tools.CycleFoundException as e:
+ self.assertEqual(e.cycles, cycles)
+
+
+class TestAsyncioToolsTable(unittest.TestCase):
+ def test_asyncio_utils(self):
+ for input_, table in TEST_INPUTS_TABLE:
+ with self.subTest(input_):
+ result = tools.build_task_table(input_)
+ self.assertEqual(result, table)
+
+
+class TestAsyncioToolsBasic(unittest.TestCase):
+ def test_empty_input_tree(self):
+ """Test build_async_tree with empty input."""
+ result = []
+ expected_output = []
+ self.assertEqual(tools.build_async_tree(result), expected_output)
+
+ def test_empty_input_table(self):
+ """Test build_task_table with empty input."""
+ result = []
+ expected_output = []
+ self.assertEqual(tools.build_task_table(result), expected_output)
+
+ def test_only_independent_tasks_tree(self):
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=10,
+ task_name="taskA",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=11,
+ task_name="taskB",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ expected = [["└── (T) taskA"], ["└── (T) taskB"]]
+ result = tools.build_async_tree(input_)
+ self.assertEqual(sorted(result), sorted(expected))
+
+ def test_only_independent_tasks_table(self):
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=10,
+ task_name="taskA",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=11,
+ task_name="taskB",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ self.assertEqual(
+ tools.build_task_table(input_),
+ [[1, '0xa', 'taskA', '', '', '', '0x0'], [1, '0xb', 'taskB', '', '', '', '0x0']]
+ )
+
+ def test_single_task_tree(self):
+ """Test build_async_tree with a single task and no awaits."""
+ result = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ expected_output = [
+ [
+ "└── (T) Task-1",
+ ]
+ ]
+ self.assertEqual(tools.build_async_tree(result), expected_output)
+
+ def test_single_task_table(self):
+ """Test build_task_table with a single task and no awaits."""
+ result = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ expected_output = [[1, '0x2', 'Task-1', '', '', '', '0x0']]
+ self.assertEqual(tools.build_task_table(result), expected_output)
+
+ def test_cycle_detection(self):
+ """Test build_async_tree raises CycleFoundException for cyclic input."""
+ result = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ )
+ ]
+ )
+ ]
+ with self.assertRaises(tools.CycleFoundException) as context:
+ tools.build_async_tree(result)
+ self.assertEqual(context.exception.cycles, [[3, 2, 3]])
+
+ def test_complex_tree(self):
+ """Test build_async_tree with a more complex tree structure."""
+ result = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
+ )
+ ]
+ expected_output = [
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " └── (T) Task-2",
+ " └── main",
+ " └── (T) Task-3",
+ ]
+ ]
+ self.assertEqual(tools.build_async_tree(result), expected_output)
+
+ def test_complex_table(self):
+ """Test build_task_table with a more complex tree structure."""
+ result = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
+ )
+ ]
+ expected_output = [
+ [1, '0x2', 'Task-1', '', '', '', '0x0'],
+ [1, '0x3', 'Task-2', '', 'main', 'Task-1', '0x2'],
+ [1, '0x4', 'Task-3', '', 'main', 'Task-2', '0x3']
+ ]
+ self.assertEqual(tools.build_task_table(result), expected_output)
+
+ def test_deep_coroutine_chain(self):
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=10,
+ task_name="leaf",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("c1", "", 0),
+ FrameInfo("c2", "", 0),
+ FrameInfo("c3", "", 0),
+ FrameInfo("c4", "", 0),
+ FrameInfo("c5", "", 0)
+ ],
+ task_name=11
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=11,
+ task_name="root",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ expected = [
+ [
+ "└── (T) root",
+ " └── c5",
+ " └── c4",
+ " └── c3",
+ " └── c2",
+ " └── c1",
+ " └── (T) leaf",
+ ]
+ ]
+ result = tools.build_async_tree(input_)
+ self.assertEqual(result, expected)
+
+ def test_multiple_cycles_same_node(self):
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("call1", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-B",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("call2", "", 0)],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-C",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("call3", "", 0)],
+ task_name=1
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("call4", "", 0)],
+ task_name=2
+ )
+ ]
+ )
+ ]
+ )
+ ]
+ with self.assertRaises(tools.CycleFoundException) as ctx:
+ tools.build_async_tree(input_)
+ cycles = ctx.exception.cycles
+ self.assertTrue(any(set(c) == {1, 2, 3} for c in cycles))
+
+ def test_table_output_format(self):
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("foo", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-B",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ table = tools.build_task_table(input_)
+ for row in table:
+ self.assertEqual(len(row), 7)
+ self.assertIsInstance(row[0], int) # thread ID
+ self.assertTrue(
+ isinstance(row[1], str) and row[1].startswith("0x")
+ ) # hex task ID
+ self.assertIsInstance(row[2], str) # task name
+ self.assertIsInstance(row[3], str) # coroutine stack
+ self.assertIsInstance(row[4], str) # coroutine chain
+ self.assertIsInstance(row[5], str) # awaiter name
+ self.assertTrue(
+ isinstance(row[6], str) and row[6].startswith("0x")
+ ) # hex awaiter ID
+
+
+class TestAsyncioToolsEdgeCases(unittest.TestCase):
+
+ def test_task_awaits_self(self):
+ """A task directly awaits itself - should raise a cycle."""
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Self-Awaiter",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("loopback", "", 0)],
+ task_name=1
+ )
+ ]
+ )
+ ]
+ )
+ ]
+ with self.assertRaises(tools.CycleFoundException) as ctx:
+ tools.build_async_tree(input_)
+ self.assertIn([1, 1], ctx.exception.cycles)
+
+ def test_task_with_missing_awaiter_id(self):
+ """Awaiter ID not in task list - should not crash, just show 'Unknown'."""
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("coro", "", 0)],
+ task_name=999
+ )
+ ]
+ )
+ ]
+ )
+ ]
+ table = tools.build_task_table(input_)
+ self.assertEqual(len(table), 1)
+ self.assertEqual(table[0][5], "Unknown")
+
+ def test_duplicate_coroutine_frames(self):
+ """Same coroutine frame repeated under a parent - should deduplicate."""
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("frameA", "", 0)],
+ task_name=2
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("frameA", "", 0)],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ tree = tools.build_async_tree(input_)
+ # Both children should be under the same coroutine node
+ flat = "\n".join(tree[0])
+ self.assertIn("frameA", flat)
+ self.assertIn("Task-2", flat)
+ self.assertIn("Task-1", flat)
+
+ flat = "\n".join(tree[1])
+ self.assertIn("frameA", flat)
+ self.assertIn("Task-3", flat)
+ self.assertIn("Task-1", flat)
+
+ def test_task_with_no_name(self):
+ """Task with no name in id2name - should still render with fallback."""
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="root",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("f1", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name=None,
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ # If name is None, fallback to string should not crash
+ tree = tools.build_async_tree(input_)
+ self.assertIn("(T) None", "\n".join(tree[0]))
+
+ def test_tree_rendering_with_custom_emojis(self):
+ """Pass custom emojis to the tree renderer."""
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="MainTask",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("f1", "", 0),
+ FrameInfo("f2", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="SubTask",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
+ tree = tools.build_async_tree(input_, task_emoji="🧵", cor_emoji="🔁")
+ flat = "\n".join(tree[0])
+ self.assertIn("🧵 MainTask", flat)
+ self.assertIn("🔁 f1", flat)
+ self.assertIn("🔁 f2", flat)
+ self.assertIn("🧵 SubTask", flat)
diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py
index 2b24b5d7927..077765fcda2 100644
--- a/Lib/test/test_audit.py
+++ b/Lib/test/test_audit.py
@@ -134,7 +134,7 @@ class AuditTest(unittest.TestCase):
self.assertEqual(events[0][0], "socket.gethostname")
self.assertEqual(events[1][0], "socket.__new__")
self.assertEqual(events[2][0], "socket.bind")
- self.assertTrue(events[2][2].endswith("('127.0.0.1', 8080)"))
+ self.assertEndsWith(events[2][2], "('127.0.0.1', 8080)")
def test_gc(self):
returncode, events, stderr = self.run_python("test_gc")
@@ -322,6 +322,14 @@ class AuditTest(unittest.TestCase):
if returncode:
self.fail(stderr)
+ @support.support_remote_exec_only
+ @support.cpython_only
+ def test_sys_remote_exec(self):
+ returncode, events, stderr = self.run_python("test_sys_remote_exec")
+ self.assertTrue(any(["sys.remote_exec" in event for event in events]))
+ self.assertTrue(any(["cpython.remote_debugger_script" in event for event in events]))
+ if returncode:
+ self.fail(stderr)
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py
index 409c8c109e8..ce2e3e3726f 100644
--- a/Lib/test/test_base64.py
+++ b/Lib/test/test_base64.py
@@ -3,8 +3,16 @@ import base64
import binascii
import os
from array import array
+from test.support import cpython_only
from test.support import os_helper
from test.support import script_helper
+from test.support.import_helper import ensure_lazy_imports
+
+
+class LazyImportTest(unittest.TestCase):
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("base64", {"re", "getopt"})
class LegacyBase64TestCase(unittest.TestCase):
@@ -804,7 +812,7 @@ class BaseXYTestCase(unittest.TestCase):
self.assertRaises(ValueError, f, 'with non-ascii \xcb')
def test_ErrorHeritage(self):
- self.assertTrue(issubclass(binascii.Error, ValueError))
+ self.assertIsSubclass(binascii.Error, ValueError)
def test_RFC4648_test_cases(self):
# test cases from RFC 4648 section 10
diff --git a/Lib/test/test_baseexception.py b/Lib/test/test_baseexception.py
index e599b02c17d..12d4088842b 100644
--- a/Lib/test/test_baseexception.py
+++ b/Lib/test/test_baseexception.py
@@ -10,13 +10,11 @@ class ExceptionClassTests(unittest.TestCase):
inheritance hierarchy)"""
def test_builtins_new_style(self):
- self.assertTrue(issubclass(Exception, object))
+ self.assertIsSubclass(Exception, object)
def verify_instance_interface(self, ins):
for attr in ("args", "__str__", "__repr__"):
- self.assertTrue(hasattr(ins, attr),
- "%s missing %s attribute" %
- (ins.__class__.__name__, attr))
+ self.assertHasAttr(ins, attr)
def test_inheritance(self):
# Make sure the inheritance hierarchy matches the documentation
@@ -65,7 +63,7 @@ class ExceptionClassTests(unittest.TestCase):
elif last_depth > depth:
while superclasses[-1][0] >= depth:
superclasses.pop()
- self.assertTrue(issubclass(exc, superclasses[-1][1]),
+ self.assertIsSubclass(exc, superclasses[-1][1],
"%s is not a subclass of %s" % (exc.__name__,
superclasses[-1][1].__name__))
try: # Some exceptions require arguments; just skip them
diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py
index 1f3b6746ce4..7ed7d7c47b6 100644
--- a/Lib/test/test_binascii.py
+++ b/Lib/test/test_binascii.py
@@ -38,13 +38,13 @@ class BinASCIITest(unittest.TestCase):
def test_exceptions(self):
# Check module exceptions
- self.assertTrue(issubclass(binascii.Error, Exception))
- self.assertTrue(issubclass(binascii.Incomplete, Exception))
+ self.assertIsSubclass(binascii.Error, Exception)
+ self.assertIsSubclass(binascii.Incomplete, Exception)
def test_functions(self):
# Check presence of all functions
for name in all_functions:
- self.assertTrue(hasattr(getattr(binascii, name), '__call__'))
+ self.assertHasAttr(getattr(binascii, name), '__call__')
self.assertRaises(TypeError, getattr(binascii, name))
def test_returned_value(self):
diff --git a/Lib/test/test_binop.py b/Lib/test/test_binop.py
index 299af09c498..b224c3d4e60 100644
--- a/Lib/test/test_binop.py
+++ b/Lib/test/test_binop.py
@@ -383,7 +383,7 @@ class OperationOrderTests(unittest.TestCase):
self.assertEqual(op_sequence(le, B, C), ['C.__ge__', 'B.__le__'])
self.assertEqual(op_sequence(le, C, B), ['C.__le__', 'B.__ge__'])
- self.assertTrue(issubclass(V, B))
+ self.assertIsSubclass(V, B)
self.assertEqual(op_sequence(eq, B, V), ['B.__eq__', 'V.__eq__'])
self.assertEqual(op_sequence(le, B, V), ['B.__le__', 'V.__ge__'])
diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py
index 61921e93e85..19582e75716 100644
--- a/Lib/test/test_buffer.py
+++ b/Lib/test/test_buffer.py
@@ -2879,11 +2879,11 @@ class TestBufferProtocol(unittest.TestCase):
def test_memoryview_repr(self):
m = memoryview(bytearray(9))
r = m.__repr__()
- self.assertTrue(r.startswith("<memory"))
+ self.assertStartsWith(r, "<memory")
m.release()
r = m.__repr__()
- self.assertTrue(r.startswith("<released"))
+ self.assertStartsWith(r, "<released")
def test_memoryview_sequence(self):
diff --git a/Lib/test/test_bufio.py b/Lib/test/test_bufio.py
index dc9a82dc635..cb9cb4d0bc7 100644
--- a/Lib/test/test_bufio.py
+++ b/Lib/test/test_bufio.py
@@ -28,7 +28,7 @@ class BufferSizeTest:
f.write(b"\n")
f.write(s)
f.close()
- f = open(os_helper.TESTFN, "rb")
+ f = self.open(os_helper.TESTFN, "rb")
line = f.readline()
self.assertEqual(line, s + b"\n")
line = f.readline()
diff --git a/Lib/test/test_build_details.py b/Lib/test/test_build_details.py
index 05ce163a337..ba4b8c5aa9b 100644
--- a/Lib/test/test_build_details.py
+++ b/Lib/test/test_build_details.py
@@ -117,12 +117,26 @@ class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase):
# Override generic format tests with tests for our specific implemenation.
@needs_installed_python
- @unittest.skipIf(is_android or is_apple_mobile, 'Android and iOS run tests via a custom testbed method that changes sys.executable')
+ @unittest.skipIf(
+ is_android or is_apple_mobile,
+ 'Android and iOS run tests via a custom testbed method that changes sys.executable'
+ )
def test_base_interpreter(self):
value = self.key('base_interpreter')
self.assertEqual(os.path.realpath(value), os.path.realpath(sys.executable))
+ @needs_installed_python
+ @unittest.skipIf(
+ is_android or is_apple_mobile,
+ "Android and iOS run tests via a custom testbed method that doesn't ship headers"
+ )
+ def test_c_api(self):
+ value = self.key('c_api')
+ self.assertTrue(os.path.exists(os.path.join(value['headers'], 'Python.h')))
+ version = sysconfig.get_config_var('VERSION')
+ self.assertTrue(os.path.exists(os.path.join(value['pkgconfig_path'], f'python-{version}.pc')))
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 31597a320d4..14fe3355239 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -393,7 +393,7 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
self.assertRaises(ValueError, chr, -2**1000)
def test_cmp(self):
- self.assertTrue(not hasattr(builtins, "cmp"))
+ self.assertNotHasAttr(builtins, "cmp")
def test_compile(self):
compile('print(1)\n', '', 'exec')
@@ -1120,6 +1120,7 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
self.check_iter_pickle(f1, list(f2), proto)
@support.skip_wasi_stack_overflow()
+ @support.skip_emscripten_stack_overflow()
@support.requires_resource('cpu')
def test_filter_dealloc(self):
# Tests recursive deallocation of nested filter objects using the
@@ -2303,7 +2304,7 @@ class BuiltinTest(ComplexesAreIdenticalMixin, unittest.TestCase):
# tests for object.__format__ really belong elsewhere, but
# there's no good place to put them
x = object().__format__('')
- self.assertTrue(x.startswith('<object object at'))
+ self.assertStartsWith(x, '<object object at')
# first argument to object.__format__ must be string
self.assertRaises(TypeError, object().__format__, 3)
@@ -2990,7 +2991,8 @@ class TestType(unittest.TestCase):
def load_tests(loader, tests, pattern):
from doctest import DocTestSuite
- tests.addTest(DocTestSuite(builtins))
+ if sys.float_repr_style == 'short':
+ tests.addTest(DocTestSuite(builtins))
return tests
if __name__ == "__main__":
diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index 82d9916e38d..bb0f8aa99da 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -1974,9 +1974,9 @@ class AssortedBytesTest(unittest.TestCase):
@test.support.requires_docstrings
def test_doc(self):
self.assertIsNotNone(bytearray.__doc__)
- self.assertTrue(bytearray.__doc__.startswith("bytearray("), bytearray.__doc__)
+ self.assertStartsWith(bytearray.__doc__, "bytearray(")
self.assertIsNotNone(bytes.__doc__)
- self.assertTrue(bytes.__doc__.startswith("bytes("), bytes.__doc__)
+ self.assertStartsWith(bytes.__doc__, "bytes(")
def test_from_bytearray(self):
sample = bytes(b"Hello world\n\x80\x81\xfe\xff")
@@ -2107,7 +2107,7 @@ class BytesAsStringTest(FixedStringTest, unittest.TestCase):
class SubclassTest:
def test_basic(self):
- self.assertTrue(issubclass(self.type2test, self.basetype))
+ self.assertIsSubclass(self.type2test, self.basetype)
self.assertIsInstance(self.type2test(), self.basetype)
a, b = b"abcd", b"efgh"
@@ -2155,7 +2155,7 @@ class SubclassTest:
self.assertEqual(a.z, b.z)
self.assertEqual(type(a), type(b))
self.assertEqual(type(a.z), type(b.z))
- self.assertFalse(hasattr(b, 'y'))
+ self.assertNotHasAttr(b, 'y')
def test_copy(self):
a = self.type2test(b"abcd")
@@ -2169,7 +2169,7 @@ class SubclassTest:
self.assertEqual(a.z, b.z)
self.assertEqual(type(a), type(b))
self.assertEqual(type(a.z), type(b.z))
- self.assertFalse(hasattr(b, 'y'))
+ self.assertNotHasAttr(b, 'y')
def test_fromhex(self):
b = self.type2test.fromhex('1a2B30')
diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py
index f32b24b39ba..3b7897b8a88 100644
--- a/Lib/test/test_bz2.py
+++ b/Lib/test/test_bz2.py
@@ -184,7 +184,7 @@ class BZ2FileTest(BaseTest):
with BZ2File(self.filename) as bz2f:
pdata = bz2f.peek()
self.assertNotEqual(len(pdata), 0)
- self.assertTrue(self.TEXT.startswith(pdata))
+ self.assertStartsWith(self.TEXT, pdata)
self.assertEqual(bz2f.read(), self.TEXT)
def testReadInto(self):
@@ -768,7 +768,7 @@ class BZ2FileTest(BaseTest):
with BZ2File(bio) as bz2f:
pdata = bz2f.peek()
self.assertNotEqual(len(pdata), 0)
- self.assertTrue(self.TEXT.startswith(pdata))
+ self.assertStartsWith(self.TEXT, pdata)
self.assertEqual(bz2f.read(), self.TEXT)
def testWriteBytesIO(self):
diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py
index 073df310bb4..bc39c86b8cf 100644
--- a/Lib/test/test_calendar.py
+++ b/Lib/test/test_calendar.py
@@ -417,7 +417,7 @@ class OutputTestCase(unittest.TestCase):
self.check_htmlcalendar_encoding('utf-8', 'utf-8')
def test_output_htmlcalendar_encoding_default(self):
- self.check_htmlcalendar_encoding(None, sys.getdefaultencoding())
+ self.check_htmlcalendar_encoding(None, 'utf-8')
def test_yeardatescalendar(self):
def shrink(cal):
@@ -987,6 +987,7 @@ class CommandLineTestCase(unittest.TestCase):
self.assertCLIFails(*args)
self.assertCmdFails(*args)
+ @support.force_not_colorized
def test_help(self):
stdout = self.run_cmd_ok('-h')
self.assertIn(b'usage:', stdout)
@@ -1097,7 +1098,7 @@ class CommandLineTestCase(unittest.TestCase):
output = run('--type', 'text', '2004')
self.assertEqual(output, conv(result_2004_text))
output = run('--type', 'html', '2004')
- self.assertEqual(output[:6], b'<?xml ')
+ self.assertStartsWith(output, b'<?xml ')
self.assertIn(b'<title>Calendar for 2004</title>', output)
def test_html_output_current_year(self):
diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py
index 185ae84dc4d..1c73aaafb71 100644
--- a/Lib/test/test_call.py
+++ b/Lib/test/test_call.py
@@ -695,8 +695,8 @@ class TestPEP590(unittest.TestCase):
UnaffectedType2 = _testcapi.make_vectorcall_class(SuperType)
# Aside: Quickly check that the C helper actually made derived types
- self.assertTrue(issubclass(UnaffectedType1, DerivedType))
- self.assertTrue(issubclass(UnaffectedType2, SuperType))
+ self.assertIsSubclass(UnaffectedType1, DerivedType)
+ self.assertIsSubclass(UnaffectedType2, SuperType)
# Initial state: tp_call
self.assertEqual(instance(), "tp_call")
diff --git a/Lib/test/test_capi/test_abstract.py b/Lib/test/test_capi/test_abstract.py
index 7d548ae87c0..3a2ed9f5db8 100644
--- a/Lib/test/test_capi/test_abstract.py
+++ b/Lib/test/test_capi/test_abstract.py
@@ -1077,6 +1077,31 @@ class CAPITest(unittest.TestCase):
with self.assertRaisesRegex(TypeError, regex):
PyIter_NextItem(10)
+ def test_object_setattr_null_exc(self):
+ class Obj:
+ pass
+ obj = Obj()
+ obj.attr = 123
+
+ exc = ValueError("error")
+ with self.assertRaises(SystemError) as cm:
+ _testcapi.object_setattr_null_exc(obj, 'attr', exc)
+ self.assertIs(cm.exception.__context__, exc)
+ self.assertIsNone(cm.exception.__cause__)
+ self.assertHasAttr(obj, 'attr')
+
+ with self.assertRaises(SystemError) as cm:
+ _testcapi.object_setattrstring_null_exc(obj, 'attr', exc)
+ self.assertIs(cm.exception.__context__, exc)
+ self.assertIsNone(cm.exception.__cause__)
+ self.assertHasAttr(obj, 'attr')
+
+ with self.assertRaises(SystemError) as cm:
+ # undecodable name
+ _testcapi.object_setattrstring_null_exc(obj, b'\xff', exc)
+ self.assertIs(cm.exception.__context__, exc)
+ self.assertIsNone(cm.exception.__cause__)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_capi/test_bytearray.py b/Lib/test/test_capi/test_bytearray.py
index dfa98de9f00..52565ea34c6 100644
--- a/Lib/test/test_capi/test_bytearray.py
+++ b/Lib/test/test_capi/test_bytearray.py
@@ -66,6 +66,7 @@ class CAPITest(unittest.TestCase):
# Test PyByteArray_FromObject()
fromobject = _testlimitedcapi.bytearray_fromobject
+ self.assertEqual(fromobject(b''), bytearray(b''))
self.assertEqual(fromobject(b'abc'), bytearray(b'abc'))
self.assertEqual(fromobject(bytearray(b'abc')), bytearray(b'abc'))
self.assertEqual(fromobject(ByteArraySubclass(b'abc')), bytearray(b'abc'))
@@ -115,6 +116,7 @@ class CAPITest(unittest.TestCase):
self.assertEqual(concat(b'abc', bytearray(b'def')), bytearray(b'abcdef'))
self.assertEqual(concat(bytearray(b'abc'), b''), bytearray(b'abc'))
self.assertEqual(concat(b'', bytearray(b'def')), bytearray(b'def'))
+ self.assertEqual(concat(bytearray(b''), bytearray(b'')), bytearray(b''))
self.assertEqual(concat(memoryview(b'xabcy')[1:4], b'def'),
bytearray(b'abcdef'))
self.assertEqual(concat(b'abc', memoryview(b'xdefy')[1:4]),
@@ -150,6 +152,10 @@ class CAPITest(unittest.TestCase):
self.assertEqual(resize(ba, 0), 0)
self.assertEqual(ba, bytearray())
+ ba = bytearray(b'')
+ self.assertEqual(resize(ba, 0), 0)
+ self.assertEqual(ba, bytearray())
+
ba = ByteArraySubclass(b'abcdef')
self.assertEqual(resize(ba, 3), 0)
self.assertEqual(ba, bytearray(b'abc'))
diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py
index 5b61c733815..bc820bd68d9 100644
--- a/Lib/test/test_capi/test_bytes.py
+++ b/Lib/test/test_capi/test_bytes.py
@@ -22,6 +22,7 @@ class CAPITest(unittest.TestCase):
# Test PyBytes_Check()
check = _testlimitedcapi.bytes_check
self.assertTrue(check(b'abc'))
+ self.assertTrue(check(b''))
self.assertFalse(check('abc'))
self.assertFalse(check(bytearray(b'abc')))
self.assertTrue(check(BytesSubclass(b'abc')))
@@ -36,6 +37,7 @@ class CAPITest(unittest.TestCase):
# Test PyBytes_CheckExact()
check = _testlimitedcapi.bytes_checkexact
self.assertTrue(check(b'abc'))
+ self.assertTrue(check(b''))
self.assertFalse(check('abc'))
self.assertFalse(check(bytearray(b'abc')))
self.assertFalse(check(BytesSubclass(b'abc')))
@@ -79,6 +81,7 @@ class CAPITest(unittest.TestCase):
# Test PyBytes_FromObject()
fromobject = _testlimitedcapi.bytes_fromobject
+ self.assertEqual(fromobject(b''), b'')
self.assertEqual(fromobject(b'abc'), b'abc')
self.assertEqual(fromobject(bytearray(b'abc')), b'abc')
self.assertEqual(fromobject(BytesSubclass(b'abc')), b'abc')
@@ -108,6 +111,7 @@ class CAPITest(unittest.TestCase):
self.assertEqual(asstring(b'abc', 4), b'abc\0')
self.assertEqual(asstring(b'abc\0def', 8), b'abc\0def\0')
+ self.assertEqual(asstring(b'', 1), b'\0')
self.assertRaises(TypeError, asstring, 'abc', 0)
self.assertRaises(TypeError, asstring, object(), 0)
@@ -120,6 +124,7 @@ class CAPITest(unittest.TestCase):
self.assertEqual(asstringandsize(b'abc', 4), (b'abc\0', 3))
self.assertEqual(asstringandsize(b'abc\0def', 8), (b'abc\0def\0', 7))
+ self.assertEqual(asstringandsize(b'', 1), (b'\0', 0))
self.assertEqual(asstringandsize_null(b'abc', 4), b'abc\0')
self.assertRaises(ValueError, asstringandsize_null, b'abc\0def', 8)
self.assertRaises(TypeError, asstringandsize, 'abc', 0)
@@ -134,6 +139,7 @@ class CAPITest(unittest.TestCase):
# Test PyBytes_Repr()
bytes_repr = _testlimitedcapi.bytes_repr
+ self.assertEqual(bytes_repr(b'', 0), r"""b''""")
self.assertEqual(bytes_repr(b'''abc''', 0), r"""b'abc'""")
self.assertEqual(bytes_repr(b'''abc''', 1), r"""b'abc'""")
self.assertEqual(bytes_repr(b'''a'b"c"d''', 0), r"""b'a\'b"c"d'""")
@@ -163,6 +169,7 @@ class CAPITest(unittest.TestCase):
self.assertEqual(concat(b'', bytearray(b'def')), b'def')
self.assertEqual(concat(memoryview(b'xabcy')[1:4], b'def'), b'abcdef')
self.assertEqual(concat(b'abc', memoryview(b'xdefy')[1:4]), b'abcdef')
+ self.assertEqual(concat(b'', b''), b'')
self.assertEqual(concat(b'abc', b'def', True), b'abcdef')
self.assertEqual(concat(b'abc', bytearray(b'def'), True), b'abcdef')
@@ -192,6 +199,7 @@ class CAPITest(unittest.TestCase):
"""Test PyBytes_DecodeEscape()"""
decodeescape = _testlimitedcapi.bytes_decodeescape
+ self.assertEqual(decodeescape(b''), b'')
self.assertEqual(decodeescape(b'abc'), b'abc')
self.assertEqual(decodeescape(br'\t\n\r\x0b\x0c\x00\\\'\"'),
b'''\t\n\r\v\f\0\\'"''')
diff --git a/Lib/test/test_capi/test_config.py b/Lib/test/test_capi/test_config.py
index bf351c4defa..04a27de8d84 100644
--- a/Lib/test/test_capi/test_config.py
+++ b/Lib/test/test_capi/test_config.py
@@ -3,7 +3,6 @@ Tests PyConfig_Get() and PyConfig_Set() C API (PEP 741).
"""
import os
import sys
-import sysconfig
import types
import unittest
from test import support
@@ -57,7 +56,7 @@ class CAPITests(unittest.TestCase):
("home", str | None, None),
("thread_inherit_context", int, None),
("context_aware_warnings", int, None),
- ("import_time", bool, None),
+ ("import_time", int, None),
("inspect", bool, None),
("install_signal_handlers", bool, None),
("int_max_str_digits", int, None),
diff --git a/Lib/test/test_capi/test_import.py b/Lib/test/test_capi/test_import.py
index 25136624ca4..57e0316fda8 100644
--- a/Lib/test/test_capi/test_import.py
+++ b/Lib/test/test_capi/test_import.py
@@ -134,7 +134,7 @@ class ImportTests(unittest.TestCase):
# CRASHES importmodule(NULL)
def test_importmodulenoblock(self):
- # Test deprecated PyImport_ImportModuleNoBlock()
+ # Test deprecated (stable ABI only) PyImport_ImportModuleNoBlock()
importmodulenoblock = _testlimitedcapi.PyImport_ImportModuleNoBlock
with check_warnings(('', DeprecationWarning)):
self.check_import_func(importmodulenoblock)
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index 98dc3b42ef0..ef950f5df04 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -306,7 +306,7 @@ class CAPITest(unittest.TestCase):
CURRENT_THREAD_REGEX +
r' File .*, line 6 in <module>\n'
r'\n'
- r'Extension modules: _testcapi, _testinternalcapi \(total: 2\)\n')
+ r'Extension modules: _testcapi \(total: 1\)\n')
else:
# Python built with NDEBUG macro defined:
# test _Py_CheckFunctionResult() instead.
@@ -412,10 +412,14 @@ class CAPITest(unittest.TestCase):
L = MyList((L,))
@support.requires_resource('cpu')
+ @support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_trashcan_python_class1(self):
self.do_test_trashcan_python_class(list)
@support.requires_resource('cpu')
+ @support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_trashcan_python_class2(self):
from _testcapi import MyList
self.do_test_trashcan_python_class(MyList)
diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py
index 54a01ac7c4a..d4056727d07 100644
--- a/Lib/test/test_capi/test_object.py
+++ b/Lib/test/test_capi/test_object.py
@@ -174,6 +174,16 @@ class EnableDeferredRefcountingTest(unittest.TestCase):
self.assertTrue(_testinternalcapi.has_deferred_refcount(silly_list))
+class IsUniquelyReferencedTest(unittest.TestCase):
+ """Test PyUnstable_Object_IsUniquelyReferenced"""
+ def test_is_uniquely_referenced(self):
+ self.assertTrue(_testcapi.is_uniquely_referenced(object()))
+ self.assertTrue(_testcapi.is_uniquely_referenced([]))
+ # Immortals
+ self.assertFalse(_testcapi.is_uniquely_referenced(()))
+ self.assertFalse(_testcapi.is_uniquely_referenced(42))
+ # CRASHES is_uniquely_referenced(NULL)
+
class CAPITest(unittest.TestCase):
def check_negative_refcount(self, code):
# bpo-35059: Check that Py_DECREF() reports the correct filename
@@ -211,6 +221,7 @@ class CAPITest(unittest.TestCase):
"""
self.check_negative_refcount(code)
+ @support.requires_resource('cpu')
def test_decref_delayed(self):
# gh-130519: Test that _PyObject_XDecRefDelayed() and QSBR code path
# handles destructors that are possibly re-entrant or trigger a GC.
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index 7e0c60d5522..7be1c9eebb3 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -407,12 +407,12 @@ class TestUops(unittest.TestCase):
x = 0
for i in range(m):
for j in MyIter(n):
- x += 1000*i + j
+ x += j
return x
- x = testfunc(TIER2_THRESHOLD, TIER2_THRESHOLD)
+ x = testfunc(TIER2_THRESHOLD, 2)
- self.assertEqual(x, sum(range(TIER2_THRESHOLD)) * TIER2_THRESHOLD * 1001)
+ self.assertEqual(x, sum(range(TIER2_THRESHOLD)) * 2)
ex = get_first_executor(testfunc)
self.assertIsNotNone(ex)
@@ -678,7 +678,7 @@ class TestUopsOptimization(unittest.TestCase):
self.assertLessEqual(len(guard_nos_float_count), 1)
# TODO gh-115506: this assertion may change after propagating constants.
# We'll also need to verify that propagation actually occurs.
- self.assertIn("_BINARY_OP_ADD_FLOAT", uops)
+ self.assertIn("_BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS", uops)
def test_float_subtract_constant_propagation(self):
def testfunc(n):
@@ -700,7 +700,7 @@ class TestUopsOptimization(unittest.TestCase):
self.assertLessEqual(len(guard_nos_float_count), 1)
# TODO gh-115506: this assertion may change after propagating constants.
# We'll also need to verify that propagation actually occurs.
- self.assertIn("_BINARY_OP_SUBTRACT_FLOAT", uops)
+ self.assertIn("_BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS", uops)
def test_float_multiply_constant_propagation(self):
def testfunc(n):
@@ -722,7 +722,7 @@ class TestUopsOptimization(unittest.TestCase):
self.assertLessEqual(len(guard_nos_float_count), 1)
# TODO gh-115506: this assertion may change after propagating constants.
# We'll also need to verify that propagation actually occurs.
- self.assertIn("_BINARY_OP_MULTIPLY_FLOAT", uops)
+ self.assertIn("_BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS", uops)
def test_add_unicode_propagation(self):
def testfunc(n):
@@ -1183,6 +1183,17 @@ class TestUopsOptimization(unittest.TestCase):
self.assertIsNotNone(ex)
self.assertIn("_RETURN_GENERATOR", get_opnames(ex))
+ def test_for_iter(self):
+ def testfunc(n):
+ t = 0
+ for i in set(range(n)):
+ t += i
+ return t
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD * (TIER2_THRESHOLD - 1) // 2)
+ self.assertIsNotNone(ex)
+ self.assertIn("_FOR_ITER_TIER_TWO", get_opnames(ex))
+
@unittest.skip("Tracing into generators currently isn't supported.")
def test_for_iter_gen(self):
def gen(n):
@@ -1280,8 +1291,8 @@ class TestUopsOptimization(unittest.TestCase):
self.assertIsNotNone(ex)
self.assertEqual(res, TIER2_THRESHOLD * 6 + 1)
call = opnames.index("_CALL_BUILTIN_FAST")
- load_attr_top = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", 0, call)
- load_attr_bottom = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", call)
+ load_attr_top = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", 0, call)
+ load_attr_bottom = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", call)
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)
@@ -1303,8 +1314,8 @@ class TestUopsOptimization(unittest.TestCase):
self.assertIsNotNone(ex)
self.assertEqual(res, TIER2_THRESHOLD * 2)
call = opnames.index("_CALL_BUILTIN_FAST_WITH_KEYWORDS")
- load_attr_top = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", 0, call)
- load_attr_bottom = opnames.index("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", call)
+ load_attr_top = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", 0, call)
+ load_attr_bottom = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", call)
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)
@@ -1370,6 +1381,21 @@ class TestUopsOptimization(unittest.TestCase):
# Removed guard
self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops)
+ def test_method_guards_removed_or_reduced(self):
+ def testfunc(n):
+ result = 0
+ for i in range(n):
+ result += test_bound_method(i)
+ return result
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, sum(range(TIER2_THRESHOLD)))
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_PUSH_FRAME", uops)
+ # Strength reduced version
+ self.assertIn("_CHECK_FUNCTION_VERSION_INLINE", uops)
+ self.assertNotIn("_CHECK_METHOD_VERSION", uops)
+
def test_jit_error_pops(self):
"""
Tests that the correct number of pops are inserted into the
@@ -1655,13 +1681,11 @@ class TestUopsOptimization(unittest.TestCase):
self.assertIn("_CONTAINS_OP_DICT", uops)
self.assertNotIn("_TO_BOOL_BOOL", uops)
-
def test_remove_guard_for_known_type_str(self):
def f(n):
for i in range(n):
false = i == TIER2_THRESHOLD
empty = "X"[:false]
- empty += "" # Make JIT realize this is a string.
if empty:
return 1
return 0
@@ -1767,11 +1791,12 @@ class TestUopsOptimization(unittest.TestCase):
self.assertNotIn("_GUARD_TOS_UNICODE", uops)
self.assertIn("_BINARY_OP_ADD_UNICODE", uops)
- def test_call_type_1(self):
+ def test_call_type_1_guards_removed(self):
def testfunc(n):
x = 0
for _ in range(n):
- x += type(42) is int
+ foo = eval('42')
+ x += type(foo) is int
return x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
@@ -1782,6 +1807,25 @@ class TestUopsOptimization(unittest.TestCase):
self.assertNotIn("_GUARD_NOS_NULL", uops)
self.assertNotIn("_GUARD_CALLABLE_TYPE_1", uops)
+ def test_call_type_1_known_type(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ x += type(42) is int
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ # When the result of type(...) is known, _CALL_TYPE_1 is replaced with
+ # _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW which is optimized away in
+ # remove_unneeded_uops.
+ self.assertNotIn("_CALL_TYPE_1", uops)
+ self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
+
def test_call_type_1_result_is_const(self):
def testfunc(n):
x = 0
@@ -1795,7 +1839,6 @@ class TestUopsOptimization(unittest.TestCase):
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
- self.assertIn("_CALL_TYPE_1", uops)
self.assertNotIn("_GUARD_IS_NOT_NONE_POP", uops)
def test_call_str_1(self):
@@ -1919,9 +1962,98 @@ class TestUopsOptimization(unittest.TestCase):
_, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
uops = get_opnames(ex)
+ self.assertNotIn("_GUARD_NOS_NULL", uops)
+ self.assertNotIn("_GUARD_CALLABLE_LEN", uops)
+ self.assertIn("_CALL_LEN", uops)
self.assertNotIn("_GUARD_NOS_INT", uops)
self.assertNotIn("_GUARD_TOS_INT", uops)
+
+ def test_call_len_known_length_small_int(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ t = (1, 2, 3, 4, 5)
+ if len(t) == 5:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ # When the length is < _PY_NSMALLPOSINTS, the len() call is replaced
+ # with just an inline load.
+ self.assertNotIn("_CALL_LEN", uops)
+ self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_call_len_known_length(self):
+ def testfunc(n):
+ class C:
+ t = tuple(range(300))
+
+ x = 0
+ for _ in range(n):
+ if len(C.t) == 300: # comparison + guard removed
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ # When the length is >= _PY_NSMALLPOSINTS, we cannot replace
+ # the len() call with an inline load, but knowing the exact
+ # length allows us to optimize more code, such as conditionals
+ # in this case
self.assertIn("_CALL_LEN", uops)
+ self.assertNotIn("_COMPARE_OP_INT", uops)
+ self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
+
+ def test_get_len_with_const_tuple(self):
+ def testfunc(n):
+ x = 0.0
+ for _ in range(n):
+ match (1, 2, 3, 4):
+ case [_, _, _, _]:
+ x += 1.0
+ return x
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(int(res), TIER2_THRESHOLD)
+ uops = get_opnames(ex)
+ self.assertNotIn("_GUARD_NOS_INT", uops)
+ self.assertNotIn("_GET_LEN", uops)
+ self.assertIn("_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_get_len_with_non_const_tuple(self):
+ def testfunc(n):
+ x = 0.0
+ for _ in range(n):
+ match object(), object():
+ case [_, _]:
+ x += 1.0
+ return x
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(int(res), TIER2_THRESHOLD)
+ uops = get_opnames(ex)
+ self.assertNotIn("_GUARD_NOS_INT", uops)
+ self.assertNotIn("_GET_LEN", uops)
+ self.assertIn("_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_get_len_with_non_tuple(self):
+ def testfunc(n):
+ x = 0.0
+ for _ in range(n):
+ match [1, 2, 3, 4]:
+ case [_, _, _, _]:
+ x += 1.0
+ return x
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(int(res), TIER2_THRESHOLD)
+ uops = get_opnames(ex)
+ self.assertNotIn("_GUARD_NOS_INT", uops)
+ self.assertIn("_GET_LEN", uops)
def test_binary_op_subscr_tuple_int(self):
def testfunc(n):
@@ -1940,9 +2072,409 @@ class TestUopsOptimization(unittest.TestCase):
self.assertNotIn("_COMPARE_OP_INT", uops)
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
+ def test_call_isinstance_guards_removed(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ y = isinstance(42, int)
+ if y:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertNotIn("_CALL_ISINSTANCE", uops)
+ self.assertNotIn("_GUARD_THIRD_NULL", uops)
+ self.assertNotIn("_GUARD_CALLABLE_ISINSTANCE", uops)
+ self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_call_list_append(self):
+ def testfunc(n):
+ a = []
+ for i in range(n):
+ a.append(i)
+ return sum(a)
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, sum(range(TIER2_THRESHOLD)))
+ uops = get_opnames(ex)
+ self.assertIn("_CALL_LIST_APPEND", uops)
+ # We should remove these in the future
+ self.assertIn("_GUARD_NOS_LIST", uops)
+ self.assertIn("_GUARD_CALLABLE_LIST_APPEND", uops)
+
+ def test_call_isinstance_is_true(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ y = isinstance(42, int)
+ if y:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertNotIn("_CALL_ISINSTANCE", uops)
+ self.assertNotIn("_TO_BOOL_BOOL", uops)
+ self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
+ self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_call_isinstance_is_false(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ y = isinstance(42, str)
+ if not y:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertNotIn("_CALL_ISINSTANCE", uops)
+ self.assertNotIn("_TO_BOOL_BOOL", uops)
+ self.assertNotIn("_GUARD_IS_FALSE_POP", uops)
+ self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_call_isinstance_subclass(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ y = isinstance(True, int)
+ if y:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertNotIn("_CALL_ISINSTANCE", uops)
+ self.assertNotIn("_TO_BOOL_BOOL", uops)
+ self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
+ self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
+ self.assertNotIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_call_isinstance_unknown_object(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ # The optimizer doesn't know the return type here:
+ bar = eval("42")
+ # This will only narrow to bool:
+ y = isinstance(bar, int)
+ if y:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_CALL_ISINSTANCE", uops)
+ self.assertNotIn("_TO_BOOL_BOOL", uops)
+ self.assertIn("_GUARD_IS_TRUE_POP", uops)
+
+ def test_call_isinstance_tuple_of_classes(self):
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ # A tuple of classes is currently not optimized,
+ # so this is only narrowed to bool:
+ y = isinstance(42, (int, str))
+ if y:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_CALL_ISINSTANCE", uops)
+ self.assertNotIn("_TO_BOOL_BOOL", uops)
+ self.assertIn("_GUARD_IS_TRUE_POP", uops)
+
+ def test_call_isinstance_metaclass(self):
+ class EvenNumberMeta(type):
+ def __instancecheck__(self, number):
+ return number % 2 == 0
+
+ class EvenNumber(metaclass=EvenNumberMeta):
+ pass
+
+ def testfunc(n):
+ x = 0
+ for _ in range(n):
+ # Only narrowed to bool
+ y = isinstance(42, EvenNumber)
+ if y:
+ x += 1
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_CALL_ISINSTANCE", uops)
+ self.assertNotIn("_TO_BOOL_BOOL", uops)
+ self.assertIn("_GUARD_IS_TRUE_POP", uops)
+
+ def test_set_type_version_sets_type(self):
+ class C:
+ A = 1
+
+ def testfunc(n):
+ x = 0
+ c = C()
+ for _ in range(n):
+ x += c.A # Guarded.
+ x += type(c).A # Unguarded!
+ return x
+
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, 2 * TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_GUARD_TYPE_VERSION", uops)
+ self.assertNotIn("_CHECK_ATTR_CLASS", uops)
+
+ def test_load_small_int(self):
+ def testfunc(n):
+ x = 0
+ for i in range(n):
+ x += 1
+ return x
+ res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
+ self.assertEqual(res, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertNotIn("_LOAD_SMALL_INT", uops)
+ self.assertIn("_LOAD_CONST_INLINE_BORROW", uops)
+
+ def test_cached_attributes(self):
+ class C:
+ A = 1
+ def m(self):
+ return 1
+ class D:
+ __slots__ = ()
+ A = 1
+ def m(self):
+ return 1
+ class E(Exception):
+ def m(self):
+ return 1
+ def f(n):
+ x = 0
+ c = C()
+ d = D()
+ e = E()
+ for _ in range(n):
+ x += C.A # _LOAD_ATTR_CLASS
+ x += c.A # _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES
+ x += d.A # _LOAD_ATTR_NONDESCRIPTOR_NO_DICT
+ x += c.m() # _LOAD_ATTR_METHOD_WITH_VALUES
+ x += d.m() # _LOAD_ATTR_METHOD_NO_DICT
+ x += e.m() # _LOAD_ATTR_METHOD_LAZY_DICT
+ return x
+
+ res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
+ self.assertEqual(res, 6 * TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertNotIn("_LOAD_ATTR_CLASS", uops)
+ self.assertNotIn("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", uops)
+ self.assertNotIn("_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", uops)
+ self.assertNotIn("_LOAD_ATTR_METHOD_WITH_VALUES", uops)
+ self.assertNotIn("_LOAD_ATTR_METHOD_NO_DICT", uops)
+ self.assertNotIn("_LOAD_ATTR_METHOD_LAZY_DICT", uops)
+
+ def test_float_op_refcount_elimination(self):
+ def testfunc(args):
+ a, b, n = args
+ c = 0.0
+ for _ in range(n):
+ c += a + b
+ return c
+
+ res, ex = self._run_with_optimizer(testfunc, (0.1, 0.1, TIER2_THRESHOLD))
+ self.assertAlmostEqual(res, TIER2_THRESHOLD * (0.1 + 0.1))
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS", uops)
+
+ def test_remove_guard_for_slice_list(self):
+ def f(n):
+ for i in range(n):
+ false = i == TIER2_THRESHOLD
+ sliced = [1, 2, 3][:false]
+ if sliced:
+ return 1
+ return 0
+
+ res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
+ self.assertEqual(res, 0)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_TO_BOOL_LIST", uops)
+ self.assertNotIn("_GUARD_TOS_LIST", uops)
+
+ def test_remove_guard_for_slice_tuple(self):
+ def f(n):
+ for i in range(n):
+ false = i == TIER2_THRESHOLD
+ a, b = (1, 2, 3)[: false + 2]
+
+ _, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+ self.assertIn("_UNPACK_SEQUENCE_TWO_TUPLE", uops)
+ self.assertNotIn("_GUARD_TOS_TUPLE", uops)
+
+ def test_unary_invert_long_type(self):
+ def testfunc(n):
+ for _ in range(n):
+ a = 9397
+ x = ~a + ~a
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+
+ self.assertNotIn("_GUARD_TOS_INT", uops)
+ self.assertNotIn("_GUARD_NOS_INT", uops)
+
+ def test_attr_promotion_failure(self):
+ # We're not testing for any specific uops here, just
+ # testing it doesn't crash.
+ script_helper.assert_python_ok('-c', textwrap.dedent("""
+ import _testinternalcapi
+ import _opcode
+ import email
+
+ def get_first_executor(func):
+ code = func.__code__
+ co_code = code.co_code
+ for i in range(0, len(co_code), 2):
+ try:
+ return _opcode.get_executor(code, i)
+ except ValueError:
+ pass
+ return None
+
+ def testfunc(n):
+ for _ in range(n):
+ email.jit_testing = None
+ prompt = email.jit_testing
+ del email.jit_testing
+
+
+ testfunc(_testinternalcapi.TIER2_THRESHOLD)
+ ex = get_first_executor(testfunc)
+ assert ex is not None
+ """))
+
+ def test_pop_top_specialize_none(self):
+ def testfunc(n):
+ for _ in range(n):
+ global_identity(None)
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+
+ self.assertIn("_POP_TOP_NOP", uops)
+
+ def test_pop_top_specialize_int(self):
+ def testfunc(n):
+ for _ in range(n):
+ global_identity(100000)
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+
+ self.assertIn("_POP_TOP_INT", uops)
+
+ def test_pop_top_specialize_float(self):
+ def testfunc(n):
+ for _ in range(n):
+ global_identity(1e6)
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+
+ self.assertIn("_POP_TOP_FLOAT", uops)
+
+
+ def test_unary_negative_long_float_type(self):
+ def testfunc(n):
+ for _ in range(n):
+ a = 9397
+ f = 9397.0
+ x = -a + -a
+ y = -f + -f
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+
+ self.assertNotIn("_GUARD_TOS_INT", uops)
+ self.assertNotIn("_GUARD_NOS_INT", uops)
+ self.assertNotIn("_GUARD_TOS_FLOAT", uops)
+ self.assertNotIn("_GUARD_NOS_FLOAT", uops)
+
+ def test_binary_op_constant_evaluate(self):
+ def testfunc(n):
+ for _ in range(n):
+ 2 ** 65
+
+ testfunc(TIER2_THRESHOLD)
+
+ ex = get_first_executor(testfunc)
+ self.assertIsNotNone(ex)
+ uops = get_opnames(ex)
+
+ # For now... until we constant propagate it away.
+ self.assertIn("_BINARY_OP", uops)
+
def global_identity(x):
return x
+class TestObject:
+ def test(self, *args, **kwargs):
+ return args[0]
+
+test_object = TestObject()
+test_bound_method = TestObject.test.__get__(test_object)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_capi/test_sys.py b/Lib/test/test_capi/test_sys.py
index d3a9b378e77..3793ce2461e 100644
--- a/Lib/test/test_capi/test_sys.py
+++ b/Lib/test/test_capi/test_sys.py
@@ -19,6 +19,68 @@ class CAPITest(unittest.TestCase):
maxDiff = None
+ @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
+ def test_sys_getattr(self):
+ # Test PySys_GetAttr()
+ sys_getattr = _testlimitedcapi.sys_getattr
+
+ self.assertIs(sys_getattr('stdout'), sys.stdout)
+ with support.swap_attr(sys, '\U0001f40d', 42):
+ self.assertEqual(sys_getattr('\U0001f40d'), 42)
+
+ with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexistent'):
+ sys_getattr('nonexistent')
+ with self.assertRaisesRegex(RuntimeError, r'lost sys\.\U0001f40d'):
+ sys_getattr('\U0001f40d')
+ self.assertRaises(TypeError, sys_getattr, 1)
+ self.assertRaises(TypeError, sys_getattr, [])
+ # CRASHES sys_getattr(NULL)
+
+ @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
+ def test_sys_getattrstring(self):
+ # Test PySys_GetAttrString()
+ getattrstring = _testlimitedcapi.sys_getattrstring
+
+ self.assertIs(getattrstring(b'stdout'), sys.stdout)
+ with support.swap_attr(sys, '\U0001f40d', 42):
+ self.assertEqual(getattrstring('\U0001f40d'.encode()), 42)
+
+ with self.assertRaisesRegex(RuntimeError, r'lost sys\.nonexistent'):
+ getattrstring(b'nonexistent')
+ with self.assertRaisesRegex(RuntimeError, r'lost sys\.\U0001f40d'):
+ getattrstring('\U0001f40d'.encode())
+ self.assertRaises(UnicodeDecodeError, getattrstring, b'\xff')
+ # CRASHES getattrstring(NULL)
+
+ @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
+ def test_sys_getoptionalattr(self):
+ # Test PySys_GetOptionalAttr()
+ getoptionalattr = _testlimitedcapi.sys_getoptionalattr
+
+ self.assertIs(getoptionalattr('stdout'), sys.stdout)
+ with support.swap_attr(sys, '\U0001f40d', 42):
+ self.assertEqual(getoptionalattr('\U0001f40d'), 42)
+
+ self.assertIs(getoptionalattr('nonexistent'), AttributeError)
+ self.assertIs(getoptionalattr('\U0001f40d'), AttributeError)
+ self.assertRaises(TypeError, getoptionalattr, 1)
+ self.assertRaises(TypeError, getoptionalattr, [])
+ # CRASHES getoptionalattr(NULL)
+
+ @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
+ def test_sys_getoptionalattrstring(self):
+ # Test PySys_GetOptionalAttrString()
+ getoptionalattrstring = _testlimitedcapi.sys_getoptionalattrstring
+
+ self.assertIs(getoptionalattrstring(b'stdout'), sys.stdout)
+ with support.swap_attr(sys, '\U0001f40d', 42):
+ self.assertEqual(getoptionalattrstring('\U0001f40d'.encode()), 42)
+
+ self.assertIs(getoptionalattrstring(b'nonexistent'), AttributeError)
+ self.assertIs(getoptionalattrstring('\U0001f40d'.encode()), AttributeError)
+ self.assertRaises(UnicodeDecodeError, getoptionalattrstring, b'\xff')
+ # CRASHES getoptionalattrstring(NULL)
+
@support.cpython_only
@unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module')
def test_sys_getobject(self):
@@ -29,7 +91,7 @@ class CAPITest(unittest.TestCase):
with support.swap_attr(sys, '\U0001f40d', 42):
self.assertEqual(getobject('\U0001f40d'.encode()), 42)
- self.assertIs(getobject(b'nonexisting'), AttributeError)
+ self.assertIs(getobject(b'nonexistent'), AttributeError)
with support.catch_unraisable_exception() as cm:
self.assertIs(getobject(b'\xff'), AttributeError)
self.assertEqual(cm.unraisable.exc_type, UnicodeDecodeError)
diff --git a/Lib/test/test_capi/test_type.py b/Lib/test/test_capi/test_type.py
index 3c9974c7387..15fb4a93e2a 100644
--- a/Lib/test/test_capi/test_type.py
+++ b/Lib/test/test_capi/test_type.py
@@ -264,3 +264,13 @@ class TypeTests(unittest.TestCase):
ManualHeapType = _testcapi.ManualHeapType
for i in range(100):
self.assertIsInstance(ManualHeapType(), ManualHeapType)
+
+ def test_extension_managed_dict_type(self):
+ ManagedDictType = _testcapi.ManagedDictType
+ obj = ManagedDictType()
+ obj.foo = 42
+ self.assertEqual(obj.foo, 42)
+ self.assertEqual(obj.__dict__, {'foo': 42})
+ obj.__dict__ = {'bar': 3}
+ self.assertEqual(obj.__dict__, {'bar': 3})
+ self.assertEqual(obj.bar, 3)
diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py
index 3408c10f426..6a9c60f3a6d 100644
--- a/Lib/test/test_capi/test_unicode.py
+++ b/Lib/test/test_capi/test_unicode.py
@@ -1739,6 +1739,20 @@ class CAPITest(unittest.TestCase):
# Check that the second call returns the same result
self.assertEqual(getargs_s_hash(s), chr(k).encode() * (i + 1))
+ @support.cpython_only
+ @unittest.skipIf(_testcapi is None, 'need _testcapi module')
+ def test_GET_CACHED_HASH(self):
+ from _testcapi import unicode_GET_CACHED_HASH
+ content_bytes = b'some new string'
+ # avoid parser interning & constant folding
+ obj = str(content_bytes, 'ascii')
+ # impl detail: fresh strings do not have cached hash
+ self.assertEqual(unicode_GET_CACHED_HASH(obj), -1)
+ # impl detail: adding string to a dict caches its hash
+ {obj: obj}
+ # impl detail: ASCII string hashes are equal to bytes ones
+ self.assertEqual(unicode_GET_CACHED_HASH(obj), hash(content_bytes))
+
class PyUnicodeWriterTest(unittest.TestCase):
def create_writer(self, size):
@@ -1776,6 +1790,13 @@ class PyUnicodeWriterTest(unittest.TestCase):
self.assertEqual(writer.finish(),
"ascii-latin1=\xE9-euro=\u20AC.")
+ def test_ascii(self):
+ writer = self.create_writer(0)
+ writer.write_ascii(b"Hello ", -1)
+ writer.write_ascii(b"", 0)
+ writer.write_ascii(b"Python! <truncated>", 6)
+ self.assertEqual(writer.finish(), "Hello Python")
+
def test_invalid_utf8(self):
writer = self.create_writer(0)
with self.assertRaises(UnicodeDecodeError):
diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py
index 4c12d43556f..8c7a62a74ba 100644
--- a/Lib/test/test_class.py
+++ b/Lib/test/test_class.py
@@ -652,6 +652,7 @@ class ClassTests(unittest.TestCase):
a = A(hash(A.f)^(-1))
hash(a.f)
+ @cpython_only
def testSetattrWrapperNameIntern(self):
# Issue #25794: __setattr__ should intern the attribute name
class A:
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index 0c99620e27c..580d54e0eb0 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -238,11 +238,11 @@ class ClinicWholeFileTest(TestCase):
# The generated output will differ for every run, but we can check that
# it starts with the clinic block, we check that it contains all the
# expected fields, and we check that it contains the checksum line.
- self.assertTrue(out.startswith(dedent("""
+ self.assertStartsWith(out, dedent("""
/*[clinic input]
output print 'I told you once.'
[clinic start generated code]*/
- """)))
+ """))
fields = {
"cpp_endif",
"cpp_if",
@@ -259,9 +259,7 @@ class ClinicWholeFileTest(TestCase):
with self.subTest(field=field):
self.assertIn(field, out)
last_line = out.rstrip().split("\n")[-1]
- self.assertTrue(
- last_line.startswith("/*[clinic end generated code: output=")
- )
+ self.assertStartsWith(last_line, "/*[clinic end generated code: output=")
def test_directive_wrong_arg_number(self):
raw = dedent("""
@@ -2705,8 +2703,7 @@ class ClinicExternalTest(TestCase):
# Note, we cannot check the entire fail msg, because the path to
# the tmp file will change for every run.
_, err = self.expect_failure(fn)
- self.assertTrue(err.endswith(fail_msg),
- f"{err!r} does not end with {fail_msg!r}")
+ self.assertEndsWith(err, fail_msg)
# Then, force regeneration; success expected.
out = self.expect_success("-f", fn)
self.assertEqual(out, "")
@@ -2717,8 +2714,7 @@ class ClinicExternalTest(TestCase):
)
with open(fn, encoding='utf-8') as f:
generated = f.read()
- self.assertTrue(generated.endswith(checksum),
- (generated, checksum))
+ self.assertEndsWith(generated, checksum)
def test_cli_make(self):
c_code = dedent("""
@@ -2835,6 +2831,10 @@ class ClinicExternalTest(TestCase):
"size_t",
"slice_index",
"str",
+ "uint16",
+ "uint32",
+ "uint64",
+ "uint8",
"unicode",
"unsigned_char",
"unsigned_int",
@@ -2863,8 +2863,8 @@ class ClinicExternalTest(TestCase):
# param may change (it's a set, thus unordered). So, let's compare the
# start and end of the expected output, and then assert that the
# converters appear lined up in alphabetical order.
- self.assertTrue(out.startswith(prelude), out)
- self.assertTrue(out.endswith(finale), out)
+ self.assertStartsWith(out, prelude)
+ self.assertEndsWith(out, finale)
out = out.removeprefix(prelude)
out = out.removesuffix(finale)
@@ -2872,10 +2872,7 @@ class ClinicExternalTest(TestCase):
for converter, line in zip(expected_converters, lines):
line = line.lstrip()
with self.subTest(converter=converter):
- self.assertTrue(
- line.startswith(converter),
- f"expected converter {converter!r}, got {line!r}"
- )
+ self.assertStartsWith(line, converter)
def test_cli_fail_converters_and_filename(self):
_, err = self.expect_failure("--converters", "test.c")
@@ -2985,7 +2982,7 @@ class ClinicFunctionalTest(unittest.TestCase):
regex = (
fr"Passing( more than)?( [0-9]+)? positional argument(s)? to "
fr"{re.escape(name)}\(\) is deprecated. Parameters? {pnames} will "
- fr"become( a)? keyword-only parameters? in Python 3\.14"
+ fr"become( a)? keyword-only parameters? in Python 3\.37"
)
self.check_depr(regex, fn, *args, **kwds)
@@ -2998,7 +2995,7 @@ class ClinicFunctionalTest(unittest.TestCase):
regex = (
fr"Passing keyword argument{pl} {pnames} to "
fr"{re.escape(name)}\(\) is deprecated. Parameter{pl} {pnames} "
- fr"will become positional-only in Python 3\.14."
+ fr"will become positional-only in Python 3\.37."
)
self.check_depr(regex, fn, *args, **kwds)
@@ -3782,9 +3779,9 @@ class ClinicFunctionalTest(unittest.TestCase):
fn("a", b="b", c="c", d="d", e="e", f="f", g="g", h="h")
errmsg = (
"Passing more than 1 positional argument to depr_star_multi() is deprecated. "
- "Parameter 'b' will become a keyword-only parameter in Python 3.16. "
- "Parameters 'c' and 'd' will become keyword-only parameters in Python 3.15. "
- "Parameters 'e', 'f' and 'g' will become keyword-only parameters in Python 3.14.")
+ "Parameter 'b' will become a keyword-only parameter in Python 3.39. "
+ "Parameters 'c' and 'd' will become keyword-only parameters in Python 3.38. "
+ "Parameters 'e', 'f' and 'g' will become keyword-only parameters in Python 3.37.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", c="c", d="d", e="e", f="f", g="g", h="h")
check("a", "b", "c", d="d", e="e", f="f", g="g", h="h")
@@ -3883,9 +3880,9 @@ class ClinicFunctionalTest(unittest.TestCase):
fn("a", "b", "c", "d", "e", "f", "g", h="h")
errmsg = (
"Passing keyword arguments 'b', 'c', 'd', 'e', 'f' and 'g' to depr_kwd_multi() is deprecated. "
- "Parameter 'b' will become positional-only in Python 3.14. "
- "Parameters 'c' and 'd' will become positional-only in Python 3.15. "
- "Parameters 'e', 'f' and 'g' will become positional-only in Python 3.16.")
+ "Parameter 'b' will become positional-only in Python 3.37. "
+ "Parameters 'c' and 'd' will become positional-only in Python 3.38. "
+ "Parameters 'e', 'f' and 'g' will become positional-only in Python 3.39.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", "c", "d", "e", "f", g="g", h="h")
check("a", "b", "c", "d", "e", f="f", g="g", h="h")
@@ -3900,8 +3897,8 @@ class ClinicFunctionalTest(unittest.TestCase):
self.assertRaises(TypeError, fn, "a", "b", "c", "d", "e", "f", "g")
errmsg = (
"Passing more than 4 positional arguments to depr_multi() is deprecated. "
- "Parameter 'e' will become a keyword-only parameter in Python 3.15. "
- "Parameter 'f' will become a keyword-only parameter in Python 3.14.")
+ "Parameter 'e' will become a keyword-only parameter in Python 3.38. "
+ "Parameter 'f' will become a keyword-only parameter in Python 3.37.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", "c", "d", "e", "f", g="g")
check("a", "b", "c", "d", "e", f="f", g="g")
@@ -3909,8 +3906,8 @@ class ClinicFunctionalTest(unittest.TestCase):
fn("a", "b", "c", d="d", e="e", f="f", g="g")
errmsg = (
"Passing keyword arguments 'b' and 'c' to depr_multi() is deprecated. "
- "Parameter 'b' will become positional-only in Python 3.14. "
- "Parameter 'c' will become positional-only in Python 3.15.")
+ "Parameter 'b' will become positional-only in Python 3.37. "
+ "Parameter 'c' will become positional-only in Python 3.38.")
check = partial(self.check_depr, re.escape(errmsg), fn)
check("a", "b", c="c", d="d", e="e", f="f", g="g")
check("a", b="b", c="c", d="d", e="e", f="f", g="g")
diff --git a/Lib/test/test_cmd.py b/Lib/test/test_cmd.py
index 46ec82b7049..dbfec42fc21 100644
--- a/Lib/test/test_cmd.py
+++ b/Lib/test/test_cmd.py
@@ -11,9 +11,15 @@ import unittest
import io
import textwrap
from test import support
-from test.support.import_helper import import_module
+from test.support.import_helper import ensure_lazy_imports, import_module
from test.support.pty_helper import run_pty
+class LazyImportTest(unittest.TestCase):
+ @support.cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("cmd", {"inspect", "string"})
+
+
class samplecmdclass(cmd.Cmd):
"""
Instance the sampleclass:
@@ -289,6 +295,30 @@ class CmdTestReadline(unittest.TestCase):
self.assertIn(b'ab_completion_test', output)
self.assertIn(b'tab completion success', output)
+ def test_bang_completion_without_do_shell(self):
+ script = textwrap.dedent("""
+ import cmd
+ class simplecmd(cmd.Cmd):
+ def completedefault(self, text, line, begidx, endidx):
+ return ["hello"]
+
+ def default(self, line):
+ if line.replace(" ", "") == "!hello":
+ print('tab completion success')
+ else:
+ print('tab completion failure')
+ return True
+
+ simplecmd().cmdloop()
+ """)
+
+ # '! h' or '!h' and complete 'ello' to 'hello'
+ for input in [b"! h\t\n", b"!h\t\n"]:
+ with self.subTest(input=input):
+ output = run_pty(script, input)
+ self.assertIn(b'hello', output)
+ self.assertIn(b'tab completion success', output)
+
def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite())
return tests
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index 36f87e259e7..c17d749d4a1 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -39,7 +39,8 @@ class CmdLineTest(unittest.TestCase):
def verify_valid_flag(self, cmd_line):
rc, out, err = assert_python_ok(cmd_line)
- self.assertTrue(out == b'' or out.endswith(b'\n'))
+ if out != b'':
+ self.assertEndsWith(out, b'\n')
self.assertNotIn(b'Traceback', out)
self.assertNotIn(b'Traceback', err)
return out
@@ -89,8 +90,8 @@ class CmdLineTest(unittest.TestCase):
version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii")
for switch in '-V', '--version', '-VV':
rc, out, err = assert_python_ok(switch)
- self.assertFalse(err.startswith(version))
- self.assertTrue(out.startswith(version))
+ self.assertNotStartsWith(err, version)
+ self.assertStartsWith(out, version)
def test_verbose(self):
# -v causes imports to write to stderr. If the write to
@@ -380,7 +381,7 @@ class CmdLineTest(unittest.TestCase):
p.stdin.flush()
data, rc = _kill_python_and_exit_code(p)
self.assertEqual(rc, 0)
- self.assertTrue(data.startswith(b'x'), data)
+ self.assertStartsWith(data, b'x')
def test_large_PYTHONPATH(self):
path1 = "ABCDE" * 100
@@ -972,10 +973,25 @@ class CmdLineTest(unittest.TestCase):
@unittest.skipUnless(support.MS_WINDOWS, 'Test only applicable on Windows')
def test_python_legacy_windows_stdio(self):
- code = "import sys; print(sys.stdin.encoding, sys.stdout.encoding)"
- expected = 'cp'
- rc, out, err = assert_python_ok('-c', code, PYTHONLEGACYWINDOWSSTDIO='1')
- self.assertIn(expected.encode(), out)
+ # Test that _WindowsConsoleIO is used when PYTHONLEGACYWINDOWSSTDIO
+ # is not set.
+ # We cannot use PIPE becase it prevents creating new console.
+ # So we use exit code.
+ code = "import sys; sys.exit(type(sys.stdout.buffer.raw).__name__ != '_WindowsConsoleIO')"
+ env = os.environ.copy()
+ env["PYTHONLEGACYWINDOWSSTDIO"] = ""
+ p = subprocess.run([sys.executable, "-c", code],
+ creationflags=subprocess.CREATE_NEW_CONSOLE,
+ env=env)
+ self.assertEqual(p.returncode, 0)
+
+ # Then test that FIleIO is used when PYTHONLEGACYWINDOWSSTDIO is set.
+ code = "import sys; sys.exit(type(sys.stdout.buffer.raw).__name__ != 'FileIO')"
+ env["PYTHONLEGACYWINDOWSSTDIO"] = "1"
+ p = subprocess.run([sys.executable, "-c", code],
+ creationflags=subprocess.CREATE_NEW_CONSOLE,
+ env=env)
+ self.assertEqual(p.returncode, 0)
@unittest.skipIf("-fsanitize" in sysconfig.get_config_vars().get('PY_CFLAGS', ()),
"PYTHONMALLOCSTATS doesn't work with ASAN")
@@ -1024,7 +1040,7 @@ class CmdLineTest(unittest.TestCase):
stderr=subprocess.PIPE,
text=True)
err_msg = "Unknown option: --unknown-option\nusage: "
- self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr)
+ self.assertStartsWith(proc.stderr, err_msg)
self.assertNotEqual(proc.returncode, 0)
def test_int_max_str_digits(self):
@@ -1158,6 +1174,24 @@ class CmdLineTest(unittest.TestCase):
res = assert_python_ok('-c', code, PYTHON_CPU_COUNT='default')
self.assertEqual(self.res2int(res), (os.cpu_count(), os.process_cpu_count()))
+ def test_import_time(self):
+ # os is not imported at startup
+ code = 'import os; import os'
+
+ for case in 'importtime', 'importtime=1', 'importtime=true':
+ res = assert_python_ok('-X', case, '-c', code)
+ res_err = res.err.decode('utf-8')
+ self.assertRegex(res_err, r'import time: \s*\d+ \| \s*\d+ \| \s*os')
+ self.assertNotRegex(res_err, r'import time: cached\s* \| cached\s* \| os')
+
+ res = assert_python_ok('-X', 'importtime=2', '-c', code)
+ res_err = res.err.decode('utf-8')
+ self.assertRegex(res_err, r'import time: \s*\d+ \| \s*\d+ \| \s*os')
+ self.assertRegex(res_err, r'import time: cached\s* \| cached\s* \| os')
+
+ assert_python_failure('-X', 'importtime=-1', '-c', code)
+ assert_python_failure('-X', 'importtime=3', '-c', code)
+
def res2int(self, res):
out = res.out.strip().decode("utf-8")
return tuple(int(i) for i in out.split())
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index 53dc9b1a7ef..784c45aa96f 100644
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -553,9 +553,9 @@ class CmdLineTest(unittest.TestCase):
exitcode, stdout, stderr = assert_python_failure(script_name)
text = stderr.decode('ascii').split('\n')
self.assertEqual(len(text), 5)
- self.assertTrue(text[0].startswith('Traceback'))
- self.assertTrue(text[1].startswith(' File '))
- self.assertTrue(text[3].startswith('NameError'))
+ self.assertStartsWith(text[0], 'Traceback')
+ self.assertStartsWith(text[1], ' File ')
+ self.assertStartsWith(text[3], 'NameError')
def test_non_ascii(self):
# Apple platforms deny the creation of a file with an invalid UTF-8 name.
@@ -708,9 +708,8 @@ class CmdLineTest(unittest.TestCase):
exitcode, stdout, stderr = assert_python_failure(script_name)
text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
# It used to crash in https://github.com/python/cpython/issues/111132
- self.assertTrue(text.endswith(
- 'SyntaxError: nonlocal declaration not allowed at module level\n',
- ), text)
+ self.assertEndsWith(text,
+ 'SyntaxError: nonlocal declaration not allowed at module level\n')
def test_consistent_sys_path_for_direct_execution(self):
# This test case ensures that the following all give the same
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index 7cf09ee7847..655f5a9be7f 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -220,6 +220,7 @@ try:
import _testinternalcapi
except ModuleNotFoundError:
_testinternalcapi = None
+import test._code_definitions as defs
COPY_FREE_VARS = opmap['COPY_FREE_VARS']
@@ -671,9 +672,82 @@ class CodeTest(unittest.TestCase):
VARARGS = CO_FAST_LOCAL | CO_FAST_ARG_VAR | CO_FAST_ARG_POS
VARKWARGS = CO_FAST_LOCAL | CO_FAST_ARG_VAR | CO_FAST_ARG_KW
- import test._code_definitions as defs
funcs = {
+ defs.simple_script: {},
+ defs.complex_script: {
+ 'obj': CO_FAST_LOCAL,
+ 'pickle': CO_FAST_LOCAL,
+ 'spam_minimal': CO_FAST_LOCAL,
+ 'data': CO_FAST_LOCAL,
+ 'res': CO_FAST_LOCAL,
+ },
+ defs.script_with_globals: {
+ 'obj1': CO_FAST_LOCAL,
+ 'obj2': CO_FAST_LOCAL,
+ },
+ defs.script_with_explicit_empty_return: {},
+ defs.script_with_return: {},
defs.spam_minimal: {},
+ defs.spam_with_builtins: {
+ 'x': CO_FAST_LOCAL,
+ 'values': CO_FAST_LOCAL,
+ 'checks': CO_FAST_LOCAL,
+ 'res': CO_FAST_LOCAL,
+ },
+ defs.spam_with_globals_and_builtins: {
+ 'func1': CO_FAST_LOCAL,
+ 'func2': CO_FAST_LOCAL,
+ 'funcs': CO_FAST_LOCAL,
+ 'checks': CO_FAST_LOCAL,
+ 'res': CO_FAST_LOCAL,
+ },
+ defs.spam_with_global_and_attr_same_name: {},
+ defs.spam_full_args: {
+ 'a': POSONLY,
+ 'b': POSONLY,
+ 'c': POSORKW,
+ 'd': POSORKW,
+ 'e': KWONLY,
+ 'f': KWONLY,
+ 'args': VARARGS,
+ 'kwargs': VARKWARGS,
+ },
+ defs.spam_full_args_with_defaults: {
+ 'a': POSONLY,
+ 'b': POSONLY,
+ 'c': POSORKW,
+ 'd': POSORKW,
+ 'e': KWONLY,
+ 'f': KWONLY,
+ 'args': VARARGS,
+ 'kwargs': VARKWARGS,
+ },
+ defs.spam_args_attrs_and_builtins: {
+ 'a': POSONLY,
+ 'b': POSONLY,
+ 'c': POSORKW,
+ 'd': POSORKW,
+ 'e': KWONLY,
+ 'f': KWONLY,
+ 'args': VARARGS,
+ 'kwargs': VARKWARGS,
+ },
+ defs.spam_returns_arg: {
+ 'x': POSORKW,
+ },
+ defs.spam_raises: {},
+ defs.spam_with_inner_not_closure: {
+ 'eggs': CO_FAST_LOCAL,
+ },
+ defs.spam_with_inner_closure: {
+ 'x': CO_FAST_CELL,
+ 'eggs': CO_FAST_LOCAL,
+ },
+ defs.spam_annotated: {
+ 'a': POSORKW,
+ 'b': POSORKW,
+ 'c': POSORKW,
+ },
defs.spam_full: {
'a': POSONLY,
'b': POSONLY,
@@ -777,6 +851,319 @@ class CodeTest(unittest.TestCase):
kinds = _testinternalcapi.get_co_localskinds(func.__code__)
self.assertEqual(kinds, expected)
+ @unittest.skipIf(_testinternalcapi is None, "missing _testinternalcapi")
+ def test_var_counts(self):
+ self.maxDiff = None
+ def new_var_counts(*,
+ posonly=0,
+ posorkw=0,
+ kwonly=0,
+ varargs=0,
+ varkwargs=0,
+ purelocals=0,
+ argcells=0,
+ othercells=0,
+ freevars=0,
+ globalvars=0,
+ attrs=0,
+ unknown=0,
+ ):
+ nargvars = posonly + posorkw + kwonly + varargs + varkwargs
+ nlocals = nargvars + purelocals + othercells
+ if isinstance(globalvars, int):
+ globalvars = {
+ 'total': globalvars,
+ 'numglobal': 0,
+ 'numbuiltin': 0,
+ 'numunknown': globalvars,
+ }
+ else:
+ g_numunknown = 0
+ if isinstance(globalvars, dict):
+ numglobal = globalvars['numglobal']
+ numbuiltin = globalvars['numbuiltin']
+ size = 2
+ if 'numunknown' in globalvars:
+ g_numunknown = globalvars['numunknown']
+ size += 1
+ assert len(globalvars) == size, globalvars
+ else:
+ assert not isinstance(globalvars, str), repr(globalvars)
+ try:
+ numglobal, numbuiltin = globalvars
+ except ValueError:
+ numglobal, numbuiltin, g_numunknown = globalvars
+ globalvars = {
+ 'total': numglobal + numbuiltin + g_numunknown,
+ 'numglobal': numglobal,
+ 'numbuiltin': numbuiltin,
+ 'numunknown': g_numunknown,
+ }
+ unbound = globalvars['total'] + attrs + unknown
+ return {
+ 'total': nlocals + freevars + unbound,
+ 'locals': {
+ 'total': nlocals,
+ 'args': {
+ 'total': nargvars,
+ 'numposonly': posonly,
+ 'numposorkw': posorkw,
+ 'numkwonly': kwonly,
+ 'varargs': varargs,
+ 'varkwargs': varkwargs,
+ },
+ 'numpure': purelocals,
+ 'cells': {
+ 'total': argcells + othercells,
+ 'numargs': argcells,
+ 'numothers': othercells,
+ },
+ 'hidden': {
+ 'total': 0,
+ 'numpure': 0,
+ 'numcells': 0,
+ },
+ },
+ 'numfree': freevars,
+ 'unbound': {
+ 'total': unbound,
+ 'globals': globalvars,
+ 'numattrs': attrs,
+ 'numunknown': unknown,
+ },
+ }
+
+ funcs = {
+ defs.simple_script: new_var_counts(),
+ defs.complex_script: new_var_counts(
+ purelocals=5,
+ globalvars=1,
+ attrs=2,
+ ),
+ defs.script_with_globals: new_var_counts(
+ purelocals=2,
+ globalvars=1,
+ ),
+ defs.script_with_explicit_empty_return: new_var_counts(),
+ defs.script_with_return: new_var_counts(),
+ defs.spam_minimal: new_var_counts(),
+ defs.spam_minimal: new_var_counts(),
+ defs.spam_with_builtins: new_var_counts(
+ purelocals=4,
+ globalvars=4,
+ ),
+ defs.spam_with_globals_and_builtins: new_var_counts(
+ purelocals=5,
+ globalvars=6,
+ ),
+ defs.spam_with_global_and_attr_same_name: new_var_counts(
+ globalvars=2,
+ attrs=1,
+ ),
+ defs.spam_full_args: new_var_counts(
+ posonly=2,
+ posorkw=2,
+ kwonly=2,
+ varargs=1,
+ varkwargs=1,
+ ),
+ defs.spam_full_args_with_defaults: new_var_counts(
+ posonly=2,
+ posorkw=2,
+ kwonly=2,
+ varargs=1,
+ varkwargs=1,
+ ),
+ defs.spam_args_attrs_and_builtins: new_var_counts(
+ posonly=2,
+ posorkw=2,
+ kwonly=2,
+ varargs=1,
+ varkwargs=1,
+ attrs=1,
+ ),
+ defs.spam_returns_arg: new_var_counts(
+ posorkw=1,
+ ),
+ defs.spam_raises: new_var_counts(
+ globalvars=1,
+ ),
+ defs.spam_with_inner_not_closure: new_var_counts(
+ purelocals=1,
+ ),
+ defs.spam_with_inner_closure: new_var_counts(
+ othercells=1,
+ purelocals=1,
+ ),
+ defs.spam_annotated: new_var_counts(
+ posorkw=3,
+ ),
+ defs.spam_full: new_var_counts(
+ posonly=2,
+ posorkw=2,
+ kwonly=2,
+ varargs=1,
+ varkwargs=1,
+ purelocals=4,
+ globalvars=3,
+ attrs=1,
+ ),
+ defs.spam: new_var_counts(
+ posorkw=1,
+ ),
+ defs.spam_N: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ ),
+ defs.spam_C: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ argcells=1,
+ othercells=1,
+ ),
+ defs.spam_NN: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ ),
+ defs.spam_NC: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ argcells=1,
+ othercells=1,
+ ),
+ defs.spam_CN: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ argcells=1,
+ othercells=1,
+ ),
+ defs.spam_CC: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ argcells=1,
+ othercells=1,
+ ),
+ defs.eggs_nested: new_var_counts(
+ posorkw=1,
+ ),
+ defs.eggs_closure: new_var_counts(
+ posorkw=1,
+ freevars=2,
+ ),
+ defs.eggs_nested_N: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ ),
+ defs.eggs_nested_C: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ argcells=1,
+ freevars=2,
+ ),
+ defs.eggs_closure_N: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ freevars=2,
+ ),
+ defs.eggs_closure_C: new_var_counts(
+ posorkw=1,
+ purelocals=1,
+ argcells=1,
+ othercells=1,
+ freevars=2,
+ ),
+ defs.ham_nested: new_var_counts(
+ posorkw=1,
+ ),
+ defs.ham_closure: new_var_counts(
+ posorkw=1,
+ freevars=3,
+ ),
+ defs.ham_C_nested: new_var_counts(
+ posorkw=1,
+ ),
+ defs.ham_C_closure: new_var_counts(
+ posorkw=1,
+ freevars=4,
+ ),
+ }
+ assert len(funcs) == len(defs.FUNCTIONS), (len(funcs), len(defs.FUNCTIONS))
+ for func in defs.FUNCTIONS:
+ with self.subTest(func):
+ expected = funcs[func]
+ counts = _testinternalcapi.get_code_var_counts(func.__code__)
+ self.assertEqual(counts, expected)
+
+ func = defs.spam_with_globals_and_builtins
+ with self.subTest(f'{func} code'):
+ expected = new_var_counts(
+ purelocals=5,
+ globalvars=6,
+ )
+ counts = _testinternalcapi.get_code_var_counts(func.__code__)
+ self.assertEqual(counts, expected)
+
+ with self.subTest(f'{func} with own globals and builtins'):
+ expected = new_var_counts(
+ purelocals=5,
+ globalvars=(2, 4),
+ )
+ counts = _testinternalcapi.get_code_var_counts(func)
+ self.assertEqual(counts, expected)
+
+ with self.subTest(f'{func} without globals'):
+ expected = new_var_counts(
+ purelocals=5,
+ globalvars=(0, 4, 2),
+ )
+ counts = _testinternalcapi.get_code_var_counts(func, globalsns={})
+ self.assertEqual(counts, expected)
+
+ with self.subTest(f'{func} without both'):
+ expected = new_var_counts(
+ purelocals=5,
+ globalvars=6,
+ )
+ counts = _testinternalcapi.get_code_var_counts(func, globalsns={},
+ builtinsns={})
+ self.assertEqual(counts, expected)
+
+ with self.subTest(f'{func} without builtins'):
+ expected = new_var_counts(
+ purelocals=5,
+ globalvars=(2, 0, 4),
+ )
+ counts = _testinternalcapi.get_code_var_counts(func, builtinsns={})
+ self.assertEqual(counts, expected)
+
+ @unittest.skipIf(_testinternalcapi is None, "missing _testinternalcapi")
+ def test_stateless(self):
+ self.maxDiff = None
+
+ STATELESS_FUNCTIONS = [
+ *defs.STATELESS_FUNCTIONS,
+ # stateless with defaults
+ defs.spam_full_args_with_defaults,
+ ]
+
+ for func in defs.STATELESS_CODE:
+ with self.subTest((func, '(code)')):
+ _testinternalcapi.verify_stateless_code(func.__code__)
+ for func in STATELESS_FUNCTIONS:
+ with self.subTest((func, '(func)')):
+ _testinternalcapi.verify_stateless_code(func)
+
+ for func in defs.FUNCTIONS:
+ if func not in defs.STATELESS_CODE:
+ with self.subTest((func, '(code)')):
+ with self.assertRaises(Exception):
+ _testinternalcapi.verify_stateless_code(func.__code__)
+
+ if func not in STATELESS_FUNCTIONS:
+ with self.subTest((func, '(func)')):
+ with self.assertRaises(Exception):
+ _testinternalcapi.verify_stateless_code(func)
+
def isinterned(s):
return s is sys.intern(('_' + s + '_')[1:-1])
diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py
index 57fb130070b..3642b47c2c1 100644
--- a/Lib/test/test_code_module.py
+++ b/Lib/test/test_code_module.py
@@ -133,7 +133,7 @@ class TestInteractiveConsole(unittest.TestCase, MockSys):
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
output = output[output.index('(InteractiveConsole)'):]
output = output[output.index('\n') + 1:]
- self.assertTrue(output.startswith('UnicodeEncodeError: '), output)
+ self.assertStartsWith(output, 'UnicodeEncodeError: ')
self.assertIs(self.sysmod.last_type, UnicodeEncodeError)
self.assertIs(type(self.sysmod.last_value), UnicodeEncodeError)
self.assertIsNone(self.sysmod.last_traceback)
diff --git a/Lib/test/test_codeccallbacks.py b/Lib/test/test_codeccallbacks.py
index 86e5e5c1474..65d54d1004d 100644
--- a/Lib/test/test_codeccallbacks.py
+++ b/Lib/test/test_codeccallbacks.py
@@ -1125,7 +1125,7 @@ class CodecCallbackTest(unittest.TestCase):
text = 'abc<def>ghi'*n
text.translate(charmap)
- def test_mutatingdecodehandler(self):
+ def test_mutating_decode_handler(self):
baddata = [
("ascii", b"\xff"),
("utf-7", b"++"),
@@ -1160,6 +1160,42 @@ class CodecCallbackTest(unittest.TestCase):
for (encoding, data) in baddata:
self.assertEqual(data.decode(encoding, "test.mutating"), "\u4242")
+ def test_mutating_decode_handler_unicode_escape(self):
+ decode = codecs.unicode_escape_decode
+ def mutating(exc):
+ if isinstance(exc, UnicodeDecodeError):
+ r = data.get(exc.object[:exc.end])
+ if r is not None:
+ exc.object = r[0] + exc.object[exc.end:]
+ return ('\u0404', r[1])
+ raise AssertionError("don't know how to handle %r" % exc)
+
+ codecs.register_error('test.mutating2', mutating)
+ data = {
+ br'\x0': (b'\\', 0),
+ br'\x3': (b'xxx\\', 3),
+ br'\x5': (b'x\\', 1),
+ }
+ def check(input, expected, msg):
+ with self.assertWarns(DeprecationWarning) as cm:
+ self.assertEqual(decode(input, 'test.mutating2'), (expected, len(input)))
+ self.assertIn(msg, str(cm.warning))
+
+ check(br'\x0n\z', '\u0404\n\\z', r'"\z" is an invalid escape sequence')
+ check(br'\x0n\501', '\u0404\n\u0141', r'"\501" is an invalid octal escape sequence')
+ check(br'\x0z', '\u0404\\z', r'"\z" is an invalid escape sequence')
+
+ check(br'\x3n\zr', '\u0404\n\\zr', r'"\z" is an invalid escape sequence')
+ check(br'\x3zr', '\u0404\\zr', r'"\z" is an invalid escape sequence')
+ check(br'\x3z5', '\u0404\\z5', r'"\z" is an invalid escape sequence')
+ check(memoryview(br'\x3z5x')[:-1], '\u0404\\z5', r'"\z" is an invalid escape sequence')
+ check(memoryview(br'\x3z5xy')[:-2], '\u0404\\z5', r'"\z" is an invalid escape sequence')
+
+ check(br'\x5n\z', '\u0404\n\\z', r'"\z" is an invalid escape sequence')
+ check(br'\x5n\501', '\u0404\n\u0141', r'"\501" is an invalid octal escape sequence')
+ check(br'\x5z', '\u0404\\z', r'"\z" is an invalid escape sequence')
+ check(memoryview(br'\x5zy')[:-1], '\u0404\\z', r'"\z" is an invalid escape sequence')
+
# issue32583
def test_crashing_decode_handler(self):
# better generating one more character to fill the extra space slot
diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
index 94fcf98e757..d8666f7290e 100644
--- a/Lib/test/test_codecs.py
+++ b/Lib/test/test_codecs.py
@@ -1,8 +1,10 @@
import codecs
import contextlib
import copy
+import importlib
import io
import pickle
+import os
import sys
import unittest
import encodings
@@ -1196,23 +1198,39 @@ class EscapeDecodeTest(unittest.TestCase):
check(br"[\1010]", b"[A0]")
check(br"[\x41]", b"[A]")
check(br"[\x410]", b"[A0]")
+
+ def test_warnings(self):
+ decode = codecs.escape_decode
+ check = coding_checker(self, decode)
for i in range(97, 123):
b = bytes([i])
if b not in b'abfnrtvx':
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\%c" is an invalid escape sequence' % i):
check(b"\\" + b, b"\\" + b)
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\%c" is an invalid escape sequence' % (i-32)):
check(b"\\" + b.upper(), b"\\" + b.upper())
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\8" is an invalid escape sequence'):
check(br"\8", b"\\8")
with self.assertWarns(DeprecationWarning):
check(br"\9", b"\\9")
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\\xfa" is an invalid escape sequence') as cm:
check(b"\\\xfa", b"\\\xfa")
for i in range(0o400, 0o1000):
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\%o" is an invalid octal escape sequence' % i):
check(rb'\%o' % i, bytes([i & 0o377]))
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\z" is an invalid escape sequence'):
+ self.assertEqual(decode(br'\x\z', 'ignore'), (b'\\z', 4))
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\501" is an invalid octal escape sequence'):
+ self.assertEqual(decode(br'\x\501', 'ignore'), (b'A', 6))
+
def test_errors(self):
decode = codecs.escape_decode
self.assertRaises(ValueError, decode, br"\x")
@@ -2661,24 +2679,40 @@ class UnicodeEscapeTest(ReadTest, unittest.TestCase):
check(br"[\x410]", "[A0]")
check(br"\u20ac", "\u20ac")
check(br"\U0001d120", "\U0001d120")
+
+ def test_decode_warnings(self):
+ decode = codecs.unicode_escape_decode
+ check = coding_checker(self, decode)
for i in range(97, 123):
b = bytes([i])
if b not in b'abfnrtuvx':
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\%c" is an invalid escape sequence' % i):
check(b"\\" + b, "\\" + chr(i))
if b.upper() not in b'UN':
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\%c" is an invalid escape sequence' % (i-32)):
check(b"\\" + b.upper(), "\\" + chr(i-32))
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\8" is an invalid escape sequence'):
check(br"\8", "\\8")
with self.assertWarns(DeprecationWarning):
check(br"\9", "\\9")
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\\xfa" is an invalid escape sequence') as cm:
check(b"\\\xfa", "\\\xfa")
for i in range(0o400, 0o1000):
- with self.assertWarns(DeprecationWarning):
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\%o" is an invalid octal escape sequence' % i):
check(rb'\%o' % i, chr(i))
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\z" is an invalid escape sequence'):
+ self.assertEqual(decode(br'\x\z', 'ignore'), ('\\z', 4))
+ with self.assertWarnsRegex(DeprecationWarning,
+ r'"\\501" is an invalid octal escape sequence'):
+ self.assertEqual(decode(br'\x\501', 'ignore'), ('\u0141', 6))
+
def test_decode_errors(self):
decode = codecs.unicode_escape_decode
for c, d in (b'x', 2), (b'u', 4), (b'U', 4):
@@ -3075,6 +3109,13 @@ class TransformCodecTest(unittest.TestCase):
info = codecs.lookup(alias)
self.assertEqual(info.name, expected_name)
+ def test_alias_modules_exist(self):
+ encodings_dir = os.path.dirname(encodings.__file__)
+ for value in encodings.aliases.aliases.values():
+ codec_mod = f"encodings.{value}"
+ self.assertIsNotNone(importlib.util.find_spec(codec_mod),
+ f"Codec module not found: {codec_mod}")
+
def test_quopri_stateless(self):
# Should encode with quotetabs=True
encoded = codecs.encode(b"space tab\teol \n", "quopri-codec")
@@ -3762,7 +3803,7 @@ class LocaleCodecTest(unittest.TestCase):
with self.assertRaises(RuntimeError) as cm:
self.decode(encoded, errors)
errmsg = str(cm.exception)
- self.assertTrue(errmsg.startswith("decode error: "), errmsg)
+ self.assertStartsWith(errmsg, "decode error: ")
else:
decoded = self.decode(encoded, errors)
self.assertEqual(decoded, expected)
diff --git a/Lib/test/test_codeop.py b/Lib/test/test_codeop.py
index 0eefc22d11b..ed10bd3dcb6 100644
--- a/Lib/test/test_codeop.py
+++ b/Lib/test/test_codeop.py
@@ -322,7 +322,7 @@ class CodeopTests(unittest.TestCase):
dedent("""\
def foo(x,x):
pass
- """), "duplicate argument 'x' in function definition")
+ """), "duplicate parameter 'x' in function definition")
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index 1e93530398b..d9d61e5c205 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -542,6 +542,8 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(Dot(1)._replace(d=999), (999,))
self.assertEqual(Dot(1)._fields, ('d',))
+ @support.requires_resource('cpu')
+ def test_large_size(self):
n = support.exceeds_recursion_limit()
names = list(set(''.join([choice(string.ascii_letters)
for j in range(10)]) for i in range(n)))
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index a580a240d9f..8384c183dd9 100644
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -316,7 +316,7 @@ class CompileallTestsBase:
self.assertTrue(mods)
for mod in mods:
- self.assertTrue(mod.startswith(self.directory), mod)
+ self.assertStartsWith(mod, self.directory)
modcode = importlib.util.cache_from_source(mod)
modpath = mod[len(self.directory+os.sep):]
_, _, err = script_helper.assert_python_failure(modcode)
diff --git a/Lib/test/test_compiler_assemble.py b/Lib/test/test_compiler_assemble.py
index c4962e35999..99a11e99d56 100644
--- a/Lib/test/test_compiler_assemble.py
+++ b/Lib/test/test_compiler_assemble.py
@@ -146,4 +146,4 @@ class IsolatedAssembleTests(AssemblerTestCase):
L1 to L2 -> L2 [0]
L2 to L3 -> L3 [1] lasti
""")
- self.assertTrue(output.getvalue().endswith(exc_table))
+ self.assertEndsWith(output.getvalue(), exc_table)
diff --git a/Lib/test/test_concurrent_futures/test_future.py b/Lib/test/test_concurrent_futures/test_future.py
index 4066ea1ee4b..06b11a3bacf 100644
--- a/Lib/test/test_concurrent_futures/test_future.py
+++ b/Lib/test/test_concurrent_futures/test_future.py
@@ -6,6 +6,7 @@ from concurrent.futures._base import (
PENDING, RUNNING, CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED, Future)
from test import support
+from test.support import threading_helper
from .util import (
PENDING_FUTURE, RUNNING_FUTURE, CANCELLED_FUTURE,
@@ -282,6 +283,62 @@ class FutureTests(BaseTestCase):
self.assertEqual(f.exception(), e)
+ def test_get_snapshot(self):
+ """Test the _get_snapshot method for atomic state retrieval."""
+ # Test with a pending future
+ f = Future()
+ done, cancelled, result, exception = f._get_snapshot()
+ self.assertFalse(done)
+ self.assertFalse(cancelled)
+ self.assertIsNone(result)
+ self.assertIsNone(exception)
+
+ # Test with a finished future (successful result)
+ f = Future()
+ f.set_result(42)
+ done, cancelled, result, exception = f._get_snapshot()
+ self.assertTrue(done)
+ self.assertFalse(cancelled)
+ self.assertEqual(result, 42)
+ self.assertIsNone(exception)
+
+ # Test with a finished future (exception)
+ f = Future()
+ exc = ValueError("test error")
+ f.set_exception(exc)
+ done, cancelled, result, exception = f._get_snapshot()
+ self.assertTrue(done)
+ self.assertFalse(cancelled)
+ self.assertIsNone(result)
+ self.assertIs(exception, exc)
+
+ # Test with a cancelled future
+ f = Future()
+ f.cancel()
+ done, cancelled, result, exception = f._get_snapshot()
+ self.assertTrue(done)
+ self.assertTrue(cancelled)
+ self.assertIsNone(result)
+ self.assertIsNone(exception)
+
+ # Test concurrent access (basic thread safety check)
+ f = Future()
+ f.set_result(100)
+ results = []
+
+ def get_snapshot():
+ for _ in range(1000):
+ snapshot = f._get_snapshot()
+ results.append(snapshot)
+
+ threads = [threading.Thread(target=get_snapshot) for _ in range(4)]
+ with threading_helper.start_threads(threads):
+ pass
+ # All snapshots should be identical for a finished future
+ expected = (True, False, 100, None)
+ for result in results:
+ self.assertEqual(result, expected)
+
def setUpModule():
setup_module()
diff --git a/Lib/test/test_concurrent_futures/test_init.py b/Lib/test/test_concurrent_futures/test_init.py
index df640929309..6b8484c0d5f 100644
--- a/Lib/test/test_concurrent_futures/test_init.py
+++ b/Lib/test/test_concurrent_futures/test_init.py
@@ -20,6 +20,10 @@ INITIALIZER_STATUS = 'uninitialized'
def init(x):
global INITIALIZER_STATUS
INITIALIZER_STATUS = x
+ # InterpreterPoolInitializerTest.test_initializer fails
+ # if we don't have a LOAD_GLOBAL. (It could be any global.)
+ # We will address this separately.
+ INITIALIZER_STATUS
def get_init_status():
return INITIALIZER_STATUS
diff --git a/Lib/test/test_concurrent_futures/test_interpreter_pool.py b/Lib/test/test_concurrent_futures/test_interpreter_pool.py
index f6c62ae4b20..844dfdd6fc9 100644
--- a/Lib/test/test_concurrent_futures/test_interpreter_pool.py
+++ b/Lib/test/test_concurrent_futures/test_interpreter_pool.py
@@ -2,35 +2,78 @@ import asyncio
import contextlib
import io
import os
-import pickle
+import sys
import time
import unittest
-from concurrent.futures.interpreter import (
- ExecutionFailed, BrokenInterpreterPool,
-)
+from concurrent.futures.interpreter import BrokenInterpreterPool
+from concurrent import interpreters
+from concurrent.interpreters import _queues as queues
import _interpreters
from test import support
+from test.support import os_helper
+from test.support import script_helper
import test.test_asyncio.utils as testasyncio_utils
-from test.support.interpreters import queues
from .executor import ExecutorTest, mul
from .util import BaseTestCase, InterpreterPoolMixin, setup_module
+WINDOWS = sys.platform.startswith('win')
+
+
+@contextlib.contextmanager
+def nonblocking(fd):
+ blocking = os.get_blocking(fd)
+ if blocking:
+ os.set_blocking(fd, False)
+ try:
+ yield
+ finally:
+ if blocking:
+ os.set_blocking(fd, blocking)
+
+
+def read_file_with_timeout(fd, nbytes, timeout):
+ with nonblocking(fd):
+ end = time.time() + timeout
+ try:
+ return os.read(fd, nbytes)
+ except BlockingIOError:
+ pass
+ while time.time() < end:
+ try:
+ return os.read(fd, nbytes)
+ except BlockingIOError:
+ continue
+ else:
+ raise TimeoutError('nothing to read')
+
+
+if not WINDOWS:
+ import select
+ def read_file_with_timeout(fd, nbytes, timeout):
+ r, _, _ = select.select([fd], [], [], timeout)
+ if fd not in r:
+ raise TimeoutError('nothing to read')
+ return os.read(fd, nbytes)
+
+
def noop():
pass
def write_msg(fd, msg):
+ import os
os.write(fd, msg + b'\0')
-def read_msg(fd):
+def read_msg(fd, timeout=10.0):
msg = b''
- while ch := os.read(fd, 1):
- if ch == b'\0':
- return msg
+ ch = read_file_with_timeout(fd, 1, timeout)
+ while ch != b'\0':
msg += ch
+ ch = os.read(fd, 1)
+ return msg
def get_current_name():
@@ -113,6 +156,38 @@ class InterpreterPoolExecutorTest(
self.assertEqual(before, b'\0')
self.assertEqual(after, msg)
+ def test_init_with___main___global(self):
+ # See https://github.com/python/cpython/pull/133957#issuecomment-2927415311.
+ text = """if True:
+ from concurrent.futures import InterpreterPoolExecutor
+
+ INITIALIZER_STATUS = 'uninitialized'
+
+ def init(x):
+ global INITIALIZER_STATUS
+ INITIALIZER_STATUS = x
+ INITIALIZER_STATUS
+
+ def get_init_status():
+ return INITIALIZER_STATUS
+
+ if __name__ == "__main__":
+ exe = InterpreterPoolExecutor(initializer=init,
+ initargs=('initialized',))
+ fut = exe.submit(get_init_status)
+ print(fut.result()) # 'initialized'
+ exe.shutdown(wait=True)
+ print(INITIALIZER_STATUS) # 'uninitialized'
+ """
+ with os_helper.temp_dir() as tempdir:
+ filename = script_helper.make_script(tempdir, 'my-script', text)
+ res = script_helper.assert_python_ok(filename)
+ stdout = res.out.decode('utf-8').strip()
+ self.assertEqual(stdout.splitlines(), [
+ 'initialized',
+ 'uninitialized',
+ ])
+
def test_init_closure(self):
count = 0
def init1():
@@ -121,10 +196,19 @@ class InterpreterPoolExecutorTest(
nonlocal count
count += 1
- with self.assertRaises(pickle.PicklingError):
- self.executor_type(initializer=init1)
- with self.assertRaises(pickle.PicklingError):
- self.executor_type(initializer=init2)
+ with contextlib.redirect_stderr(io.StringIO()) as stderr:
+ with self.executor_type(initializer=init1) as executor:
+ fut = executor.submit(lambda: None)
+ self.assertIn('NotShareableError', stderr.getvalue())
+ with self.assertRaises(BrokenInterpreterPool):
+ fut.result()
+
+ with contextlib.redirect_stderr(io.StringIO()) as stderr:
+ with self.executor_type(initializer=init2) as executor:
+ fut = executor.submit(lambda: None)
+ self.assertIn('NotShareableError', stderr.getvalue())
+ with self.assertRaises(BrokenInterpreterPool):
+ fut.result()
def test_init_instance_method(self):
class Spam:
@@ -132,26 +216,12 @@ class InterpreterPoolExecutorTest(
raise NotImplementedError
spam = Spam()
- with self.assertRaises(pickle.PicklingError):
- self.executor_type(initializer=spam.initializer)
-
- def test_init_shared(self):
- msg = b'eggs'
- r, w = self.pipe()
- script = f"""if True:
- import os
- if __name__ != '__main__':
- import __main__
- spam = __main__.spam
- os.write({w}, spam + b'\\0')
- """
-
- executor = self.executor_type(shared={'spam': msg})
- fut = executor.submit(exec, script)
- fut.result()
- after = read_msg(r)
-
- self.assertEqual(after, msg)
+ with contextlib.redirect_stderr(io.StringIO()) as stderr:
+ with self.executor_type(initializer=spam.initializer) as executor:
+ fut = executor.submit(lambda: None)
+ self.assertIn('NotShareableError', stderr.getvalue())
+ with self.assertRaises(BrokenInterpreterPool):
+ fut.result()
@unittest.expectedFailure
def test_init_exception_in_script(self):
@@ -178,8 +248,6 @@ class InterpreterPoolExecutorTest(
stderr = stderr.getvalue()
self.assertIn('ExecutionFailed: Exception: spam', stderr)
self.assertIn('Uncaught in the interpreter:', stderr)
- self.assertIn('The above exception was the direct cause of the following exception:',
- stderr)
@unittest.expectedFailure
def test_submit_script(self):
@@ -208,10 +276,14 @@ class InterpreterPoolExecutorTest(
return spam
executor = self.executor_type()
- with self.assertRaises(pickle.PicklingError):
- executor.submit(task1)
- with self.assertRaises(pickle.PicklingError):
- executor.submit(task2)
+
+ fut = executor.submit(task1)
+ with self.assertRaises(_interpreters.NotShareableError):
+ fut.result()
+
+ fut = executor.submit(task2)
+ with self.assertRaises(_interpreters.NotShareableError):
+ fut.result()
def test_submit_local_instance(self):
class Spam:
@@ -219,8 +291,9 @@ class InterpreterPoolExecutorTest(
self.value = True
executor = self.executor_type()
- with self.assertRaises(pickle.PicklingError):
- executor.submit(Spam)
+ fut = executor.submit(Spam)
+ with self.assertRaises(_interpreters.NotShareableError):
+ fut.result()
def test_submit_instance_method(self):
class Spam:
@@ -229,8 +302,9 @@ class InterpreterPoolExecutorTest(
spam = Spam()
executor = self.executor_type()
- with self.assertRaises(pickle.PicklingError):
- executor.submit(spam.run)
+ fut = executor.submit(spam.run)
+ with self.assertRaises(_interpreters.NotShareableError):
+ fut.result()
def test_submit_func_globals(self):
executor = self.executor_type()
@@ -242,13 +316,14 @@ class InterpreterPoolExecutorTest(
@unittest.expectedFailure
def test_submit_exception_in_script(self):
+ # Scripts are not supported currently.
fut = self.executor.submit('raise Exception("spam")')
with self.assertRaises(Exception) as captured:
fut.result()
self.assertIs(type(captured.exception), Exception)
self.assertEqual(str(captured.exception), 'spam')
cause = captured.exception.__cause__
- self.assertIs(type(cause), ExecutionFailed)
+ self.assertIs(type(cause), interpreters.ExecutionFailed)
for attr in ('__name__', '__qualname__', '__module__'):
self.assertEqual(getattr(cause.excinfo.type, attr),
getattr(Exception, attr))
@@ -261,7 +336,7 @@ class InterpreterPoolExecutorTest(
self.assertIs(type(captured.exception), Exception)
self.assertEqual(str(captured.exception), 'spam')
cause = captured.exception.__cause__
- self.assertIs(type(cause), ExecutionFailed)
+ self.assertIs(type(cause), interpreters.ExecutionFailed)
for attr in ('__name__', '__qualname__', '__module__'):
self.assertEqual(getattr(cause.excinfo.type, attr),
getattr(Exception, attr))
@@ -269,16 +344,93 @@ class InterpreterPoolExecutorTest(
def test_saturation(self):
blocker = queues.create()
- executor = self.executor_type(4, shared=dict(blocker=blocker))
+ executor = self.executor_type(4)
for i in range(15 * executor._max_workers):
- executor.submit(exec, 'import __main__; __main__.blocker.get()')
- #executor.submit('blocker.get()')
+ executor.submit(blocker.get)
self.assertEqual(len(executor._threads), executor._max_workers)
for i in range(15 * executor._max_workers):
blocker.put_nowait(None)
executor.shutdown(wait=True)
+ def test_blocking(self):
+ # There is no guarantee that a worker will be created for every
+ # submitted task. That's because there's a race between:
+ #
+ # * a new worker thread, created when task A was just submitted,
+ # becoming non-idle when it picks up task A
+ # * after task B is added to the queue, a new worker thread
+ # is started only if there are no idle workers
+ # (the check in ThreadPoolExecutor._adjust_thread_count())
+ #
+ # That means we must not block waiting for *all* tasks to report
+ # "ready" before we unblock the known-ready workers.
+ ready = queues.create()
+ blocker = queues.create()
+
+ def run(taskid, ready, blocker):
+ # There can't be any globals here.
+ ready.put_nowait(taskid)
+ blocker.get() # blocking
+
+ numtasks = 10
+ futures = []
+ with self.executor_type() as executor:
+ # Request the jobs.
+ for i in range(numtasks):
+ fut = executor.submit(run, i, ready, blocker)
+ futures.append(fut)
+ pending = numtasks
+ while pending > 0:
+ # Wait for any to be ready.
+ done = 0
+ for _ in range(pending):
+ try:
+ ready.get(timeout=1) # blocking
+ except interpreters.QueueEmpty:
+ pass
+ else:
+ done += 1
+ pending -= done
+ # Unblock the workers.
+ for _ in range(done):
+ blocker.put_nowait(None)
+
+ def test_blocking_with_limited_workers(self):
+ # This is essentially the same as test_blocking,
+ # but we explicitly force a limited number of workers,
+ # instead of it happening implicitly sometimes due to a race.
+ ready = queues.create()
+ blocker = queues.create()
+
+ def run(taskid, ready, blocker):
+ # There can't be any globals here.
+ ready.put_nowait(taskid)
+ blocker.get() # blocking
+
+ numtasks = 10
+ futures = []
+ with self.executor_type(4) as executor:
+ # Request the jobs.
+ for i in range(numtasks):
+ fut = executor.submit(run, i, ready, blocker)
+ futures.append(fut)
+ pending = numtasks
+ while pending > 0:
+ # Wait for any to be ready.
+ done = 0
+ for _ in range(pending):
+ try:
+ ready.get(timeout=1) # blocking
+ except interpreters.QueueEmpty:
+ pass
+ else:
+ done += 1
+ pending -= done
+ # Unblock the workers.
+ for _ in range(done):
+ blocker.put_nowait(None)
+
@support.requires_gil_enabled("gh-117344: test is flaky without the GIL")
def test_idle_thread_reuse(self):
executor = self.executor_type()
@@ -289,12 +441,21 @@ class InterpreterPoolExecutorTest(
executor.shutdown(wait=True)
def test_pickle_errors_propagate(self):
- # GH-125864: Pickle errors happen before the script tries to execute, so the
- # queue used to wait infinitely.
-
+ # GH-125864: Pickle errors happen before the script tries to execute,
+ # so the queue used to wait infinitely.
fut = self.executor.submit(PickleShenanigans(0))
- with self.assertRaisesRegex(RuntimeError, "gotcha"):
+ expected = interpreters.NotShareableError
+ with self.assertRaisesRegex(expected, 'args not shareable') as cm:
fut.result()
+ self.assertRegex(str(cm.exception.__cause__), 'unpickled')
+
+ def test_no_stale_references(self):
+ # Weak references don't cross between interpreters.
+ raise unittest.SkipTest('not applicable')
+
+ def test_free_reference(self):
+ # Weak references don't cross between interpreters.
+ raise unittest.SkipTest('not applicable')
class AsyncioTest(InterpretersMixin, testasyncio_utils.TestCase):
diff --git a/Lib/test/test_concurrent_futures/test_shutdown.py b/Lib/test/test_concurrent_futures/test_shutdown.py
index 7a4065afd46..99b315b47e2 100644
--- a/Lib/test/test_concurrent_futures/test_shutdown.py
+++ b/Lib/test/test_concurrent_futures/test_shutdown.py
@@ -330,6 +330,64 @@ class ProcessPoolShutdownTest(ExecutorShutdownTest):
# shutdown.
assert all([r == abs(v) for r, v in zip(res, range(-5, 5))])
+ @classmethod
+ def _failing_task_gh_132969(cls, n):
+ raise ValueError("failing task")
+
+ @classmethod
+ def _good_task_gh_132969(cls, n):
+ time.sleep(0.1 * n)
+ return n
+
+ def _run_test_issue_gh_132969(self, max_workers):
+ # max_workers=2 will repro exception
+ # max_workers=4 will repro exception and then hang
+
+ # Repro conditions
+ # max_tasks_per_child=1
+ # a task ends abnormally
+ # shutdown(wait=False) is called
+ start_method = self.get_context().get_start_method()
+ if (start_method == "fork" or
+ (start_method == "forkserver" and sys.platform.startswith("win"))):
+ self.skipTest(f"Skipping test for {start_method = }")
+ executor = futures.ProcessPoolExecutor(
+ max_workers=max_workers,
+ max_tasks_per_child=1,
+ mp_context=self.get_context())
+ f1 = executor.submit(ProcessPoolShutdownTest._good_task_gh_132969, 1)
+ f2 = executor.submit(ProcessPoolShutdownTest._failing_task_gh_132969, 2)
+ f3 = executor.submit(ProcessPoolShutdownTest._good_task_gh_132969, 3)
+ result = 0
+ try:
+ result += f1.result()
+ result += f2.result()
+ result += f3.result()
+ except ValueError:
+ # stop processing results upon first exception
+ pass
+
+ # Ensure that the executor cleans up after called
+ # shutdown with wait=False
+ executor_manager_thread = executor._executor_manager_thread
+ executor.shutdown(wait=False)
+ time.sleep(0.2)
+ executor_manager_thread.join()
+ return result
+
+ def test_shutdown_gh_132969_case_1(self):
+ # gh-132969: test that exception "object of type 'NoneType' has no len()"
+ # is not raised when shutdown(wait=False) is called.
+ result = self._run_test_issue_gh_132969(2)
+ self.assertEqual(result, 1)
+
+ def test_shutdown_gh_132969_case_2(self):
+ # gh-132969: test that process does not hang and
+ # exception "object of type 'NoneType' has no len()" is not raised
+ # when shutdown(wait=False) is called.
+ result = self._run_test_issue_gh_132969(4)
+ self.assertEqual(result, 1)
+
create_executor_tests(globals(), ProcessPoolShutdownTest,
executor_mixins=(ProcessPoolForkMixin,
diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py
index 23904d17d32..e7364e18742 100644
--- a/Lib/test/test_configparser.py
+++ b/Lib/test/test_configparser.py
@@ -986,12 +986,12 @@ class ConfigParserTestCase(BasicTestCase, unittest.TestCase):
def test_defaults_keyword(self):
"""bpo-23835 fix for ConfigParser"""
- cf = self.newconfig(defaults={1: 2.4})
- self.assertEqual(cf[self.default_section]['1'], '2.4')
- self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
- cf = self.newconfig(defaults={"A": 5.2})
- self.assertEqual(cf[self.default_section]['a'], '5.2')
- self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
+ cf = self.newconfig(defaults={1: 2.5})
+ self.assertEqual(cf[self.default_section]['1'], '2.5')
+ self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.5)
+ cf = self.newconfig(defaults={"A": 5.25})
+ self.assertEqual(cf[self.default_section]['a'], '5.25')
+ self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.25)
class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase):
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index cf651959803..6a3329fa5aa 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -48,23 +48,23 @@ class TestAbstractContextManager(unittest.TestCase):
def __exit__(self, exc_type, exc_value, traceback):
return None
- self.assertTrue(issubclass(ManagerFromScratch, AbstractContextManager))
+ self.assertIsSubclass(ManagerFromScratch, AbstractContextManager)
class DefaultEnter(AbstractContextManager):
def __exit__(self, *args):
super().__exit__(*args)
- self.assertTrue(issubclass(DefaultEnter, AbstractContextManager))
+ self.assertIsSubclass(DefaultEnter, AbstractContextManager)
class NoEnter(ManagerFromScratch):
__enter__ = None
- self.assertFalse(issubclass(NoEnter, AbstractContextManager))
+ self.assertNotIsSubclass(NoEnter, AbstractContextManager)
class NoExit(ManagerFromScratch):
__exit__ = None
- self.assertFalse(issubclass(NoExit, AbstractContextManager))
+ self.assertNotIsSubclass(NoExit, AbstractContextManager)
class ContextManagerTestCase(unittest.TestCase):
diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py
index 7750186e56a..dcd00720379 100644
--- a/Lib/test/test_contextlib_async.py
+++ b/Lib/test/test_contextlib_async.py
@@ -77,23 +77,23 @@ class TestAbstractAsyncContextManager(unittest.TestCase):
async def __aexit__(self, exc_type, exc_value, traceback):
return None
- self.assertTrue(issubclass(ManagerFromScratch, AbstractAsyncContextManager))
+ self.assertIsSubclass(ManagerFromScratch, AbstractAsyncContextManager)
class DefaultEnter(AbstractAsyncContextManager):
async def __aexit__(self, *args):
await super().__aexit__(*args)
- self.assertTrue(issubclass(DefaultEnter, AbstractAsyncContextManager))
+ self.assertIsSubclass(DefaultEnter, AbstractAsyncContextManager)
class NoneAenter(ManagerFromScratch):
__aenter__ = None
- self.assertFalse(issubclass(NoneAenter, AbstractAsyncContextManager))
+ self.assertNotIsSubclass(NoneAenter, AbstractAsyncContextManager)
class NoneAexit(ManagerFromScratch):
__aexit__ = None
- self.assertFalse(issubclass(NoneAexit, AbstractAsyncContextManager))
+ self.assertNotIsSubclass(NoneAexit, AbstractAsyncContextManager)
class AsyncContextManagerTestCase(unittest.TestCase):
diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py
index d76341417e9..467ec09d99e 100644
--- a/Lib/test/test_copy.py
+++ b/Lib/test/test_copy.py
@@ -19,7 +19,7 @@ class TestCopy(unittest.TestCase):
def test_exceptions(self):
self.assertIs(copy.Error, copy.error)
- self.assertTrue(issubclass(copy.Error, Exception))
+ self.assertIsSubclass(copy.Error, Exception)
# The copy() method
@@ -372,6 +372,7 @@ class TestCopy(unittest.TestCase):
self.assertIsNot(x[0], y[0])
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_deepcopy_reflexive_list(self):
x = []
x.append(x)
@@ -400,6 +401,7 @@ class TestCopy(unittest.TestCase):
self.assertIs(x, y)
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_deepcopy_reflexive_tuple(self):
x = ([],)
x[0].append(x)
@@ -418,6 +420,7 @@ class TestCopy(unittest.TestCase):
self.assertIsNot(x["foo"], y["foo"])
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_deepcopy_reflexive_dict(self):
x = {}
x['foo'] = x
diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index 761cb230277..4755046fe19 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -527,7 +527,7 @@ class CoroutineTest(unittest.TestCase):
def test_gen_1(self):
def gen(): yield
- self.assertFalse(hasattr(gen, '__await__'))
+ self.assertNotHasAttr(gen, '__await__')
def test_func_1(self):
async def foo():
diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py
index 192c8eab26e..57e818b1c68 100644
--- a/Lib/test/test_cprofile.py
+++ b/Lib/test/test_cprofile.py
@@ -125,21 +125,22 @@ class CProfileTest(ProfileTest):
"""
gh-106152
generator.throw() should trigger a call in cProfile
- In the any() call below, there should be two entries for the generator:
- * one for the call to __next__ which gets a True and terminates any
- * one when the generator is garbage collected which will effectively
- do a throw.
"""
+
+ def gen():
+ yield
+
pr = self.profilerclass()
pr.enable()
- any(a == 1 for a in (1, 2))
+ g = gen()
+ try:
+ g.throw(SyntaxError)
+ except SyntaxError:
+ pass
pr.disable()
pr.create_stats()
- for func, (cc, nc, _, _, _) in pr.stats.items():
- if func[2] == "<genexpr>":
- self.assertEqual(cc, 1)
- self.assertEqual(nc, 1)
+ self.assertTrue(any("throw" in func[2] for func in pr.stats.keys())),
def test_bad_descriptor(self):
# gh-132250
diff --git a/Lib/test/test_crossinterp.py b/Lib/test/test_crossinterp.py
index 32d6fd4e94b..2fa0077a09b 100644
--- a/Lib/test/test_crossinterp.py
+++ b/Lib/test/test_crossinterp.py
@@ -1,10 +1,9 @@
import contextlib
-import importlib
-import importlib.util
import itertools
import sys
import types
import unittest
+import warnings
from test.support import import_helper
@@ -16,13 +15,281 @@ from test import _code_definitions as code_defs
from test import _crossinterp_definitions as defs
-BUILTIN_TYPES = [o for _, o in __builtins__.items()
- if isinstance(o, type)]
-EXCEPTION_TYPES = [cls for cls in BUILTIN_TYPES
+@contextlib.contextmanager
+def ignore_byteswarning():
+ with warnings.catch_warnings():
+ warnings.filterwarnings('ignore', category=BytesWarning)
+ yield
+
+
+# builtin types
+
+BUILTINS_TYPES = [o for _, o in __builtins__.items() if isinstance(o, type)]
+EXCEPTION_TYPES = [cls for cls in BUILTINS_TYPES
if issubclass(cls, BaseException)]
OTHER_TYPES = [o for n, o in vars(types).items()
if (isinstance(o, type) and
- n not in ('DynamicClassAttribute', '_GeneratorWrapper'))]
+ n not in ('DynamicClassAttribute', '_GeneratorWrapper'))]
+BUILTIN_TYPES = [
+ *BUILTINS_TYPES,
+ *OTHER_TYPES,
+]
+
+# builtin exceptions
+
+try:
+ raise Exception
+except Exception as exc:
+ CAUGHT = exc
+EXCEPTIONS_WITH_SPECIAL_SIG = {
+ BaseExceptionGroup: (lambda msg: (msg, [CAUGHT])),
+ ExceptionGroup: (lambda msg: (msg, [CAUGHT])),
+ UnicodeError: (lambda msg: (None, msg, None, None, None)),
+ UnicodeEncodeError: (lambda msg: ('utf-8', '', 1, 3, msg)),
+ UnicodeDecodeError: (lambda msg: ('utf-8', b'', 1, 3, msg)),
+ UnicodeTranslateError: (lambda msg: ('', 1, 3, msg)),
+}
+BUILTIN_EXCEPTIONS = [
+ *(cls(*sig('error!')) for cls, sig in EXCEPTIONS_WITH_SPECIAL_SIG.items()),
+ *(cls('error!') for cls in EXCEPTION_TYPES
+ if cls not in EXCEPTIONS_WITH_SPECIAL_SIG),
+]
+
+# other builtin objects
+
+METHOD = defs.SpamOkay().okay
+BUILTIN_METHOD = [].append
+METHOD_DESCRIPTOR_WRAPPER = str.join
+METHOD_WRAPPER = object().__str__
+WRAPPER_DESCRIPTOR = object.__init__
+BUILTIN_WRAPPERS = {
+ METHOD: types.MethodType,
+ BUILTIN_METHOD: types.BuiltinMethodType,
+ dict.__dict__['fromkeys']: types.ClassMethodDescriptorType,
+ types.FunctionType.__code__: types.GetSetDescriptorType,
+ types.FunctionType.__globals__: types.MemberDescriptorType,
+ METHOD_DESCRIPTOR_WRAPPER: types.MethodDescriptorType,
+ METHOD_WRAPPER: types.MethodWrapperType,
+ WRAPPER_DESCRIPTOR: types.WrapperDescriptorType,
+ staticmethod(defs.SpamOkay.okay): None,
+ classmethod(defs.SpamOkay.okay): None,
+ property(defs.SpamOkay.okay): None,
+}
+BUILTIN_FUNCTIONS = [
+ # types.BuiltinFunctionType
+ len,
+ sys.is_finalizing,
+ sys.exit,
+ _testinternalcapi.get_crossinterp_data,
+]
+assert 'emptymod' not in sys.modules
+with import_helper.ready_to_import('emptymod', ''):
+ import emptymod as EMPTYMOD
+MODULES = [
+ sys,
+ defs,
+ unittest,
+ EMPTYMOD,
+]
+OBJECT = object()
+EXCEPTION = Exception()
+LAMBDA = (lambda: None)
+BUILTIN_SIMPLE = [
+ OBJECT,
+ # singletons
+ None,
+ True,
+ False,
+ Ellipsis,
+ NotImplemented,
+ # bytes
+ *(i.to_bytes(2, 'little', signed=True)
+ for i in range(-1, 258)),
+ # str
+ 'hello world',
+ '你好世界',
+ '',
+ # int
+ sys.maxsize + 1,
+ sys.maxsize,
+ -sys.maxsize - 1,
+ -sys.maxsize - 2,
+ *range(-1, 258),
+ 2**1000,
+ # float
+ 0.0,
+ 1.1,
+ -1.0,
+ 0.12345678,
+ -0.12345678,
+]
+TUPLE_EXCEPTION = (0, 1.0, EXCEPTION)
+TUPLE_OBJECT = (0, 1.0, OBJECT)
+TUPLE_NESTED_EXCEPTION = (0, 1.0, (EXCEPTION,))
+TUPLE_NESTED_OBJECT = (0, 1.0, (OBJECT,))
+MEMORYVIEW_EMPTY = memoryview(b'')
+MEMORYVIEW_NOT_EMPTY = memoryview(b'spam'*42)
+MAPPING_PROXY_EMPTY = types.MappingProxyType({})
+BUILTIN_CONTAINERS = [
+ # tuple (flat)
+ (),
+ (1,),
+ ("hello", "world", ),
+ (1, True, "hello"),
+ TUPLE_EXCEPTION,
+ TUPLE_OBJECT,
+ # tuple (nested)
+ ((1,),),
+ ((1, 2), (3, 4)),
+ ((1, 2), (3, 4), (5, 6)),
+ TUPLE_NESTED_EXCEPTION,
+ TUPLE_NESTED_OBJECT,
+ # buffer
+ MEMORYVIEW_EMPTY,
+ MEMORYVIEW_NOT_EMPTY,
+ # list
+ [],
+ [1, 2, 3],
+ [[1], (2,), {3: 4}],
+ # dict
+ {},
+ {1: 7, 2: 8, 3: 9},
+ {1: [1], 2: (2,), 3: {3: 4}},
+ # set
+ set(),
+ {1, 2, 3},
+ {frozenset({1}), (2,)},
+ # frozenset
+ frozenset([]),
+ frozenset({frozenset({1}), (2,)}),
+ # bytearray
+ bytearray(b''),
+ # other
+ MAPPING_PROXY_EMPTY,
+ types.SimpleNamespace(),
+]
+ns = {}
+exec("""
+try:
+ raise Exception
+except Exception as exc:
+ TRACEBACK = exc.__traceback__
+ FRAME = TRACEBACK.tb_frame
+""", ns, ns)
+BUILTIN_OTHER = [
+ # types.CellType
+ types.CellType(),
+ # types.FrameType
+ ns['FRAME'],
+ # types.TracebackType
+ ns['TRACEBACK'],
+]
+del ns
+
+# user-defined objects
+
+USER_TOP_INSTANCES = [c(*a) for c, a in defs.TOP_CLASSES.items()]
+USER_NESTED_INSTANCES = [c(*a) for c, a in defs.NESTED_CLASSES.items()]
+USER_INSTANCES = [
+ *USER_TOP_INSTANCES,
+ *USER_NESTED_INSTANCES,
+]
+USER_EXCEPTIONS = [
+ defs.MimimalError('error!'),
+]
+
+# shareable objects
+
+TUPLES_WITHOUT_EQUALITY = [
+ TUPLE_EXCEPTION,
+ TUPLE_OBJECT,
+ TUPLE_NESTED_EXCEPTION,
+ TUPLE_NESTED_OBJECT,
+]
+_UNSHAREABLE_SIMPLE = [
+ Ellipsis,
+ NotImplemented,
+ OBJECT,
+ sys.maxsize + 1,
+ -sys.maxsize - 2,
+ 2**1000,
+]
+with ignore_byteswarning():
+ _SHAREABLE_SIMPLE = [o for o in BUILTIN_SIMPLE
+ if o not in _UNSHAREABLE_SIMPLE]
+ _SHAREABLE_CONTAINERS = [
+ *(o for o in BUILTIN_CONTAINERS if type(o) is memoryview),
+ *(o for o in BUILTIN_CONTAINERS
+ if type(o) is tuple and o not in TUPLES_WITHOUT_EQUALITY),
+ ]
+ _UNSHAREABLE_CONTAINERS = [o for o in BUILTIN_CONTAINERS
+ if o not in _SHAREABLE_CONTAINERS]
+SHAREABLE = [
+ *_SHAREABLE_SIMPLE,
+ *_SHAREABLE_CONTAINERS,
+]
+NOT_SHAREABLE = [
+ *_UNSHAREABLE_SIMPLE,
+ *_UNSHAREABLE_CONTAINERS,
+ *BUILTIN_TYPES,
+ *BUILTIN_WRAPPERS,
+ *BUILTIN_EXCEPTIONS,
+ *BUILTIN_FUNCTIONS,
+ *MODULES,
+ *BUILTIN_OTHER,
+ # types.CodeType
+ *(f.__code__ for f in defs.FUNCTIONS),
+ *(f.__code__ for f in defs.FUNCTION_LIKE),
+ # types.FunctionType
+ *defs.FUNCTIONS,
+ defs.SpamOkay.okay,
+ LAMBDA,
+ *defs.FUNCTION_LIKE,
+ # coroutines and generators
+ *defs.FUNCTION_LIKE_APPLIED,
+ # user classes
+ *defs.CLASSES,
+ *USER_INSTANCES,
+ # user exceptions
+ *USER_EXCEPTIONS,
+]
+
+# pickleable objects
+
+PICKLEABLE = [
+ *BUILTIN_SIMPLE,
+ *(o for o in BUILTIN_CONTAINERS if o not in [
+ MEMORYVIEW_EMPTY,
+ MEMORYVIEW_NOT_EMPTY,
+ MAPPING_PROXY_EMPTY,
+ ] or type(o) is dict),
+ *BUILTINS_TYPES,
+ *BUILTIN_EXCEPTIONS,
+ *BUILTIN_FUNCTIONS,
+ *defs.TOP_FUNCTIONS,
+ defs.SpamOkay.okay,
+ *defs.FUNCTION_LIKE,
+ *defs.TOP_CLASSES,
+ *USER_TOP_INSTANCES,
+ *USER_EXCEPTIONS,
+ # from OTHER_TYPES
+ types.NoneType,
+ types.EllipsisType,
+ types.NotImplementedType,
+ types.GenericAlias,
+ types.UnionType,
+ types.SimpleNamespace,
+ # from BUILTIN_WRAPPERS
+ METHOD,
+ BUILTIN_METHOD,
+ METHOD_DESCRIPTOR_WRAPPER,
+ METHOD_WRAPPER,
+ WRAPPER_DESCRIPTOR,
+]
+assert not any(isinstance(o, types.MappingProxyType) for o in PICKLEABLE)
+
+
+# helpers
DEFS = defs
with open(code_defs.__file__) as infile:
@@ -111,6 +378,77 @@ class _GetXIDataTests(unittest.TestCase):
MODE = None
+ def assert_functions_equal(self, func1, func2):
+ assert type(func1) is types.FunctionType, repr(func1)
+ assert type(func2) is types.FunctionType, repr(func2)
+ self.assertEqual(func1.__name__, func2.__name__)
+ self.assertEqual(func1.__code__, func2.__code__)
+ self.assertEqual(func1.__defaults__, func2.__defaults__)
+ self.assertEqual(func1.__kwdefaults__, func2.__kwdefaults__)
+ # We don't worry about __globals__ for now.
+
+ def assert_exc_args_equal(self, exc1, exc2):
+ args1 = exc1.args
+ args2 = exc2.args
+ if isinstance(exc1, ExceptionGroup):
+ self.assertIs(type(args1), type(args2))
+ self.assertEqual(len(args1), 2)
+ self.assertEqual(len(args1), len(args2))
+ self.assertEqual(args1[0], args2[0])
+ group1 = args1[1]
+ group2 = args2[1]
+ self.assertEqual(len(group1), len(group2))
+ for grouped1, grouped2 in zip(group1, group2):
+ # Currently the "extra" attrs are not preserved
+ # (via __reduce__).
+ self.assertIs(type(exc1), type(exc2))
+ self.assert_exc_equal(grouped1, grouped2)
+ else:
+ self.assertEqual(args1, args2)
+
+ def assert_exc_equal(self, exc1, exc2):
+ self.assertIs(type(exc1), type(exc2))
+
+ if type(exc1).__eq__ is not object.__eq__:
+ self.assertEqual(exc1, exc2)
+
+ self.assert_exc_args_equal(exc1, exc2)
+ # XXX For now we do not preserve tracebacks.
+ if exc1.__traceback__ is not None:
+ self.assertEqual(exc1.__traceback__, exc2.__traceback__)
+ self.assertEqual(
+ getattr(exc1, '__notes__', None),
+ getattr(exc2, '__notes__', None),
+ )
+ # We assume there are no cycles.
+ if exc1.__cause__ is None:
+ self.assertIs(exc1.__cause__, exc2.__cause__)
+ else:
+ self.assert_exc_equal(exc1.__cause__, exc2.__cause__)
+ if exc1.__context__ is None:
+ self.assertIs(exc1.__context__, exc2.__context__)
+ else:
+ self.assert_exc_equal(exc1.__context__, exc2.__context__)
+
+ def assert_equal_or_equalish(self, obj, expected):
+ cls = type(expected)
+ if cls.__eq__ is not object.__eq__:
+ self.assertEqual(obj, expected)
+ elif cls is types.FunctionType:
+ self.assert_functions_equal(obj, expected)
+ elif isinstance(expected, BaseException):
+ self.assert_exc_equal(obj, expected)
+ elif cls is types.MethodType:
+ raise NotImplementedError(cls)
+ elif cls is types.BuiltinMethodType:
+ raise NotImplementedError(cls)
+ elif cls is types.MethodWrapperType:
+ raise NotImplementedError(cls)
+ elif cls.__bases__ == (object,):
+ self.assertEqual(obj.__dict__, expected.__dict__)
+ else:
+ raise NotImplementedError(cls)
+
def get_xidata(self, obj, *, mode=None):
mode = self._resolve_mode(mode)
return _testinternalcapi.get_crossinterp_data(obj, mode)
@@ -126,35 +464,37 @@ class _GetXIDataTests(unittest.TestCase):
def assert_roundtrip_identical(self, values, *, mode=None):
mode = self._resolve_mode(mode)
for obj in values:
- with self.subTest(obj):
+ with self.subTest(repr(obj)):
got = self._get_roundtrip(obj, mode)
self.assertIs(got, obj)
def assert_roundtrip_equal(self, values, *, mode=None, expecttype=None):
mode = self._resolve_mode(mode)
for obj in values:
- with self.subTest(obj):
+ with self.subTest(repr(obj)):
got = self._get_roundtrip(obj, mode)
- self.assertEqual(got, obj)
+ if got is obj:
+ continue
self.assertIs(type(got),
type(obj) if expecttype is None else expecttype)
+ self.assert_equal_or_equalish(got, obj)
def assert_roundtrip_equal_not_identical(self, values, *,
mode=None, expecttype=None):
mode = self._resolve_mode(mode)
for obj in values:
- with self.subTest(obj):
+ with self.subTest(repr(obj)):
got = self._get_roundtrip(obj, mode)
self.assertIsNot(got, obj)
self.assertIs(type(got),
type(obj) if expecttype is None else expecttype)
- self.assertEqual(got, obj)
+ self.assert_equal_or_equalish(got, obj)
def assert_roundtrip_not_equal(self, values, *,
mode=None, expecttype=None):
mode = self._resolve_mode(mode)
for obj in values:
- with self.subTest(obj):
+ with self.subTest(repr(obj)):
got = self._get_roundtrip(obj, mode)
self.assertIsNot(got, obj)
self.assertIs(type(got),
@@ -164,7 +504,7 @@ class _GetXIDataTests(unittest.TestCase):
def assert_not_shareable(self, values, exctype=None, *, mode=None):
mode = self._resolve_mode(mode)
for obj in values:
- with self.subTest(obj):
+ with self.subTest(repr(obj)):
with self.assertRaises(NotShareableError) as cm:
_testinternalcapi.get_crossinterp_data(obj, mode)
if exctype is not None:
@@ -182,49 +522,26 @@ class PickleTests(_GetXIDataTests):
MODE = 'pickle'
def test_shareable(self):
- self.assert_roundtrip_equal([
- # singletons
- None,
- True,
- False,
- # bytes
- *(i.to_bytes(2, 'little', signed=True)
- for i in range(-1, 258)),
- # str
- 'hello world',
- '你好世界',
- '',
- # int
- sys.maxsize,
- -sys.maxsize - 1,
- *range(-1, 258),
- # float
- 0.0,
- 1.1,
- -1.0,
- 0.12345678,
- -0.12345678,
- # tuple
- (),
- (1,),
- ("hello", "world", ),
- (1, True, "hello"),
- ((1,),),
- ((1, 2), (3, 4)),
- ((1, 2), (3, 4), (5, 6)),
- ])
- # not shareable using xidata
- self.assert_roundtrip_equal([
- # int
- sys.maxsize + 1,
- -sys.maxsize - 2,
- 2**1000,
- # tuple
- (0, 1.0, []),
- (0, 1.0, {}),
- (0, 1.0, ([],)),
- (0, 1.0, ({},)),
- ])
+ with ignore_byteswarning():
+ for obj in SHAREABLE:
+ if obj in PICKLEABLE:
+ self.assert_roundtrip_equal([obj])
+ else:
+ self.assert_not_shareable([obj])
+
+ def test_not_shareable(self):
+ with ignore_byteswarning():
+ for obj in NOT_SHAREABLE:
+ if type(obj) is types.MappingProxyType:
+ self.assert_not_shareable([obj])
+ elif obj in PICKLEABLE:
+ with self.subTest(repr(obj)):
+ # We don't worry about checking the actual value.
+ # The other tests should cover that well enough.
+ got = self.get_roundtrip(obj)
+ self.assertIs(type(got), type(obj))
+ else:
+ self.assert_not_shareable([obj])
def test_list(self):
self.assert_roundtrip_equal_not_identical([
@@ -266,7 +583,7 @@ class PickleTests(_GetXIDataTests):
if cls not in defs.CLASSES_WITHOUT_EQUALITY:
continue
instances.append(cls(*args))
- self.assert_roundtrip_not_equal(instances)
+ self.assert_roundtrip_equal(instances)
def assert_class_defs_other_pickle(self, defs, mod):
# Pickle relative to a different module than the original.
@@ -286,7 +603,7 @@ class PickleTests(_GetXIDataTests):
instances = []
for cls, args in defs.TOP_CLASSES.items():
- with self.subTest(cls):
+ with self.subTest(repr(cls)):
setattr(mod, cls.__name__, cls)
xid = self.get_xidata(cls)
inst = cls(*args)
@@ -295,7 +612,7 @@ class PickleTests(_GetXIDataTests):
(cls, xid, inst, instxid))
for cls, xid, inst, instxid in instances:
- with self.subTest(cls):
+ with self.subTest(repr(cls)):
delattr(mod, cls.__name__)
if fail:
with self.assertRaises(NotShareableError):
@@ -403,13 +720,13 @@ class PickleTests(_GetXIDataTests):
def assert_func_defs_other_pickle(self, defs, mod):
# Pickle relative to a different module than the original.
for func in defs.TOP_FUNCTIONS:
- assert not hasattr(mod, func.__name__), (cls, getattr(mod, func.__name__))
+ assert not hasattr(mod, func.__name__), (getattr(mod, func.__name__),)
self.assert_not_shareable(defs.TOP_FUNCTIONS)
def assert_func_defs_other_unpickle(self, defs, mod, *, fail=False):
# Unpickle relative to a different module than the original.
for func in defs.TOP_FUNCTIONS:
- assert not hasattr(mod, func.__name__), (cls, getattr(mod, func.__name__))
+ assert not hasattr(mod, func.__name__), (getattr(mod, func.__name__),)
captured = []
for func in defs.TOP_FUNCTIONS:
@@ -434,7 +751,7 @@ class PickleTests(_GetXIDataTests):
self.assert_not_shareable(defs.TOP_FUNCTIONS)
def test_user_function_normal(self):
-# self.assert_roundtrip_equal(defs.TOP_FUNCTIONS)
+ self.assert_roundtrip_equal(defs.TOP_FUNCTIONS)
self.assert_func_defs_same(defs)
def test_user_func_in___main__(self):
@@ -505,7 +822,7 @@ class PickleTests(_GetXIDataTests):
# exceptions
def test_user_exception_normal(self):
- self.assert_roundtrip_not_equal([
+ self.assert_roundtrip_equal([
defs.MimimalError('error!'),
])
self.assert_roundtrip_equal_not_identical([
@@ -521,7 +838,7 @@ class PickleTests(_GetXIDataTests):
special = {
BaseExceptionGroup: (msg, [caught]),
ExceptionGroup: (msg, [caught]),
-# UnicodeError: (None, msg, None, None, None),
+ UnicodeError: (None, msg, None, None, None),
UnicodeEncodeError: ('utf-8', '', 1, 3, msg),
UnicodeDecodeError: ('utf-8', b'', 1, 3, msg),
UnicodeTranslateError: ('', 1, 3, msg),
@@ -531,7 +848,7 @@ class PickleTests(_GetXIDataTests):
args = special.get(cls) or (msg,)
exceptions.append(cls(*args))
- self.assert_roundtrip_not_equal(exceptions)
+ self.assert_roundtrip_equal(exceptions)
class MarshalTests(_GetXIDataTests):
@@ -576,7 +893,7 @@ class MarshalTests(_GetXIDataTests):
'',
])
self.assert_not_shareable([
- object(),
+ OBJECT,
types.SimpleNamespace(),
])
@@ -647,10 +964,7 @@ class MarshalTests(_GetXIDataTests):
shareable = [
StopIteration,
]
- types = [
- *BUILTIN_TYPES,
- *OTHER_TYPES,
- ]
+ types = BUILTIN_TYPES
self.assert_not_shareable(cls for cls in types
if cls not in shareable)
self.assert_roundtrip_identical(cls for cls in types
@@ -725,10 +1039,236 @@ class MarshalTests(_GetXIDataTests):
])
+class CodeTests(_GetXIDataTests):
+
+ MODE = 'code'
+
+ def test_function_code(self):
+ self.assert_roundtrip_equal_not_identical([
+ *(f.__code__ for f in defs.FUNCTIONS),
+ *(f.__code__ for f in defs.FUNCTION_LIKE),
+ ])
+
+ def test_functions(self):
+ self.assert_not_shareable([
+ *defs.FUNCTIONS,
+ *defs.FUNCTION_LIKE,
+ ])
+
+ def test_other_objects(self):
+ self.assert_not_shareable([
+ None,
+ True,
+ False,
+ Ellipsis,
+ NotImplemented,
+ 9999,
+ 'spam',
+ b'spam',
+ (),
+ [],
+ {},
+ object(),
+ ])
+
+
+class ShareableFuncTests(_GetXIDataTests):
+
+ MODE = 'func'
+
+ def test_stateless(self):
+ self.assert_roundtrip_equal([
+ *defs.STATELESS_FUNCTIONS,
+ # Generators can be stateless too.
+ *defs.FUNCTION_LIKE,
+ ])
+
+ def test_not_stateless(self):
+ self.assert_not_shareable([
+ *(f for f in defs.FUNCTIONS
+ if f not in defs.STATELESS_FUNCTIONS),
+ ])
+
+ def test_other_objects(self):
+ self.assert_not_shareable([
+ None,
+ True,
+ False,
+ Ellipsis,
+ NotImplemented,
+ 9999,
+ 'spam',
+ b'spam',
+ (),
+ [],
+ {},
+ object(),
+ ])
+
+
+class PureShareableScriptTests(_GetXIDataTests):
+
+ MODE = 'script-pure'
+
+ VALID_SCRIPTS = [
+ '',
+ 'spam',
+ '# a comment',
+ 'print("spam")',
+ 'raise Exception("spam")',
+ """if True:
+ do_something()
+ """,
+ """if True:
+ def spam(x):
+ return x
+ class Spam:
+ def eggs(self):
+ return 42
+ x = Spam().eggs()
+ raise ValueError(spam(x))
+ """,
+ ]
+ INVALID_SCRIPTS = [
+ ' pass', # IndentationError
+ '----', # SyntaxError
+ """if True:
+ def spam():
+ # no body
+ spam()
+ """, # IndentationError
+ ]
+
+ def test_valid_str(self):
+ self.assert_roundtrip_not_equal([
+ *self.VALID_SCRIPTS,
+ ], expecttype=types.CodeType)
+
+ def test_invalid_str(self):
+ self.assert_not_shareable([
+ *self.INVALID_SCRIPTS,
+ ])
+
+ def test_valid_bytes(self):
+ self.assert_roundtrip_not_equal([
+ *(s.encode('utf8') for s in self.VALID_SCRIPTS),
+ ], expecttype=types.CodeType)
+
+ def test_invalid_bytes(self):
+ self.assert_not_shareable([
+ *(s.encode('utf8') for s in self.INVALID_SCRIPTS),
+ ])
+
+ def test_pure_script_code(self):
+ self.assert_roundtrip_equal_not_identical([
+ *(f.__code__ for f in defs.PURE_SCRIPT_FUNCTIONS),
+ ])
+
+ def test_impure_script_code(self):
+ self.assert_not_shareable([
+ *(f.__code__ for f in defs.SCRIPT_FUNCTIONS
+ if f not in defs.PURE_SCRIPT_FUNCTIONS),
+ ])
+
+ def test_other_code(self):
+ self.assert_not_shareable([
+ *(f.__code__ for f in defs.FUNCTIONS
+ if f not in defs.SCRIPT_FUNCTIONS),
+ *(f.__code__ for f in defs.FUNCTION_LIKE),
+ ])
+
+ def test_pure_script_function(self):
+ self.assert_roundtrip_not_equal([
+ *defs.PURE_SCRIPT_FUNCTIONS,
+ ], expecttype=types.CodeType)
+
+ def test_impure_script_function(self):
+ self.assert_not_shareable([
+ *(f for f in defs.SCRIPT_FUNCTIONS
+ if f not in defs.PURE_SCRIPT_FUNCTIONS),
+ ])
+
+ def test_other_function(self):
+ self.assert_not_shareable([
+ *(f for f in defs.FUNCTIONS
+ if f not in defs.SCRIPT_FUNCTIONS),
+ *defs.FUNCTION_LIKE,
+ ])
+
+ def test_other_objects(self):
+ self.assert_not_shareable([
+ None,
+ True,
+ False,
+ Ellipsis,
+ NotImplemented,
+ (),
+ [],
+ {},
+ object(),
+ ])
+
+
+class ShareableScriptTests(PureShareableScriptTests):
+
+ MODE = 'script'
+
+ def test_impure_script_code(self):
+ self.assert_roundtrip_equal_not_identical([
+ *(f.__code__ for f in defs.SCRIPT_FUNCTIONS
+ if f not in defs.PURE_SCRIPT_FUNCTIONS),
+ ])
+
+ def test_impure_script_function(self):
+ self.assert_roundtrip_not_equal([
+ *(f for f in defs.SCRIPT_FUNCTIONS
+ if f not in defs.PURE_SCRIPT_FUNCTIONS),
+ ], expecttype=types.CodeType)
+
+
+class ShareableFallbackTests(_GetXIDataTests):
+
+ MODE = 'fallback'
+
+ def test_shareable(self):
+ self.assert_roundtrip_equal(SHAREABLE)
+
+ def test_not_shareable(self):
+ okay = [
+ *PICKLEABLE,
+ *defs.STATELESS_FUNCTIONS,
+ LAMBDA,
+ ]
+ ignored = [
+ *TUPLES_WITHOUT_EQUALITY,
+ OBJECT,
+ METHOD,
+ BUILTIN_METHOD,
+ METHOD_WRAPPER,
+ ]
+ with ignore_byteswarning():
+ self.assert_roundtrip_equal([
+ *(o for o in NOT_SHAREABLE
+ if o in okay and o not in ignored
+ and o is not MAPPING_PROXY_EMPTY),
+ ])
+ self.assert_roundtrip_not_equal([
+ *(o for o in NOT_SHAREABLE
+ if o in ignored and o is not MAPPING_PROXY_EMPTY),
+ ])
+ self.assert_not_shareable([
+ *(o for o in NOT_SHAREABLE if o not in okay),
+ MAPPING_PROXY_EMPTY,
+ ])
+
+
class ShareableTypeTests(_GetXIDataTests):
MODE = 'xidata'
+ def test_shareable(self):
+ self.assert_roundtrip_equal(SHAREABLE)
+
def test_singletons(self):
self.assert_roundtrip_identical([
None,
@@ -796,8 +1336,8 @@ class ShareableTypeTests(_GetXIDataTests):
def test_tuples_containing_non_shareable_types(self):
non_shareables = [
- Exception(),
- object(),
+ EXCEPTION,
+ OBJECT,
]
for s in non_shareables:
value = tuple([0, 1.0, s])
@@ -812,21 +1352,31 @@ class ShareableTypeTests(_GetXIDataTests):
# The rest are not shareable.
+ def test_not_shareable(self):
+ self.assert_not_shareable(NOT_SHAREABLE)
+
def test_object(self):
self.assert_not_shareable([
object(),
])
+ def test_code(self):
+ # types.CodeType
+ self.assert_not_shareable([
+ *(f.__code__ for f in defs.FUNCTIONS),
+ *(f.__code__ for f in defs.FUNCTION_LIKE),
+ ])
+
def test_function_object(self):
for func in defs.FUNCTIONS:
assert type(func) is types.FunctionType, func
assert type(defs.SpamOkay.okay) is types.FunctionType, func
- assert type(lambda: None) is types.LambdaType
+ assert type(LAMBDA) is types.LambdaType
self.assert_not_shareable([
*defs.FUNCTIONS,
defs.SpamOkay.okay,
- (lambda: None),
+ LAMBDA,
])
def test_builtin_function(self):
@@ -891,10 +1441,7 @@ class ShareableTypeTests(_GetXIDataTests):
self.assert_not_shareable(instances)
def test_builtin_type(self):
- self.assert_not_shareable([
- *BUILTIN_TYPES,
- *OTHER_TYPES,
- ])
+ self.assert_not_shareable(BUILTIN_TYPES)
def test_exception(self):
self.assert_not_shareable([
@@ -933,14 +1480,8 @@ class ShareableTypeTests(_GetXIDataTests):
""", ns, ns)
self.assert_not_shareable([
- types.MappingProxyType({}),
+ MAPPING_PROXY_EMPTY,
types.SimpleNamespace(),
- # types.CodeType
- defs.spam_minimal.__code__,
- defs.spam_full.__code__,
- defs.spam_CC.__code__,
- defs.eggs_closure_C.__code__,
- defs.ham_C_closure.__code__,
# types.CellType
types.CellType(),
# types.FrameType
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index 4af8f7f480e..60feab225a1 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -10,7 +10,8 @@ import csv
import gc
import pickle
from test import support
-from test.support import import_helper, check_disallow_instantiation
+from test.support import cpython_only, import_helper, check_disallow_instantiation
+from test.support.import_helper import ensure_lazy_imports
from itertools import permutations
from textwrap import dedent
from collections import OrderedDict
@@ -1121,19 +1122,22 @@ class TestDialectValidity(unittest.TestCase):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"quotechar" must be a 1-character string')
+ '"quotechar" must be a unicode character or None, '
+ 'not a string of length 0')
mydialect.quotechar = "''"
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"quotechar" must be a 1-character string')
+ '"quotechar" must be a unicode character or None, '
+ 'not a string of length 2')
mydialect.quotechar = 4
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"quotechar" must be string or None, not int')
+ '"quotechar" must be a unicode character or None, '
+ 'not int')
def test_delimiter(self):
class mydialect(csv.Dialect):
@@ -1150,31 +1154,32 @@ class TestDialectValidity(unittest.TestCase):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"delimiter" must be a 1-character string')
+ '"delimiter" must be a unicode character, '
+ 'not a string of length 3')
mydialect.delimiter = ""
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"delimiter" must be a 1-character string')
+ '"delimiter" must be a unicode character, not a string of length 0')
mydialect.delimiter = b","
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"delimiter" must be string, not bytes')
+ '"delimiter" must be a unicode character, not bytes')
mydialect.delimiter = 4
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"delimiter" must be string, not int')
+ '"delimiter" must be a unicode character, not int')
mydialect.delimiter = None
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"delimiter" must be string, not NoneType')
+ '"delimiter" must be a unicode character, not NoneType')
def test_escapechar(self):
class mydialect(csv.Dialect):
@@ -1188,20 +1193,32 @@ class TestDialectValidity(unittest.TestCase):
self.assertEqual(d.escapechar, "\\")
mydialect.escapechar = ""
- with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'):
+ with self.assertRaises(csv.Error) as cm:
mydialect()
+ self.assertEqual(str(cm.exception),
+ '"escapechar" must be a unicode character or None, '
+ 'not a string of length 0')
mydialect.escapechar = "**"
- with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'):
+ with self.assertRaises(csv.Error) as cm:
mydialect()
+ self.assertEqual(str(cm.exception),
+ '"escapechar" must be a unicode character or None, '
+ 'not a string of length 2')
mydialect.escapechar = b"*"
- with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not bytes'):
+ with self.assertRaises(csv.Error) as cm:
mydialect()
+ self.assertEqual(str(cm.exception),
+ '"escapechar" must be a unicode character or None, '
+ 'not bytes')
mydialect.escapechar = 4
- with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not int'):
+ with self.assertRaises(csv.Error) as cm:
mydialect()
+ self.assertEqual(str(cm.exception),
+ '"escapechar" must be a unicode character or None, '
+ 'not int')
def test_lineterminator(self):
class mydialect(csv.Dialect):
@@ -1222,7 +1239,13 @@ class TestDialectValidity(unittest.TestCase):
with self.assertRaises(csv.Error) as cm:
mydialect()
self.assertEqual(str(cm.exception),
- '"lineterminator" must be a string')
+ '"lineterminator" must be a string, not int')
+
+ mydialect.lineterminator = None
+ with self.assertRaises(csv.Error) as cm:
+ mydialect()
+ self.assertEqual(str(cm.exception),
+ '"lineterminator" must be a string, not NoneType')
def test_invalid_chars(self):
def create_invalid(field_name, value, **kwargs):
@@ -1565,6 +1588,10 @@ class MiscTestCase(unittest.TestCase):
def test__all__(self):
support.check__all__(self, csv, ('csv', '_csv'))
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("csv", {"re"})
+
def test_subclassable(self):
# issue 44089
class Foo(csv.Error): ...
diff --git a/Lib/test/test_ctypes/_support.py b/Lib/test/test_ctypes/_support.py
index 946d654a19a..700657a4e41 100644
--- a/Lib/test/test_ctypes/_support.py
+++ b/Lib/test/test_ctypes/_support.py
@@ -3,7 +3,6 @@
import ctypes
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
import sys
-from test import support
_CData = Structure.__base__
diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py
index 0c563ab8055..50b4d729b9d 100644
--- a/Lib/test/test_ctypes/test_aligned_structures.py
+++ b/Lib/test/test_ctypes/test_aligned_structures.py
@@ -316,6 +316,7 @@ class TestAlignedStructures(unittest.TestCase, StructCheckMixin):
class Main(sbase):
_pack_ = 1
+ _layout_ = "ms"
_fields_ = [
("a", c_ubyte),
("b", Inner),
diff --git a/Lib/test/test_ctypes/test_bitfields.py b/Lib/test/test_ctypes/test_bitfields.py
index dc81e752567..518f838219e 100644
--- a/Lib/test/test_ctypes/test_bitfields.py
+++ b/Lib/test/test_ctypes/test_bitfields.py
@@ -430,6 +430,7 @@ class BitFieldTest(unittest.TestCase, StructCheckMixin):
def test_gh_84039(self):
class Bad(Structure):
_pack_ = 1
+ _layout_ = "ms"
_fields_ = [
("a0", c_uint8, 1),
("a1", c_uint8, 1),
@@ -443,9 +444,9 @@ class BitFieldTest(unittest.TestCase, StructCheckMixin):
("b1", c_uint16, 12),
]
-
class GoodA(Structure):
_pack_ = 1
+ _layout_ = "ms"
_fields_ = [
("a0", c_uint8, 1),
("a1", c_uint8, 1),
@@ -460,6 +461,7 @@ class BitFieldTest(unittest.TestCase, StructCheckMixin):
class Good(Structure):
_pack_ = 1
+ _layout_ = "ms"
_fields_ = [
("a", GoodA),
("b0", c_uint16, 4),
@@ -475,6 +477,7 @@ class BitFieldTest(unittest.TestCase, StructCheckMixin):
def test_gh_73939(self):
class MyStructure(Structure):
_pack_ = 1
+ _layout_ = "ms"
_fields_ = [
("P", c_uint16),
("L", c_uint16, 9),
diff --git a/Lib/test/test_ctypes/test_byteswap.py b/Lib/test/test_ctypes/test_byteswap.py
index 9f9904282e4..f14e1aa32e1 100644
--- a/Lib/test/test_ctypes/test_byteswap.py
+++ b/Lib/test/test_ctypes/test_byteswap.py
@@ -1,5 +1,4 @@
import binascii
-import ctypes
import math
import struct
import sys
@@ -269,6 +268,7 @@ class Test(unittest.TestCase, StructCheckMixin):
class S(base):
_pack_ = 1
+ _layout_ = "ms"
_fields_ = [("b", c_byte),
("h", c_short),
@@ -296,6 +296,7 @@ class Test(unittest.TestCase, StructCheckMixin):
class S(Structure):
_pack_ = 1
+ _layout_ = "ms"
_fields_ = [("b", c_byte),
("h", c_short),
diff --git a/Lib/test/test_ctypes/test_generated_structs.py b/Lib/test/test_ctypes/test_generated_structs.py
index 9a8102219d8..1cb46a82701 100644
--- a/Lib/test/test_ctypes/test_generated_structs.py
+++ b/Lib/test/test_ctypes/test_generated_structs.py
@@ -10,7 +10,7 @@ Run this module to regenerate the files:
"""
import unittest
-from test.support import import_helper, verbose
+from test.support import import_helper
import re
from dataclasses import dataclass
from functools import cached_property
@@ -125,18 +125,21 @@ class Nested(Structure):
class Packed1(Structure):
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 1
+ _layout_ = 'ms'
@register()
class Packed2(Structure):
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 2
+ _layout_ = 'ms'
@register()
class Packed3(Structure):
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 4
+ _layout_ = 'ms'
@register()
@@ -155,6 +158,7 @@ class Packed4(Structure):
_fields_ = [('a', c_int8), ('b', c_int64)]
_pack_ = 8
+ _layout_ = 'ms'
@register()
class X86_32EdgeCase(Structure):
@@ -366,6 +370,7 @@ class Example_gh_95496(Structure):
@register()
class Example_gh_84039_bad(Structure):
_pack_ = 1
+ _layout_ = 'ms'
_fields_ = [("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
@@ -380,6 +385,7 @@ class Example_gh_84039_bad(Structure):
@register()
class Example_gh_84039_good_a(Structure):
_pack_ = 1
+ _layout_ = 'ms'
_fields_ = [("a0", c_uint8, 1),
("a1", c_uint8, 1),
("a2", c_uint8, 1),
@@ -392,6 +398,7 @@ class Example_gh_84039_good_a(Structure):
@register()
class Example_gh_84039_good(Structure):
_pack_ = 1
+ _layout_ = 'ms'
_fields_ = [("a", Example_gh_84039_good_a),
("b0", c_uint16, 4),
("b1", c_uint16, 12)]
@@ -399,6 +406,7 @@ class Example_gh_84039_good(Structure):
@register()
class Example_gh_73939(Structure):
_pack_ = 1
+ _layout_ = 'ms'
_fields_ = [("P", c_uint16),
("L", c_uint16, 9),
("Pro", c_uint16, 1),
@@ -419,6 +427,7 @@ class Example_gh_86098(Structure):
@register()
class Example_gh_86098_pack(Structure):
_pack_ = 1
+ _layout_ = 'ms'
_fields_ = [("a", c_uint8, 8),
("b", c_uint8, 8),
("c", c_uint32, 16)]
@@ -528,7 +537,7 @@ def dump_ctype(tp, struct_or_union_tag='', variable_name='', semi=''):
pushes.append(f'#pragma pack(push, {pack})')
pops.append(f'#pragma pack(pop)')
layout = getattr(tp, '_layout_', None)
- if layout == 'ms' or pack:
+ if layout == 'ms':
# The 'ms_struct' attribute only works on x86 and PowerPC
requires.add(
'defined(MS_WIN32) || ('
diff --git a/Lib/test/test_ctypes/test_incomplete.py b/Lib/test/test_ctypes/test_incomplete.py
index fefdfe9102e..3189fcd1bd1 100644
--- a/Lib/test/test_ctypes/test_incomplete.py
+++ b/Lib/test/test_ctypes/test_incomplete.py
@@ -1,6 +1,5 @@
import ctypes
import unittest
-import warnings
from ctypes import Structure, POINTER, pointer, c_char_p
# String-based "incomplete pointers" were implemented in ctypes 0.6.3 (2003, when
@@ -21,9 +20,7 @@ class TestSetPointerType(unittest.TestCase):
_fields_ = [("name", c_char_p),
("next", lpcell)]
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', DeprecationWarning)
- ctypes.SetPointerType(lpcell, cell)
+ lpcell.set_type(cell)
self.assertIs(POINTER(cell), lpcell)
@@ -50,10 +47,9 @@ class TestSetPointerType(unittest.TestCase):
_fields_ = [("name", c_char_p),
("next", lpcell)]
- with self.assertWarns(DeprecationWarning):
- ctypes.SetPointerType(lpcell, cell)
-
+ lpcell.set_type(cell)
self.assertIs(POINTER(cell), lpcell)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py
index f89521cf8b3..46f8ff93efa 100644
--- a/Lib/test/test_ctypes/test_parameters.py
+++ b/Lib/test/test_ctypes/test_parameters.py
@@ -1,3 +1,4 @@
+import sys
import unittest
import test.support
from ctypes import (CDLL, PyDLL, ArgumentError,
@@ -240,7 +241,8 @@ class SimpleTypesTestCase(unittest.TestCase):
self.assertRegex(repr(c_ulonglong.from_param(20000)), r"^<cparam '[LIQ]' \(20000\)>$")
self.assertEqual(repr(c_float.from_param(1.5)), "<cparam 'f' (1.5)>")
self.assertEqual(repr(c_double.from_param(1.5)), "<cparam 'd' (1.5)>")
- self.assertEqual(repr(c_double.from_param(1e300)), "<cparam 'd' (1e+300)>")
+ if sys.float_repr_style == 'short':
+ self.assertEqual(repr(c_double.from_param(1e300)), "<cparam 'd' (1e+300)>")
self.assertRegex(repr(c_longdouble.from_param(1.5)), r"^<cparam ('d' \(1.5\)|'g' at 0x[A-Fa-f0-9]+)>$")
self.assertRegex(repr(c_char_p.from_param(b'hihi')), r"^<cparam 'z' \(0x[A-Fa-f0-9]+\)>$")
self.assertRegex(repr(c_wchar_p.from_param('hihi')), r"^<cparam 'Z' \(0x[A-Fa-f0-9]+\)>$")
diff --git a/Lib/test/test_ctypes/test_pep3118.py b/Lib/test/test_ctypes/test_pep3118.py
index 06b2ccecade..11a0744f5a8 100644
--- a/Lib/test/test_ctypes/test_pep3118.py
+++ b/Lib/test/test_ctypes/test_pep3118.py
@@ -81,6 +81,7 @@ class Point(Structure):
class PackedPoint(Structure):
_pack_ = 2
+ _layout_ = 'ms'
_fields_ = [("x", c_long), ("y", c_long)]
class PointMidPad(Structure):
@@ -88,6 +89,7 @@ class PointMidPad(Structure):
class PackedPointMidPad(Structure):
_pack_ = 2
+ _layout_ = 'ms'
_fields_ = [("x", c_byte), ("y", c_uint64)]
class PointEndPad(Structure):
@@ -95,6 +97,7 @@ class PointEndPad(Structure):
class PackedPointEndPad(Structure):
_pack_ = 2
+ _layout_ = 'ms'
_fields_ = [("x", c_uint64), ("y", c_byte)]
class Point2(Structure):
diff --git a/Lib/test/test_ctypes/test_structunion.py b/Lib/test/test_ctypes/test_structunion.py
index 8d8b7e5e995..5b21d48d99c 100644
--- a/Lib/test/test_ctypes/test_structunion.py
+++ b/Lib/test/test_ctypes/test_structunion.py
@@ -11,6 +11,8 @@ from ._support import (_CData, PyCStructType, UnionType,
Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
from struct import calcsize
+import contextlib
+from test.support import MS_WINDOWS
class StructUnionTestBase:
@@ -335,6 +337,22 @@ class StructUnionTestBase:
self.assertIn("from_address", dir(type(self.cls)))
self.assertIn("in_dll", dir(type(self.cls)))
+ def test_pack_layout_switch(self):
+ # Setting _pack_ implicitly sets default layout to MSVC;
+ # this is deprecated on non-Windows platforms.
+ if MS_WINDOWS:
+ warn_context = contextlib.nullcontext()
+ else:
+ warn_context = self.assertWarns(DeprecationWarning)
+ with warn_context:
+ class X(self.cls):
+ _pack_ = 1
+ # _layout_ missing
+ _fields_ = [('a', c_int8, 1), ('b', c_int16, 2)]
+
+ # Check MSVC layout (bitfields of different types aren't combined)
+ self.check_sizeof(X, struct_size=3, union_size=2)
+
class StructureTestCase(unittest.TestCase, StructUnionTestBase):
cls = Structure
diff --git a/Lib/test/test_ctypes/test_structures.py b/Lib/test/test_ctypes/test_structures.py
index 221319642e8..92d4851d739 100644
--- a/Lib/test/test_ctypes/test_structures.py
+++ b/Lib/test/test_ctypes/test_structures.py
@@ -25,6 +25,7 @@ class StructureTestCase(unittest.TestCase, StructCheckMixin):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 1
+ _layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), 9)
@@ -34,6 +35,7 @@ class StructureTestCase(unittest.TestCase, StructCheckMixin):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 2
+ _layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), 10)
self.assertEqual(X.b.offset, 2)
@@ -45,6 +47,7 @@ class StructureTestCase(unittest.TestCase, StructCheckMixin):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 4
+ _layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), min(4, longlong_align) + longlong_size)
self.assertEqual(X.b.offset, min(4, longlong_align))
@@ -53,27 +56,33 @@ class StructureTestCase(unittest.TestCase, StructCheckMixin):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 8
+ _layout_ = 'ms'
self.check_struct(X)
self.assertEqual(sizeof(X), min(8, longlong_align) + longlong_size)
self.assertEqual(X.b.offset, min(8, longlong_align))
-
- d = {"_fields_": [("a", "b"),
- ("b", "q")],
- "_pack_": -1}
- self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
+ with self.assertRaises(ValueError):
+ class X(Structure):
+ _fields_ = [("a", "b"), ("b", "q")]
+ _pack_ = -1
+ _layout_ = "ms"
@support.cpython_only
def test_packed_c_limits(self):
# Issue 15989
import _testcapi
- d = {"_fields_": [("a", c_byte)],
- "_pack_": _testcapi.INT_MAX + 1}
- self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
- d = {"_fields_": [("a", c_byte)],
- "_pack_": _testcapi.UINT_MAX + 2}
- self.assertRaises(ValueError, type(Structure), "X", (Structure,), d)
+ with self.assertRaises(ValueError):
+ class X(Structure):
+ _fields_ = [("a", c_byte)]
+ _pack_ = _testcapi.INT_MAX + 1
+ _layout_ = "ms"
+
+ with self.assertRaises(ValueError):
+ class X(Structure):
+ _fields_ = [("a", c_byte)]
+ _pack_ = _testcapi.UINT_MAX + 2
+ _layout_ = "ms"
def test_initializers(self):
class Person(Structure):
diff --git a/Lib/test/test_ctypes/test_unaligned_structures.py b/Lib/test/test_ctypes/test_unaligned_structures.py
index 58a00597ef5..b5fb4c0df77 100644
--- a/Lib/test/test_ctypes/test_unaligned_structures.py
+++ b/Lib/test/test_ctypes/test_unaligned_structures.py
@@ -19,10 +19,12 @@ for typ in [c_short, c_int, c_long, c_longlong,
c_ushort, c_uint, c_ulong, c_ulonglong]:
class X(Structure):
_pack_ = 1
+ _layout_ = 'ms'
_fields_ = [("pad", c_byte),
("value", typ)]
class Y(SwappedStructure):
_pack_ = 1
+ _layout_ = 'ms'
_fields_ = [("pad", c_byte),
("value", typ)]
structures.append(X)
diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py
index 6fe0e7fd4b7..d5ca7f2ca1a 100644
--- a/Lib/test/test_curses.py
+++ b/Lib/test/test_curses.py
@@ -8,7 +8,8 @@ import unittest
from unittest.mock import MagicMock
from test.support import (requires, verbose, SaveSignals, cpython_only,
- check_disallow_instantiation, MISSING_C_DOCSTRINGS)
+ check_disallow_instantiation, MISSING_C_DOCSTRINGS,
+ gc_collect)
from test.support.import_helper import import_module
# Optionally test curses module. This currently requires that the
@@ -51,12 +52,6 @@ def requires_colors(test):
term = os.environ.get('TERM')
SHORT_MAX = 0x7fff
-DEFAULT_PAIR_CONTENTS = [
- (curses.COLOR_WHITE, curses.COLOR_BLACK),
- (0, 0),
- (-1, -1),
- (15, 0), # for xterm-256color (15 is for BRIGHT WHITE)
-]
# If newterm was supported we could use it instead of initscr and not exit
@unittest.skipIf(not term or term == 'unknown',
@@ -135,6 +130,9 @@ class TestCurses(unittest.TestCase):
curses.use_env(False)
curses.use_env(True)
+ def test_error(self):
+ self.assertIsSubclass(curses.error, Exception)
+
def test_create_windows(self):
win = curses.newwin(5, 10)
self.assertEqual(win.getbegyx(), (0, 0))
@@ -187,6 +185,14 @@ class TestCurses(unittest.TestCase):
self.assertEqual(win3.getparyx(), (2, 1))
self.assertEqual(win3.getmaxyx(), (6, 11))
+ def test_subwindows_references(self):
+ win = curses.newwin(5, 10)
+ win2 = win.subwin(3, 7)
+ del win
+ gc_collect()
+ del win2
+ gc_collect()
+
def test_move_cursor(self):
stdscr = self.stdscr
win = stdscr.subwin(10, 15, 2, 5)
@@ -948,8 +954,6 @@ class TestCurses(unittest.TestCase):
@requires_colors
def test_pair_content(self):
- if not hasattr(curses, 'use_default_colors'):
- self.assertIn(curses.pair_content(0), DEFAULT_PAIR_CONTENTS)
curses.pair_content(0)
maxpair = self.get_pair_limit() - 1
if maxpair > 0:
@@ -994,13 +998,27 @@ class TestCurses(unittest.TestCase):
@requires_curses_func('use_default_colors')
@requires_colors
def test_use_default_colors(self):
- old = curses.pair_content(0)
try:
curses.use_default_colors()
except curses.error:
self.skipTest('cannot change color (use_default_colors() failed)')
self.assertEqual(curses.pair_content(0), (-1, -1))
- self.assertIn(old, DEFAULT_PAIR_CONTENTS)
+
+ @requires_curses_func('assume_default_colors')
+ @requires_colors
+ def test_assume_default_colors(self):
+ try:
+ curses.assume_default_colors(-1, -1)
+ except curses.error:
+ self.skipTest('cannot change color (assume_default_colors() failed)')
+ self.assertEqual(curses.pair_content(0), (-1, -1))
+ curses.assume_default_colors(curses.COLOR_YELLOW, curses.COLOR_BLUE)
+ self.assertEqual(curses.pair_content(0), (curses.COLOR_YELLOW, curses.COLOR_BLUE))
+ curses.assume_default_colors(curses.COLOR_RED, -1)
+ self.assertEqual(curses.pair_content(0), (curses.COLOR_RED, -1))
+ curses.assume_default_colors(-1, curses.COLOR_GREEN)
+ self.assertEqual(curses.pair_content(0), (-1, curses.COLOR_GREEN))
+ curses.assume_default_colors(-1, -1)
def test_keyname(self):
# TODO: key_name()
@@ -1242,7 +1260,7 @@ class TestAscii(unittest.TestCase):
def test_controlnames(self):
for name in curses.ascii.controlnames:
- self.assertTrue(hasattr(curses.ascii, name), name)
+ self.assertHasAttr(curses.ascii, name)
def test_ctypes(self):
def check(func, expected):
diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py
index 99fefb57fd0..e98a8f284ce 100644
--- a/Lib/test/test_dataclasses/__init__.py
+++ b/Lib/test/test_dataclasses/__init__.py
@@ -5,6 +5,7 @@
from dataclasses import *
import abc
+import annotationlib
import io
import pickle
import inspect
@@ -12,6 +13,7 @@ import builtins
import types
import weakref
import traceback
+import sys
import textwrap
import unittest
from unittest.mock import Mock
@@ -25,6 +27,7 @@ import typing # Needed for the string "typing.ClassVar[int]" to work as an
import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation.
from test import support
+from test.support import import_helper
# Just any custom exception we can catch.
class CustomError(Exception): pass
@@ -117,7 +120,7 @@ class TestCase(unittest.TestCase):
for param in inspect.signature(dataclass).parameters:
if param == 'cls':
continue
- self.assertTrue(hasattr(Some.__dataclass_params__, param), msg=param)
+ self.assertHasAttr(Some.__dataclass_params__, param)
def test_named_init_params(self):
@dataclass
@@ -668,7 +671,7 @@ class TestCase(unittest.TestCase):
self.assertEqual(the_fields[0].name, 'x')
self.assertEqual(the_fields[0].type, int)
- self.assertFalse(hasattr(C, 'x'))
+ self.assertNotHasAttr(C, 'x')
self.assertTrue (the_fields[0].init)
self.assertTrue (the_fields[0].repr)
self.assertEqual(the_fields[1].name, 'y')
@@ -678,7 +681,7 @@ class TestCase(unittest.TestCase):
self.assertTrue (the_fields[1].repr)
self.assertEqual(the_fields[2].name, 'z')
self.assertEqual(the_fields[2].type, str)
- self.assertFalse(hasattr(C, 'z'))
+ self.assertNotHasAttr(C, 'z')
self.assertTrue (the_fields[2].init)
self.assertFalse(the_fields[2].repr)
@@ -729,8 +732,8 @@ class TestCase(unittest.TestCase):
z: object = default
t: int = field(default=100)
- self.assertFalse(hasattr(C, 'x'))
- self.assertFalse(hasattr(C, 'y'))
+ self.assertNotHasAttr(C, 'x')
+ self.assertNotHasAttr(C, 'y')
self.assertIs (C.z, default)
self.assertEqual(C.t, 100)
@@ -2909,10 +2912,10 @@ class TestFrozen(unittest.TestCase):
pass
c = C()
- self.assertFalse(hasattr(c, 'i'))
+ self.assertNotHasAttr(c, 'i')
with self.assertRaises(FrozenInstanceError):
c.i = 5
- self.assertFalse(hasattr(c, 'i'))
+ self.assertNotHasAttr(c, 'i')
with self.assertRaises(FrozenInstanceError):
del c.i
@@ -3141,7 +3144,7 @@ class TestFrozen(unittest.TestCase):
del s.y
self.assertEqual(s.y, 10)
del s.cached
- self.assertFalse(hasattr(s, 'cached'))
+ self.assertNotHasAttr(s, 'cached')
with self.assertRaises(AttributeError) as cm:
del s.cached
self.assertNotIsInstance(cm.exception, FrozenInstanceError)
@@ -3155,12 +3158,12 @@ class TestFrozen(unittest.TestCase):
pass
s = S()
- self.assertFalse(hasattr(s, 'x'))
+ self.assertNotHasAttr(s, 'x')
s.x = 5
self.assertEqual(s.x, 5)
del s.x
- self.assertFalse(hasattr(s, 'x'))
+ self.assertNotHasAttr(s, 'x')
with self.assertRaises(AttributeError) as cm:
del s.x
self.assertNotIsInstance(cm.exception, FrozenInstanceError)
@@ -3390,8 +3393,8 @@ class TestSlots(unittest.TestCase):
B = dataclass(A, slots=True)
self.assertIsNot(A, B)
- self.assertFalse(hasattr(A, "__slots__"))
- self.assertTrue(hasattr(B, "__slots__"))
+ self.assertNotHasAttr(A, "__slots__")
+ self.assertHasAttr(B, "__slots__")
# Can't be local to test_frozen_pickle.
@dataclass(frozen=True, slots=True)
@@ -3754,7 +3757,6 @@ class TestSlots(unittest.TestCase):
@support.cpython_only
def test_dataclass_slot_dict_ctype(self):
# https://github.com/python/cpython/issues/123935
- from test.support import import_helper
# Skips test if `_testcapi` is not present:
_testcapi = import_helper.import_module('_testcapi')
@@ -4246,16 +4248,56 @@ class TestMakeDataclass(unittest.TestCase):
C = make_dataclass('Point', ['x', 'y', 'z'])
c = C(1, 2, 3)
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
- self.assertEqual(C.__annotations__, {'x': 'typing.Any',
- 'y': 'typing.Any',
- 'z': 'typing.Any'})
+ self.assertEqual(C.__annotations__, {'x': typing.Any,
+ 'y': typing.Any,
+ 'z': typing.Any})
C = make_dataclass('Point', ['x', ('y', int), 'z'])
c = C(1, 2, 3)
self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3})
- self.assertEqual(C.__annotations__, {'x': 'typing.Any',
+ self.assertEqual(C.__annotations__, {'x': typing.Any,
'y': int,
- 'z': 'typing.Any'})
+ 'z': typing.Any})
+
+ def test_no_types_get_annotations(self):
+ C = make_dataclass('C', ['x', ('y', int), 'z'])
+
+ self.assertEqual(
+ annotationlib.get_annotations(C, format=annotationlib.Format.VALUE),
+ {'x': typing.Any, 'y': int, 'z': typing.Any},
+ )
+ self.assertEqual(
+ annotationlib.get_annotations(
+ C, format=annotationlib.Format.FORWARDREF),
+ {'x': typing.Any, 'y': int, 'z': typing.Any},
+ )
+ self.assertEqual(
+ annotationlib.get_annotations(
+ C, format=annotationlib.Format.STRING),
+ {'x': 'typing.Any', 'y': 'int', 'z': 'typing.Any'},
+ )
+
+ def test_no_types_no_typing_import(self):
+ with import_helper.CleanImport('typing'):
+ self.assertNotIn('typing', sys.modules)
+ C = make_dataclass('C', ['x', ('y', int)])
+
+ self.assertNotIn('typing', sys.modules)
+ self.assertEqual(
+ C.__annotate__(annotationlib.Format.FORWARDREF),
+ {
+ 'x': annotationlib.ForwardRef('Any', module='typing'),
+ 'y': int,
+ },
+ )
+ self.assertNotIn('typing', sys.modules)
+
+ for field in fields(C):
+ if field.name == "x":
+ self.assertEqual(field.type, annotationlib.ForwardRef('Any', module='typing'))
+ else:
+ self.assertEqual(field.name, "y")
+ self.assertIs(field.type, int)
def test_module_attr(self):
self.assertEqual(ByMakeDataClass.__module__, __name__)
diff --git a/Lib/test/test_dbm.py b/Lib/test/test_dbm.py
index 4be7c5649da..7e8d78b8940 100644
--- a/Lib/test/test_dbm.py
+++ b/Lib/test/test_dbm.py
@@ -66,7 +66,7 @@ class AnyDBMTestCase:
return keys
def test_error(self):
- self.assertTrue(issubclass(self.module.error, OSError))
+ self.assertIsSubclass(self.module.error, OSError)
def test_anydbm_not_existing(self):
self.assertRaises(dbm.error, dbm.open, _fname)
@@ -135,6 +135,67 @@ class AnyDBMTestCase:
assert(f[key] == b"Python:")
f.close()
+ def test_anydbm_readonly_reorganize(self):
+ self.init_db()
+ with dbm.open(_fname, 'r') as d:
+ # Early stopping.
+ if not hasattr(d, 'reorganize'):
+ self.skipTest("method reorganize not available this dbm submodule")
+
+ self.assertRaises(dbm.error, lambda: d.reorganize())
+
+ def test_anydbm_reorganize_not_changed_content(self):
+ self.init_db()
+ with dbm.open(_fname, 'c') as d:
+ # Early stopping.
+ if not hasattr(d, 'reorganize'):
+ self.skipTest("method reorganize not available this dbm submodule")
+
+ keys_before = sorted(d.keys())
+ values_before = [d[k] for k in keys_before]
+ d.reorganize()
+ keys_after = sorted(d.keys())
+ values_after = [d[k] for k in keys_before]
+ self.assertEqual(keys_before, keys_after)
+ self.assertEqual(values_before, values_after)
+
+ def test_anydbm_reorganize_decreased_size(self):
+
+ def _calculate_db_size(db_path):
+ if os.path.isfile(db_path):
+ return os.path.getsize(db_path)
+ total_size = 0
+ for root, _, filenames in os.walk(db_path):
+ for filename in filenames:
+ file_path = os.path.join(root, filename)
+ total_size += os.path.getsize(file_path)
+ return total_size
+
+ # This test requires relatively large databases to reliably show difference in size before and after reorganizing.
+ with dbm.open(_fname, 'n') as f:
+ # Early stopping.
+ if not hasattr(f, 'reorganize'):
+ self.skipTest("method reorganize not available this dbm submodule")
+
+ for k in self._dict:
+ f[k.encode('ascii')] = self._dict[k] * 100000
+ db_keys = list(f.keys())
+
+ # Make sure to calculate size of database only after file is closed to ensure file content are flushed to disk.
+ size_before = _calculate_db_size(os.path.dirname(_fname))
+
+ # Delete some elements from the start of the database.
+ keys_to_delete = db_keys[:len(db_keys) // 2]
+ with dbm.open(_fname, 'c') as f:
+ for k in keys_to_delete:
+ del f[k]
+ f.reorganize()
+
+ # Make sure to calculate size of database only after file is closed to ensure file content are flushed to disk.
+ size_after = _calculate_db_size(os.path.dirname(_fname))
+
+ self.assertLess(size_after, size_before)
+
def test_open_with_bytes(self):
dbm.open(os.fsencode(_fname), "c").close()
diff --git a/Lib/test/test_dbm_gnu.py b/Lib/test/test_dbm_gnu.py
index 66268c42a30..e0b988b7b95 100644
--- a/Lib/test/test_dbm_gnu.py
+++ b/Lib/test/test_dbm_gnu.py
@@ -74,12 +74,12 @@ class TestGdbm(unittest.TestCase):
# Test the flag parameter open() by trying all supported flag modes.
all = set(gdbm.open_flags)
# Test standard flags (presumably "crwn").
- modes = all - set('fsu')
+ modes = all - set('fsum')
for mode in sorted(modes): # put "c" mode first
self.g = gdbm.open(filename, mode)
self.g.close()
- # Test additional flags (presumably "fsu").
+ # Test additional flags (presumably "fsum").
flags = all - set('crwn')
for mode in modes:
for flag in flags:
@@ -217,6 +217,29 @@ class TestGdbm(unittest.TestCase):
create_empty_file(os.path.join(d, 'test'))
self.assertRaises(gdbm.error, gdbm.open, filename, 'r')
+ @unittest.skipUnless('m' in gdbm.open_flags, "requires 'm' in open_flags")
+ def test_nommap_no_crash(self):
+ self.g = g = gdbm.open(filename, 'nm')
+ os.truncate(filename, 0)
+
+ g.get(b'a', b'c')
+ g.keys()
+ g.firstkey()
+ g.nextkey(b'a')
+ with self.assertRaises(KeyError):
+ g[b'a']
+ with self.assertRaises(gdbm.error):
+ len(g)
+
+ with self.assertRaises(gdbm.error):
+ g[b'a'] = b'c'
+ with self.assertRaises(gdbm.error):
+ del g[b'a']
+ with self.assertRaises(gdbm.error):
+ g.setdefault(b'a', b'c')
+ with self.assertRaises(gdbm.error):
+ g.reorganize()
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_dbm_sqlite3.py b/Lib/test/test_dbm_sqlite3.py
index 2e1f2d32924..9216da8a63f 100644
--- a/Lib/test/test_dbm_sqlite3.py
+++ b/Lib/test/test_dbm_sqlite3.py
@@ -36,7 +36,7 @@ class URI(unittest.TestCase):
)
for path, normalized in dataset:
with self.subTest(path=path, normalized=normalized):
- self.assertTrue(_normalize_uri(path).endswith(normalized))
+ self.assertEndsWith(_normalize_uri(path), normalized)
@unittest.skipUnless(sys.platform == "win32", "requires Windows")
def test_uri_windows(self):
@@ -55,7 +55,7 @@ class URI(unittest.TestCase):
with self.subTest(path=path, normalized=normalized):
if not Path(path).is_absolute():
self.skipTest(f"skipping relative path: {path!r}")
- self.assertTrue(_normalize_uri(path).endswith(normalized))
+ self.assertEndsWith(_normalize_uri(path), normalized)
class ReadOnly(_SQLiteDbmTests):
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 9e298401dc3..ef64b878805 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -28,7 +28,6 @@ import logging
import math
import os, sys
import operator
-import warnings
import pickle, copy
import unittest
import numbers
@@ -982,6 +981,7 @@ class FormatTest:
('.0f', '0e-2', '0'),
('.0f', '3.14159265', '3'),
('.1f', '3.14159265', '3.1'),
+ ('.01f', '3.14159265', '3.1'), # leading zero in precision
('.4f', '3.14159265', '3.1416'),
('.6f', '3.14159265', '3.141593'),
('.7f', '3.14159265', '3.1415926'), # round-half-even!
@@ -1067,6 +1067,7 @@ class FormatTest:
('8,', '123456', ' 123,456'),
('08,', '123456', '0,123,456'), # special case: extra 0 needed
('+08,', '123456', '+123,456'), # but not if there's a sign
+ ('008,', '123456', '0,123,456'), # leading zero in width
(' 08,', '123456', ' 123,456'),
('08,', '-123456', '-123,456'),
('+09,', '123456', '+0,123,456'),
diff --git a/Lib/test/test_deque.py b/Lib/test/test_deque.py
index 4679f297fd7..4e1a489205a 100644
--- a/Lib/test/test_deque.py
+++ b/Lib/test/test_deque.py
@@ -838,7 +838,7 @@ class TestSubclass(unittest.TestCase):
self.assertEqual(list(d), list(e))
self.assertEqual(e.x, d.x)
self.assertEqual(e.z, d.z)
- self.assertFalse(hasattr(e, 'y'))
+ self.assertNotHasAttr(e, 'y')
def test_pickle_recursive(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 76937432a43..f6ec2cf5ce8 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -409,7 +409,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
def test_python_dicts(self):
# Testing Python subclass of dict...
- self.assertTrue(issubclass(dict, dict))
+ self.assertIsSubclass(dict, dict)
self.assertIsInstance({}, dict)
d = dict()
self.assertEqual(d, {})
@@ -433,7 +433,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
self.state = state
def getstate(self):
return self.state
- self.assertTrue(issubclass(C, dict))
+ self.assertIsSubclass(C, dict)
a1 = C(12)
self.assertEqual(a1.state, 12)
a2 = C(foo=1, bar=2)
@@ -1048,15 +1048,15 @@ class ClassPropertiesAndMethods(unittest.TestCase):
m = types.ModuleType("m")
self.assertTrue(m.__class__ is types.ModuleType)
- self.assertFalse(hasattr(m, "a"))
+ self.assertNotHasAttr(m, "a")
m.__class__ = SubType
self.assertTrue(m.__class__ is SubType)
- self.assertTrue(hasattr(m, "a"))
+ self.assertHasAttr(m, "a")
m.__class__ = types.ModuleType
self.assertTrue(m.__class__ is types.ModuleType)
- self.assertFalse(hasattr(m, "a"))
+ self.assertNotHasAttr(m, "a")
# Make sure that builtin immutable objects don't support __class__
# assignment, because the object instances may be interned.
@@ -1780,7 +1780,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
class E: # *not* subclassing from C
foo = C.foo
self.assertEqual(E().foo.__func__, C.foo) # i.e., unbound
- self.assertTrue(repr(C.foo.__get__(C())).startswith("<bound method "))
+ self.assertStartsWith(repr(C.foo.__get__(C())), "<bound method ")
def test_compattr(self):
# Testing computed attributes...
@@ -2058,7 +2058,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
class E(object):
foo = C.foo
self.assertEqual(E().foo.__func__, C.foo) # i.e., unbound
- self.assertTrue(repr(C.foo.__get__(C(1))).startswith("<bound method "))
+ self.assertStartsWith(repr(C.foo.__get__(C(1))), "<bound method ")
@support.impl_detail("testing error message from implementation")
def test_methods_in_c(self):
@@ -3943,6 +3943,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
del C.__del__
@unittest.skipIf(support.is_emscripten, "Seems to works in Pyodide?")
+ @support.skip_wasi_stack_overflow()
def test_slots_trash(self):
# Testing slot trash...
# Deallocating deeply nested slotted trash caused stack overflows
@@ -4113,6 +4114,34 @@ class ClassPropertiesAndMethods(unittest.TestCase):
else:
self.fail("shouldn't be able to create inheritance cycles")
+ def test_assign_bases_many_subclasses(self):
+ # This is intended to check that typeobject.c:queue_slot_update() can
+ # handle updating many subclasses when a slot method is re-assigned.
+ class A:
+ x = 'hello'
+ def __call__(self):
+ return 123
+ def __getitem__(self, index):
+ return None
+
+ class X:
+ x = 'bye'
+
+ class B(A):
+ pass
+
+ subclasses = []
+ for i in range(1000):
+ sc = type(f'Sub{i}', (B,), {})
+ subclasses.append(sc)
+
+ self.assertEqual(subclasses[0]()(), 123)
+ self.assertEqual(subclasses[0]().x, 'hello')
+ B.__bases__ = (X,)
+ with self.assertRaises(TypeError):
+ subclasses[0]()()
+ self.assertEqual(subclasses[0]().x, 'bye')
+
def test_builtin_bases(self):
# Make sure all the builtin types can have their base queried without
# segfaulting. See issue #5787.
@@ -4523,6 +4552,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
del o
@support.skip_wasi_stack_overflow()
+ @support.skip_emscripten_stack_overflow()
@support.requires_resource('cpu')
def test_wrapper_segfault(self):
# SF 927248: deeply nested wrappers could cause stack overflow
@@ -4867,6 +4897,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
deque.append(thing, thing)
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_repr_as_str(self):
# Issue #11603: crash or infinite loop when rebinding __str__ as
# __repr__.
@@ -5194,8 +5225,8 @@ class DictProxyTests(unittest.TestCase):
# We can't blindly compare with the repr of another dict as ordering
# of keys and values is arbitrary and may differ.
r = repr(self.C.__dict__)
- self.assertTrue(r.startswith('mappingproxy('), r)
- self.assertTrue(r.endswith(')'), r)
+ self.assertStartsWith(r, 'mappingproxy(')
+ self.assertEndsWith(r, ')')
for k, v in self.C.__dict__.items():
self.assertIn('{!r}: {!r}'.format(k, v), r)
diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index 3104cbc66cb..60c62430370 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -290,6 +290,38 @@ class DictTest(unittest.TestCase):
['Cannot convert dictionary update sequence element #0 to a sequence'],
)
+ def test_update_shared_keys(self):
+ class MyClass: pass
+
+ # Subclass str to enable us to create an object during the
+ # dict.update() call.
+ class MyStr(str):
+ def __hash__(self):
+ return super().__hash__()
+
+ def __eq__(self, other):
+ # Create an object that shares the same PyDictKeysObject as
+ # obj.__dict__.
+ obj2 = MyClass()
+ obj2.a = "a"
+ obj2.b = "b"
+ obj2.c = "c"
+ return super().__eq__(other)
+
+ obj = MyClass()
+ obj.a = "a"
+ obj.b = "b"
+
+ x = {}
+ x[MyStr("a")] = MyStr("a")
+
+ # gh-132617: this previously raised "dict mutated during update" error
+ x.update(obj.__dict__)
+
+ self.assertEqual(x, {
+ MyStr("a"): "a",
+ "b": "b",
+ })
def test_fromkeys(self):
self.assertEqual(dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None})
@@ -338,17 +370,34 @@ class DictTest(unittest.TestCase):
self.assertRaises(Exc, baddict2.fromkeys, [1])
# test fast path for dictionary inputs
+ res = dict(zip(range(6), [0]*6))
d = dict(zip(range(6), range(6)))
- self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6)))
-
+ self.assertEqual(dict.fromkeys(d, 0), res)
+ # test fast path for set inputs
+ d = set(range(6))
+ self.assertEqual(dict.fromkeys(d, 0), res)
+ # test slow path for other iterable inputs
+ d = list(range(6))
+ self.assertEqual(dict.fromkeys(d, 0), res)
+
+ # test fast path when object's constructor returns large non-empty dict
class baddict3(dict):
def __new__(cls):
return d
- d = {i : i for i in range(10)}
+ d = {i : i for i in range(1000)}
res = d.copy()
res.update(a=None, b=None, c=None)
self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res)
+ # test slow path when object is a proper subclass of dict
+ class baddict4(dict):
+ def __init__(self):
+ dict.__init__(self, d)
+ d = {i : i for i in range(1000)}
+ res = d.copy()
+ res.update(a=None, b=None, c=None)
+ self.assertEqual(baddict4.fromkeys({"a", "b", "c"}), res)
+
def test_copy(self):
d = {1: 1, 2: 2, 3: 3}
self.assertIsNot(d.copy(), d)
@@ -770,8 +819,8 @@ class DictTest(unittest.TestCase):
def test_missing(self):
# Make sure dict doesn't have a __missing__ method
- self.assertFalse(hasattr(dict, "__missing__"))
- self.assertFalse(hasattr({}, "__missing__"))
+ self.assertNotHasAttr(dict, "__missing__")
+ self.assertNotHasAttr({}, "__missing__")
# Test several cases:
# (D) subclass defines __missing__ method returning a value
# (E) subclass defines __missing__ method raising RuntimeError
@@ -1022,10 +1071,8 @@ class DictTest(unittest.TestCase):
a = C()
a.x = 1
d = a.__dict__
- before_resize = sys.getsizeof(d)
d[2] = 2 # split table is resized to a generic combined table
- self.assertGreater(sys.getsizeof(d), before_resize)
self.assertEqual(list(d), ['x', 2])
def test_iterator_pickling(self):
diff --git a/Lib/test/test_difflib.py b/Lib/test/test_difflib.py
index 9e217249be7..6ac584a08d1 100644
--- a/Lib/test/test_difflib.py
+++ b/Lib/test/test_difflib.py
@@ -255,21 +255,21 @@ class TestSFpatches(unittest.TestCase):
html_diff = difflib.HtmlDiff()
output = html_diff.make_file(patch914575_from1.splitlines(),
patch914575_to1.splitlines())
- self.assertIn('content="text/html; charset=utf-8"', output)
+ self.assertIn('charset="utf-8"', output)
def test_make_file_iso88591_charset(self):
html_diff = difflib.HtmlDiff()
output = html_diff.make_file(patch914575_from1.splitlines(),
patch914575_to1.splitlines(),
charset='iso-8859-1')
- self.assertIn('content="text/html; charset=iso-8859-1"', output)
+ self.assertIn('charset="iso-8859-1"', output)
def test_make_file_usascii_charset_with_nonascii_input(self):
html_diff = difflib.HtmlDiff()
output = html_diff.make_file(patch914575_nonascii_from1.splitlines(),
patch914575_nonascii_to1.splitlines(),
charset='us-ascii')
- self.assertIn('content="text/html; charset=us-ascii"', output)
+ self.assertIn('charset="us-ascii"', output)
self.assertIn('&#305;mpl&#305;c&#305;t', output)
class TestDiffer(unittest.TestCase):
diff --git a/Lib/test/test_difflib_expect.html b/Lib/test/test_difflib_expect.html
index 9f33a9e9c9c..2346a6f9f8d 100644
--- a/Lib/test/test_difflib_expect.html
+++ b/Lib/test/test_difflib_expect.html
@@ -1,22 +1,42 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<html>
-
+<!DOCTYPE html>
+<html lang="en">
<head>
- <meta http-equiv="Content-Type"
- content="text/html; charset=utf-8" />
- <title></title>
- <style type="text/css">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Diff comparison</title>
+ <style>
:root {color-scheme: light dark}
- table.diff {font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; border:medium}
- .diff_header {background-color:#e0e0e0}
- td.diff_header {text-align:right}
- .diff_next {background-color:#c0c0c0}
+ table.diff {
+ font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
+ border: medium;
+ }
+ .diff_header {
+ background-color: #e0e0e0;
+ font-weight: bold;
+ }
+ td.diff_header {
+ text-align: right;
+ padding: 0 8px;
+ }
+ .diff_next {
+ background-color: #c0c0c0;
+ padding: 4px 0;
+ }
.diff_add {background-color:palegreen}
.diff_chg {background-color:#ffff77}
.diff_sub {background-color:#ffaaaa}
+ table.diff[summary="Legends"] {
+ margin-top: 20px;
+ border: 1px solid #ccc;
+ }
+ table.diff[summary="Legends"] th {
+ background-color: #e0e0e0;
+ padding: 4px 8px;
+ }
+ table.diff[summary="Legends"] td {
+ padding: 4px 8px;
+ }
@media (prefers-color-scheme: dark) {
.diff_header {background-color:#666}
@@ -24,6 +44,8 @@
.diff_add {background-color:darkgreen}
.diff_chg {background-color:#847415}
.diff_sub {background-color:darkred}
+ table.diff[summary="Legends"] {border-color:#555}
+ table.diff[summary="Legends"] th{background-color:#666}
}
</style>
</head>
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index f2586fcee57..355990ed58e 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -606,7 +606,7 @@ dis_asyncwith = """\
POP_TOP
L1: RESUME 0
-%4d LOAD_FAST_BORROW 0 (c)
+%4d LOAD_FAST 0 (c)
COPY 1
LOAD_SPECIAL 3 (__aexit__)
SWAP 2
@@ -851,7 +851,7 @@ Disassembly of <code object <genexpr> at 0x..., file "%s", line %d>:
%4d RETURN_GENERATOR
POP_TOP
L1: RESUME 0
- LOAD_FAST_BORROW 0 (.0)
+ LOAD_FAST 0 (.0)
GET_ITER
L2: FOR_ITER 14 (to L3)
STORE_FAST 1 (z)
@@ -902,7 +902,7 @@ dis_loop_test_quickened_code = """\
%3d RESUME_CHECK 0
%3d BUILD_LIST 0
- LOAD_CONST_MORTAL 2 ((1, 2, 3))
+ LOAD_CONST 2 ((1, 2, 3))
LIST_EXTEND 1
LOAD_SMALL_INT 3
BINARY_OP 5 (*)
@@ -918,7 +918,7 @@ dis_loop_test_quickened_code = """\
%3d L2: END_FOR
POP_ITER
- LOAD_CONST_IMMORTAL 1 (None)
+ LOAD_CONST 1 (None)
RETURN_VALUE
""" % (loop_test.__code__.co_firstlineno,
loop_test.__code__.co_firstlineno + 1,
@@ -1304,7 +1304,7 @@ class DisTests(DisTestBase):
load_attr_quicken = """\
0 RESUME_CHECK 0
- 1 LOAD_CONST_IMMORTAL 0 ('a')
+ 1 LOAD_CONST 0 ('a')
LOAD_ATTR_SLOT 0 (__class__)
RETURN_VALUE
"""
@@ -1336,7 +1336,7 @@ class DisTests(DisTestBase):
# Loop can trigger a quicken where the loop is located
self.code_quicken(loop_test)
got = self.get_disassembly(loop_test, adaptive=True)
- jit = import_helper.import_module("_testinternalcapi").jit_enabled()
+ jit = sys._jit.is_enabled()
expected = dis_loop_test_quickened_code.format("JIT" if jit else "NO_JIT")
self.do_disassembly_compare(got, expected)
@@ -1821,7 +1821,7 @@ expected_opinfo_jumpy = [
make_inst(opname='LOAD_SMALL_INT', arg=10, argval=10, argrepr='', offset=12, start_offset=12, starts_line=False, line_number=3),
make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
make_inst(opname='GET_ITER', arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3),
- make_inst(opname='FOR_ITER', arg=32, argval=92, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='FOR_ITER', arg=33, argval=94, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, cache_info=[('counter', 1, b'\x00\x00')]),
make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=28, start_offset=28, starts_line=False, line_number=3),
make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=4, 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_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=40, start_offset=40, starts_line=False, line_number=4),
@@ -1840,110 +1840,111 @@ expected_opinfo_jumpy = [
make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=82, start_offset=82, starts_line=False, line_number=7),
make_inst(opname='JUMP_BACKWARD', arg=32, argval=24, argrepr='to L1', offset=84, start_offset=84, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00')]),
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=8, label=3),
- make_inst(opname='JUMP_FORWARD', arg=13, argval=118, argrepr='to L5', offset=90, start_offset=90, starts_line=False, line_number=8),
- make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=92, start_offset=92, starts_line=True, line_number=3, label=4),
- make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=94, start_offset=94, starts_line=False, line_number=3),
- make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=96, start_offset=96, starts_line=True, line_number=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=1, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=106, start_offset=106, starts_line=False, line_number=10),
- make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=108, start_offset=108, starts_line=False, line_number=10, 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=116, start_offset=116, starts_line=False, line_number=10),
- make_inst(opname='LOAD_FAST_CHECK', arg=0, argval='i', argrepr='i', offset=118, start_offset=118, starts_line=True, line_number=11, label=5),
- make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=120, start_offset=120, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]),
- make_inst(opname='POP_JUMP_IF_FALSE', arg=40, argval=212, argrepr='to L8', offset=128, start_offset=128, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=132, start_offset=132, starts_line=False, line_number=11),
- make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=134, start_offset=134, starts_line=True, line_number=12, 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_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=144, start_offset=144, starts_line=False, line_number=12),
- make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=146, start_offset=146, starts_line=False, line_number=12, 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=154, start_offset=154, starts_line=False, line_number=12),
- make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=156, start_offset=156, starts_line=True, line_number=13),
- make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=158, start_offset=158, starts_line=False, line_number=13),
- make_inst(opname='BINARY_OP', arg=23, argval=23, argrepr='-=', offset=160, start_offset=160, starts_line=False, line_number=13, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]),
- make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=172, start_offset=172, starts_line=False, line_number=13),
- make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=174, start_offset=174, starts_line=True, line_number=14),
- make_inst(opname='LOAD_SMALL_INT', arg=6, argval=6, argrepr='', offset=176, start_offset=176, starts_line=False, line_number=14),
- make_inst(opname='COMPARE_OP', arg=148, argval='>', argrepr='bool(>)', offset=178, start_offset=178, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='POP_JUMP_IF_FALSE', arg=3, argval=192, argrepr='to L6', offset=182, start_offset=182, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=186, start_offset=186, starts_line=False, line_number=14),
- make_inst(opname='JUMP_BACKWARD', arg=37, argval=118, argrepr='to L5', offset=188, start_offset=188, starts_line=True, line_number=15, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=192, start_offset=192, starts_line=True, line_number=16, label=6),
- make_inst(opname='LOAD_SMALL_INT', arg=4, argval=4, argrepr='', offset=194, start_offset=194, starts_line=False, line_number=16),
- make_inst(opname='COMPARE_OP', arg=18, argval='<', argrepr='bool(<)', offset=196, start_offset=196, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='POP_JUMP_IF_TRUE', arg=3, argval=210, argrepr='to L7', offset=200, start_offset=200, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=204, start_offset=204, starts_line=False, line_number=16),
- make_inst(opname='JUMP_BACKWARD', arg=46, argval=118, argrepr='to L5', offset=206, start_offset=206, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='JUMP_FORWARD', arg=11, argval=234, argrepr='to L9', offset=210, start_offset=210, starts_line=True, line_number=17, label=7),
- make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=212, start_offset=212, starts_line=True, line_number=19, label=8, 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=2, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=222, start_offset=222, starts_line=False, line_number=19),
- make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=224, start_offset=224, starts_line=False, line_number=19, 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=232, start_offset=232, starts_line=False, line_number=19),
- make_inst(opname='NOP', arg=None, argval=None, argrepr='', offset=234, start_offset=234, starts_line=True, line_number=20, label=9),
- make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=236, start_offset=236, starts_line=True, line_number=21),
- make_inst(opname='LOAD_SMALL_INT', arg=0, argval=0, argrepr='', offset=238, start_offset=238, starts_line=False, line_number=21),
- make_inst(opname='BINARY_OP', arg=11, argval=11, argrepr='/', offset=240, start_offset=240, starts_line=False, line_number=21, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]),
- make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=252, start_offset=252, starts_line=False, line_number=21),
- make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=254, start_offset=254, starts_line=True, line_number=25),
- make_inst(opname='COPY', arg=1, argval=1, argrepr='', offset=256, start_offset=256, starts_line=False, line_number=25),
- make_inst(opname='LOAD_SPECIAL', arg=1, argval=1, argrepr='__exit__', offset=258, start_offset=258, starts_line=False, line_number=25),
- make_inst(opname='SWAP', arg=2, argval=2, argrepr='', offset=260, start_offset=260, starts_line=False, line_number=25),
- make_inst(opname='SWAP', arg=3, argval=3, argrepr='', offset=262, start_offset=262, starts_line=False, line_number=25),
- make_inst(opname='LOAD_SPECIAL', arg=0, argval=0, argrepr='__enter__', offset=264, start_offset=264, starts_line=False, line_number=25),
- make_inst(opname='CALL', arg=0, argval=0, argrepr='', offset=266, start_offset=266, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
- make_inst(opname='STORE_FAST', arg=1, argval='dodgy', argrepr='dodgy', offset=274, start_offset=274, starts_line=False, line_number=25),
- make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=276, start_offset=276, starts_line=True, line_number=26, 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=3, argval='Never reach this', argrepr="'Never reach this'", offset=286, start_offset=286, starts_line=False, line_number=26),
- make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=288, start_offset=288, 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=296, start_offset=296, starts_line=False, line_number=26),
- make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=298, start_offset=298, starts_line=True, line_number=25),
- make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=300, start_offset=300, starts_line=False, line_number=25),
+ make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=90, start_offset=90, starts_line=False, line_number=8),
+ make_inst(opname='JUMP_FORWARD', arg=13, argval=120, argrepr='to L5', offset=92, start_offset=92, starts_line=False, line_number=8),
+ make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=94, start_offset=94, starts_line=True, line_number=3, label=4),
+ make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=96, start_offset=96, starts_line=False, line_number=3),
+ make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=98, start_offset=98, starts_line=True, line_number=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=1, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=108, start_offset=108, starts_line=False, line_number=10),
+ make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=110, start_offset=110, starts_line=False, line_number=10, 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=118, start_offset=118, starts_line=False, line_number=10),
+ make_inst(opname='LOAD_FAST_CHECK', arg=0, argval='i', argrepr='i', offset=120, start_offset=120, starts_line=True, line_number=11, label=5),
+ make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=122, start_offset=122, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]),
+ make_inst(opname='POP_JUMP_IF_FALSE', arg=40, argval=214, argrepr='to L8', offset=130, start_offset=130, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=134, start_offset=134, starts_line=False, line_number=11),
+ make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=136, start_offset=136, starts_line=True, line_number=12, 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_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=146, start_offset=146, starts_line=False, line_number=12),
+ make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=148, start_offset=148, starts_line=False, line_number=12, 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=156, start_offset=156, starts_line=False, line_number=12),
+ make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=158, start_offset=158, starts_line=True, line_number=13),
+ make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=160, start_offset=160, starts_line=False, line_number=13),
+ make_inst(opname='BINARY_OP', arg=23, argval=23, argrepr='-=', offset=162, start_offset=162, starts_line=False, line_number=13, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]),
+ make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=174, start_offset=174, starts_line=False, line_number=13),
+ make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=176, start_offset=176, starts_line=True, line_number=14),
+ make_inst(opname='LOAD_SMALL_INT', arg=6, argval=6, argrepr='', offset=178, start_offset=178, starts_line=False, line_number=14),
+ make_inst(opname='COMPARE_OP', arg=148, argval='>', argrepr='bool(>)', offset=180, start_offset=180, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='POP_JUMP_IF_FALSE', arg=3, argval=194, argrepr='to L6', offset=184, start_offset=184, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=188, start_offset=188, starts_line=False, line_number=14),
+ make_inst(opname='JUMP_BACKWARD', arg=37, argval=120, argrepr='to L5', offset=190, start_offset=190, starts_line=True, line_number=15, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=194, start_offset=194, starts_line=True, line_number=16, label=6),
+ make_inst(opname='LOAD_SMALL_INT', arg=4, argval=4, argrepr='', offset=196, start_offset=196, starts_line=False, line_number=16),
+ make_inst(opname='COMPARE_OP', arg=18, argval='<', argrepr='bool(<)', offset=198, start_offset=198, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='POP_JUMP_IF_TRUE', arg=3, argval=212, argrepr='to L7', offset=202, start_offset=202, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=206, start_offset=206, starts_line=False, line_number=16),
+ make_inst(opname='JUMP_BACKWARD', arg=46, argval=120, argrepr='to L5', offset=208, start_offset=208, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='JUMP_FORWARD', arg=11, argval=236, argrepr='to L9', offset=212, start_offset=212, starts_line=True, line_number=17, label=7),
+ make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=214, start_offset=214, starts_line=True, line_number=19, label=8, 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=2, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=224, start_offset=224, starts_line=False, line_number=19),
+ make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=226, start_offset=226, starts_line=False, line_number=19, 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=234, start_offset=234, starts_line=False, line_number=19),
+ make_inst(opname='NOP', arg=None, argval=None, argrepr='', offset=236, start_offset=236, starts_line=True, line_number=20, label=9),
+ make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=238, start_offset=238, starts_line=True, line_number=21),
+ make_inst(opname='LOAD_SMALL_INT', arg=0, argval=0, argrepr='', offset=240, start_offset=240, starts_line=False, line_number=21),
+ make_inst(opname='BINARY_OP', arg=11, argval=11, argrepr='/', offset=242, start_offset=242, starts_line=False, line_number=21, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]),
+ make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=254, start_offset=254, starts_line=False, line_number=21),
+ make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=256, start_offset=256, starts_line=True, line_number=25),
+ make_inst(opname='COPY', arg=1, argval=1, argrepr='', offset=258, start_offset=258, starts_line=False, line_number=25),
+ make_inst(opname='LOAD_SPECIAL', arg=1, argval=1, argrepr='__exit__', offset=260, start_offset=260, starts_line=False, line_number=25),
+ make_inst(opname='SWAP', arg=2, argval=2, argrepr='', offset=262, start_offset=262, starts_line=False, line_number=25),
+ make_inst(opname='SWAP', arg=3, argval=3, argrepr='', offset=264, start_offset=264, starts_line=False, line_number=25),
+ make_inst(opname='LOAD_SPECIAL', arg=0, argval=0, argrepr='__enter__', offset=266, start_offset=266, starts_line=False, line_number=25),
+ make_inst(opname='CALL', arg=0, argval=0, argrepr='', offset=268, start_offset=268, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
+ make_inst(opname='STORE_FAST', arg=1, argval='dodgy', argrepr='dodgy', offset=276, start_offset=276, starts_line=False, line_number=25),
+ make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=278, start_offset=278, starts_line=True, line_number=26, 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=3, argval='Never reach this', argrepr="'Never reach this'", offset=288, start_offset=288, starts_line=False, line_number=26),
+ make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=290, start_offset=290, 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=298, start_offset=298, starts_line=False, line_number=26),
+ make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=300, start_offset=300, starts_line=True, line_number=25),
make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=302, start_offset=302, starts_line=False, line_number=25),
- make_inst(opname='CALL', arg=3, argval=3, argrepr='', offset=304, start_offset=304, 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=312, start_offset=312, starts_line=False, line_number=25),
- make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=314, start_offset=314, 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=324, start_offset=324, starts_line=False, line_number=28),
- make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=326, start_offset=326, 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=334, start_offset=334, starts_line=False, line_number=28),
- make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=336, start_offset=336, starts_line=False, line_number=28),
- make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=338, start_offset=338, starts_line=False, line_number=28),
- make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=True, line_number=25),
- make_inst(opname='WITH_EXCEPT_START', arg=None, argval=None, argrepr='', offset=342, start_offset=342, starts_line=False, line_number=25),
- make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]),
- make_inst(opname='POP_JUMP_IF_TRUE', arg=2, argval=360, argrepr='to L11', offset=352, start_offset=352, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=356, start_offset=356, starts_line=False, line_number=25),
- make_inst(opname='RERAISE', arg=2, argval=2, argrepr='', offset=358, start_offset=358, starts_line=False, line_number=25),
- make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=360, start_offset=360, starts_line=False, line_number=25, label=11),
- make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=25),
- make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=364, start_offset=364, starts_line=False, line_number=25),
+ make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=304, start_offset=304, starts_line=False, line_number=25),
+ make_inst(opname='CALL', arg=3, argval=3, argrepr='', offset=306, start_offset=306, 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=314, start_offset=314, starts_line=False, line_number=25),
+ make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=316, start_offset=316, 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=326, start_offset=326, starts_line=False, line_number=28),
+ make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=328, start_offset=328, 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=336, start_offset=336, starts_line=False, line_number=28),
+ make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=338, start_offset=338, starts_line=False, line_number=28),
+ make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=28),
+ make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=342, start_offset=342, starts_line=True, line_number=25),
+ make_inst(opname='WITH_EXCEPT_START', arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=25),
+ make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=346, start_offset=346, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]),
+ make_inst(opname='POP_JUMP_IF_TRUE', arg=2, argval=362, argrepr='to L11', offset=354, start_offset=354, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=358, start_offset=358, starts_line=False, line_number=25),
+ make_inst(opname='RERAISE', arg=2, argval=2, argrepr='', offset=360, start_offset=360, starts_line=False, line_number=25),
+ make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=25, label=11),
+ make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=364, start_offset=364, starts_line=False, line_number=25),
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=366, start_offset=366, starts_line=False, line_number=25),
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=368, start_offset=368, starts_line=False, line_number=25),
- make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=29, argval=314, argrepr='to L10', offset=370, start_offset=370, starts_line=False, line_number=25),
- make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=372, start_offset=372, starts_line=True, line_number=None),
- make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=374, start_offset=374, starts_line=False, line_number=None),
- make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=376, start_offset=376, starts_line=False, line_number=None),
- make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=378, start_offset=378, starts_line=False, line_number=None),
- make_inst(opname='LOAD_GLOBAL', arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=380, start_offset=380, starts_line=True, line_number=22, 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='CHECK_EXC_MATCH', arg=None, argval=None, argrepr='', offset=390, start_offset=390, starts_line=False, line_number=22),
- make_inst(opname='POP_JUMP_IF_FALSE', arg=15, argval=426, argrepr='to L12', offset=392, start_offset=392, starts_line=False, line_number=22, cache_info=[('counter', 1, b'\x00\x00')]),
- make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=22),
- make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=398, start_offset=398, starts_line=False, line_number=22),
- make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=400, start_offset=400, 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=410, start_offset=410, starts_line=False, line_number=23),
- make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=412, start_offset=412, 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=420, start_offset=420, starts_line=False, line_number=23),
- make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=422, start_offset=422, starts_line=False, line_number=23),
- make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=56, argval=314, argrepr='to L10', offset=424, start_offset=424, starts_line=False, line_number=23),
- make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=426, start_offset=426, starts_line=True, line_number=22, label=12),
- make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=428, start_offset=428, starts_line=True, line_number=None),
- make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=430, start_offset=430, starts_line=False, line_number=None),
- make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=432, start_offset=432, starts_line=False, line_number=None),
- make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=434, start_offset=434, starts_line=False, line_number=None),
- make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=436, start_offset=436, 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=446, start_offset=446, starts_line=False, line_number=28),
- make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=448, start_offset=448, 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=456, start_offset=456, starts_line=False, line_number=28),
- make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=458, start_offset=458, starts_line=False, line_number=28),
- make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=460, start_offset=460, starts_line=True, line_number=None),
- make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=462, start_offset=462, starts_line=False, line_number=None),
- make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=464, start_offset=464, starts_line=False, line_number=None),
+ make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=370, start_offset=370, starts_line=False, line_number=25),
+ make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=29, argval=316, argrepr='to L10', offset=372, start_offset=372, starts_line=False, line_number=25),
+ make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=374, start_offset=374, starts_line=True, line_number=None),
+ make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=376, start_offset=376, starts_line=False, line_number=None),
+ make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=378, start_offset=378, starts_line=False, line_number=None),
+ make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=380, start_offset=380, starts_line=False, line_number=None),
+ make_inst(opname='LOAD_GLOBAL', arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=382, start_offset=382, starts_line=True, line_number=22, 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='CHECK_EXC_MATCH', arg=None, argval=None, argrepr='', offset=392, start_offset=392, starts_line=False, line_number=22),
+ make_inst(opname='POP_JUMP_IF_FALSE', arg=15, argval=428, argrepr='to L12', offset=394, start_offset=394, starts_line=False, line_number=22, cache_info=[('counter', 1, b'\x00\x00')]),
+ make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=398, start_offset=398, starts_line=False, line_number=22),
+ make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=400, start_offset=400, starts_line=False, line_number=22),
+ make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=402, start_offset=402, 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=412, start_offset=412, starts_line=False, line_number=23),
+ make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=414, start_offset=414, 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=422, start_offset=422, starts_line=False, line_number=23),
+ make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=424, start_offset=424, starts_line=False, line_number=23),
+ make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=56, argval=316, argrepr='to L10', offset=426, start_offset=426, starts_line=False, line_number=23),
+ make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=428, start_offset=428, starts_line=True, line_number=22, label=12),
+ make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=430, start_offset=430, starts_line=True, line_number=None),
+ make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=432, start_offset=432, starts_line=False, line_number=None),
+ make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=434, start_offset=434, starts_line=False, line_number=None),
+ make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=436, start_offset=436, starts_line=False, line_number=None),
+ make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=438, start_offset=438, 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=448, start_offset=448, starts_line=False, line_number=28),
+ make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=450, start_offset=450, 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=458, start_offset=458, starts_line=False, line_number=28),
+ make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=460, start_offset=460, starts_line=False, line_number=28),
+ make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=462, start_offset=462, starts_line=True, line_number=None),
+ make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=464, start_offset=464, starts_line=False, line_number=None),
+ make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=466, start_offset=466, starts_line=False, line_number=None),
]
# One last piece of inspect fodder to check the default line number handling
diff --git a/Lib/test/test_doctest/sample_doctest_errors.py b/Lib/test/test_doctest/sample_doctest_errors.py
new file mode 100644
index 00000000000..4a6f07af2d4
--- /dev/null
+++ b/Lib/test/test_doctest/sample_doctest_errors.py
@@ -0,0 +1,46 @@
+"""This is a sample module used for testing doctest.
+
+This module includes various scenarios involving errors.
+
+>>> 2 + 2
+5
+>>> 1/0
+1
+"""
+
+def g():
+ [][0] # line 12
+
+def errors():
+ """
+ >>> 2 + 2
+ 5
+ >>> 1/0
+ 1
+ >>> def f():
+ ... 2 + '2'
+ ...
+ >>> f()
+ 1
+ >>> g()
+ 1
+ """
+
+def syntax_error():
+ """
+ >>> 2+*3
+ 5
+ """
+
+__test__ = {
+ 'bad': """
+ >>> 2 + 2
+ 5
+ >>> 1/0
+ 1
+ """,
+}
+
+def test_suite():
+ import doctest
+ return doctest.DocTestSuite()
diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py
index a4a49298bab..72763d4a013 100644
--- a/Lib/test/test_doctest/test_doctest.py
+++ b/Lib/test/test_doctest/test_doctest.py
@@ -2267,14 +2267,24 @@ def test_DocTestSuite():
>>> import unittest
>>> import test.test_doctest.sample_doctest
>>> suite = doctest.DocTestSuite(test.test_doctest.sample_doctest)
- >>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=4>
+ >>> result = suite.run(unittest.TestResult())
+ >>> result
+ <unittest.result.TestResult run=9 errors=2 failures=2>
+ >>> for tst, _ in result.failures:
+ ... print(tst)
+ bad (test.test_doctest.sample_doctest.__test__) [0]
+ foo (test.test_doctest.sample_doctest) [0]
+ >>> for tst, _ in result.errors:
+ ... print(tst)
+ test_silly_setup (test.test_doctest.sample_doctest) [1]
+ y_is_one (test.test_doctest.sample_doctest) [0]
We can also supply the module by name:
>>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest')
- >>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=4>
+ >>> result = suite.run(unittest.TestResult())
+ >>> result
+ <unittest.result.TestResult run=9 errors=2 failures=2>
The module need not contain any doctest examples:
@@ -2296,13 +2306,26 @@ def test_DocTestSuite():
>>> result
<unittest.result.TestResult run=6 errors=0 failures=2>
>>> len(result.skipped)
- 2
+ 7
+ >>> for tst, _ in result.skipped:
+ ... print(tst)
+ double_skip (test.test_doctest.sample_doctest_skip) [0]
+ double_skip (test.test_doctest.sample_doctest_skip) [1]
+ double_skip (test.test_doctest.sample_doctest_skip)
+ partial_skip_fail (test.test_doctest.sample_doctest_skip) [0]
+ partial_skip_pass (test.test_doctest.sample_doctest_skip) [0]
+ single_skip (test.test_doctest.sample_doctest_skip) [0]
+ single_skip (test.test_doctest.sample_doctest_skip)
+ >>> for tst, _ in result.failures:
+ ... print(tst)
+ no_skip_fail (test.test_doctest.sample_doctest_skip) [0]
+ partial_skip_fail (test.test_doctest.sample_doctest_skip) [1]
We can use the current module:
>>> suite = test.test_doctest.sample_doctest.test_suite()
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=4>
+ <unittest.result.TestResult run=9 errors=2 failures=2>
We can also provide a DocTestFinder:
@@ -2310,7 +2333,7 @@ def test_DocTestSuite():
>>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest',
... test_finder=finder)
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=4>
+ <unittest.result.TestResult run=9 errors=2 failures=2>
The DocTestFinder need not return any tests:
@@ -2326,7 +2349,7 @@ def test_DocTestSuite():
>>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', globs={})
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=5>
+ <unittest.result.TestResult run=9 errors=3 failures=2>
Alternatively, we can provide extra globals. Here we'll make an
error go away by providing an extra global variable:
@@ -2334,7 +2357,7 @@ def test_DocTestSuite():
>>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest',
... extraglobs={'y': 1})
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=3>
+ <unittest.result.TestResult run=9 errors=1 failures=2>
You can pass option flags. Here we'll cause an extra error
by disabling the blank-line feature:
@@ -2342,7 +2365,7 @@ def test_DocTestSuite():
>>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest',
... optionflags=doctest.DONT_ACCEPT_BLANKLINE)
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=5>
+ <unittest.result.TestResult run=9 errors=2 failures=3>
You can supply setUp and tearDown functions:
@@ -2359,7 +2382,7 @@ def test_DocTestSuite():
>>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest',
... setUp=setUp, tearDown=tearDown)
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=3>
+ <unittest.result.TestResult run=9 errors=1 failures=2>
But the tearDown restores sanity:
@@ -2377,13 +2400,115 @@ def test_DocTestSuite():
>>> suite = doctest.DocTestSuite('test.test_doctest.sample_doctest', setUp=setUp)
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=3>
+ <unittest.result.TestResult run=9 errors=1 failures=2>
Here, we didn't need to use a tearDown function because we
modified the test globals, which are a copy of the
sample_doctest module dictionary. The test globals are
automatically cleared for us after a test.
- """
+ """
+
+def test_DocTestSuite_errors():
+ """Tests for error reporting in DocTestSuite.
+
+ >>> import unittest
+ >>> import test.test_doctest.sample_doctest_errors as mod
+ >>> suite = doctest.DocTestSuite(mod)
+ >>> result = suite.run(unittest.TestResult())
+ >>> result
+ <unittest.result.TestResult run=4 errors=6 failures=3>
+ >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors
+ >...>> 2 + 2
+ AssertionError: Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ <BLANKLINE>
+ >>> print(result.failures[1][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line None, in test.test_doctest.sample_doctest_errors.__test__.bad
+ AssertionError: Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ <BLANKLINE>
+ >>> print(result.failures[2][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors
+ >...>> 2 + 2
+ AssertionError: Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ <BLANKLINE>
+ >>> print(result.errors[0][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors
+ >...>> 1/0
+ File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ <BLANKLINE>
+ >>> print(result.errors[1][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line None, in test.test_doctest.sample_doctest_errors.__test__.bad
+ File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ <BLANKLINE>
+ >>> print(result.errors[2][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors
+ >...>> 1/0
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ <BLANKLINE>
+ >>> print(result.errors[3][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors
+ >...>> f()
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module>
+ f()
+ ~^^
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[2]>", line 2, in f
+ 2 + '2'
+ ~~^~~~~
+ TypeError: ...
+ <BLANKLINE>
+ >>> print(result.errors[4][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors
+ >...>> g()
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module>
+ g()
+ ~^^
+ File "...sample_doctest_errors.py", line 12, in g
+ [][0] # line 12
+ ~~^^^
+ IndexError: list index out of range
+ <BLANKLINE>
+ >>> print(result.errors[5][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error
+ >...>> 2+*3
+ File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1
+ 2+*3
+ ^
+ SyntaxError: invalid syntax
+ <BLANKLINE>
+ """
def test_DocFileSuite():
"""We can test tests found in text files using a DocFileSuite.
@@ -2396,7 +2521,7 @@ def test_DocFileSuite():
... 'test_doctest2.txt',
... 'test_doctest4.txt')
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=3 errors=0 failures=2>
+ <unittest.result.TestResult run=3 errors=2 failures=0>
The test files are looked for in the directory containing the
calling module. A package keyword argument can be provided to
@@ -2408,14 +2533,14 @@ def test_DocFileSuite():
... 'test_doctest4.txt',
... package='test.test_doctest')
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=3 errors=0 failures=2>
+ <unittest.result.TestResult run=3 errors=2 failures=0>
'/' should be used as a path separator. It will be converted
to a native separator at run time:
>>> suite = doctest.DocFileSuite('../test_doctest/test_doctest.txt')
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=1 errors=0 failures=1>
+ <unittest.result.TestResult run=1 errors=1 failures=0>
If DocFileSuite is used from an interactive session, then files
are resolved relative to the directory of sys.argv[0]:
@@ -2441,7 +2566,7 @@ def test_DocFileSuite():
>>> suite = doctest.DocFileSuite(test_file, module_relative=False)
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=1 errors=0 failures=1>
+ <unittest.result.TestResult run=1 errors=1 failures=0>
It is an error to specify `package` when `module_relative=False`:
@@ -2455,12 +2580,19 @@ def test_DocFileSuite():
>>> suite = doctest.DocFileSuite('test_doctest.txt',
... 'test_doctest4.txt',
- ... 'test_doctest_skip.txt')
+ ... 'test_doctest_skip.txt',
+ ... 'test_doctest_skip2.txt')
>>> result = suite.run(unittest.TestResult())
>>> result
- <unittest.result.TestResult run=3 errors=0 failures=1>
- >>> len(result.skipped)
- 1
+ <unittest.result.TestResult run=4 errors=1 failures=0>
+ >>> len(result.skipped)
+ 4
+ >>> for tst, _ in result.skipped: # doctest: +ELLIPSIS
+ ... print('=', tst)
+ = ...test_doctest_skip.txt [0]
+ = ...test_doctest_skip.txt [1]
+ = ...test_doctest_skip.txt
+ = ...test_doctest_skip2.txt [0]
You can specify initial global variables:
@@ -2469,7 +2601,7 @@ def test_DocFileSuite():
... 'test_doctest4.txt',
... globs={'favorite_color': 'blue'})
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=3 errors=0 failures=1>
+ <unittest.result.TestResult run=3 errors=1 failures=0>
In this case, we supplied a missing favorite color. You can
provide doctest options:
@@ -2480,7 +2612,7 @@ def test_DocFileSuite():
... optionflags=doctest.DONT_ACCEPT_BLANKLINE,
... globs={'favorite_color': 'blue'})
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=3 errors=0 failures=2>
+ <unittest.result.TestResult run=3 errors=1 failures=1>
And, you can provide setUp and tearDown functions:
@@ -2499,7 +2631,7 @@ def test_DocFileSuite():
... 'test_doctest4.txt',
... setUp=setUp, tearDown=tearDown)
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=3 errors=0 failures=1>
+ <unittest.result.TestResult run=3 errors=1 failures=0>
But the tearDown restores sanity:
@@ -2541,9 +2673,60 @@ def test_DocFileSuite():
... 'test_doctest4.txt',
... encoding='utf-8')
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=3 errors=0 failures=2>
+ <unittest.result.TestResult run=3 errors=2 failures=0>
+ """
- """
+def test_DocFileSuite_errors():
+ """Tests for error reporting in DocTestSuite.
+
+ >>> import unittest
+ >>> suite = doctest.DocFileSuite('test_doctest_errors.txt')
+ >>> result = suite.run(unittest.TestResult())
+ >>> result
+ <unittest.result.TestResult run=1 errors=3 failures=1>
+ >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt
+ >...>> 2 + 2
+ AssertionError: Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ <BLANKLINE>
+ >>> print(result.errors[0][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt
+ >...>> 1/0
+ File "<doctest test_doctest_errors.txt[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ <BLANKLINE>
+ >>> print(result.errors[1][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt
+ >...>> f()
+ File "<doctest test_doctest_errors.txt[3]>", line 1, in <module>
+ f()
+ ~^^
+ File "<doctest test_doctest_errors.txt[2]>", line 2, in f
+ 2 + '2'
+ ~~^~~~~
+ TypeError: ...
+ <BLANKLINE>
+ >>> print(result.errors[2][1]) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt
+ >...>> 2+*3
+ File "<doctest test_doctest_errors.txt[4]>", line 1
+ 2+*3
+ ^
+ SyntaxError: invalid syntax
+ <BLANKLINE>
+
+ """
def test_trailing_space_in_test():
"""
@@ -2612,14 +2795,26 @@ def test_unittest_reportflags():
... optionflags=doctest.DONT_ACCEPT_BLANKLINE)
>>> import unittest
>>> result = suite.run(unittest.TestResult())
+ >>> result
+ <unittest.result.TestResult run=1 errors=1 failures=1>
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS
- Traceback ...
- Failed example:
- favorite_color
- ...
- Failed example:
+ Traceback (most recent call last):
+ File ...
+ >...>> if 1:
+ AssertionError: Failed example:
if 1:
- ...
+ print('a')
+ print()
+ print('b')
+ Expected:
+ a
+ <BLANKLINE>
+ b
+ Got:
+ a
+ <BLANKLINE>
+ b
+ <BLANKLINE>
Note that we see both failures displayed.
@@ -2628,16 +2823,8 @@ def test_unittest_reportflags():
Now, when we run the test:
- >>> result = suite.run(unittest.TestResult())
- >>> print(result.failures[0][1]) # doctest: +ELLIPSIS
- Traceback ...
- Failed example:
- favorite_color
- Exception raised:
- ...
- NameError: name 'favorite_color' is not defined
- <BLANKLINE>
- <BLANKLINE>
+ >>> suite.run(unittest.TestResult())
+ <unittest.result.TestResult run=1 errors=1 failures=0>
We get only the first failure.
@@ -2647,19 +2834,20 @@ def test_unittest_reportflags():
>>> suite = doctest.DocFileSuite('test_doctest.txt',
... optionflags=doctest.DONT_ACCEPT_BLANKLINE | doctest.REPORT_NDIFF)
- Then the default eporting options are ignored:
+ Then the default reporting options are ignored:
>>> result = suite.run(unittest.TestResult())
+ >>> result
+ <unittest.result.TestResult run=1 errors=1 failures=1>
*NOTE*: These doctest are intentionally not placed in raw string to depict
the trailing whitespace using `\x20` in the diff below.
>>> print(result.failures[0][1]) # doctest: +ELLIPSIS
Traceback ...
- Failed example:
- favorite_color
- ...
- Failed example:
+ File ...
+ >...>> if 1:
+ AssertionError: Failed example:
if 1:
print('a')
print()
@@ -2670,7 +2858,6 @@ def test_unittest_reportflags():
+\x20
b
<BLANKLINE>
- <BLANKLINE>
Test runners can restore the formatting flags after they run:
@@ -2860,6 +3047,57 @@ Test the verbose output:
>>> _colorize.COLORIZE = save_colorize
"""
+def test_testfile_errors(): r"""
+Tests for error reporting in the testfile() function.
+
+ >>> doctest.testfile('test_doctest_errors.txt', verbose=False) # doctest: +ELLIPSIS
+ **********************************************************************
+ File "...test_doctest_errors.txt", line 4, in test_doctest_errors.txt
+ Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ **********************************************************************
+ File "...test_doctest_errors.txt", line 6, in test_doctest_errors.txt
+ Failed example:
+ 1/0
+ Exception raised:
+ Traceback (most recent call last):
+ File "<doctest test_doctest_errors.txt[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ **********************************************************************
+ File "...test_doctest_errors.txt", line 11, in test_doctest_errors.txt
+ Failed example:
+ f()
+ Exception raised:
+ Traceback (most recent call last):
+ File "<doctest test_doctest_errors.txt[3]>", line 1, in <module>
+ f()
+ ~^^
+ File "<doctest test_doctest_errors.txt[2]>", line 2, in f
+ 2 + '2'
+ ~~^~~~~
+ TypeError: ...
+ **********************************************************************
+ File "...test_doctest_errors.txt", line 13, in test_doctest_errors.txt
+ Failed example:
+ 2+*3
+ Exception raised:
+ File "<doctest test_doctest_errors.txt[4]>", line 1
+ 2+*3
+ ^
+ SyntaxError: invalid syntax
+ **********************************************************************
+ 1 item had failures:
+ 4 of 5 in test_doctest_errors.txt
+ ***Test Failed*** 4 failures.
+ TestResults(failed=4, attempted=5)
+"""
+
class TestImporter(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
@@ -2990,6 +3228,110 @@ out of the binary module.
TestResults(failed=0, attempted=0)
"""
+def test_testmod_errors(): r"""
+Tests for error reporting in the testmod() function.
+
+ >>> import test.test_doctest.sample_doctest_errors as mod
+ >>> doctest.testmod(mod, verbose=False) # doctest: +ELLIPSIS
+ **********************************************************************
+ File "...sample_doctest_errors.py", line 5, in test.test_doctest.sample_doctest_errors
+ Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ **********************************************************************
+ File "...sample_doctest_errors.py", line 7, in test.test_doctest.sample_doctest_errors
+ Failed example:
+ 1/0
+ Exception raised:
+ Traceback (most recent call last):
+ File "<doctest test.test_doctest.sample_doctest_errors[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ **********************************************************************
+ File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad
+ Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ **********************************************************************
+ File "...sample_doctest_errors.py", line ?, in test.test_doctest.sample_doctest_errors.__test__.bad
+ Failed example:
+ 1/0
+ Exception raised:
+ Traceback (most recent call last):
+ File "<doctest test.test_doctest.sample_doctest_errors.__test__.bad[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ **********************************************************************
+ File "...sample_doctest_errors.py", line 16, in test.test_doctest.sample_doctest_errors.errors
+ Failed example:
+ 2 + 2
+ Expected:
+ 5
+ Got:
+ 4
+ **********************************************************************
+ File "...sample_doctest_errors.py", line 18, in test.test_doctest.sample_doctest_errors.errors
+ Failed example:
+ 1/0
+ Exception raised:
+ Traceback (most recent call last):
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[1]>", line 1, in <module>
+ 1/0
+ ~^~
+ ZeroDivisionError: division by zero
+ **********************************************************************
+ File "...sample_doctest_errors.py", line 23, in test.test_doctest.sample_doctest_errors.errors
+ Failed example:
+ f()
+ Exception raised:
+ Traceback (most recent call last):
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[3]>", line 1, in <module>
+ f()
+ ~^^
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[2]>", line 2, in f
+ 2 + '2'
+ ~~^~~~~
+ TypeError: ...
+ **********************************************************************
+ File "...sample_doctest_errors.py", line 25, in test.test_doctest.sample_doctest_errors.errors
+ Failed example:
+ g()
+ Exception raised:
+ Traceback (most recent call last):
+ File "<doctest test.test_doctest.sample_doctest_errors.errors[4]>", line 1, in <module>
+ g()
+ ~^^
+ File "...sample_doctest_errors.py", line 12, in g
+ [][0] # line 12
+ ~~^^^
+ IndexError: list index out of range
+ **********************************************************************
+ File "...sample_doctest_errors.py", line 31, in test.test_doctest.sample_doctest_errors.syntax_error
+ Failed example:
+ 2+*3
+ Exception raised:
+ File "<doctest test.test_doctest.sample_doctest_errors.syntax_error[0]>", line 1
+ 2+*3
+ ^
+ SyntaxError: invalid syntax
+ **********************************************************************
+ 4 items had failures:
+ 2 of 2 in test.test_doctest.sample_doctest_errors
+ 2 of 2 in test.test_doctest.sample_doctest_errors.__test__.bad
+ 4 of 5 in test.test_doctest.sample_doctest_errors.errors
+ 1 of 1 in test.test_doctest.sample_doctest_errors.syntax_error
+ ***Test Failed*** 9 failures.
+ TestResults(failed=9, attempted=10)
+"""
+
try:
os.fsencode("foo-bär@baz.py")
supports_unicode = True
@@ -3021,11 +3363,6 @@ Check doctest with a non-ascii filename:
raise Exception('clé')
Exception raised:
Traceback (most recent call last):
- File ...
- exec(compile(example.source, filename, "single",
- ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- compileflags, True), test.globs)
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<doctest foo-bär@baz[0]>", line 1, in <module>
raise Exception('clé')
Exception: clé
@@ -3318,9 +3655,9 @@ def test_run_doctestsuite_multiple_times():
>>> import test.test_doctest.sample_doctest
>>> suite = doctest.DocTestSuite(test.test_doctest.sample_doctest)
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=4>
+ <unittest.result.TestResult run=9 errors=2 failures=2>
>>> suite.run(unittest.TestResult())
- <unittest.result.TestResult run=9 errors=0 failures=4>
+ <unittest.result.TestResult run=9 errors=2 failures=2>
"""
diff --git a/Lib/test/test_doctest/test_doctest_errors.txt b/Lib/test/test_doctest/test_doctest_errors.txt
new file mode 100644
index 00000000000..93c3c106e60
--- /dev/null
+++ b/Lib/test/test_doctest/test_doctest_errors.txt
@@ -0,0 +1,14 @@
+This is a sample doctest in a text file, in which all examples fail
+or raise an exception.
+
+ >>> 2 + 2
+ 5
+ >>> 1/0
+ 1
+ >>> def f():
+ ... 2 + '2'
+ ...
+ >>> f()
+ 1
+ >>> 2+*3
+ 5
diff --git a/Lib/test/test_doctest/test_doctest_skip.txt b/Lib/test/test_doctest/test_doctest_skip.txt
index f340e2b8141..06c23d06e60 100644
--- a/Lib/test/test_doctest/test_doctest_skip.txt
+++ b/Lib/test/test_doctest/test_doctest_skip.txt
@@ -2,3 +2,5 @@ This is a sample doctest in a text file, in which all examples are skipped.
>>> 2 + 2 # doctest: +SKIP
5
+ >>> 2 + 2 # doctest: +SKIP
+ 4
diff --git a/Lib/test/test_doctest/test_doctest_skip2.txt b/Lib/test/test_doctest/test_doctest_skip2.txt
new file mode 100644
index 00000000000..85e4938c346
--- /dev/null
+++ b/Lib/test/test_doctest/test_doctest_skip2.txt
@@ -0,0 +1,6 @@
+This is a sample doctest in a text file, in which some examples are skipped.
+
+ >>> 2 + 2 # doctest: +SKIP
+ 5
+ >>> 2 + 2
+ 4
diff --git a/Lib/test/test_dynamicclassattribute.py b/Lib/test/test_dynamicclassattribute.py
index 9f694d9eb46..b19be33c72f 100644
--- a/Lib/test/test_dynamicclassattribute.py
+++ b/Lib/test/test_dynamicclassattribute.py
@@ -104,8 +104,8 @@ class PropertyTests(unittest.TestCase):
self.assertEqual(base.spam, 10)
self.assertEqual(base._spam, 10)
delattr(base, "spam")
- self.assertTrue(not hasattr(base, "spam"))
- self.assertTrue(not hasattr(base, "_spam"))
+ self.assertNotHasAttr(base, "spam")
+ self.assertNotHasAttr(base, "_spam")
base.spam = 20
self.assertEqual(base.spam, 20)
self.assertEqual(base._spam, 20)
diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py
index ac12c3b2306..179e236ecdf 100644
--- a/Lib/test/test_email/test__header_value_parser.py
+++ b/Lib/test/test_email/test__header_value_parser.py
@@ -463,6 +463,19 @@ class TestParser(TestParserMixin, TestEmailBase):
[errors.NonPrintableDefect], ')')
self.assertEqual(ptext.defects[0].non_printables[0], '\x00')
+ def test_get_qp_ctext_close_paren_only(self):
+ self._test_get_x(parser.get_qp_ctext,
+ ')', '', ' ', [], ')')
+
+ def test_get_qp_ctext_open_paren_only(self):
+ self._test_get_x(parser.get_qp_ctext,
+ '(', '', ' ', [], '(')
+
+ def test_get_qp_ctext_no_end_char(self):
+ self._test_get_x(parser.get_qp_ctext,
+ '', '', ' ', [], '')
+
+
# get_qcontent
def test_get_qcontent_only(self):
@@ -503,6 +516,14 @@ class TestParser(TestParserMixin, TestEmailBase):
[errors.NonPrintableDefect], '"')
self.assertEqual(ptext.defects[0].non_printables[0], '\x00')
+ def test_get_qcontent_empty(self):
+ self._test_get_x(parser.get_qcontent,
+ '"', '', '', [], '"')
+
+ def test_get_qcontent_no_end_char(self):
+ self._test_get_x(parser.get_qcontent,
+ '', '', '', [], '')
+
# get_atext
def test_get_atext_only(self):
@@ -1283,6 +1304,18 @@ class TestParser(TestParserMixin, TestEmailBase):
self._test_get_x(parser.get_dtext,
'foo[bar', 'foo', 'foo', [], '[bar')
+ def test_get_dtext_open_bracket_only(self):
+ self._test_get_x(parser.get_dtext,
+ '[', '', '', [], '[')
+
+ def test_get_dtext_close_bracket_only(self):
+ self._test_get_x(parser.get_dtext,
+ ']', '', '', [], ']')
+
+ def test_get_dtext_empty(self):
+ self._test_get_x(parser.get_dtext,
+ '', '', '', [], '')
+
# get_domain_literal
def test_get_domain_literal_only(self):
@@ -2458,6 +2491,38 @@ class TestParser(TestParserMixin, TestEmailBase):
self.assertEqual(address.all_mailboxes[0].domain, 'example.com')
self.assertEqual(address.all_mailboxes[0].addr_spec, '"example example"@example.com')
+ def test_get_address_with_invalid_domain(self):
+ address = self._test_get_x(parser.get_address,
+ '<T@[',
+ '<T@[]>',
+ '<T@[]>',
+ [errors.InvalidHeaderDefect, # missing trailing '>' on angle-addr
+ errors.InvalidHeaderDefect, # end of input inside domain-literal
+ ],
+ '')
+ self.assertEqual(address.token_type, 'address')
+ self.assertEqual(len(address.mailboxes), 0)
+ self.assertEqual(len(address.all_mailboxes), 1)
+ self.assertEqual(address.all_mailboxes[0].domain, '[]')
+ self.assertEqual(address.all_mailboxes[0].local_part, 'T')
+ self.assertEqual(address.all_mailboxes[0].token_type, 'invalid-mailbox')
+ self.assertEqual(address[0].token_type, 'invalid-mailbox')
+
+ address = self._test_get_x(parser.get_address,
+ '!an??:=m==fr2@[C',
+ '!an??:=m==fr2@[C];',
+ '!an??:=m==fr2@[C];',
+ [errors.InvalidHeaderDefect, # end of header in group
+ errors.InvalidHeaderDefect, # end of input inside domain-literal
+ ],
+ '')
+ self.assertEqual(address.token_type, 'address')
+ self.assertEqual(len(address.mailboxes), 0)
+ self.assertEqual(len(address.all_mailboxes), 1)
+ self.assertEqual(address.all_mailboxes[0].domain, '[C]')
+ self.assertEqual(address.all_mailboxes[0].local_part, '=m==fr2')
+ self.assertEqual(address.all_mailboxes[0].token_type, 'invalid-mailbox')
+ self.assertEqual(address[0].token_type, 'group')
# get_address_list
@@ -2732,6 +2797,19 @@ class TestParser(TestParserMixin, TestEmailBase):
)
self.assertEqual(message_id.token_type, 'message-id')
+ def test_parse_message_id_with_invalid_domain(self):
+ message_id = self._test_parse_x(
+ parser.parse_message_id,
+ "<T@[",
+ "<T@[]>",
+ "<T@[]>",
+ [errors.ObsoleteHeaderDefect] + [errors.InvalidHeaderDefect] * 2,
+ [],
+ )
+ self.assertEqual(message_id.token_type, 'message-id')
+ self.assertEqual(str(message_id.all_defects[-1]),
+ "end of input inside domain-literal")
+
def test_parse_message_id_with_remaining(self):
message_id = self._test_parse_x(
parser.parse_message_id,
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index 7b14305f997..b8116d073a2 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -389,6 +389,24 @@ class TestMessageAPI(TestEmailBase):
msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
self.assertEqual(msg.get_param('baz'), '')
+ def test_continuation_sorting_part_order(self):
+ msg = email.message_from_string(
+ "Content-Disposition: attachment; "
+ "filename*=\"ignored\"; "
+ "filename*0*=\"utf-8''foo%20\"; "
+ "filename*1*=\"bar.txt\"\n"
+ )
+ filename = msg.get_filename()
+ self.assertEqual(filename, 'foo bar.txt')
+
+ def test_sorting_no_continuations(self):
+ msg = email.message_from_string(
+ "Content-Disposition: attachment; "
+ "filename*=\"bar.txt\"; "
+ )
+ filename = msg.get_filename()
+ self.assertEqual(filename, 'bar.txt')
+
def test_missing_filename(self):
msg = email.message_from_string("From: foo\n")
self.assertEqual(msg.get_filename(), None)
@@ -2550,6 +2568,18 @@ Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar =?mac-iceland?q?r=8Aksm?=
self.assertEqual(str(make_header(decode_header(s))),
'"Müller T" <T.Mueller@xxx.com>')
+ def test_unencoded_ascii(self):
+ # bpo-22833/gh-67022: returns [(str, None)] rather than [(bytes, None)]
+ s = 'header without encoded words'
+ self.assertEqual(decode_header(s),
+ [('header without encoded words', None)])
+
+ def test_unencoded_utf8(self):
+ # bpo-22833/gh-67022: returns [(str, None)] rather than [(bytes, None)]
+ s = 'header with unexpected non ASCII caract\xe8res'
+ self.assertEqual(decode_header(s),
+ [('header with unexpected non ASCII caract\xe8res', None)])
+
# Test the MIMEMessage class
class TestMIMEMessage(TestEmailBase):
diff --git a/Lib/test/test_email/test_utils.py b/Lib/test/test_email/test_utils.py
index 4e6201e13c8..c9d09098b50 100644
--- a/Lib/test/test_email/test_utils.py
+++ b/Lib/test/test_email/test_utils.py
@@ -4,6 +4,16 @@ import test.support
import time
import unittest
+from test.support import cpython_only
+from test.support.import_helper import ensure_lazy_imports
+
+
+class TestImportTime(unittest.TestCase):
+
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("email.utils", {"random", "socket"})
+
class DateTimeTests(unittest.TestCase):
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index e06e684408c..89f4aebe28f 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -296,7 +296,7 @@ class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
if MS_WINDOWS:
expected_path = self.test_exe
else:
- expected_path = os.path.join(os.getcwd(), "spam")
+ expected_path = os.path.join(os.getcwd(), "_testembed")
expected_output = f"sys.executable: {expected_path}\n"
self.assertIn(expected_output, out)
self.assertEqual(err, '')
@@ -585,7 +585,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'faulthandler': False,
'tracemalloc': 0,
'perf_profiling': 0,
- 'import_time': False,
+ 'import_time': 0,
'thread_inherit_context': DEFAULT_THREAD_INHERIT_CONTEXT,
'context_aware_warnings': DEFAULT_CONTEXT_AWARE_WARNINGS,
'code_debug_ranges': True,
@@ -969,7 +969,6 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'utf8_mode': True,
}
config = {
- 'program_name': './globalvar',
'site_import': False,
'bytes_warning': True,
'warnoptions': ['default::BytesWarning'],
@@ -998,7 +997,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'hash_seed': 123,
'tracemalloc': 2,
'perf_profiling': 0,
- 'import_time': True,
+ 'import_time': 2,
'code_debug_ranges': False,
'show_ref_count': True,
'malloc_stats': True,
@@ -1064,7 +1063,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'use_hash_seed': True,
'hash_seed': 42,
'tracemalloc': 2,
- 'import_time': True,
+ 'import_time': 1,
'code_debug_ranges': False,
'malloc_stats': True,
'inspect': True,
@@ -1100,7 +1099,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'use_hash_seed': True,
'hash_seed': 42,
'tracemalloc': 2,
- 'import_time': True,
+ 'import_time': 1,
'code_debug_ranges': False,
'malloc_stats': True,
'inspect': True,
@@ -1916,6 +1915,10 @@ class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
self.run_embedded_interpreter("test_get_incomplete_frame")
+ def test_gilstate_after_finalization(self):
+ self.run_embedded_interpreter("test_gilstate_after_finalization")
+
+
class MiscTests(EmbeddingTestsMixin, unittest.TestCase):
def test_unicode_id_init(self):
# bpo-42882: Test that _PyUnicode_FromId() works
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index 68cedc666a5..bbc7630fa83 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -19,7 +19,8 @@ from io import StringIO
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
from test import support
from test.support import ALWAYS_EQ, REPO_ROOT
-from test.support import threading_helper
+from test.support import threading_helper, cpython_only
+from test.support.import_helper import ensure_lazy_imports
from datetime import timedelta
python_version = sys.version_info[:2]
@@ -35,7 +36,7 @@ def load_tests(loader, tests, ignore):
optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE,
))
howto_tests = os.path.join(REPO_ROOT, 'Doc/howto/enum.rst')
- if os.path.exists(howto_tests):
+ if os.path.exists(howto_tests) and sys.float_repr_style == 'short':
tests.addTests(doctest.DocFileSuite(
howto_tests,
module_relative=False,
@@ -433,9 +434,9 @@ class _EnumTests:
def spam(cls):
pass
#
- self.assertTrue(hasattr(Season, 'spam'))
+ self.assertHasAttr(Season, 'spam')
del Season.spam
- self.assertFalse(hasattr(Season, 'spam'))
+ self.assertNotHasAttr(Season, 'spam')
#
with self.assertRaises(AttributeError):
del Season.SPRING
@@ -2651,12 +2652,12 @@ class TestSpecial(unittest.TestCase):
OneDay = day_1
OneWeek = week_1
OneMonth = month_1
- self.assertFalse(hasattr(Period, '_ignore_'))
- self.assertFalse(hasattr(Period, 'Period'))
- self.assertFalse(hasattr(Period, 'i'))
- self.assertTrue(isinstance(Period.day_1, timedelta))
- self.assertTrue(Period.month_1 is Period.day_30)
- self.assertTrue(Period.week_4 is Period.day_28)
+ self.assertNotHasAttr(Period, '_ignore_')
+ self.assertNotHasAttr(Period, 'Period')
+ self.assertNotHasAttr(Period, 'i')
+ self.assertIsInstance(Period.day_1, timedelta)
+ self.assertIs(Period.month_1, Period.day_30)
+ self.assertIs(Period.week_4, Period.day_28)
def test_nonhash_value(self):
class AutoNumberInAList(Enum):
@@ -2876,7 +2877,7 @@ class TestSpecial(unittest.TestCase):
self.assertEqual(str(ReformedColor.BLUE), 'blue')
self.assertEqual(ReformedColor.RED.behavior(), 'booyah')
self.assertEqual(ConfusedColor.RED.social(), "what's up?")
- self.assertTrue(issubclass(ReformedColor, int))
+ self.assertIsSubclass(ReformedColor, int)
def test_multiple_inherited_mixin(self):
@unique
@@ -5288,6 +5289,10 @@ class MiscTestCase(unittest.TestCase):
def test__all__(self):
support.check__all__(self, enum, not_exported={'bin', 'show_flag_values'})
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("enum", {"functools", "warnings", "inspect", "re"})
+
def test_doc_1(self):
class Single(Enum):
ONE = 1
diff --git a/Lib/test/test_errno.py b/Lib/test/test_errno.py
index 5c437e9ccea..e7f185c6b1a 100644
--- a/Lib/test/test_errno.py
+++ b/Lib/test/test_errno.py
@@ -12,14 +12,12 @@ class ErrnoAttributeTests(unittest.TestCase):
def test_for_improper_attributes(self):
# No unexpected attributes should be on the module.
for error_code in std_c_errors:
- self.assertTrue(hasattr(errno, error_code),
- "errno is missing %s" % error_code)
+ self.assertHasAttr(errno, error_code)
def test_using_errorcode(self):
# Every key value in errno.errorcode should be on the module.
for value in errno.errorcode.values():
- self.assertTrue(hasattr(errno, value),
- 'no %s attr in errno' % value)
+ self.assertHasAttr(errno, value)
class ErrorcodeTests(unittest.TestCase):
diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py
index 92bbf791764..5df2c41c6b5 100644
--- a/Lib/test/test_exception_group.py
+++ b/Lib/test/test_exception_group.py
@@ -1,13 +1,13 @@
import collections.abc
import types
import unittest
-from test.support import skip_emscripten_stack_overflow, exceeds_recursion_limit
+from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow, exceeds_recursion_limit
class TestExceptionGroupTypeHierarchy(unittest.TestCase):
def test_exception_group_types(self):
- self.assertTrue(issubclass(ExceptionGroup, Exception))
- self.assertTrue(issubclass(ExceptionGroup, BaseExceptionGroup))
- self.assertTrue(issubclass(BaseExceptionGroup, BaseException))
+ self.assertIsSubclass(ExceptionGroup, Exception)
+ self.assertIsSubclass(ExceptionGroup, BaseExceptionGroup)
+ self.assertIsSubclass(BaseExceptionGroup, BaseException)
def test_exception_is_not_generic_type(self):
with self.assertRaisesRegex(TypeError, 'Exception'):
@@ -465,12 +465,14 @@ class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
return e
@skip_emscripten_stack_overflow()
+ @skip_wasi_stack_overflow()
def test_deep_split(self):
e = self.make_deep_eg()
with self.assertRaises(RecursionError):
e.split(TypeError)
@skip_emscripten_stack_overflow()
+ @skip_wasi_stack_overflow()
def test_deep_subgroup(self):
e = self.make_deep_eg()
with self.assertRaises(RecursionError):
@@ -812,8 +814,8 @@ class NestedExceptionGroupSplitTest(ExceptionGroupSplitTestBase):
eg = ExceptionGroup("eg", [ValueError(1), TypeError(2)])
eg.__notes__ = 123
match, rest = eg.split(TypeError)
- self.assertFalse(hasattr(match, '__notes__'))
- self.assertFalse(hasattr(rest, '__notes__'))
+ self.assertNotHasAttr(match, '__notes__')
+ self.assertNotHasAttr(rest, '__notes__')
def test_drive_invalid_return_value(self):
class MyEg(ExceptionGroup):
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index d177e3dc0f5..57d0656487d 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -357,7 +357,7 @@ class ExceptionTests(unittest.TestCase):
except TypeError as err:
co = err.__traceback__.tb_frame.f_code
self.assertEqual(co.co_name, "test_capi1")
- self.assertTrue(co.co_filename.endswith('test_exceptions.py'))
+ self.assertEndsWith(co.co_filename, 'test_exceptions.py')
else:
self.fail("Expected exception")
@@ -369,7 +369,7 @@ class ExceptionTests(unittest.TestCase):
tb = err.__traceback__.tb_next
co = tb.tb_frame.f_code
self.assertEqual(co.co_name, "__init__")
- self.assertTrue(co.co_filename.endswith('test_exceptions.py'))
+ self.assertEndsWith(co.co_filename, 'test_exceptions.py')
co2 = tb.tb_frame.f_back.f_code
self.assertEqual(co2.co_name, "test_capi2")
else:
@@ -598,7 +598,7 @@ class ExceptionTests(unittest.TestCase):
def test_notes(self):
for e in [BaseException(1), Exception(2), ValueError(3)]:
with self.subTest(e=e):
- self.assertFalse(hasattr(e, '__notes__'))
+ self.assertNotHasAttr(e, '__notes__')
e.add_note("My Note")
self.assertEqual(e.__notes__, ["My Note"])
@@ -610,7 +610,7 @@ class ExceptionTests(unittest.TestCase):
self.assertEqual(e.__notes__, ["My Note", "Your Note"])
del e.__notes__
- self.assertFalse(hasattr(e, '__notes__'))
+ self.assertNotHasAttr(e, '__notes__')
e.add_note("Our Note")
self.assertEqual(e.__notes__, ["Our Note"])
@@ -1429,6 +1429,7 @@ class ExceptionTests(unittest.TestCase):
self.assertIn("maximum recursion depth exceeded", str(exc))
@support.skip_wasi_stack_overflow()
+ @support.skip_emscripten_stack_overflow()
@cpython_only
@support.requires_resource('cpu')
def test_trashcan_recursion(self):
@@ -1444,6 +1445,7 @@ class ExceptionTests(unittest.TestCase):
foo()
support.gc_collect()
+ @support.skip_emscripten_stack_overflow()
@cpython_only
def test_recursion_normalizing_exception(self):
import_module("_testinternalcapi")
@@ -1521,6 +1523,7 @@ class ExceptionTests(unittest.TestCase):
self.assertIn(b'Done.', out)
+ @support.skip_emscripten_stack_overflow()
def test_recursion_in_except_handler(self):
def set_relative_recursion_limit(n):
@@ -1626,7 +1629,7 @@ class ExceptionTests(unittest.TestCase):
# test basic usage of PyErr_NewException
error1 = _testcapi.make_exception_with_doc("_testcapi.error1")
self.assertIs(type(error1), type)
- self.assertTrue(issubclass(error1, Exception))
+ self.assertIsSubclass(error1, Exception)
self.assertIsNone(error1.__doc__)
# test with given docstring
@@ -1636,21 +1639,21 @@ class ExceptionTests(unittest.TestCase):
# test with explicit base (without docstring)
error3 = _testcapi.make_exception_with_doc("_testcapi.error3",
base=error2)
- self.assertTrue(issubclass(error3, error2))
+ self.assertIsSubclass(error3, error2)
# test with explicit base tuple
class C(object):
pass
error4 = _testcapi.make_exception_with_doc("_testcapi.error4", doc4,
(error3, C))
- self.assertTrue(issubclass(error4, error3))
- self.assertTrue(issubclass(error4, C))
+ self.assertIsSubclass(error4, error3)
+ self.assertIsSubclass(error4, C)
self.assertEqual(error4.__doc__, doc4)
# test with explicit dictionary
error5 = _testcapi.make_exception_with_doc("_testcapi.error5", "",
error4, {'a': 1})
- self.assertTrue(issubclass(error5, error4))
+ self.assertIsSubclass(error5, error4)
self.assertEqual(error5.a, 1)
self.assertEqual(error5.__doc__, "")
@@ -1743,7 +1746,7 @@ class ExceptionTests(unittest.TestCase):
self.assertIn("<exception str() failed>", report)
else:
self.assertIn("test message", report)
- self.assertTrue(report.endswith("\n"))
+ self.assertEndsWith(report, "\n")
@cpython_only
# Python built with Py_TRACE_REFS fail with a fatal error in
diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py
index aa05db972f0..0f31c225e68 100644
--- a/Lib/test/test_external_inspection.py
+++ b/Lib/test/test_external_inspection.py
@@ -4,7 +4,10 @@ import textwrap
import importlib
import sys
import socket
-from test.support import os_helper, SHORT_TIMEOUT, busy_retry
+import threading
+from asyncio import staggered, taskgroups, base_events, tasks
+from unittest.mock import ANY
+from test.support import os_helper, SHORT_TIMEOUT, busy_retry, requires_gil_enabled
from test.support.script_helper import make_script
from test.support.socket_helper import find_unused_port
@@ -13,33 +16,60 @@ import subprocess
PROCESS_VM_READV_SUPPORTED = False
try:
- from _testexternalinspection import PROCESS_VM_READV_SUPPORTED
- from _testexternalinspection import get_stack_trace
- from _testexternalinspection import get_async_stack_trace
- from _testexternalinspection import get_all_awaited_by
+ from _remote_debugging import PROCESS_VM_READV_SUPPORTED
+ from _remote_debugging import RemoteUnwinder
+ from _remote_debugging import FrameInfo, CoroInfo, TaskInfo
except ImportError:
raise unittest.SkipTest(
- "Test only runs when _testexternalinspection is available")
+ "Test only runs when _remote_debugging is available"
+ )
+
def _make_test_script(script_dir, script_basename, source):
to_return = make_script(script_dir, script_basename, source)
importlib.invalidate_caches()
return to_return
-skip_if_not_supported = unittest.skipIf((sys.platform != "darwin"
- and sys.platform != "linux"
- and sys.platform != "win32"),
- "Test only runs on Linux, Windows and MacOS")
+
+skip_if_not_supported = unittest.skipIf(
+ (
+ sys.platform != "darwin"
+ and sys.platform != "linux"
+ and sys.platform != "win32"
+ ),
+ "Test only runs on Linux, Windows and MacOS",
+)
+
+
+def get_stack_trace(pid):
+ unwinder = RemoteUnwinder(pid, all_threads=True, debug=True)
+ return unwinder.get_stack_trace()
+
+
+def get_async_stack_trace(pid):
+ unwinder = RemoteUnwinder(pid, debug=True)
+ return unwinder.get_async_stack_trace()
+
+
+def get_all_awaited_by(pid):
+ unwinder = RemoteUnwinder(pid, debug=True)
+ return unwinder.get_all_awaited_by()
+
+
class TestGetStackTrace(unittest.TestCase):
+ maxDiff = None
@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")
+ @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(self):
# Spawn a process with some realistic Python code
port = find_unused_port()
- script = textwrap.dedent(f"""\
- import time, sys, socket
+ script = textwrap.dedent(
+ f"""\
+ import time, sys, socket, threading
# Connect to the test process
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', {port}))
@@ -48,15 +78,18 @@ class TestGetStackTrace(unittest.TestCase):
for x in range(100):
if x == 50:
baz()
+
def baz():
foo()
def foo():
- sock.sendall(b"ready")
- time.sleep(1000)
+ sock.sendall(b"ready:thread\\n"); time.sleep(10_000) # same line number
- bar()
- """)
+ t = threading.Thread(target=bar)
+ t.start()
+ sock.sendall(b"ready:main\\n"); t.join() # same line number
+ """
+ )
stack_trace = None
with os_helper.temp_dir() as work_dir:
script_dir = os.path.join(work_dir, "script_pkg")
@@ -65,21 +98,27 @@ class TestGetStackTrace(unittest.TestCase):
# Create a socket server to communicate with the target process
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- server_socket.bind(('localhost', port))
+ server_socket.bind(("localhost", port))
server_socket.settimeout(SHORT_TIMEOUT)
server_socket.listen(1)
- script_name = _make_test_script(script_dir, 'script', script)
+ script_name = _make_test_script(script_dir, "script", script)
client_socket = None
try:
p = subprocess.Popen([sys.executable, script_name])
client_socket, _ = server_socket.accept()
server_socket.close()
- response = client_socket.recv(1024)
- self.assertEqual(response, b"ready")
+ response = b""
+ while (
+ b"ready:main" not in response
+ or b"ready:thread" not in response
+ ):
+ response += client_socket.recv(1024)
stack_trace = get_stack_trace(p.pid)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -87,22 +126,34 @@ class TestGetStackTrace(unittest.TestCase):
p.terminate()
p.wait(timeout=SHORT_TIMEOUT)
-
- expected_stack_trace = [
- 'foo',
- 'baz',
- 'bar',
- '<module>'
+ thread_expected_stack_trace = [
+ FrameInfo([script_name, 15, "foo"]),
+ FrameInfo([script_name, 12, "baz"]),
+ FrameInfo([script_name, 9, "bar"]),
+ FrameInfo([threading.__file__, ANY, "Thread.run"]),
]
- self.assertEqual(stack_trace, expected_stack_trace)
+ # Is possible that there are more threads, so we check that the
+ # expected stack traces are in the result (looking at you Windows!)
+ self.assertIn((ANY, thread_expected_stack_trace), stack_trace)
+
+ # Check that the main thread stack trace is in the result
+ frame = FrameInfo([script_name, 19, "<module>"])
+ for _, stack in stack_trace:
+ if frame in stack:
+ break
+ else:
+ self.fail("Main thread stack trace not found in result")
@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")
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
def test_async_remote_stack_trace(self):
# Spawn a process with some realistic Python code
port = find_unused_port()
- script = textwrap.dedent(f"""\
+ script = textwrap.dedent(
+ f"""\
import asyncio
import time
import sys
@@ -112,8 +163,7 @@ class TestGetStackTrace(unittest.TestCase):
sock.connect(('localhost', {port}))
def c5():
- sock.sendall(b"ready")
- time.sleep(10000)
+ sock.sendall(b"ready"); time.sleep(10_000) # same line number
async def c4():
await asyncio.sleep(0)
@@ -142,7 +192,8 @@ class TestGetStackTrace(unittest.TestCase):
return loop
asyncio.run(main(), loop_factory={{TASK_FACTORY}})
- """)
+ """
+ )
stack_trace = None
for task_factory_variant in "asyncio.new_event_loop", "new_eager_loop":
with (
@@ -151,19 +202,23 @@ class TestGetStackTrace(unittest.TestCase):
):
script_dir = os.path.join(work_dir, "script_pkg")
os.mkdir(script_dir)
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- server_socket.bind(('localhost', port))
+ server_socket = socket.socket(
+ socket.AF_INET, socket.SOCK_STREAM
+ )
+ server_socket.setsockopt(
+ socket.SOL_SOCKET, socket.SO_REUSEADDR, 1
+ )
+ server_socket.bind(("localhost", port))
server_socket.settimeout(SHORT_TIMEOUT)
server_socket.listen(1)
script_name = _make_test_script(
- script_dir, 'script',
- script.format(TASK_FACTORY=task_factory_variant))
+ script_dir,
+ "script",
+ script.format(TASK_FACTORY=task_factory_variant),
+ )
client_socket = None
try:
- p = subprocess.Popen(
- [sys.executable, script_name]
- )
+ p = subprocess.Popen([sys.executable, script_name])
client_socket, _ = server_socket.accept()
server_socket.close()
response = client_socket.recv(1024)
@@ -171,7 +226,8 @@ class TestGetStackTrace(unittest.TestCase):
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
self.skipTest(
- "Insufficient permissions to read the stack trace")
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -182,25 +238,63 @@ class TestGetStackTrace(unittest.TestCase):
# sets are unordered, so we want to sort "awaited_by"s
stack_trace[2].sort(key=lambda x: x[1])
- root_task = "Task-1"
expected_stack_trace = [
- ["c5", "c4", "c3", "c2"],
+ [
+ FrameInfo([script_name, 10, "c5"]),
+ FrameInfo([script_name, 14, "c4"]),
+ FrameInfo([script_name, 17, "c3"]),
+ FrameInfo([script_name, 20, "c2"]),
+ ],
"c2_root",
[
- [["main"], root_task, []],
- [["c1"], "sub_main_1", [[["main"], root_task, []]]],
- [["c1"], "sub_main_2", [[["main"], root_task, []]]],
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo([script_name, 26, "main"]),
+ ],
+ "Task-1",
+ ]
+ ),
+ CoroInfo(
+ [
+ [FrameInfo([script_name, 23, "c1"])],
+ "sub_main_1",
+ ]
+ ),
+ CoroInfo(
+ [
+ [FrameInfo([script_name, 23, "c1"])],
+ "sub_main_2",
+ ]
+ ),
],
]
self.assertEqual(stack_trace, expected_stack_trace)
@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")
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
def test_asyncgen_remote_stack_trace(self):
# Spawn a process with some realistic Python code
port = find_unused_port()
- script = textwrap.dedent(f"""\
+ script = textwrap.dedent(
+ f"""\
import asyncio
import time
import sys
@@ -210,8 +304,7 @@ class TestGetStackTrace(unittest.TestCase):
sock.connect(('localhost', {port}))
async def gen_nested_call():
- sock.sendall(b"ready")
- time.sleep(10000)
+ sock.sendall(b"ready"); time.sleep(10_000) # same line number
async def gen():
for num in range(2):
@@ -224,7 +317,8 @@ class TestGetStackTrace(unittest.TestCase):
pass
asyncio.run(main())
- """)
+ """
+ )
stack_trace = None
with os_helper.temp_dir() as work_dir:
script_dir = os.path.join(work_dir, "script_pkg")
@@ -232,10 +326,10 @@ class TestGetStackTrace(unittest.TestCase):
# Create a socket server to communicate with the target process
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- server_socket.bind(('localhost', port))
+ server_socket.bind(("localhost", port))
server_socket.settimeout(SHORT_TIMEOUT)
server_socket.listen(1)
- script_name = _make_test_script(script_dir, 'script', script)
+ script_name = _make_test_script(script_dir, "script", script)
client_socket = None
try:
p = subprocess.Popen([sys.executable, script_name])
@@ -245,7 +339,9 @@ class TestGetStackTrace(unittest.TestCase):
self.assertEqual(response, b"ready")
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -257,17 +353,26 @@ class TestGetStackTrace(unittest.TestCase):
stack_trace[2].sort(key=lambda x: x[1])
expected_stack_trace = [
- ['gen_nested_call', 'gen', 'main'], 'Task-1', []
+ [
+ FrameInfo([script_name, 10, "gen_nested_call"]),
+ FrameInfo([script_name, 16, "gen"]),
+ FrameInfo([script_name, 19, "main"]),
+ ],
+ "Task-1",
+ [],
]
self.assertEqual(stack_trace, expected_stack_trace)
@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")
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
def test_async_gather_remote_stack_trace(self):
# Spawn a process with some realistic Python code
port = find_unused_port()
- script = textwrap.dedent(f"""\
+ script = textwrap.dedent(
+ f"""\
import asyncio
import time
import sys
@@ -278,8 +383,7 @@ class TestGetStackTrace(unittest.TestCase):
async def deep():
await asyncio.sleep(0)
- sock.sendall(b"ready")
- time.sleep(10000)
+ sock.sendall(b"ready"); time.sleep(10_000) # same line number
async def c1():
await asyncio.sleep(0)
@@ -292,7 +396,8 @@ class TestGetStackTrace(unittest.TestCase):
await asyncio.gather(c1(), c2())
asyncio.run(main())
- """)
+ """
+ )
stack_trace = None
with os_helper.temp_dir() as work_dir:
script_dir = os.path.join(work_dir, "script_pkg")
@@ -300,10 +405,10 @@ class TestGetStackTrace(unittest.TestCase):
# Create a socket server to communicate with the target process
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- server_socket.bind(('localhost', port))
+ server_socket.bind(("localhost", port))
server_socket.settimeout(SHORT_TIMEOUT)
server_socket.listen(1)
- script_name = _make_test_script(script_dir, 'script', script)
+ script_name = _make_test_script(script_dir, "script", script)
client_socket = None
try:
p = subprocess.Popen([sys.executable, script_name])
@@ -314,7 +419,8 @@ class TestGetStackTrace(unittest.TestCase):
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
self.skipTest(
- "Insufficient permissions to read the stack trace")
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -325,18 +431,26 @@ class TestGetStackTrace(unittest.TestCase):
# sets are unordered, so we want to sort "awaited_by"s
stack_trace[2].sort(key=lambda x: x[1])
- expected_stack_trace = [
- ['deep', 'c1'], 'Task-2', [[['main'], 'Task-1', []]]
+ expected_stack_trace = [
+ [
+ FrameInfo([script_name, 11, "deep"]),
+ FrameInfo([script_name, 15, "c1"]),
+ ],
+ "Task-2",
+ [CoroInfo([[FrameInfo([script_name, 21, "main"])], "Task-1"])],
]
self.assertEqual(stack_trace, expected_stack_trace)
@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")
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
def test_async_staggered_race_remote_stack_trace(self):
# Spawn a process with some realistic Python code
port = find_unused_port()
- script = textwrap.dedent(f"""\
+ script = textwrap.dedent(
+ f"""\
import asyncio.staggered
import time
import sys
@@ -347,15 +461,14 @@ class TestGetStackTrace(unittest.TestCase):
async def deep():
await asyncio.sleep(0)
- sock.sendall(b"ready")
- time.sleep(10000)
+ sock.sendall(b"ready"); time.sleep(10_000) # same line number
async def c1():
await asyncio.sleep(0)
await deep()
async def c2():
- await asyncio.sleep(10000)
+ await asyncio.sleep(10_000)
async def main():
await asyncio.staggered.staggered_race(
@@ -364,7 +477,8 @@ class TestGetStackTrace(unittest.TestCase):
)
asyncio.run(main())
- """)
+ """
+ )
stack_trace = None
with os_helper.temp_dir() as work_dir:
script_dir = os.path.join(work_dir, "script_pkg")
@@ -372,10 +486,10 @@ class TestGetStackTrace(unittest.TestCase):
# Create a socket server to communicate with the target process
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- server_socket.bind(('localhost', port))
+ server_socket.bind(("localhost", port))
server_socket.settimeout(SHORT_TIMEOUT)
server_socket.listen(1)
- script_name = _make_test_script(script_dir, 'script', script)
+ script_name = _make_test_script(script_dir, "script", script)
client_socket = None
try:
p = subprocess.Popen([sys.executable, script_name])
@@ -386,7 +500,8 @@ class TestGetStackTrace(unittest.TestCase):
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
self.skipTest(
- "Insufficient permissions to read the stack trace")
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -396,18 +511,44 @@ class TestGetStackTrace(unittest.TestCase):
# sets are unordered, so we want to sort "awaited_by"s
stack_trace[2].sort(key=lambda x: x[1])
-
- expected_stack_trace = [
- ['deep', 'c1', 'run_one_coro'], 'Task-2', [[['main'], 'Task-1', []]]
+ expected_stack_trace = [
+ [
+ FrameInfo([script_name, 11, "deep"]),
+ FrameInfo([script_name, 15, "c1"]),
+ FrameInfo(
+ [
+ staggered.__file__,
+ ANY,
+ "staggered_race.<locals>.run_one_coro",
+ ]
+ ),
+ ],
+ "Task-2",
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [staggered.__file__, ANY, "staggered_race"]
+ ),
+ FrameInfo([script_name, 21, "main"]),
+ ],
+ "Task-1",
+ ]
+ )
+ ],
]
self.assertEqual(stack_trace, expected_stack_trace)
@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")
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
def test_async_global_awaited_by(self):
port = find_unused_port()
- script = textwrap.dedent(f"""\
+ script = textwrap.dedent(
+ f"""\
import asyncio
import os
import random
@@ -443,6 +584,8 @@ class TestGetStackTrace(unittest.TestCase):
assert message == data.decode()
writer.close()
await writer.wait_closed()
+ # Signal we are ready to sleep
+ sock.sendall(b"ready")
await asyncio.sleep(SHORT_TIMEOUT)
async def echo_client_spam(server):
@@ -452,8 +595,10 @@ class TestGetStackTrace(unittest.TestCase):
random.shuffle(msg)
tg.create_task(echo_client("".join(msg)))
await asyncio.sleep(0)
- # at least a 1000 tasks created
- sock.sendall(b"ready")
+ # at least a 1000 tasks created. Each task will signal
+ # when is ready to avoid the race caused by the fact that
+ # tasks are waited on tg.__exit__ and we cannot signal when
+ # that happens otherwise
# at this point all client tasks completed without assertion errors
# let's wrap up the test
server.close()
@@ -468,7 +613,8 @@ class TestGetStackTrace(unittest.TestCase):
tg.create_task(echo_client_spam(server), name="echo client spam")
asyncio.run(main())
- """)
+ """
+ )
stack_trace = None
with os_helper.temp_dir() as work_dir:
script_dir = os.path.join(work_dir, "script_pkg")
@@ -476,17 +622,19 @@ class TestGetStackTrace(unittest.TestCase):
# Create a socket server to communicate with the target process
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- server_socket.bind(('localhost', port))
+ server_socket.bind(("localhost", port))
server_socket.settimeout(SHORT_TIMEOUT)
server_socket.listen(1)
- script_name = _make_test_script(script_dir, 'script', script)
+ script_name = _make_test_script(script_dir, "script", script)
client_socket = None
try:
p = subprocess.Popen([sys.executable, script_name])
client_socket, _ = server_socket.accept()
server_socket.close()
- response = client_socket.recv(1024)
- self.assertEqual(response, b"ready")
+ for _ in range(1000):
+ expected_response = b"ready"
+ response = client_socket.recv(len(expected_response))
+ self.assertEqual(response, expected_response)
for _ in busy_retry(SHORT_TIMEOUT):
try:
all_awaited_by = get_all_awaited_by(p.pid)
@@ -497,7 +645,9 @@ class TestGetStackTrace(unittest.TestCase):
msg = str(re)
if msg.startswith("Task list appears corrupted"):
continue
- elif msg.startswith("Invalid linked list structure reading remote memory"):
+ elif msg.startswith(
+ "Invalid linked list structure reading remote memory"
+ ):
continue
elif msg.startswith("Unknown error reading memory"):
continue
@@ -516,22 +666,174 @@ class TestGetStackTrace(unittest.TestCase):
# expected: at least 1000 pending tasks
self.assertGreaterEqual(len(entries), 1000)
# the first three tasks stem from the code structure
- self.assertIn(('Task-1', []), entries)
- self.assertIn(('server task', [[['main'], 'Task-1', []]]), entries)
- self.assertIn(('echo client spam', [[['main'], 'Task-1', []]]), entries)
+ main_stack = [
+ FrameInfo([taskgroups.__file__, ANY, "TaskGroup._aexit"]),
+ FrameInfo(
+ [taskgroups.__file__, ANY, "TaskGroup.__aexit__"]
+ ),
+ FrameInfo([script_name, 60, "main"]),
+ ]
+ self.assertIn(
+ TaskInfo(
+ [ANY, "Task-1", [CoroInfo([main_stack, ANY])], []]
+ ),
+ entries,
+ )
+ self.assertIn(
+ TaskInfo(
+ [
+ ANY,
+ "server task",
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ base_events.__file__,
+ ANY,
+ "Server.serve_forever",
+ ]
+ )
+ ],
+ ANY,
+ ]
+ )
+ ],
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo(
+ [script_name, ANY, "main"]
+ ),
+ ],
+ ANY,
+ ]
+ )
+ ],
+ ]
+ ),
+ entries,
+ )
+ self.assertIn(
+ TaskInfo(
+ [
+ ANY,
+ "Task-4",
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [tasks.__file__, ANY, "sleep"]
+ ),
+ FrameInfo(
+ [
+ script_name,
+ 38,
+ "echo_client",
+ ]
+ ),
+ ],
+ ANY,
+ ]
+ )
+ ],
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo(
+ [
+ script_name,
+ 41,
+ "echo_client_spam",
+ ]
+ ),
+ ],
+ ANY,
+ ]
+ )
+ ],
+ ]
+ ),
+ entries,
+ )
- expected_stack = [[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]]
- tasks_with_stack = [task for task in entries if task[1] == expected_stack]
- self.assertGreaterEqual(len(tasks_with_stack), 1000)
+ expected_awaited_by = [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo(
+ [script_name, 41, "echo_client_spam"]
+ ),
+ ],
+ ANY,
+ ]
+ )
+ ]
+ tasks_with_awaited = [
+ task
+ for task in entries
+ if task.awaited_by == expected_awaited_by
+ ]
+ self.assertGreaterEqual(len(tasks_with_awaited), 1000)
# the final task will have some random number, but it should for
# sure be one of the echo client spam horde (In windows this is not true
# for some reason)
if sys.platform != "win32":
- self.assertEqual([[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]], entries[-1][1])
+ self.assertEqual(
+ tasks_with_awaited[-1].awaited_by,
+ entries[-1].awaited_by,
+ )
except PermissionError:
self.skipTest(
- "Insufficient permissions to read the stack trace")
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -540,12 +842,160 @@ class TestGetStackTrace(unittest.TestCase):
p.wait(timeout=SHORT_TIMEOUT)
@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")
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
def test_self_trace(self):
stack_trace = get_stack_trace(os.getpid())
- print(stack_trace)
- self.assertEqual(stack_trace[0], "test_self_trace")
+ # Is possible that there are more threads, so we check that the
+ # expected stack traces are in the result (looking at you Windows!)
+ this_tread_stack = None
+ for thread_id, stack in stack_trace:
+ if thread_id == threading.get_native_id():
+ this_tread_stack = stack
+ break
+ self.assertIsNotNone(this_tread_stack)
+ self.assertEqual(
+ stack[:2],
+ [
+ FrameInfo(
+ [
+ __file__,
+ get_stack_trace.__code__.co_firstlineno + 2,
+ "get_stack_trace",
+ ]
+ ),
+ FrameInfo(
+ [
+ __file__,
+ self.test_self_trace.__code__.co_firstlineno + 6,
+ "TestGetStackTrace.test_self_trace",
+ ]
+ ),
+ ],
+ )
+
+ @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",
+ )
+ @requires_gil_enabled("Free threaded builds don't have an 'active thread'")
+ def test_only_active_thread(self):
+ # Test that only_active_thread parameter works correctly
+ port = find_unused_port()
+ script = textwrap.dedent(
+ f"""\
+ import time, sys, socket, threading
+
+ # Connect to the test process
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect(('localhost', {port}))
+
+ def worker_thread(name, barrier, ready_event):
+ barrier.wait() # Synchronize thread start
+ ready_event.wait() # Wait for main thread signal
+ # Sleep to keep thread alive
+ time.sleep(10_000)
+
+ def main_work():
+ # Do busy work to hold the GIL
+ sock.sendall(b"working\\n")
+ count = 0
+ while count < 100000000:
+ count += 1
+ if count % 10000000 == 0:
+ pass # Keep main thread busy
+ sock.sendall(b"done\\n")
+
+ # Create synchronization primitives
+ num_threads = 3
+ barrier = threading.Barrier(num_threads + 1) # +1 for main thread
+ ready_event = threading.Event()
+
+ # Start worker threads
+ threads = []
+ for i in range(num_threads):
+ t = threading.Thread(target=worker_thread, args=(f"Worker-{{i}}", barrier, ready_event))
+ t.start()
+ threads.append(t)
+
+ # Wait for all threads to be ready
+ barrier.wait()
+
+ # Signal ready to parent process
+ sock.sendall(b"ready\\n")
+
+ # Signal threads to start waiting
+ ready_event.set()
+
+ # Give threads time to start sleeping
+ time.sleep(0.1)
+
+ # Now do busy work to hold the GIL
+ main_work()
+ """
+ )
+
+ with os_helper.temp_dir() as work_dir:
+ script_dir = os.path.join(work_dir, "script_pkg")
+ os.mkdir(script_dir)
+
+ # Create a socket server to communicate with the target process
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ server_socket.bind(("localhost", port))
+ server_socket.settimeout(SHORT_TIMEOUT)
+ server_socket.listen(1)
+
+ script_name = _make_test_script(script_dir, "script", script)
+ client_socket = None
+ try:
+ p = subprocess.Popen([sys.executable, script_name])
+ client_socket, _ = server_socket.accept()
+ server_socket.close()
+
+ # Wait for ready signal
+ response = b""
+ while b"ready" not in response:
+ response += client_socket.recv(1024)
+
+ # Wait for the main thread to start its busy work
+ while b"working" not in response:
+ response += client_socket.recv(1024)
+
+ # Get stack trace with all threads
+ unwinder_all = RemoteUnwinder(p.pid, all_threads=True)
+ all_traces = unwinder_all.get_stack_trace()
+
+ # Get stack trace with only GIL holder
+ unwinder_gil = RemoteUnwinder(p.pid, only_active_thread=True)
+ gil_traces = unwinder_gil.get_stack_trace()
+
+ except PermissionError:
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
+ finally:
+ if client_socket is not None:
+ client_socket.close()
+ p.kill()
+ p.terminate()
+ p.wait(timeout=SHORT_TIMEOUT)
+
+ # Verify we got multiple threads in all_traces
+ self.assertGreater(len(all_traces), 1, "Should have multiple threads")
+
+ # Verify we got exactly one thread in gil_traces
+ self.assertEqual(len(gil_traces), 1, "Should have exactly one GIL holder")
+
+ # The GIL holder should be in the all_traces list
+ gil_thread_id = gil_traces[0][0]
+ all_thread_ids = [trace[0] for trace in all_traces]
+ self.assertIn(gil_thread_id, all_thread_ids,
+ "GIL holder should be among all threads")
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index 371c63adce9..2fb963f52e5 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -166,29 +166,6 @@ class FaultHandlerTests(unittest.TestCase):
fatal_error = 'Windows fatal exception: %s' % name_regex
self.check_error(code, line_number, fatal_error, **kw)
- @unittest.skipIf(sys.platform.startswith('aix'),
- "the first page of memory is a mapped read-only on AIX")
- def test_read_null(self):
- if not MS_WINDOWS:
- self.check_fatal_error("""
- import faulthandler
- faulthandler.enable()
- faulthandler._read_null()
- """,
- 3,
- # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion
- '(?:Segmentation fault'
- '|Bus error'
- '|Illegal instruction)')
- else:
- self.check_windows_exception("""
- import faulthandler
- faulthandler.enable()
- faulthandler._read_null()
- """,
- 3,
- 'access violation')
-
@skip_segfault_on_android
def test_sigsegv(self):
self.check_fatal_error("""
diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py
index b84c98ef3a2..7140a7b4f29 100644
--- a/Lib/test/test_fcntl.py
+++ b/Lib/test/test_fcntl.py
@@ -11,7 +11,7 @@ from test.support import (
cpython_only, get_pagesize, is_apple, requires_subprocess, verbose
)
from test.support.import_helper import import_module
-from test.support.os_helper import TESTFN, unlink
+from test.support.os_helper import TESTFN, unlink, make_bad_fd
# Skip test if no fcntl module.
@@ -228,6 +228,63 @@ class TestFcntl(unittest.TestCase):
os.close(test_pipe_r)
os.close(test_pipe_w)
+ def _check_fcntl_not_mutate_len(self, nbytes=None):
+ self.f = open(TESTFN, 'wb')
+ buf = struct.pack('ii', fcntl.F_OWNER_PID, os.getpid())
+ if nbytes is not None:
+ buf += b' ' * (nbytes - len(buf))
+ else:
+ nbytes = len(buf)
+ save_buf = bytes(buf)
+ r = fcntl.fcntl(self.f, fcntl.F_SETOWN_EX, buf)
+ self.assertIsInstance(r, bytes)
+ self.assertEqual(len(r), len(save_buf))
+ self.assertEqual(buf, save_buf)
+ type, pid = memoryview(r).cast('i')[:2]
+ self.assertEqual(type, fcntl.F_OWNER_PID)
+ self.assertEqual(pid, os.getpid())
+
+ buf = b' ' * nbytes
+ r = fcntl.fcntl(self.f, fcntl.F_GETOWN_EX, buf)
+ self.assertIsInstance(r, bytes)
+ self.assertEqual(len(r), len(save_buf))
+ self.assertEqual(buf, b' ' * nbytes)
+ type, pid = memoryview(r).cast('i')[:2]
+ self.assertEqual(type, fcntl.F_OWNER_PID)
+ self.assertEqual(pid, os.getpid())
+
+ buf = memoryview(b' ' * nbytes)
+ r = fcntl.fcntl(self.f, fcntl.F_GETOWN_EX, buf)
+ self.assertIsInstance(r, bytes)
+ self.assertEqual(len(r), len(save_buf))
+ self.assertEqual(bytes(buf), b' ' * nbytes)
+ type, pid = memoryview(r).cast('i')[:2]
+ self.assertEqual(type, fcntl.F_OWNER_PID)
+ self.assertEqual(pid, os.getpid())
+
+ @unittest.skipUnless(
+ hasattr(fcntl, "F_SETOWN_EX") and hasattr(fcntl, "F_GETOWN_EX"),
+ "requires F_SETOWN_EX and F_GETOWN_EX")
+ def test_fcntl_small_buffer(self):
+ self._check_fcntl_not_mutate_len()
+
+ @unittest.skipUnless(
+ hasattr(fcntl, "F_SETOWN_EX") and hasattr(fcntl, "F_GETOWN_EX"),
+ "requires F_SETOWN_EX and F_GETOWN_EX")
+ def test_fcntl_large_buffer(self):
+ self._check_fcntl_not_mutate_len(2024)
+
+ @unittest.skipUnless(hasattr(fcntl, 'F_DUPFD'), 'need fcntl.F_DUPFD')
+ def test_bad_fd(self):
+ # gh-134744: Test error handling
+ fd = make_bad_fd()
+ with self.assertRaises(OSError):
+ fcntl.fcntl(fd, fcntl.F_DUPFD, 0)
+ with self.assertRaises(OSError):
+ fcntl.fcntl(fd, fcntl.F_DUPFD, b'\0' * 10)
+ with self.assertRaises(OSError):
+ fcntl.fcntl(fd, fcntl.F_DUPFD, b'\0' * 2048)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_fileinput.py b/Lib/test/test_fileinput.py
index b340ef7ed16..6524baabe7f 100644
--- a/Lib/test/test_fileinput.py
+++ b/Lib/test/test_fileinput.py
@@ -245,7 +245,7 @@ class FileInputTests(BaseTests, unittest.TestCase):
orig_stdin = sys.stdin
try:
sys.stdin = BytesIO(b'spam, bacon, sausage, and spam')
- self.assertFalse(hasattr(sys.stdin, 'buffer'))
+ self.assertNotHasAttr(sys.stdin, 'buffer')
fi = FileInput(files=['-'], mode='rb')
lines = list(fi)
self.assertEqual(lines, [b'spam, bacon, sausage, and spam'])
diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py
index 5a0f033ebb8..e3d54f6315a 100644
--- a/Lib/test/test_fileio.py
+++ b/Lib/test/test_fileio.py
@@ -591,7 +591,7 @@ class OtherFileTests:
try:
f.write(b"abc")
f.close()
- with open(TESTFN_ASCII, "rb") as f:
+ with self.open(TESTFN_ASCII, "rb") as f:
self.assertEqual(f.read(), b"abc")
finally:
os.unlink(TESTFN_ASCII)
@@ -608,7 +608,7 @@ class OtherFileTests:
try:
f.write(b"abc")
f.close()
- with open(TESTFN_UNICODE, "rb") as f:
+ with self.open(TESTFN_UNICODE, "rb") as f:
self.assertEqual(f.read(), b"abc")
finally:
os.unlink(TESTFN_UNICODE)
@@ -692,13 +692,13 @@ class OtherFileTests:
def testAppend(self):
try:
- f = open(TESTFN, 'wb')
+ f = self.FileIO(TESTFN, 'wb')
f.write(b'spam')
f.close()
- f = open(TESTFN, 'ab')
+ f = self.FileIO(TESTFN, 'ab')
f.write(b'eggs')
f.close()
- f = open(TESTFN, 'rb')
+ f = self.FileIO(TESTFN, 'rb')
d = f.read()
f.close()
self.assertEqual(d, b'spameggs')
@@ -734,6 +734,7 @@ class OtherFileTests:
class COtherFileTests(OtherFileTests, unittest.TestCase):
FileIO = _io.FileIO
modulename = '_io'
+ open = _io.open
@cpython_only
def testInvalidFd_overflow(self):
@@ -755,6 +756,7 @@ class COtherFileTests(OtherFileTests, unittest.TestCase):
class PyOtherFileTests(OtherFileTests, unittest.TestCase):
FileIO = _pyio.FileIO
modulename = '_pyio'
+ open = _pyio.open
def test_open_code(self):
# Check that the default behaviour of open_code matches
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
index 237d7b5d35e..00518abcb11 100644
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -795,6 +795,8 @@ class FormatTestCase(unittest.TestCase):
self.assertRaises(ValueError, format, x, '.6,n')
@support.requires_IEEE_754
+ @unittest.skipUnless(sys.float_repr_style == 'short',
+ "applies only when using short float repr style")
def test_format_testfile(self):
with open(format_testfile, encoding="utf-8") as testfile:
for line in testfile:
diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py
index c7cc32e0949..1f626d87fa6 100644
--- a/Lib/test/test_format.py
+++ b/Lib/test/test_format.py
@@ -346,12 +346,12 @@ class FormatTest(unittest.TestCase):
testcommon(b"%s", memoryview(b"abc"), b"abc")
# %a will give the equivalent of
# repr(some_obj).encode('ascii', 'backslashreplace')
- testcommon(b"%a", 3.14, b"3.14")
+ testcommon(b"%a", 3.25, b"3.25")
testcommon(b"%a", b"ghi", b"b'ghi'")
testcommon(b"%a", "jkl", b"'jkl'")
testcommon(b"%a", "\u0544", b"'\\u0544'")
# %r is an alias for %a
- testcommon(b"%r", 3.14, b"3.14")
+ testcommon(b"%r", 3.25, b"3.25")
testcommon(b"%r", b"ghi", b"b'ghi'")
testcommon(b"%r", "jkl", b"'jkl'")
testcommon(b"%r", "\u0544", b"'\\u0544'")
@@ -407,19 +407,19 @@ class FormatTest(unittest.TestCase):
self.assertEqual(format("abc", "\u2007<5"), "abc\u2007\u2007")
self.assertEqual(format(123, "\u2007<5"), "123\u2007\u2007")
- self.assertEqual(format(12.3, "\u2007<6"), "12.3\u2007\u2007")
+ self.assertEqual(format(12.5, "\u2007<6"), "12.5\u2007\u2007")
self.assertEqual(format(0j, "\u2007<4"), "0j\u2007\u2007")
self.assertEqual(format(1+2j, "\u2007<8"), "(1+2j)\u2007\u2007")
self.assertEqual(format("abc", "\u2007>5"), "\u2007\u2007abc")
self.assertEqual(format(123, "\u2007>5"), "\u2007\u2007123")
- self.assertEqual(format(12.3, "\u2007>6"), "\u2007\u200712.3")
+ self.assertEqual(format(12.5, "\u2007>6"), "\u2007\u200712.5")
self.assertEqual(format(1+2j, "\u2007>8"), "\u2007\u2007(1+2j)")
self.assertEqual(format(0j, "\u2007>4"), "\u2007\u20070j")
self.assertEqual(format("abc", "\u2007^5"), "\u2007abc\u2007")
self.assertEqual(format(123, "\u2007^5"), "\u2007123\u2007")
- self.assertEqual(format(12.3, "\u2007^6"), "\u200712.3\u2007")
+ self.assertEqual(format(12.5, "\u2007^6"), "\u200712.5\u2007")
self.assertEqual(format(1+2j, "\u2007^8"), "\u2007(1+2j)\u2007")
self.assertEqual(format(0j, "\u2007^4"), "\u20070j\u2007")
diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py
index 84faa636064..1875a2f529c 100644
--- a/Lib/test/test_fractions.py
+++ b/Lib/test/test_fractions.py
@@ -1,7 +1,7 @@
"""Tests for Lib/fractions.py."""
from decimal import Decimal
-from test.support import requires_IEEE_754
+from test.support import requires_IEEE_754, adjust_int_max_str_digits
import math
import numbers
import operator
@@ -395,12 +395,14 @@ class FractionTest(unittest.TestCase):
def testFromString(self):
self.assertEqual((5, 1), _components(F("5")))
+ self.assertEqual((5, 1), _components(F("005")))
self.assertEqual((3, 2), _components(F("3/2")))
self.assertEqual((3, 2), _components(F("3 / 2")))
self.assertEqual((3, 2), _components(F(" \n +3/2")))
self.assertEqual((-3, 2), _components(F("-3/2 ")))
- self.assertEqual((13, 2), _components(F(" 013/02 \n ")))
+ self.assertEqual((13, 2), _components(F(" 0013/002 \n ")))
self.assertEqual((16, 5), _components(F(" 3.2 ")))
+ self.assertEqual((16, 5), _components(F("003.2")))
self.assertEqual((-16, 5), _components(F(" -3.2 ")))
self.assertEqual((-3, 1), _components(F(" -3. ")))
self.assertEqual((3, 5), _components(F(" .6 ")))
@@ -419,116 +421,102 @@ class FractionTest(unittest.TestCase):
self.assertRaisesMessage(
ZeroDivisionError, "Fraction(3, 0)",
F, "3/0")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '3/'",
- F, "3/")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '/2'",
- F, "/2")
- self.assertRaisesMessage(
- # Denominators don't need a sign.
- ValueError, "Invalid literal for Fraction: '3/+2'",
- F, "3/+2")
- self.assertRaisesMessage(
- # Imitate float's parsing.
- ValueError, "Invalid literal for Fraction: '+ 3/2'",
- F, "+ 3/2")
- self.assertRaisesMessage(
- # Avoid treating '.' as a regex special character.
- ValueError, "Invalid literal for Fraction: '3a2'",
- F, "3a2")
- self.assertRaisesMessage(
- # Don't accept combinations of decimals and rationals.
- ValueError, "Invalid literal for Fraction: '3/7.2'",
- F, "3/7.2")
- self.assertRaisesMessage(
- # Don't accept combinations of decimals and rationals.
- ValueError, "Invalid literal for Fraction: '3.2/7'",
- F, "3.2/7")
- self.assertRaisesMessage(
- # Allow 3. and .3, but not .
- ValueError, "Invalid literal for Fraction: '.'",
- F, ".")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '_'",
- F, "_")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '_1'",
- F, "_1")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1__2'",
- F, "1__2")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '/_'",
- F, "/_")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1_/'",
- F, "1_/")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '_1/'",
- F, "_1/")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1__2/'",
- F, "1__2/")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1/_'",
- F, "1/_")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1/_1'",
- F, "1/_1")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1/1__2'",
- F, "1/1__2")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1._111'",
- F, "1._111")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1.1__1'",
- F, "1.1__1")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1.1e+_1'",
- F, "1.1e+_1")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1.1e+1__1'",
- F, "1.1e+1__1")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '123.dd'",
- F, "123.dd")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '123.5_dd'",
- F, "123.5_dd")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: 'dd.5'",
- F, "dd.5")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '7_dd'",
- F, "7_dd")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1/dd'",
- F, "1/dd")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1/123_dd'",
- F, "1/123_dd")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '789edd'",
- F, "789edd")
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '789e2_dd'",
- F, "789e2_dd")
+
+ def check_invalid(s):
+ msg = "Invalid literal for Fraction: " + repr(s)
+ self.assertRaisesMessage(ValueError, msg, F, s)
+
+ check_invalid("3/")
+ check_invalid("/2")
+ # Denominators don't need a sign.
+ check_invalid("3/+2")
+ check_invalid("3/-2")
+ # Imitate float's parsing.
+ check_invalid("+ 3/2")
+ check_invalid("- 3/2")
+ # Avoid treating '.' as a regex special character.
+ check_invalid("3a2")
+ # Don't accept combinations of decimals and rationals.
+ check_invalid("3/7.2")
+ check_invalid("3.2/7")
+ # No space around dot.
+ check_invalid("3 .2")
+ check_invalid("3. 2")
+ # No space around e.
+ check_invalid("3.2 e1")
+ check_invalid("3.2e 1")
+ # Fractional part don't need a sign.
+ check_invalid("3.+2")
+ check_invalid("3.-2")
+ # Only accept base 10.
+ check_invalid("0x10")
+ check_invalid("0x10/1")
+ check_invalid("1/0x10")
+ check_invalid("0x10.")
+ check_invalid("0x10.1")
+ check_invalid("1.0x10")
+ check_invalid("1.0e0x10")
+ # Only accept decimal digits.
+ check_invalid("³")
+ check_invalid("³/2")
+ check_invalid("3/²")
+ check_invalid("³.2")
+ check_invalid("3.²")
+ check_invalid("3.2e²")
+ check_invalid("¼")
+ # Allow 3. and .3, but not .
+ check_invalid(".")
+ check_invalid("_")
+ check_invalid("_1")
+ check_invalid("1__2")
+ check_invalid("/_")
+ check_invalid("1_/")
+ check_invalid("_1/")
+ check_invalid("1__2/")
+ check_invalid("1/_")
+ check_invalid("1/_1")
+ check_invalid("1/1__2")
+ check_invalid("1._111")
+ check_invalid("1.1__1")
+ check_invalid("1.1e+_1")
+ check_invalid("1.1e+1__1")
+ check_invalid("123.dd")
+ check_invalid("123.5_dd")
+ check_invalid("dd.5")
+ check_invalid("7_dd")
+ check_invalid("1/dd")
+ check_invalid("1/123_dd")
+ check_invalid("789edd")
+ check_invalid("789e2_dd")
# Test catastrophic backtracking.
val = "9"*50 + "_"
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '" + val + "'",
- F, val)
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1/" + val + "'",
- F, "1/" + val)
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1." + val + "'",
- F, "1." + val)
- self.assertRaisesMessage(
- ValueError, "Invalid literal for Fraction: '1.1+e" + val + "'",
- F, "1.1+e" + val)
+ check_invalid(val)
+ check_invalid("1/" + val)
+ check_invalid("1." + val)
+ check_invalid("." + val)
+ check_invalid("1.1+e" + val)
+ check_invalid("1.1e" + val)
+
+ def test_limit_int(self):
+ maxdigits = 5000
+ with adjust_int_max_str_digits(maxdigits):
+ msg = 'Exceeds the limit'
+ val = '1' * maxdigits
+ num = (10**maxdigits - 1)//9
+ self.assertEqual((num, 1), _components(F(val)))
+ self.assertRaisesRegex(ValueError, msg, F, val + '1')
+ self.assertEqual((num, 2), _components(F(val + '/2')))
+ self.assertRaisesRegex(ValueError, msg, F, val + '1/2')
+ self.assertEqual((1, num), _components(F('1/' + val)))
+ self.assertRaisesRegex(ValueError, msg, F, '1/1' + val)
+ self.assertEqual(((10**(maxdigits+1) - 1)//9, 10**maxdigits),
+ _components(F('1.' + val)))
+ self.assertRaisesRegex(ValueError, msg, F, '1.1' + val)
+ self.assertEqual((num, 10**maxdigits), _components(F('.' + val)))
+ self.assertRaisesRegex(ValueError, msg, F, '.1' + val)
+ self.assertRaisesRegex(ValueError, msg, F, '1.1e1' + val)
+ self.assertEqual((11, 10), _components(F('1.1e' + '0' * maxdigits)))
+ self.assertRaisesRegex(ValueError, msg, F, '1.1e' + '0' * (maxdigits+1))
def testImmutable(self):
r = F(7, 3)
@@ -1492,11 +1480,8 @@ class FractionTest(unittest.TestCase):
(F('-1234.5678'), '08,.0f', '-001,235'),
(F('-1234.5678'), '09,.0f', '-0,001,235'),
# Corner-case - zero-padding specified through fill and align
- # instead of the zero-pad character - in this case, treat '0' as a
- # regular fill character and don't attempt to insert commas into
- # the filled portion. This differs from the int and float
- # behaviour.
- (F('1234.5678'), '0=12,.2f', '00001,234.57'),
+ # instead of the zero-pad character.
+ (F('1234.5678'), '0=12,.2f', '0,001,234.57'),
# Corner case where it's not clear whether the '0' indicates zero
# padding or gives the minimum width, but there's still an obvious
# answer to give. We want this to work in case the minimum width
@@ -1530,6 +1515,8 @@ class FractionTest(unittest.TestCase):
(F(51, 1000), '.1f', '0.1'),
(F(149, 1000), '.1f', '0.1'),
(F(151, 1000), '.1f', '0.2'),
+ (F(22, 7), '.02f', '3.14'), # issue gh-130662
+ (F(22, 7), '005.02f', '03.14'),
]
for fraction, spec, expected in testcases:
with self.subTest(fraction=fraction, spec=spec):
@@ -1628,12 +1615,6 @@ class FractionTest(unittest.TestCase):
'=010%',
'>00.2f',
'>00f',
- # Too many zeros - minimum width should not have leading zeros
- '006f',
- # Leading zeros in precision
- '.010f',
- '.02f',
- '.000f',
# Missing precision
'.e',
'.f',
diff --git a/Lib/test/test_free_threading/test_dict.py b/Lib/test/test_free_threading/test_dict.py
index 476cc3178d8..5d5d4e226ca 100644
--- a/Lib/test/test_free_threading/test_dict.py
+++ b/Lib/test/test_free_threading/test_dict.py
@@ -228,6 +228,22 @@ class TestDict(TestCase):
self.assertEqual(count, 0)
+ def test_racing_object_get_set_dict(self):
+ e = Exception()
+
+ def writer():
+ for i in range(10000):
+ e.__dict__ = {1:2}
+
+ def reader():
+ for i in range(10000):
+ e.__dict__
+
+ t1 = Thread(target=writer)
+ t2 = Thread(target=reader)
+
+ with threading_helper.start_threads([t1, t2]):
+ pass
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_free_threading/test_functools.py b/Lib/test/test_free_threading/test_functools.py
new file mode 100644
index 00000000000..a442fe056ce
--- /dev/null
+++ b/Lib/test/test_free_threading/test_functools.py
@@ -0,0 +1,75 @@
+import random
+import unittest
+
+from functools import lru_cache
+from threading import Barrier, Thread
+
+from test.support import threading_helper
+
+@threading_helper.requires_working_threading()
+class TestLRUCache(unittest.TestCase):
+
+ def _test_concurrent_operations(self, maxsize):
+ num_threads = 10
+ b = Barrier(num_threads)
+ @lru_cache(maxsize=maxsize)
+ def func(arg=0):
+ return object()
+
+
+ def thread_func():
+ b.wait()
+ for i in range(1000):
+ r = random.randint(0, 1000)
+ if i < 800:
+ func(i)
+ elif i < 900:
+ func.cache_info()
+ else:
+ func.cache_clear()
+
+ threads = []
+ for i in range(num_threads):
+ t = Thread(target=thread_func)
+ threads.append(t)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+ def test_concurrent_operations_unbounded(self):
+ self._test_concurrent_operations(maxsize=None)
+
+ def test_concurrent_operations_bounded(self):
+ self._test_concurrent_operations(maxsize=128)
+
+ def _test_reentrant_cache_clear(self, maxsize):
+ num_threads = 10
+ b = Barrier(num_threads)
+ @lru_cache(maxsize=maxsize)
+ def func(arg=0):
+ func.cache_clear()
+ return object()
+
+
+ def thread_func():
+ b.wait()
+ for i in range(1000):
+ func(random.randint(0, 10000))
+
+ threads = []
+ for i in range(num_threads):
+ t = Thread(target=thread_func)
+ threads.append(t)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+ def test_reentrant_cache_clear_unbounded(self):
+ self._test_reentrant_cache_clear(maxsize=None)
+
+ def test_reentrant_cache_clear_bounded(self):
+ self._test_reentrant_cache_clear(maxsize=128)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_free_threading/test_generators.py b/Lib/test/test_free_threading/test_generators.py
new file mode 100644
index 00000000000..d01675eb38b
--- /dev/null
+++ b/Lib/test/test_free_threading/test_generators.py
@@ -0,0 +1,51 @@
+import concurrent.futures
+import unittest
+from threading import Barrier
+from unittest import TestCase
+import random
+import time
+
+from test.support import threading_helper, Py_GIL_DISABLED
+
+threading_helper.requires_working_threading(module=True)
+
+
+def random_sleep():
+ delay_us = random.randint(50, 100)
+ time.sleep(delay_us * 1e-6)
+
+def random_string():
+ return ''.join(random.choice('0123456789ABCDEF') for _ in range(10))
+
+def set_gen_name(g, b):
+ b.wait()
+ random_sleep()
+ g.__name__ = random_string()
+ return g.__name__
+
+def set_gen_qualname(g, b):
+ b.wait()
+ random_sleep()
+ g.__qualname__ = random_string()
+ return g.__qualname__
+
+
+@unittest.skipUnless(Py_GIL_DISABLED, "Enable only in FT build")
+class TestFTGenerators(TestCase):
+ NUM_THREADS = 4
+
+ def concurrent_write_with_func(self, func):
+ gen = (x for x in range(42))
+ for j in range(1000):
+ with concurrent.futures.ThreadPoolExecutor(max_workers=self.NUM_THREADS) as executor:
+ b = Barrier(self.NUM_THREADS)
+ futures = {executor.submit(func, gen, b): i for i in range(self.NUM_THREADS)}
+ for fut in concurrent.futures.as_completed(futures):
+ gen_name = fut.result()
+ self.assertEqual(len(gen_name), 10)
+
+ def test_concurrent_write(self):
+ with self.subTest(func=set_gen_name):
+ self.concurrent_write_with_func(func=set_gen_name)
+ with self.subTest(func=set_gen_qualname):
+ self.concurrent_write_with_func(func=set_gen_qualname)
diff --git a/Lib/test/test_free_threading/test_heapq.py b/Lib/test/test_free_threading/test_heapq.py
new file mode 100644
index 00000000000..ee7adfb2b78
--- /dev/null
+++ b/Lib/test/test_free_threading/test_heapq.py
@@ -0,0 +1,267 @@
+import unittest
+
+import heapq
+
+from enum import Enum
+from threading import Thread, Barrier, Lock
+from random import shuffle, randint
+
+from test.support import threading_helper
+from test import test_heapq
+
+
+NTHREADS = 10
+OBJECT_COUNT = 5_000
+
+
+class Heap(Enum):
+ MIN = 1
+ MAX = 2
+
+
+@threading_helper.requires_working_threading()
+class TestHeapq(unittest.TestCase):
+ def setUp(self):
+ self.test_heapq = test_heapq.TestHeapPython()
+
+ def test_racing_heapify(self):
+ heap = list(range(OBJECT_COUNT))
+ shuffle(heap)
+
+ self.run_concurrently(
+ worker_func=heapq.heapify, args=(heap,), nthreads=NTHREADS
+ )
+ self.test_heapq.check_invariant(heap)
+
+ def test_racing_heappush(self):
+ heap = []
+
+ def heappush_func(heap):
+ for item in reversed(range(OBJECT_COUNT)):
+ heapq.heappush(heap, item)
+
+ self.run_concurrently(
+ worker_func=heappush_func, args=(heap,), nthreads=NTHREADS
+ )
+ self.test_heapq.check_invariant(heap)
+
+ def test_racing_heappop(self):
+ heap = self.create_heap(OBJECT_COUNT, Heap.MIN)
+
+ # Each thread pops (OBJECT_COUNT / NTHREADS) items
+ self.assertEqual(OBJECT_COUNT % NTHREADS, 0)
+ per_thread_pop_count = OBJECT_COUNT // NTHREADS
+
+ def heappop_func(heap, pop_count):
+ local_list = []
+ for _ in range(pop_count):
+ item = heapq.heappop(heap)
+ local_list.append(item)
+
+ # Each local list should be sorted
+ self.assertTrue(self.is_sorted_ascending(local_list))
+
+ self.run_concurrently(
+ worker_func=heappop_func,
+ args=(heap, per_thread_pop_count),
+ nthreads=NTHREADS,
+ )
+ self.assertEqual(len(heap), 0)
+
+ def test_racing_heappushpop(self):
+ heap = self.create_heap(OBJECT_COUNT, Heap.MIN)
+ pushpop_items = self.create_random_list(-5_000, 10_000, OBJECT_COUNT)
+
+ def heappushpop_func(heap, pushpop_items):
+ for item in pushpop_items:
+ popped_item = heapq.heappushpop(heap, item)
+ self.assertTrue(popped_item <= item)
+
+ self.run_concurrently(
+ worker_func=heappushpop_func,
+ args=(heap, pushpop_items),
+ nthreads=NTHREADS,
+ )
+ self.assertEqual(len(heap), OBJECT_COUNT)
+ self.test_heapq.check_invariant(heap)
+
+ def test_racing_heapreplace(self):
+ heap = self.create_heap(OBJECT_COUNT, Heap.MIN)
+ replace_items = self.create_random_list(-5_000, 10_000, OBJECT_COUNT)
+
+ def heapreplace_func(heap, replace_items):
+ for item in replace_items:
+ heapq.heapreplace(heap, item)
+
+ self.run_concurrently(
+ worker_func=heapreplace_func,
+ args=(heap, replace_items),
+ nthreads=NTHREADS,
+ )
+ self.assertEqual(len(heap), OBJECT_COUNT)
+ self.test_heapq.check_invariant(heap)
+
+ def test_racing_heapify_max(self):
+ max_heap = list(range(OBJECT_COUNT))
+ shuffle(max_heap)
+
+ self.run_concurrently(
+ worker_func=heapq.heapify_max, args=(max_heap,), nthreads=NTHREADS
+ )
+ self.test_heapq.check_max_invariant(max_heap)
+
+ def test_racing_heappush_max(self):
+ max_heap = []
+
+ def heappush_max_func(max_heap):
+ for item in range(OBJECT_COUNT):
+ heapq.heappush_max(max_heap, item)
+
+ self.run_concurrently(
+ worker_func=heappush_max_func, args=(max_heap,), nthreads=NTHREADS
+ )
+ self.test_heapq.check_max_invariant(max_heap)
+
+ def test_racing_heappop_max(self):
+ max_heap = self.create_heap(OBJECT_COUNT, Heap.MAX)
+
+ # Each thread pops (OBJECT_COUNT / NTHREADS) items
+ self.assertEqual(OBJECT_COUNT % NTHREADS, 0)
+ per_thread_pop_count = OBJECT_COUNT // NTHREADS
+
+ def heappop_max_func(max_heap, pop_count):
+ local_list = []
+ for _ in range(pop_count):
+ item = heapq.heappop_max(max_heap)
+ local_list.append(item)
+
+ # Each local list should be sorted
+ self.assertTrue(self.is_sorted_descending(local_list))
+
+ self.run_concurrently(
+ worker_func=heappop_max_func,
+ args=(max_heap, per_thread_pop_count),
+ nthreads=NTHREADS,
+ )
+ self.assertEqual(len(max_heap), 0)
+
+ def test_racing_heappushpop_max(self):
+ max_heap = self.create_heap(OBJECT_COUNT, Heap.MAX)
+ pushpop_items = self.create_random_list(-5_000, 10_000, OBJECT_COUNT)
+
+ def heappushpop_max_func(max_heap, pushpop_items):
+ for item in pushpop_items:
+ popped_item = heapq.heappushpop_max(max_heap, item)
+ self.assertTrue(popped_item >= item)
+
+ self.run_concurrently(
+ worker_func=heappushpop_max_func,
+ args=(max_heap, pushpop_items),
+ nthreads=NTHREADS,
+ )
+ self.assertEqual(len(max_heap), OBJECT_COUNT)
+ self.test_heapq.check_max_invariant(max_heap)
+
+ def test_racing_heapreplace_max(self):
+ max_heap = self.create_heap(OBJECT_COUNT, Heap.MAX)
+ replace_items = self.create_random_list(-5_000, 10_000, OBJECT_COUNT)
+
+ def heapreplace_max_func(max_heap, replace_items):
+ for item in replace_items:
+ heapq.heapreplace_max(max_heap, item)
+
+ self.run_concurrently(
+ worker_func=heapreplace_max_func,
+ args=(max_heap, replace_items),
+ nthreads=NTHREADS,
+ )
+ self.assertEqual(len(max_heap), OBJECT_COUNT)
+ self.test_heapq.check_max_invariant(max_heap)
+
+ def test_lock_free_list_read(self):
+ n, n_threads = 1_000, 10
+ l = []
+ barrier = Barrier(n_threads * 2)
+
+ count = 0
+ lock = Lock()
+
+ def worker():
+ with lock:
+ nonlocal count
+ x = count
+ count += 1
+
+ barrier.wait()
+ for i in range(n):
+ if x % 2:
+ heapq.heappush(l, 1)
+ heapq.heappop(l)
+ else:
+ try:
+ l[0]
+ except IndexError:
+ pass
+
+ self.run_concurrently(worker, (), n_threads * 2)
+
+ @staticmethod
+ def is_sorted_ascending(lst):
+ """
+ Check if the list is sorted in ascending order (non-decreasing).
+ """
+ return all(lst[i - 1] <= lst[i] for i in range(1, len(lst)))
+
+ @staticmethod
+ def is_sorted_descending(lst):
+ """
+ Check if the list is sorted in descending order (non-increasing).
+ """
+ return all(lst[i - 1] >= lst[i] for i in range(1, len(lst)))
+
+ @staticmethod
+ def create_heap(size, heap_kind):
+ """
+ Create a min/max heap where elements are in the range (0, size - 1) and
+ shuffled before heapify.
+ """
+ heap = list(range(OBJECT_COUNT))
+ shuffle(heap)
+ if heap_kind == Heap.MIN:
+ heapq.heapify(heap)
+ else:
+ heapq.heapify_max(heap)
+
+ return heap
+
+ @staticmethod
+ def create_random_list(a, b, size):
+ """
+ Create a list of random numbers between a and b (inclusive).
+ """
+ return [randint(-a, b) for _ in range(size)]
+
+ def run_concurrently(self, worker_func, args, nthreads):
+ """
+ Run the worker function concurrently in multiple threads.
+ """
+ barrier = Barrier(nthreads)
+
+ def wrapper_func(*args):
+ # Wait for all threads to reach this point before proceeding.
+ barrier.wait()
+ worker_func(*args)
+
+ with threading_helper.catch_threading_exception() as cm:
+ workers = (
+ Thread(target=wrapper_func, args=args) for _ in range(nthreads)
+ )
+ with threading_helper.start_threads(workers):
+ pass
+
+ # Worker threads should not raise any exceptions
+ self.assertIsNone(cm.exc_value)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_free_threading/test_io.py b/Lib/test/test_free_threading/test_io.py
new file mode 100644
index 00000000000..f9bec740ddf
--- /dev/null
+++ b/Lib/test/test_free_threading/test_io.py
@@ -0,0 +1,109 @@
+import threading
+from unittest import TestCase
+from test.support import threading_helper
+from random import randint
+from io import BytesIO
+from sys import getsizeof
+
+
+class TestBytesIO(TestCase):
+ # Test pretty much everything that can break under free-threading.
+ # Non-deterministic, but at least one of these things will fail if
+ # BytesIO object is not free-thread safe.
+
+ def check(self, funcs, *args):
+ barrier = threading.Barrier(len(funcs))
+ threads = []
+
+ for func in funcs:
+ thread = threading.Thread(target=func, args=(barrier, *args))
+
+ threads.append(thread)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+ @threading_helper.requires_working_threading()
+ @threading_helper.reap_threads
+ def test_free_threading(self):
+ """Test for segfaults and aborts."""
+
+ def write(barrier, b, *ignore):
+ barrier.wait()
+ try: b.write(b'0' * randint(100, 1000))
+ except ValueError: pass # ignore write fail to closed file
+
+ def writelines(barrier, b, *ignore):
+ barrier.wait()
+ b.write(b'0\n' * randint(100, 1000))
+
+ def truncate(barrier, b, *ignore):
+ barrier.wait()
+ try: b.truncate(0)
+ except: BufferError # ignore exported buffer
+
+ def read(barrier, b, *ignore):
+ barrier.wait()
+ b.read()
+
+ def read1(barrier, b, *ignore):
+ barrier.wait()
+ b.read1()
+
+ def readline(barrier, b, *ignore):
+ barrier.wait()
+ b.readline()
+
+ def readlines(barrier, b, *ignore):
+ barrier.wait()
+ b.readlines()
+
+ def readinto(barrier, b, into, *ignore):
+ barrier.wait()
+ b.readinto(into)
+
+ def close(barrier, b, *ignore):
+ barrier.wait()
+ b.close()
+
+ def getvalue(barrier, b, *ignore):
+ barrier.wait()
+ b.getvalue()
+
+ def getbuffer(barrier, b, *ignore):
+ barrier.wait()
+ b.getbuffer()
+
+ def iter(barrier, b, *ignore):
+ barrier.wait()
+ list(b)
+
+ def getstate(barrier, b, *ignore):
+ barrier.wait()
+ b.__getstate__()
+
+ def setstate(barrier, b, st, *ignore):
+ barrier.wait()
+ b.__setstate__(st)
+
+ def sizeof(barrier, b, *ignore):
+ barrier.wait()
+ getsizeof(b)
+
+ self.check([write] * 10, BytesIO())
+ self.check([writelines] * 10, BytesIO())
+ self.check([write] * 10 + [truncate] * 10, BytesIO())
+ self.check([truncate] + [read] * 10, BytesIO(b'0\n'*204800))
+ self.check([truncate] + [read1] * 10, BytesIO(b'0\n'*204800))
+ self.check([truncate] + [readline] * 10, BytesIO(b'0\n'*20480))
+ self.check([truncate] + [readlines] * 10, BytesIO(b'0\n'*20480))
+ self.check([truncate] + [readinto] * 10, BytesIO(b'0\n'*204800), bytearray(b'0\n'*204800))
+ self.check([close] + [write] * 10, BytesIO())
+ self.check([truncate] + [getvalue] * 10, BytesIO(b'0\n'*204800))
+ self.check([truncate] + [getbuffer] * 10, BytesIO(b'0\n'*204800))
+ self.check([truncate] + [iter] * 10, BytesIO(b'0\n'*20480))
+ self.check([truncate] + [getstate] * 10, BytesIO(b'0\n'*204800))
+ self.check([truncate] + [setstate] * 10, BytesIO(b'0\n'*204800), (b'123', 0, None))
+ self.check([truncate] + [sizeof] * 10, BytesIO(b'0\n'*204800))
+
+ # no tests for seek or tell because they don't break anything
diff --git a/Lib/test/test_free_threading/test_itertools.py b/Lib/test/test_free_threading/test_itertools.py
new file mode 100644
index 00000000000..9d366041917
--- /dev/null
+++ b/Lib/test/test_free_threading/test_itertools.py
@@ -0,0 +1,95 @@
+import unittest
+from threading import Thread, Barrier
+from itertools import batched, chain, cycle
+from test.support import threading_helper
+
+
+threading_helper.requires_working_threading(module=True)
+
+class ItertoolsThreading(unittest.TestCase):
+
+ @threading_helper.reap_threads
+ def test_batched(self):
+ number_of_threads = 10
+ number_of_iterations = 20
+ barrier = Barrier(number_of_threads)
+ def work(it):
+ barrier.wait()
+ while True:
+ try:
+ next(it)
+ except StopIteration:
+ break
+
+ data = tuple(range(1000))
+ for it in range(number_of_iterations):
+ batch_iterator = batched(data, 2)
+ worker_threads = []
+ for ii in range(number_of_threads):
+ worker_threads.append(
+ Thread(target=work, args=[batch_iterator]))
+
+ with threading_helper.start_threads(worker_threads):
+ pass
+
+ barrier.reset()
+
+ @threading_helper.reap_threads
+ def test_cycle(self):
+ number_of_threads = 6
+ number_of_iterations = 10
+ number_of_cycles = 400
+
+ barrier = Barrier(number_of_threads)
+ def work(it):
+ barrier.wait()
+ for _ in range(number_of_cycles):
+ try:
+ next(it)
+ except StopIteration:
+ pass
+
+ data = (1, 2, 3, 4)
+ for it in range(number_of_iterations):
+ cycle_iterator = cycle(data)
+ worker_threads = []
+ for ii in range(number_of_threads):
+ worker_threads.append(
+ Thread(target=work, args=[cycle_iterator]))
+
+ with threading_helper.start_threads(worker_threads):
+ pass
+
+ barrier.reset()
+
+ @threading_helper.reap_threads
+ def test_chain(self):
+ number_of_threads = 6
+ number_of_iterations = 20
+
+ barrier = Barrier(number_of_threads)
+ def work(it):
+ barrier.wait()
+ while True:
+ try:
+ next(it)
+ except StopIteration:
+ break
+
+ data = [(1, )] * 200
+ for it in range(number_of_iterations):
+ chain_iterator = chain(*data)
+ worker_threads = []
+ for ii in range(number_of_threads):
+ worker_threads.append(
+ Thread(target=work, args=[chain_iterator]))
+
+ with threading_helper.start_threads(worker_threads):
+ pass
+
+ barrier.reset()
+
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_free_threading/test_itertools_batched.py b/Lib/test/test_free_threading/test_itertools_batched.py
deleted file mode 100644
index a754b4f9ea9..00000000000
--- a/Lib/test/test_free_threading/test_itertools_batched.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import unittest
-from threading import Thread, Barrier
-from itertools import batched
-from test.support import threading_helper
-
-
-threading_helper.requires_working_threading(module=True)
-
-class EnumerateThreading(unittest.TestCase):
-
- @threading_helper.reap_threads
- def test_threading(self):
- number_of_threads = 10
- number_of_iterations = 20
- barrier = Barrier(number_of_threads)
- def work(it):
- barrier.wait()
- while True:
- try:
- _ = next(it)
- except StopIteration:
- break
-
- data = tuple(range(1000))
- for it in range(number_of_iterations):
- batch_iterator = batched(data, 2)
- worker_threads = []
- for ii in range(number_of_threads):
- worker_threads.append(
- Thread(target=work, args=[batch_iterator]))
-
- with threading_helper.start_threads(worker_threads):
- pass
-
- barrier.reset()
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/Lib/test/test_free_threading/test_itertools_combinatoric.py b/Lib/test/test_free_threading/test_itertools_combinatoric.py
new file mode 100644
index 00000000000..5b3b88deedd
--- /dev/null
+++ b/Lib/test/test_free_threading/test_itertools_combinatoric.py
@@ -0,0 +1,51 @@
+import unittest
+from threading import Thread, Barrier
+from itertools import combinations, product
+from test.support import threading_helper
+
+
+threading_helper.requires_working_threading(module=True)
+
+def test_concurrent_iteration(iterator, number_of_threads):
+ barrier = Barrier(number_of_threads)
+ def iterator_worker(it):
+ barrier.wait()
+ while True:
+ try:
+ _ = next(it)
+ except StopIteration:
+ return
+
+ worker_threads = []
+ for ii in range(number_of_threads):
+ worker_threads.append(
+ Thread(target=iterator_worker, args=[iterator]))
+
+ with threading_helper.start_threads(worker_threads):
+ pass
+
+ barrier.reset()
+
+class ItertoolsThreading(unittest.TestCase):
+
+ @threading_helper.reap_threads
+ def test_combinations(self):
+ number_of_threads = 10
+ number_of_iterations = 24
+
+ for it in range(number_of_iterations):
+ iterator = combinations((1, 2, 3, 4, 5), 2)
+ test_concurrent_iteration(iterator, number_of_threads)
+
+ @threading_helper.reap_threads
+ def test_product(self):
+ number_of_threads = 10
+ number_of_iterations = 24
+
+ for it in range(number_of_iterations):
+ iterator = product((1, 2, 3, 4, 5), (10, 20, 30))
+ test_concurrent_iteration(iterator, number_of_threads)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index dd58e032a8b..58a30c8e6ac 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -1336,9 +1336,9 @@ x = (
def test_conversions(self):
self.assertEqual(f'{3.14:10.10}', ' 3.14')
- self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
- self.assertEqual(f'{3.14!r:10.10}', '3.14 ')
- self.assertEqual(f'{3.14!a:10.10}', '3.14 ')
+ self.assertEqual(f'{1.25!s:10.10}', '1.25 ')
+ self.assertEqual(f'{1.25!r:10.10}', '1.25 ')
+ self.assertEqual(f'{1.25!a:10.10}', '1.25 ')
self.assertEqual(f'{"a"}', 'a')
self.assertEqual(f'{"a"!r}', "'a'")
@@ -1347,7 +1347,7 @@ x = (
# Conversions can have trailing whitespace after them since it
# does not provide any significance
self.assertEqual(f"{3!s }", "3")
- self.assertEqual(f'{3.14!s :10.10}', '3.14 ')
+ self.assertEqual(f'{1.25!s :10.10}', '1.25 ')
# Not a conversion.
self.assertEqual(f'{"a!r"}', "a!r")
@@ -1380,7 +1380,7 @@ x = (
for conv in ' s', ' s ':
self.assertAllRaise(SyntaxError,
"f-string: conversion type must come right after the"
- " exclamanation mark",
+ " exclamation mark",
["f'{3!" + conv + "}'"])
self.assertAllRaise(SyntaxError,
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index e3b449f2d24..f7e09fd771e 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -21,8 +21,10 @@ from weakref import proxy
import contextlib
from inspect import Signature
+from test.support import ALWAYS_EQ
from test.support import import_helper
from test.support import threading_helper
+from test.support import cpython_only
from test.support import EqualToForwardRef
import functools
@@ -63,6 +65,14 @@ class BadTuple(tuple):
class MyDict(dict):
pass
+class TestImportTime(unittest.TestCase):
+
+ @cpython_only
+ def test_lazy_import(self):
+ import_helper.ensure_lazy_imports(
+ "functools", {"os", "weakref", "typing", "annotationlib", "warnings"}
+ )
+
class TestPartial:
@@ -235,6 +245,13 @@ class TestPartial:
actual_args, actual_kwds = p('x', 'y')
self.assertEqual(actual_args, ('x', 0, 'y', 1))
self.assertEqual(actual_kwds, {})
+ # Checks via `is` and not `eq`
+ # thus ALWAYS_EQ isn't treated as Placeholder
+ p = self.partial(capture, ALWAYS_EQ)
+ actual_args, actual_kwds = p()
+ self.assertEqual(len(actual_args), 1)
+ self.assertIs(actual_args[0], ALWAYS_EQ)
+ self.assertEqual(actual_kwds, {})
def test_placeholders_optimization(self):
PH = self.module.Placeholder
@@ -251,6 +268,17 @@ class TestPartial:
self.assertEqual(p2.args, (PH, 0))
self.assertEqual(p2(1), ((1, 0), {}))
+ def test_placeholders_kw_restriction(self):
+ PH = self.module.Placeholder
+ with self.assertRaisesRegex(TypeError, "Placeholder"):
+ self.partial(capture, a=PH)
+ # Passes, as checks via `is` and not `eq`
+ p = self.partial(capture, a=ALWAYS_EQ)
+ actual_args, actual_kwds = p()
+ self.assertEqual(actual_args, ())
+ self.assertEqual(len(actual_kwds), 1)
+ self.assertIs(actual_kwds['a'], ALWAYS_EQ)
+
def test_construct_placeholder_singleton(self):
PH = self.module.Placeholder
tp = type(PH)
diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py
index 42c6cb3fefa..71f1e616116 100644
--- a/Lib/test/test_future_stmt/test_future.py
+++ b/Lib/test/test_future_stmt/test_future.py
@@ -422,6 +422,11 @@ class AnnotationsFutureTestCase(unittest.TestCase):
eq('(((a)))', 'a')
eq('(((a, b)))', '(a, b)')
eq("1 + 2 + 3")
+ eq("t''")
+ eq("t'{a + b}'")
+ eq("t'{a!s}'")
+ eq("t'{a:b}'")
+ eq("t'{a:b=}'")
def test_fstring_debug_annotations(self):
# f-strings with '=' don't round trip very well, so set the expected
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index 8fae12c478c..b4cbfb6d774 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -7,7 +7,7 @@ from test.support import (verbose, refcount_test,
Py_GIL_DISABLED)
from test.support.import_helper import import_module
from test.support.os_helper import temp_dir, TESTFN, unlink
-from test.support.script_helper import assert_python_ok, make_script
+from test.support.script_helper import assert_python_ok, make_script, run_test_script
from test.support import threading_helper, gc_threshold
import gc
@@ -914,7 +914,7 @@ class GCTests(unittest.TestCase):
gc.collect()
self.assertEqual(len(Lazarus.resurrected_instances), 1)
instance = Lazarus.resurrected_instances.pop()
- self.assertTrue(hasattr(instance, "cargo"))
+ self.assertHasAttr(instance, "cargo")
self.assertEqual(id(instance.cargo), cargo_id)
gc.collect()
@@ -1127,64 +1127,14 @@ class GCTests(unittest.TestCase):
class IncrementalGCTests(unittest.TestCase):
-
- def setUp(self):
- # Reenable GC as it is disabled module-wide
- gc.enable()
-
- def tearDown(self):
- gc.disable()
-
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
@requires_gil_enabled("Free threading does not support incremental GC")
- # Use small increments to emulate longer running process in a shorter time
- @gc_threshold(200, 10)
def test_incremental_gc_handles_fast_cycle_creation(self):
-
- class LinkedList:
-
- #Use slots to reduce number of implicit objects
- __slots__ = "next", "prev", "surprise"
-
- def __init__(self, next=None, prev=None):
- self.next = next
- if next is not None:
- next.prev = self
- self.prev = prev
- if prev is not None:
- prev.next = self
-
- def make_ll(depth):
- head = LinkedList()
- for i in range(depth):
- head = LinkedList(head, head.prev)
- return head
-
- head = make_ll(1000)
- count = 1000
-
- # There will be some objects we aren't counting,
- # e.g. the gc stats dicts. This test checks
- # that the counts don't grow, so we try to
- # correct for the uncounted objects
- # This is just an estimate.
- CORRECTION = 20
-
- enabled = gc.isenabled()
- gc.enable()
- olds = []
- initial_heap_size = _testinternalcapi.get_tracked_heap_size()
- for i in range(20_000):
- newhead = make_ll(20)
- count += 20
- newhead.surprise = head
- olds.append(newhead)
- if len(olds) == 20:
- new_objects = _testinternalcapi.get_tracked_heap_size() - initial_heap_size
- self.assertLess(new_objects, 27_000, f"Heap growing. Reached limit after {i} iterations")
- del olds[:]
- if not enabled:
- gc.disable()
+ # Run this test in a fresh process. The number of alive objects (which can
+ # be from unit tests run before this one) can influence how quickly cyclic
+ # garbage is found.
+ script = support.findfile("_test_gc_fast_cycles.py")
+ run_test_script(script)
class GCCallbackTests(unittest.TestCase):
diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py
index d481cb07f75..81d4e39f5be 100644
--- a/Lib/test/test_generated_cases.py
+++ b/Lib/test/test_generated_cases.py
@@ -1,11 +1,9 @@
import contextlib
import os
-import re
import sys
import tempfile
import unittest
-from io import StringIO
from test import support
from test import test_tools
@@ -31,12 +29,11 @@ skip_if_different_mount_drives()
test_tools.skip_if_missing("cases_generator")
with test_tools.imports_under_tool("cases_generator"):
- from analyzer import analyze_forest, StackItem
+ from analyzer import StackItem
from cwriter import CWriter
import parser
from stack import Local, Stack
import tier1_generator
- import opcode_metadata_generator
import optimizer_generator
@@ -59,14 +56,14 @@ class TestEffects(unittest.TestCase):
def test_effect_sizes(self):
stack = Stack()
inputs = [
- x := StackItem("x", None, "1"),
- y := StackItem("y", None, "oparg"),
- z := StackItem("z", None, "oparg*2"),
+ x := StackItem("x", "1"),
+ y := StackItem("y", "oparg"),
+ z := StackItem("z", "oparg*2"),
]
outputs = [
- StackItem("x", None, "1"),
- StackItem("b", None, "oparg*4"),
- StackItem("c", None, "1"),
+ StackItem("x", "1"),
+ StackItem("b", "oparg*4"),
+ StackItem("c", "1"),
]
null = CWriter.null()
stack.pop(z, null)
@@ -1106,32 +1103,6 @@ class TestGeneratedCases(unittest.TestCase):
"""
self.run_cases_test(input, output)
- def test_pointer_to_stackref(self):
- input = """
- inst(OP, (arg: _PyStackRef * -- out)) {
- out = *arg;
- DEAD(arg);
- }
- """
- output = """
- TARGET(OP) {
- #if Py_TAIL_CALL_INTERP
- int opcode = OP;
- (void)(opcode);
- #endif
- frame->instr_ptr = next_instr;
- next_instr += 1;
- INSTRUCTION_STATS(OP);
- _PyStackRef *arg;
- _PyStackRef out;
- arg = (_PyStackRef *)stack_pointer[-1].bits;
- out = *arg;
- stack_pointer[-1] = out;
- DISPATCH();
- }
- """
- self.run_cases_test(input, output)
-
def test_unused_cached_value(self):
input = """
op(FIRST, (arg1 -- out)) {
@@ -2005,8 +1976,8 @@ class TestGeneratedAbstractCases(unittest.TestCase):
"""
output = """
case OP: {
- JitOptSymbol *arg1;
- JitOptSymbol *out;
+ JitOptRef arg1;
+ JitOptRef out;
arg1 = stack_pointer[-1];
out = EGGS(arg1);
stack_pointer[-1] = out;
@@ -2014,7 +1985,7 @@ class TestGeneratedAbstractCases(unittest.TestCase):
}
case OP2: {
- JitOptSymbol *out;
+ JitOptRef out;
out = sym_new_not_null(ctx);
stack_pointer[-1] = out;
break;
@@ -2039,14 +2010,14 @@ class TestGeneratedAbstractCases(unittest.TestCase):
"""
output = """
case OP: {
- JitOptSymbol *out;
+ JitOptRef out;
out = sym_new_not_null(ctx);
stack_pointer[-1] = out;
break;
}
case OP2: {
- JitOptSymbol *out;
+ JitOptRef out;
out = NULL;
stack_pointer[-1] = out;
break;
@@ -2069,6 +2040,433 @@ class TestGeneratedAbstractCases(unittest.TestCase):
with self.assertRaisesRegex(AssertionError, "All abstract uops"):
self.run_cases_test(input, input2, output)
+ def test_validate_uop_input_length_mismatch(self):
+ input = """
+ op(OP, (arg1 -- out)) {
+ SPAM();
+ }
+ """
+ input2 = """
+ op(OP, (arg1, arg2 -- out)) {
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Must have the same number of inputs"):
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_output_length_mismatch(self):
+ input = """
+ op(OP, (arg1 -- out)) {
+ SPAM();
+ }
+ """
+ input2 = """
+ op(OP, (arg1 -- out1, out2)) {
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Must have the same number of outputs"):
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_input_name_mismatch(self):
+ input = """
+ op(OP, (foo -- out)) {
+ SPAM();
+ }
+ """
+ input2 = """
+ op(OP, (bar -- out)) {
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Inputs must have equal names"):
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_output_name_mismatch(self):
+ input = """
+ op(OP, (arg1 -- foo)) {
+ SPAM();
+ }
+ """
+ input2 = """
+ op(OP, (arg1 -- bar)) {
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Outputs must have equal names"):
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_unused_input(self):
+ input = """
+ op(OP, (unused -- )) {
+ }
+ """
+ input2 = """
+ op(OP, (foo -- )) {
+ }
+ """
+ output = """
+ case OP: {
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ input = """
+ op(OP, (foo -- )) {
+ }
+ """
+ input2 = """
+ op(OP, (unused -- )) {
+ }
+ """
+ output = """
+ case OP: {
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_unused_output(self):
+ input = """
+ op(OP, ( -- unused)) {
+ }
+ """
+ input2 = """
+ op(OP, ( -- foo)) {
+ foo = NULL;
+ }
+ """
+ output = """
+ case OP: {
+ JitOptRef foo;
+ foo = NULL;
+ stack_pointer[0] = foo;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ input = """
+ op(OP, ( -- foo)) {
+ foo = NULL;
+ }
+ """
+ input2 = """
+ op(OP, ( -- unused)) {
+ }
+ """
+ output = """
+ case OP: {
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_input_size_mismatch(self):
+ input = """
+ op(OP, (arg1[2] -- )) {
+ }
+ """
+ input2 = """
+ op(OP, (arg1[4] -- )) {
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Inputs must have equal sizes"):
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_output_size_mismatch(self):
+ input = """
+ op(OP, ( -- out[2])) {
+ }
+ """
+ input2 = """
+ op(OP, ( -- out[4])) {
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Outputs must have equal sizes"):
+ self.run_cases_test(input, input2, output)
+
+ def test_validate_uop_unused_size_mismatch(self):
+ input = """
+ op(OP, (foo[2] -- )) {
+ }
+ """
+ input2 = """
+ op(OP, (unused[4] -- )) {
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Inputs must have equal sizes"):
+ self.run_cases_test(input, input2, output)
+
+ def test_pure_uop_body_copied_in(self):
+ # Note: any non-escaping call works.
+ # In this case, we use PyStackRef_IsNone.
+ input = """
+ pure op(OP, (foo -- res)) {
+ res = PyStackRef_IsNone(foo);
+ }
+ """
+ input2 = """
+ op(OP, (foo -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
+ res = sym_new_known(ctx, foo);
+ }
+ """
+ output = """
+ case OP: {
+ JitOptRef foo;
+ JitOptRef res;
+ foo = stack_pointer[-1];
+ if (
+ sym_is_safe_const(ctx, foo)
+ ) {
+ JitOptRef foo_sym = foo;
+ _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ res_stackref = PyStackRef_IsNone(foo);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-1] = res;
+ break;
+ }
+ res = sym_new_known(ctx, foo);
+ stack_pointer[-1] = res;
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ def test_pure_uop_body_copied_in_deopt(self):
+ # Note: any non-escaping call works.
+ # In this case, we use PyStackRef_IsNone.
+ input = """
+ pure op(OP, (foo -- res)) {
+ DEOPT_IF(PyStackRef_IsNull(foo));
+ res = foo;
+ }
+ """
+ input2 = """
+ op(OP, (foo -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
+ res = foo;
+ }
+ """
+ output = """
+ case OP: {
+ JitOptRef foo;
+ JitOptRef res;
+ foo = stack_pointer[-1];
+ if (
+ sym_is_safe_const(ctx, foo)
+ ) {
+ JitOptRef foo_sym = foo;
+ _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ if (PyStackRef_IsNull(foo)) {
+ ctx->done = true;
+ break;
+ }
+ res_stackref = foo;
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-1] = res;
+ break;
+ }
+ res = foo;
+ stack_pointer[-1] = res;
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ def test_pure_uop_body_copied_in_error_if(self):
+ # Note: any non-escaping call works.
+ # In this case, we use PyStackRef_IsNone.
+ input = """
+ pure op(OP, (foo -- res)) {
+ ERROR_IF(PyStackRef_IsNull(foo));
+ res = foo;
+ }
+ """
+ input2 = """
+ op(OP, (foo -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
+ res = foo;
+ }
+ """
+ output = """
+ case OP: {
+ JitOptRef foo;
+ JitOptRef res;
+ foo = stack_pointer[-1];
+ if (
+ sym_is_safe_const(ctx, foo)
+ ) {
+ JitOptRef foo_sym = foo;
+ _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ if (PyStackRef_IsNull(foo)) {
+ goto error;
+ }
+ res_stackref = foo;
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-1] = res;
+ break;
+ }
+ res = foo;
+ stack_pointer[-1] = res;
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+
+ def test_replace_opcode_uop_body_copied_in_complex(self):
+ input = """
+ pure op(OP, (foo -- res)) {
+ if (foo) {
+ res = PyStackRef_IsNone(foo);
+ }
+ else {
+ res = 1;
+ }
+ }
+ """
+ input2 = """
+ op(OP, (foo -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
+ res = sym_new_known(ctx, foo);
+ }
+ """
+ output = """
+ case OP: {
+ JitOptRef foo;
+ JitOptRef res;
+ foo = stack_pointer[-1];
+ if (
+ sym_is_safe_const(ctx, foo)
+ ) {
+ JitOptRef foo_sym = foo;
+ _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ if (foo) {
+ res_stackref = PyStackRef_IsNone(foo);
+ }
+ else {
+ res_stackref = 1;
+ }
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-1] = res;
+ break;
+ }
+ res = sym_new_known(ctx, foo);
+ stack_pointer[-1] = res;
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ def test_replace_opcode_escaping_uop_body_copied_in_complex(self):
+ input = """
+ pure op(OP, (foo -- res)) {
+ if (foo) {
+ res = ESCAPING_CODE(foo);
+ }
+ else {
+ res = 1;
+ }
+ }
+ """
+ input2 = """
+ op(OP, (foo -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
+ res = sym_new_known(ctx, foo);
+ }
+ """
+ output = """
+ case OP: {
+ JitOptRef foo;
+ JitOptRef res;
+ foo = stack_pointer[-1];
+ if (
+ sym_is_safe_const(ctx, foo)
+ ) {
+ JitOptRef foo_sym = foo;
+ _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ if (foo) {
+ res_stackref = ESCAPING_CODE(foo);
+ }
+ else {
+ res_stackref = 1;
+ }
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-1] = res;
+ break;
+ }
+ res = sym_new_known(ctx, foo);
+ stack_pointer[-1] = res;
+ break;
+ }
+ """
+ self.run_cases_test(input, input2, output)
+
+ def test_replace_opocode_uop_reject_array_effects(self):
+ input = """
+ pure op(OP, (foo[2] -- res)) {
+ if (foo) {
+ res = PyStackRef_IsNone(foo);
+ }
+ else {
+ res = 1;
+ }
+ }
+ """
+ input2 = """
+ op(OP, (foo[2] -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
+ res = sym_new_unknown(ctx);
+ }
+ """
+ output = """
+ """
+ with self.assertRaisesRegex(SyntaxError,
+ "Pure evaluation cannot take array-like inputs"):
+ self.run_cases_test(input, input2, output)
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index 5c13897b8d9..7601cb00ff6 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -61,6 +61,7 @@ try:
from tkinter import Event
except ImportError:
Event = None
+from string.templatelib import Template, Interpolation
from typing import TypeVar
T = TypeVar('T')
@@ -137,7 +138,12 @@ class BaseTest(unittest.TestCase):
Future, _WorkItem,
Morsel,
DictReader, DictWriter,
- array]
+ array,
+ staticmethod,
+ classmethod,
+ Template,
+ Interpolation,
+ ]
if ctypes is not None:
generic_types.extend((ctypes.Array, ctypes.LibraryLoader, ctypes.py_object))
if ValueProxy is not None:
@@ -230,13 +236,13 @@ class BaseTest(unittest.TestCase):
self.assertEqual(repr(x2), 'tuple[*tuple[int, str]]')
x3 = tuple[*tuple[int, ...]]
self.assertEqual(repr(x3), 'tuple[*tuple[int, ...]]')
- self.assertTrue(repr(MyList[int]).endswith('.BaseTest.test_repr.<locals>.MyList[int]'))
+ self.assertEndsWith(repr(MyList[int]), '.BaseTest.test_repr.<locals>.MyList[int]')
self.assertEqual(repr(list[str]()), '[]') # instances should keep their normal repr
# gh-105488
- self.assertTrue(repr(MyGeneric[int]).endswith('MyGeneric[int]'))
- self.assertTrue(repr(MyGeneric[[]]).endswith('MyGeneric[[]]'))
- self.assertTrue(repr(MyGeneric[[int, str]]).endswith('MyGeneric[[int, str]]'))
+ self.assertEndsWith(repr(MyGeneric[int]), 'MyGeneric[int]')
+ self.assertEndsWith(repr(MyGeneric[[]]), 'MyGeneric[[]]')
+ self.assertEndsWith(repr(MyGeneric[[int, str]]), 'MyGeneric[[int, str]]')
def test_exposed_type(self):
import types
@@ -356,7 +362,7 @@ class BaseTest(unittest.TestCase):
def test_issubclass(self):
class L(list): ...
- self.assertTrue(issubclass(L, list))
+ self.assertIsSubclass(L, list)
with self.assertRaises(TypeError):
issubclass(L, list[str])
diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
index 6c3abe602f5..16c3268fefb 100644
--- a/Lib/test/test_genericpath.py
+++ b/Lib/test/test_genericpath.py
@@ -8,7 +8,7 @@ import sys
import unittest
import warnings
from test.support import (
- is_apple, is_emscripten, os_helper, warnings_helper
+ is_apple, os_helper, warnings_helper
)
from test.support.script_helper import assert_python_ok
from test.support.os_helper import FakePath
@@ -92,8 +92,8 @@ class GenericTest:
for s1 in testlist:
for s2 in testlist:
p = commonprefix([s1, s2])
- self.assertTrue(s1.startswith(p))
- self.assertTrue(s2.startswith(p))
+ self.assertStartsWith(s1, p)
+ self.assertStartsWith(s2, p)
if s1 != s2:
n = len(p)
self.assertNotEqual(s1[n:n+1], s2[n:n+1])
diff --git a/Lib/test/test_getpass.py b/Lib/test/test_getpass.py
index 80dda2caaa3..ab36535a1cf 100644
--- a/Lib/test/test_getpass.py
+++ b/Lib/test/test_getpass.py
@@ -161,6 +161,45 @@ class UnixGetpassTest(unittest.TestCase):
self.assertIn('Warning', stderr.getvalue())
self.assertIn('Password:', stderr.getvalue())
+ def test_echo_char_replaces_input_with_asterisks(self):
+ mock_result = '*************'
+ with mock.patch('os.open') as os_open, \
+ mock.patch('io.FileIO'), \
+ mock.patch('io.TextIOWrapper') as textio, \
+ mock.patch('termios.tcgetattr'), \
+ mock.patch('termios.tcsetattr'), \
+ mock.patch('getpass._raw_input') as mock_input:
+ os_open.return_value = 3
+ mock_input.return_value = mock_result
+
+ result = getpass.unix_getpass(echo_char='*')
+ mock_input.assert_called_once_with('Password: ', textio(),
+ input=textio(), echo_char='*')
+ self.assertEqual(result, mock_result)
+
+ def test_raw_input_with_echo_char(self):
+ passwd = 'my1pa$$word!'
+ mock_input = StringIO(f'{passwd}\n')
+ mock_output = StringIO()
+ with mock.patch('sys.stdin', mock_input), \
+ mock.patch('sys.stdout', mock_output):
+ result = getpass._raw_input('Password: ', mock_output, mock_input,
+ '*')
+ self.assertEqual(result, passwd)
+ self.assertEqual('Password: ************', mock_output.getvalue())
+
+ def test_control_chars_with_echo_char(self):
+ passwd = 'pass\twd\b'
+ expect_result = 'pass\tw'
+ mock_input = StringIO(f'{passwd}\n')
+ mock_output = StringIO()
+ with mock.patch('sys.stdin', mock_input), \
+ mock.patch('sys.stdout', mock_output):
+ result = getpass._raw_input('Password: ', mock_output, mock_input,
+ '*')
+ self.assertEqual(result, expect_result)
+ self.assertEqual('Password: *******\x08 \x08', mock_output.getvalue())
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py
index 585ed08ea14..33b7d75e3ff 100644
--- a/Lib/test/test_gettext.py
+++ b/Lib/test/test_gettext.py
@@ -6,7 +6,8 @@ import unittest.mock
from functools import partial
from test import support
-from test.support import os_helper
+from test.support import cpython_only, os_helper
+from test.support.import_helper import ensure_lazy_imports
# TODO:
@@ -931,6 +932,10 @@ class MiscTestCase(unittest.TestCase):
support.check__all__(self, gettext,
not_exported={'c2py', 'ENOENT'})
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("gettext", {"re", "warnings", "locale"})
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index c39565144bf..7f5d48b9c63 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -1,7 +1,7 @@
# Python test set -- part 1, grammar.
# This just tests whether the parser accepts them all.
-from test.support import check_syntax_error
+from test.support import check_syntax_error, skip_wasi_stack_overflow
from test.support import import_helper
import annotationlib
import inspect
@@ -249,6 +249,18 @@ the \'lazy\' dog.\n\
compile(s, "<test>", "exec")
self.assertIn("was never closed", str(cm.exception))
+ @skip_wasi_stack_overflow()
+ def test_max_level(self):
+ # Macro defined in Parser/lexer/state.h
+ MAXLEVEL = 200
+
+ result = eval("(" * MAXLEVEL + ")" * MAXLEVEL)
+ self.assertEqual(result, ())
+
+ with self.assertRaises(SyntaxError) as cm:
+ eval("(" * (MAXLEVEL + 1) + ")" * (MAXLEVEL + 1))
+ self.assertStartsWith(str(cm.exception), 'too many nested parentheses')
+
var_annot_global: int # a global annotated is necessary for test_var_annot
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index fa5de7c190e..a12ff5662a7 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -9,7 +9,6 @@ import os
import struct
import sys
import unittest
-import warnings
from subprocess import PIPE, Popen
from test.support import catch_unraisable_exception
from test.support import import_helper
@@ -331,13 +330,13 @@ class TestGzip(BaseTest):
def test_1647484(self):
for mode in ('wb', 'rb'):
with gzip.GzipFile(self.filename, mode) as f:
- self.assertTrue(hasattr(f, "name"))
+ self.assertHasAttr(f, "name")
self.assertEqual(f.name, self.filename)
def test_paddedfile_getattr(self):
self.test_write()
with gzip.GzipFile(self.filename, 'rb') as f:
- self.assertTrue(hasattr(f.fileobj, "name"))
+ self.assertHasAttr(f.fileobj, "name")
self.assertEqual(f.fileobj.name, self.filename)
def test_mtime(self):
@@ -345,7 +344,7 @@ class TestGzip(BaseTest):
with gzip.GzipFile(self.filename, 'w', mtime = mtime) as fWrite:
fWrite.write(data1)
with gzip.GzipFile(self.filename) as fRead:
- self.assertTrue(hasattr(fRead, 'mtime'))
+ self.assertHasAttr(fRead, 'mtime')
self.assertIsNone(fRead.mtime)
dataRead = fRead.read()
self.assertEqual(dataRead, data1)
@@ -460,7 +459,7 @@ class TestGzip(BaseTest):
self.assertEqual(d, data1 * 50, "Incorrect data in file")
def test_gzip_BadGzipFile_exception(self):
- self.assertTrue(issubclass(gzip.BadGzipFile, OSError))
+ self.assertIsSubclass(gzip.BadGzipFile, OSError)
def test_bad_gzip_file(self):
with open(self.filename, 'wb') as file:
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
index 5e3356a02f3..5bad483ae9d 100644
--- a/Lib/test/test_hashlib.py
+++ b/Lib/test/test_hashlib.py
@@ -12,12 +12,12 @@ import io
import itertools
import logging
import os
+import re
import sys
import sysconfig
import tempfile
import threading
import unittest
-import warnings
from test import support
from test.support import _4G, bigmemtest
from test.support import hashlib_helper
@@ -98,6 +98,14 @@ def read_vectors(hash_name):
yield parts
+DEPRECATED_STRING_PARAMETER = re.escape(
+ "the 'string' keyword parameter is deprecated since "
+ "Python 3.15 and slated for removal in Python 3.19; "
+ "use the 'data' keyword parameter or pass the data "
+ "to hash as a positional argument instead"
+)
+
+
class HashLibTestCase(unittest.TestCase):
supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1',
'sha224', 'SHA224', 'sha256', 'SHA256',
@@ -141,19 +149,18 @@ class HashLibTestCase(unittest.TestCase):
# of hashlib.new given the algorithm name.
for algorithm, constructors in self.constructors_to_test.items():
constructors.add(getattr(hashlib, algorithm))
- def _test_algorithm_via_hashlib_new(data=None, _alg=algorithm, **kwargs):
- if data is None:
- return hashlib.new(_alg, **kwargs)
- return hashlib.new(_alg, data, **kwargs)
- constructors.add(_test_algorithm_via_hashlib_new)
+ def c(*args, __algorithm_name=algorithm, **kwargs):
+ return hashlib.new(__algorithm_name, *args, **kwargs)
+ c.__name__ = f'do_test_algorithm_via_hashlib_new_{algorithm}'
+ constructors.add(c)
_hashlib = self._conditional_import_module('_hashlib')
self._hashlib = _hashlib
if _hashlib:
# These algorithms should always be present when this module
# is compiled. If not, something was compiled wrong.
- self.assertTrue(hasattr(_hashlib, 'openssl_md5'))
- self.assertTrue(hasattr(_hashlib, 'openssl_sha1'))
+ self.assertHasAttr(_hashlib, 'openssl_md5')
+ self.assertHasAttr(_hashlib, 'openssl_sha1')
for algorithm, constructors in self.constructors_to_test.items():
constructor = getattr(_hashlib, 'openssl_'+algorithm, None)
if constructor:
@@ -201,6 +208,11 @@ class HashLibTestCase(unittest.TestCase):
return itertools.chain.from_iterable(constructors)
@property
+ def shake_constructors(self):
+ for shake_name in self.shakes:
+ yield from self.constructors_to_test.get(shake_name, ())
+
+ @property
def is_fips_mode(self):
return get_fips_mode()
@@ -250,6 +262,85 @@ class HashLibTestCase(unittest.TestCase):
self._hashlib.new("md5", usedforsecurity=False)
self._hashlib.openssl_md5(usedforsecurity=False)
+ @unittest.skipIf(get_fips_mode(), "skip in FIPS mode")
+ def test_clinic_signature(self):
+ for constructor in self.hash_constructors:
+ with self.subTest(constructor.__name__):
+ constructor(b'')
+ constructor(data=b'')
+ with self.assertWarnsRegex(DeprecationWarning,
+ DEPRECATED_STRING_PARAMETER):
+ constructor(string=b'')
+
+ digest_name = constructor(b'').name
+ with self.subTest(digest_name):
+ hashlib.new(digest_name, b'')
+ hashlib.new(digest_name, data=b'')
+ with self.assertWarnsRegex(DeprecationWarning,
+ DEPRECATED_STRING_PARAMETER):
+ hashlib.new(digest_name, string=b'')
+ # Make sure that _hashlib contains the constructor
+ # to test when using a combination of libcrypto and
+ # interned hash implementations.
+ if self._hashlib and digest_name in self._hashlib._constructors:
+ self._hashlib.new(digest_name, b'')
+ self._hashlib.new(digest_name, data=b'')
+ with self.assertWarnsRegex(DeprecationWarning,
+ DEPRECATED_STRING_PARAMETER):
+ self._hashlib.new(digest_name, string=b'')
+
+ @unittest.skipIf(get_fips_mode(), "skip in FIPS mode")
+ def test_clinic_signature_errors(self):
+ nomsg = b''
+ mymsg = b'msg'
+ conflicting_call = re.escape(
+ "'data' and 'string' are mutually exclusive "
+ "and support for 'string' keyword parameter "
+ "is slated for removal in a future version."
+ )
+ duplicated_param = re.escape("given by name ('data') and position")
+ unexpected_param = re.escape("got an unexpected keyword argument '_'")
+ for args, kwds, errmsg in [
+ # Reject duplicated arguments before unknown keyword arguments.
+ ((nomsg,), dict(data=nomsg, _=nomsg), duplicated_param),
+ ((mymsg,), dict(data=nomsg, _=nomsg), duplicated_param),
+ # Reject duplicated arguments before conflicting ones.
+ *itertools.product(
+ [[nomsg], [mymsg]],
+ [dict(data=nomsg), dict(data=nomsg, string=nomsg)],
+ [duplicated_param]
+ ),
+ # Reject unknown keyword arguments before conflicting ones.
+ *itertools.product(
+ [()],
+ [
+ dict(_=None),
+ dict(data=nomsg, _=None),
+ dict(string=nomsg, _=None),
+ dict(string=nomsg, data=nomsg, _=None),
+ ],
+ [unexpected_param]
+ ),
+ ((nomsg,), dict(_=None), unexpected_param),
+ ((mymsg,), dict(_=None), unexpected_param),
+ # Reject conflicting arguments.
+ [(nomsg,), dict(string=nomsg), conflicting_call],
+ [(mymsg,), dict(string=nomsg), conflicting_call],
+ [(), dict(data=nomsg, string=nomsg), conflicting_call],
+ ]:
+ for constructor in self.hash_constructors:
+ digest_name = constructor(b'').name
+ with self.subTest(constructor.__name__, args=args, kwds=kwds):
+ with self.assertRaisesRegex(TypeError, errmsg):
+ constructor(*args, **kwds)
+ with self.subTest(digest_name, args=args, kwds=kwds):
+ with self.assertRaisesRegex(TypeError, errmsg):
+ hashlib.new(digest_name, *args, **kwds)
+ if (self._hashlib and
+ digest_name in self._hashlib._constructors):
+ with self.assertRaisesRegex(TypeError, errmsg):
+ self._hashlib.new(digest_name, *args, **kwds)
+
def test_unknown_hash(self):
self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam')
self.assertRaises(TypeError, hashlib.new, 1)
@@ -284,6 +375,16 @@ class HashLibTestCase(unittest.TestCase):
self.assertIs(constructor, _md5.md5)
self.assertEqual(sorted(builtin_constructor_cache), ['MD5', 'md5'])
+ def test_copy(self):
+ for cons in self.hash_constructors:
+ h1 = cons(os.urandom(16), usedforsecurity=False)
+ h2 = h1.copy()
+ self.assertIs(type(h1), type(h2))
+ self.assertEqual(h1.name, h2.name)
+ size = (16,) if h1.name in self.shakes else ()
+ self.assertEqual(h1.digest(*size), h2.digest(*size))
+ self.assertEqual(h1.hexdigest(*size), h2.hexdigest(*size))
+
def test_hexdigest(self):
for cons in self.hash_constructors:
h = cons(usedforsecurity=False)
@@ -294,21 +395,50 @@ class HashLibTestCase(unittest.TestCase):
self.assertIsInstance(h.digest(), bytes)
self.assertEqual(hexstr(h.digest()), h.hexdigest())
- def test_digest_length_overflow(self):
- # See issue #34922
- large_sizes = (2**29, 2**32-10, 2**32+10, 2**61, 2**64-10, 2**64+10)
- for cons in self.hash_constructors:
- h = cons(usedforsecurity=False)
- if h.name not in self.shakes:
- continue
- if HASH is not None and isinstance(h, HASH):
- # _hashopenssl's take a size_t
- continue
- for digest in h.digest, h.hexdigest:
- self.assertRaises(ValueError, digest, -10)
- for length in large_sizes:
- with self.assertRaises((ValueError, OverflowError)):
- digest(length)
+ def test_shakes_zero_digest_length(self):
+ for constructor in self.shake_constructors:
+ with self.subTest(constructor=constructor):
+ h = constructor(b'abcdef', usedforsecurity=False)
+ self.assertEqual(h.digest(0), b'')
+ self.assertEqual(h.hexdigest(0), '')
+
+ def test_shakes_invalid_digest_length(self):
+ # See https://github.com/python/cpython/issues/79103.
+ for constructor in self.shake_constructors:
+ with self.subTest(constructor=constructor):
+ h = constructor(usedforsecurity=False)
+ # Note: digest() and hexdigest() take a signed input and
+ # raise if it is negative; the rationale is that we use
+ # internally PyBytes_FromStringAndSize() and _Py_strhex()
+ # which both take a Py_ssize_t.
+ for negative_size in (-1, -10, -(1 << 31), -sys.maxsize):
+ self.assertRaises(ValueError, h.digest, negative_size)
+ self.assertRaises(ValueError, h.hexdigest, negative_size)
+
+ def test_shakes_overflow_digest_length(self):
+ # See https://github.com/python/cpython/issues/135759.
+
+ exc_types = (OverflowError, ValueError)
+ # HACL* accepts an 'uint32_t' while OpenSSL accepts a 'size_t'.
+ openssl_overflown_sizes = (sys.maxsize + 1, 2 * sys.maxsize)
+ # https://github.com/python/cpython/issues/79103 restricts
+ # the accepted built-in lengths to 2 ** 29, even if OpenSSL
+ # accepts such lengths.
+ builtin_overflown_sizes = openssl_overflown_sizes + (
+ 2 ** 29, 2 ** 32 - 10, 2 ** 32, 2 ** 32 + 10,
+ 2 ** 61, 2 ** 64 - 10, 2 ** 64, 2 ** 64 + 10,
+ )
+
+ for constructor in self.shake_constructors:
+ with self.subTest(constructor=constructor):
+ h = constructor(usedforsecurity=False)
+ if HASH is not None and isinstance(h, HASH):
+ overflown_sizes = openssl_overflown_sizes
+ else:
+ overflown_sizes = builtin_overflown_sizes
+ for invalid_size in overflown_sizes:
+ self.assertRaises(exc_types, h.digest, invalid_size)
+ self.assertRaises(exc_types, h.hexdigest, invalid_size)
def test_name_attribute(self):
for cons in self.hash_constructors:
@@ -719,8 +849,6 @@ class HashLibTestCase(unittest.TestCase):
self.assertRaises(ValueError, constructor, node_offset=-1)
self.assertRaises(OverflowError, constructor, node_offset=max_offset+1)
- self.assertRaises(TypeError, constructor, data=b'')
- self.assertRaises(TypeError, constructor, string=b'')
self.assertRaises(TypeError, constructor, '')
constructor(
@@ -929,49 +1057,67 @@ class HashLibTestCase(unittest.TestCase):
def test_sha256_gil(self):
gil_minsize = hashlib_helper.find_gil_minsize(['_sha2', '_hashlib'])
+ data = b'1' + b'#' * gil_minsize + b'1'
+ expected = hashlib.sha256(data).hexdigest()
+
m = hashlib.sha256()
m.update(b'1')
m.update(b'#' * gil_minsize)
m.update(b'1')
- self.assertEqual(
- m.hexdigest(),
- '1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
- )
+ self.assertEqual(m.hexdigest(), expected)
- m = hashlib.sha256(b'1' + b'#' * gil_minsize + b'1')
- self.assertEqual(
- m.hexdigest(),
- '1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
- )
+ @threading_helper.reap_threads
+ @threading_helper.requires_working_threading()
+ def test_threaded_hashing_fast(self):
+ # Same as test_threaded_hashing_slow() but only tests some functions
+ # since otherwise test_hashlib.py becomes too slow during development.
+ for name in ['md5', 'sha1', 'sha256', 'sha3_256', 'blake2s']:
+ if constructor := getattr(hashlib, name, None):
+ with self.subTest(name):
+ self.do_test_threaded_hashing(constructor, is_shake=False)
+ if shake_128 := getattr(hashlib, 'shake_128', None):
+ self.do_test_threaded_hashing(shake_128, is_shake=True)
+ @requires_resource('cpu')
@threading_helper.reap_threads
@threading_helper.requires_working_threading()
- def test_threaded_hashing(self):
+ def test_threaded_hashing_slow(self):
+ for algorithm, constructors in self.constructors_to_test.items():
+ is_shake = algorithm in self.shakes
+ for constructor in constructors:
+ with self.subTest(constructor.__name__, is_shake=is_shake):
+ self.do_test_threaded_hashing(constructor, is_shake)
+
+ def do_test_threaded_hashing(self, constructor, is_shake):
# Updating the same hash object from several threads at once
# using data chunk sizes containing the same byte sequences.
#
# If the internal locks are working to prevent multiple
# updates on the same object from running at once, the resulting
# hash will be the same as doing it single threaded upfront.
- hasher = hashlib.sha1()
- num_threads = 5
- smallest_data = b'swineflu'
- data = smallest_data * 200000
- expected_hash = hashlib.sha1(data*num_threads).hexdigest()
-
- def hash_in_chunks(chunk_size):
- index = 0
- while index < len(data):
- hasher.update(data[index:index + chunk_size])
- index += chunk_size
+
+ # The data to hash has length s|M|q^N and the chunk size for the i-th
+ # thread is s|M|q^(N-i), where N is the number of threads, M is a fixed
+ # message of small length, and s >= 1 and q >= 2 are small integers.
+ smallest_size, num_threads, s, q = 8, 5, 2, 10
+
+ smallest_data = os.urandom(smallest_size)
+ data = s * smallest_data * (q ** num_threads)
+
+ h1 = constructor(usedforsecurity=False)
+ h2 = constructor(data * num_threads, usedforsecurity=False)
+
+ def update(chunk_size):
+ for index in range(0, len(data), chunk_size):
+ h1.update(data[index:index + chunk_size])
threads = []
- for threadnum in range(num_threads):
- chunk_size = len(data) // (10 ** threadnum)
+ for thread_num in range(num_threads):
+ # chunk_size = len(data) // (q ** thread_num)
+ chunk_size = s * smallest_size * q ** (num_threads - thread_num)
self.assertGreater(chunk_size, 0)
- self.assertEqual(chunk_size % len(smallest_data), 0)
- thread = threading.Thread(target=hash_in_chunks,
- args=(chunk_size,))
+ self.assertEqual(chunk_size % smallest_size, 0)
+ thread = threading.Thread(target=update, args=(chunk_size,))
threads.append(thread)
for thread in threads:
@@ -979,7 +1125,10 @@ class HashLibTestCase(unittest.TestCase):
for thread in threads:
thread.join()
- self.assertEqual(expected_hash, hasher.hexdigest())
+ if is_shake:
+ self.assertEqual(h1.hexdigest(16), h2.hexdigest(16))
+ else:
+ self.assertEqual(h1.hexdigest(), h2.hexdigest())
def test_get_fips_mode(self):
fips_mode = self.is_fips_mode
diff --git a/Lib/test/test_heapq.py b/Lib/test/test_heapq.py
index 1aa8e4e2897..d6623fee9bb 100644
--- a/Lib/test/test_heapq.py
+++ b/Lib/test/test_heapq.py
@@ -13,8 +13,9 @@ c_heapq = import_helper.import_fresh_module('heapq', fresh=['_heapq'])
# _heapq.nlargest/nsmallest are saved in heapq._nlargest/_smallest when
# _heapq is imported, so check them there
-func_names = ['heapify', 'heappop', 'heappush', 'heappushpop', 'heapreplace',
- '_heappop_max', '_heapreplace_max', '_heapify_max']
+func_names = ['heapify', 'heappop', 'heappush', 'heappushpop', 'heapreplace']
+# Add max-heap variants
+func_names += [func + '_max' for func in func_names]
class TestModules(TestCase):
def test_py_functions(self):
@@ -24,7 +25,7 @@ class TestModules(TestCase):
@skipUnless(c_heapq, 'requires _heapq')
def test_c_functions(self):
for fname in func_names:
- self.assertEqual(getattr(c_heapq, fname).__module__, '_heapq')
+ self.assertEqual(getattr(c_heapq, fname).__module__, '_heapq', fname)
def load_tests(loader, tests, ignore):
@@ -74,6 +75,34 @@ class TestHeap:
except AttributeError:
pass
+ def test_max_push_pop(self):
+ # 1) Push 256 random numbers and pop them off, verifying all's OK.
+ heap = []
+ data = []
+ self.check_max_invariant(heap)
+ for i in range(256):
+ item = random.random()
+ data.append(item)
+ self.module.heappush_max(heap, item)
+ self.check_max_invariant(heap)
+ results = []
+ while heap:
+ item = self.module.heappop_max(heap)
+ self.check_max_invariant(heap)
+ results.append(item)
+ data_sorted = data[:]
+ data_sorted.sort(reverse=True)
+
+ self.assertEqual(data_sorted, results)
+ # 2) Check that the invariant holds for a sorted array
+ self.check_max_invariant(results)
+
+ self.assertRaises(TypeError, self.module.heappush_max, [])
+
+ exc_types = (AttributeError, TypeError)
+ self.assertRaises(exc_types, self.module.heappush_max, None, None)
+ self.assertRaises(exc_types, self.module.heappop_max, None)
+
def check_invariant(self, heap):
# Check the heap invariant.
for pos, item in enumerate(heap):
@@ -81,6 +110,11 @@ class TestHeap:
parentpos = (pos-1) >> 1
self.assertTrue(heap[parentpos] <= item)
+ def check_max_invariant(self, heap):
+ for pos, item in enumerate(heap[1:], start=1):
+ parentpos = (pos - 1) >> 1
+ self.assertGreaterEqual(heap[parentpos], item)
+
def test_heapify(self):
for size in list(range(30)) + [20000]:
heap = [random.random() for dummy in range(size)]
@@ -89,6 +123,14 @@ class TestHeap:
self.assertRaises(TypeError, self.module.heapify, None)
+ def test_heapify_max(self):
+ for size in list(range(30)) + [20000]:
+ heap = [random.random() for dummy in range(size)]
+ self.module.heapify_max(heap)
+ self.check_max_invariant(heap)
+
+ self.assertRaises(TypeError, self.module.heapify_max, None)
+
def test_naive_nbest(self):
data = [random.randrange(2000) for i in range(1000)]
heap = []
@@ -109,10 +151,7 @@ class TestHeap:
def test_nbest(self):
# Less-naive "N-best" algorithm, much faster (if len(data) is big
- # enough <wink>) than sorting all of data. However, if we had a max
- # heap instead of a min heap, it could go faster still via
- # heapify'ing all of data (linear time), then doing 10 heappops
- # (10 log-time steps).
+ # enough <wink>) than sorting all of data.
data = [random.randrange(2000) for i in range(1000)]
heap = data[:10]
self.module.heapify(heap)
@@ -125,6 +164,17 @@ class TestHeap:
self.assertRaises(TypeError, self.module.heapreplace, None, None)
self.assertRaises(IndexError, self.module.heapreplace, [], None)
+ def test_nbest_maxheap(self):
+ # With a max heap instead of a min heap, the "N-best" algorithm can
+ # go even faster still via heapify'ing all of data (linear time), then
+ # doing 10 heappops (10 log-time steps).
+ data = [random.randrange(2000) for i in range(1000)]
+ heap = data[:]
+ self.module.heapify_max(heap)
+ result = [self.module.heappop_max(heap) for _ in range(10)]
+ result.reverse()
+ self.assertEqual(result, sorted(data)[-10:])
+
def test_nbest_with_pushpop(self):
data = [random.randrange(2000) for i in range(1000)]
heap = data[:10]
@@ -134,6 +184,62 @@ class TestHeap:
self.assertEqual(list(self.heapiter(heap)), sorted(data)[-10:])
self.assertEqual(self.module.heappushpop([], 'x'), 'x')
+ def test_naive_nworst(self):
+ # Max-heap variant of "test_naive_nbest"
+ data = [random.randrange(2000) for i in range(1000)]
+ heap = []
+ for item in data:
+ self.module.heappush_max(heap, item)
+ if len(heap) > 10:
+ self.module.heappop_max(heap)
+ heap.sort()
+ expected = sorted(data)[:10]
+ self.assertEqual(heap, expected)
+
+ def heapiter_max(self, heap):
+ # An iterator returning a max-heap's elements, largest-first.
+ try:
+ while 1:
+ yield self.module.heappop_max(heap)
+ except IndexError:
+ pass
+
+ def test_nworst(self):
+ # Max-heap variant of "test_nbest"
+ data = [random.randrange(2000) for i in range(1000)]
+ heap = data[:10]
+ self.module.heapify_max(heap)
+ for item in data[10:]:
+ if item < heap[0]: # this gets rarer the longer we run
+ self.module.heapreplace_max(heap, item)
+ expected = sorted(data, reverse=True)[-10:]
+ self.assertEqual(list(self.heapiter_max(heap)), expected)
+
+ self.assertRaises(TypeError, self.module.heapreplace_max, None)
+ self.assertRaises(TypeError, self.module.heapreplace_max, None, None)
+ self.assertRaises(IndexError, self.module.heapreplace_max, [], None)
+
+ def test_nworst_minheap(self):
+ # Min-heap variant of "test_nbest_maxheap"
+ data = [random.randrange(2000) for i in range(1000)]
+ heap = data[:]
+ self.module.heapify(heap)
+ result = [self.module.heappop(heap) for _ in range(10)]
+ result.reverse()
+ expected = sorted(data, reverse=True)[-10:]
+ self.assertEqual(result, expected)
+
+ def test_nworst_with_pushpop(self):
+ # Max-heap variant of "test_nbest_with_pushpop"
+ data = [random.randrange(2000) for i in range(1000)]
+ heap = data[:10]
+ self.module.heapify_max(heap)
+ for item in data[10:]:
+ self.module.heappushpop_max(heap, item)
+ expected = sorted(data, reverse=True)[-10:]
+ self.assertEqual(list(self.heapiter_max(heap)), expected)
+ self.assertEqual(self.module.heappushpop_max([], 'x'), 'x')
+
def test_heappushpop(self):
h = []
x = self.module.heappushpop(h, 10)
@@ -153,12 +259,31 @@ class TestHeap:
x = self.module.heappushpop(h, 11)
self.assertEqual((h, x), ([11], 10))
+ def test_heappushpop_max(self):
+ h = []
+ x = self.module.heappushpop_max(h, 10)
+ self.assertTupleEqual((h, x), ([], 10))
+
+ h = [10]
+ x = self.module.heappushpop_max(h, 10.0)
+ self.assertTupleEqual((h, x), ([10], 10.0))
+ self.assertIsInstance(h[0], int)
+ self.assertIsInstance(x, float)
+
+ h = [10]
+ x = self.module.heappushpop_max(h, 11)
+ self.assertTupleEqual((h, x), ([10], 11))
+
+ h = [10]
+ x = self.module.heappushpop_max(h, 9)
+ self.assertTupleEqual((h, x), ([9], 10))
+
def test_heappop_max(self):
- # _heapop_max has an optimization for one-item lists which isn't
+ # heapop_max has an optimization for one-item lists which isn't
# covered in other tests, so test that case explicitly here
h = [3, 2]
- self.assertEqual(self.module._heappop_max(h), 3)
- self.assertEqual(self.module._heappop_max(h), 2)
+ self.assertEqual(self.module.heappop_max(h), 3)
+ self.assertEqual(self.module.heappop_max(h), 2)
def test_heapsort(self):
# Exercise everything with repeated heapsort checks
@@ -175,6 +300,20 @@ class TestHeap:
heap_sorted = [self.module.heappop(heap) for i in range(size)]
self.assertEqual(heap_sorted, sorted(data))
+ def test_heapsort_max(self):
+ for trial in range(100):
+ size = random.randrange(50)
+ data = [random.randrange(25) for i in range(size)]
+ if trial & 1: # Half of the time, use heapify_max
+ heap = data[:]
+ self.module.heapify_max(heap)
+ else: # The rest of the time, use heappush_max
+ heap = []
+ for item in data:
+ self.module.heappush_max(heap, item)
+ heap_sorted = [self.module.heappop_max(heap) for i in range(size)]
+ self.assertEqual(heap_sorted, sorted(data, reverse=True))
+
def test_merge(self):
inputs = []
for i in range(random.randrange(25)):
@@ -377,16 +516,20 @@ class SideEffectLT:
class TestErrorHandling:
def test_non_sequence(self):
- for f in (self.module.heapify, self.module.heappop):
+ for f in (self.module.heapify, self.module.heappop,
+ self.module.heapify_max, self.module.heappop_max):
self.assertRaises((TypeError, AttributeError), f, 10)
for f in (self.module.heappush, self.module.heapreplace,
+ self.module.heappush_max, self.module.heapreplace_max,
self.module.nlargest, self.module.nsmallest):
self.assertRaises((TypeError, AttributeError), f, 10, 10)
def test_len_only(self):
- for f in (self.module.heapify, self.module.heappop):
+ for f in (self.module.heapify, self.module.heappop,
+ self.module.heapify_max, self.module.heappop_max):
self.assertRaises((TypeError, AttributeError), f, LenOnly())
- for f in (self.module.heappush, self.module.heapreplace):
+ for f in (self.module.heappush, self.module.heapreplace,
+ self.module.heappush_max, self.module.heapreplace_max):
self.assertRaises((TypeError, AttributeError), f, LenOnly(), 10)
for f in (self.module.nlargest, self.module.nsmallest):
self.assertRaises(TypeError, f, 2, LenOnly())
@@ -395,7 +538,8 @@ class TestErrorHandling:
seq = [CmpErr(), CmpErr(), CmpErr()]
for f in (self.module.heapify, self.module.heappop):
self.assertRaises(ZeroDivisionError, f, seq)
- for f in (self.module.heappush, self.module.heapreplace):
+ for f in (self.module.heappush, self.module.heapreplace,
+ self.module.heappush_max, self.module.heapreplace_max):
self.assertRaises(ZeroDivisionError, f, seq, 10)
for f in (self.module.nlargest, self.module.nsmallest):
self.assertRaises(ZeroDivisionError, f, 2, seq)
@@ -403,6 +547,8 @@ class TestErrorHandling:
def test_arg_parsing(self):
for f in (self.module.heapify, self.module.heappop,
self.module.heappush, self.module.heapreplace,
+ self.module.heapify_max, self.module.heappop_max,
+ self.module.heappush_max, self.module.heapreplace_max,
self.module.nlargest, self.module.nsmallest):
self.assertRaises((TypeError, AttributeError), f, 10)
@@ -424,6 +570,10 @@ class TestErrorHandling:
# Python version raises IndexError, C version RuntimeError
with self.assertRaises((IndexError, RuntimeError)):
self.module.heappush(heap, SideEffectLT(5, heap))
+ heap = []
+ heap.extend(SideEffectLT(i, heap) for i in range(200))
+ with self.assertRaises((IndexError, RuntimeError)):
+ self.module.heappush_max(heap, SideEffectLT(5, heap))
def test_heappop_mutating_heap(self):
heap = []
@@ -431,8 +581,12 @@ class TestErrorHandling:
# Python version raises IndexError, C version RuntimeError
with self.assertRaises((IndexError, RuntimeError)):
self.module.heappop(heap)
+ heap = []
+ heap.extend(SideEffectLT(i, heap) for i in range(200))
+ with self.assertRaises((IndexError, RuntimeError)):
+ self.module.heappop_max(heap)
- def test_comparison_operator_modifiying_heap(self):
+ def test_comparison_operator_modifying_heap(self):
# See bpo-39421: Strong references need to be taken
# when comparing objects as they can alter the heap
class EvilClass(int):
@@ -444,7 +598,7 @@ class TestErrorHandling:
self.module.heappush(heap, EvilClass(0))
self.assertRaises(IndexError, self.module.heappushpop, heap, 1)
- def test_comparison_operator_modifiying_heap_two_heaps(self):
+ def test_comparison_operator_modifying_heap_two_heaps(self):
class h(int):
def __lt__(self, o):
@@ -464,6 +618,17 @@ class TestErrorHandling:
self.assertRaises((IndexError, RuntimeError), self.module.heappush, list1, g(1))
self.assertRaises((IndexError, RuntimeError), self.module.heappush, list2, h(1))
+ list1, list2 = [], []
+
+ self.module.heappush_max(list1, h(0))
+ self.module.heappush_max(list2, g(0))
+ self.module.heappush_max(list1, g(1))
+ self.module.heappush_max(list2, h(1))
+
+ self.assertRaises((IndexError, RuntimeError), self.module.heappush_max, list1, g(1))
+ self.assertRaises((IndexError, RuntimeError), self.module.heappush_max, list2, h(1))
+
+
class TestErrorHandlingPython(TestErrorHandling, TestCase):
module = py_heapq
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index 70c79437722..ff6e1bce0ef 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -1,3 +1,21 @@
+"""Test suite for HMAC.
+
+Python provides three different implementations of HMAC:
+
+- OpenSSL HMAC using OpenSSL hash functions.
+- HACL* HMAC using HACL* hash functions.
+- Generic Python HMAC using user-defined hash functions.
+
+The generic Python HMAC implementation is able to use OpenSSL
+callables or names, HACL* named hash functions or arbitrary
+objects implementing PEP 247 interface.
+
+In the two first cases, Python HMAC wraps a C HMAC object (either OpenSSL
+or HACL*-based). As a last resort, HMAC is re-implemented in pure Python.
+It is however interesting to test the pure Python implementation against
+the OpenSSL and HACL* hash functions.
+"""
+
import binascii
import functools
import hmac
@@ -10,6 +28,12 @@ import unittest.mock as mock
import warnings
from _operator import _compare_digest as operator_compare_digest
from test.support import check_disallow_instantiation
+from test.support.hashlib_helper import (
+ BuiltinHashFunctionsTrait,
+ HashFunctionsTrait,
+ NamedHashFunctionsTrait,
+ OpenSSLHashFunctionsTrait,
+)
from test.support.import_helper import import_fresh_module, import_module
try:
@@ -382,50 +406,7 @@ class BuiltinAssertersMixin(ThroughBuiltinAPIMixin, AssertersMixin):
pass
-class HashFunctionsTrait:
- """Trait class for 'hashfunc' in hmac_new() and hmac_digest()."""
-
- ALGORITHMS = [
- 'md5', 'sha1',
- 'sha224', 'sha256', 'sha384', 'sha512',
- 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
- ]
-
- # By default, a missing algorithm skips the test that uses it.
- _ = property(lambda self: self.skipTest("missing hash function"))
- md5 = sha1 = _
- sha224 = sha256 = sha384 = sha512 = _
- sha3_224 = sha3_256 = sha3_384 = sha3_512 = _
- del _
-
-
-class WithOpenSSLHashFunctions(HashFunctionsTrait):
- """Test a HMAC implementation with an OpenSSL-based callable 'hashfunc'."""
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
-
- for name in cls.ALGORITHMS:
- @property
- @hashlib_helper.requires_openssl_hashdigest(name)
- def func(self, *, __name=name): # __name needed to bind 'name'
- return getattr(_hashlib, f'openssl_{__name}')
- setattr(cls, name, func)
-
-
-class WithNamedHashFunctions(HashFunctionsTrait):
- """Test a HMAC implementation with a named 'hashfunc'."""
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
-
- for name in cls.ALGORITHMS:
- setattr(cls, name, name)
-
-
-class RFCTestCaseMixin(AssertersMixin, HashFunctionsTrait):
+class RFCTestCaseMixin(HashFunctionsTrait, AssertersMixin):
"""Test HMAC implementations against RFC 2202/4231 and NIST test vectors.
- Test vectors for MD5 and SHA-1 are taken from RFC 2202.
@@ -739,26 +720,83 @@ class RFCTestCaseMixin(AssertersMixin, HashFunctionsTrait):
)
-class PyRFCTestCase(ThroughObjectMixin, PyAssertersMixin,
- WithOpenSSLHashFunctions, RFCTestCaseMixin,
- unittest.TestCase):
+class PurePythonInitHMAC(PyModuleMixin, HashFunctionsTrait):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ for meth in ['_init_openssl_hmac', '_init_builtin_hmac']:
+ fn = getattr(cls.hmac.HMAC, meth)
+ cm = mock.patch.object(cls.hmac.HMAC, meth, autospec=True, wraps=fn)
+ cls.enterClassContext(cm)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.hmac.HMAC._init_openssl_hmac.assert_not_called()
+ cls.hmac.HMAC._init_builtin_hmac.assert_not_called()
+ # Do not assert that HMAC._init_old() has been called as it's tricky
+ # to determine whether a test for a specific hash function has been
+ # executed or not. On regular builds, it will be called but if a
+ # hash function is not available, it's hard to detect for which
+ # test we should checj HMAC._init_old() or not.
+ super().tearDownClass()
+
+
+class PyRFCOpenSSLTestCase(ThroughObjectMixin,
+ PyAssertersMixin,
+ OpenSSLHashFunctionsTrait,
+ RFCTestCaseMixin,
+ PurePythonInitHMAC,
+ unittest.TestCase):
"""Python implementation of HMAC using hmac.HMAC().
- The underlying hash functions are OpenSSL-based.
+ The underlying hash functions are OpenSSL-based but
+ _init_old() is used instead of _init_openssl_hmac().
"""
-class PyDotNewRFCTestCase(ThroughModuleAPIMixin, PyAssertersMixin,
- WithOpenSSLHashFunctions, RFCTestCaseMixin,
- unittest.TestCase):
+class PyRFCBuiltinTestCase(ThroughObjectMixin,
+ PyAssertersMixin,
+ BuiltinHashFunctionsTrait,
+ RFCTestCaseMixin,
+ PurePythonInitHMAC,
+ unittest.TestCase):
+ """Python implementation of HMAC using hmac.HMAC().
+
+ The underlying hash functions are HACL*-based but
+ _init_old() is used instead of _init_builtin_hmac().
+ """
+
+
+class PyDotNewOpenSSLRFCTestCase(ThroughModuleAPIMixin,
+ PyAssertersMixin,
+ OpenSSLHashFunctionsTrait,
+ RFCTestCaseMixin,
+ PurePythonInitHMAC,
+ unittest.TestCase):
+ """Python implementation of HMAC using hmac.new().
+
+ The underlying hash functions are OpenSSL-based but
+ _init_old() is used instead of _init_openssl_hmac().
+ """
+
+
+class PyDotNewBuiltinRFCTestCase(ThroughModuleAPIMixin,
+ PyAssertersMixin,
+ BuiltinHashFunctionsTrait,
+ RFCTestCaseMixin,
+ PurePythonInitHMAC,
+ unittest.TestCase):
"""Python implementation of HMAC using hmac.new().
- The underlying hash functions are OpenSSL-based.
+ The underlying hash functions are HACL-based but
+ _init_old() is used instead of _init_openssl_hmac().
"""
class OpenSSLRFCTestCase(OpenSSLAssertersMixin,
- WithOpenSSLHashFunctions, RFCTestCaseMixin,
+ OpenSSLHashFunctionsTrait,
+ RFCTestCaseMixin,
unittest.TestCase):
"""OpenSSL implementation of HMAC.
@@ -767,7 +805,8 @@ class OpenSSLRFCTestCase(OpenSSLAssertersMixin,
class BuiltinRFCTestCase(BuiltinAssertersMixin,
- WithNamedHashFunctions, RFCTestCaseMixin,
+ NamedHashFunctionsTrait,
+ RFCTestCaseMixin,
unittest.TestCase):
"""Built-in HACL* implementation of HMAC.
@@ -784,12 +823,6 @@ class BuiltinRFCTestCase(BuiltinAssertersMixin,
self.check_hmac_hexdigest(key, msg, hexdigest, digest_size, func)
-# TODO(picnixz): once we have a HACL* HMAC, we should also test the Python
-# implementation of HMAC with a HACL*-based hash function. For now, we only
-# test it partially via the '_sha2' module, but for completeness we could
-# also test the RFC test vectors against all possible implementations.
-
-
class DigestModTestCaseMixin(CreatorMixin, DigestMixin):
"""Tests for the 'digestmod' parameter for hmac_new() and hmac_digest()."""
diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py
index b42a611c62c..d0d2c54217c 100644
--- a/Lib/test/test_htmlparser.py
+++ b/Lib/test/test_htmlparser.py
@@ -5,6 +5,7 @@ import pprint
import unittest
from unittest.mock import patch
+from test import support
class EventCollector(html.parser.HTMLParser):
@@ -80,6 +81,13 @@ class EventCollectorCharrefs(EventCollector):
self.fail('This should never be called with convert_charrefs=True')
+# The normal event collector normalizes the events in get_events,
+# so we override it to return the original list of events.
+class EventCollectorNoNormalize(EventCollector):
+ def get_events(self):
+ return self.events
+
+
class TestCaseBase(unittest.TestCase):
def get_collector(self):
@@ -264,8 +272,7 @@ text
("starttag", "foo:bar", [("one", "1"), ("two", "2")]),
("starttag_text", s)])
- def test_cdata_content(self):
- contents = [
+ @support.subTests('content', [
'<!-- not a comment --> &not-an-entity-ref;',
"<not a='start tag'>",
'<a href="" /> <p> <span></span>',
@@ -278,44 +285,83 @@ text
'src="http://www.example.org/r=\'+new '
'Date().getTime()+\'"><\\/s\'+\'cript>\');\n//]]>'),
'\n<!-- //\nvar foo = 3.14;\n// -->\n',
- 'foo = "</sty" + "le>";',
'<!-- \u2603 -->',
- # these two should be invalid according to the HTML 5 spec,
- # section 8.1.2.2
- #'foo = </\nscript>',
- #'foo = </ script>',
- ]
- elements = ['script', 'style', 'SCRIPT', 'STYLE', 'Script', 'Style']
- for content in contents:
- for element in elements:
- element_lower = element.lower()
- s = '<{element}>{content}</{element}>'.format(element=element,
- content=content)
- self._run_check(s, [("starttag", element_lower, []),
- ("data", content),
- ("endtag", element_lower)])
-
- def test_cdata_with_closing_tags(self):
+ 'foo = "</ script>"',
+ 'foo = "</scripture>"',
+ 'foo = "</script\v>"',
+ 'foo = "</script\xa0>"',
+ 'foo = "</ſcript>"',
+ 'foo = "</scrıpt>"',
+ ])
+ def test_script_content(self, content):
+ s = f'<script>{content}</script>'
+ self._run_check(s, [("starttag", "script", []),
+ ("data", content),
+ ("endtag", "script")])
+
+ @support.subTests('content', [
+ 'a::before { content: "<!-- not a comment -->"; }',
+ 'a::before { content: "&not-an-entity-ref;"; }',
+ 'a::before { content: "<not a=\'start tag\'>"; }',
+ 'a::before { content: "\u2603"; }',
+ 'a::before { content: "< /style>"; }',
+ 'a::before { content: "</ style>"; }',
+ 'a::before { content: "</styled>"; }',
+ 'a::before { content: "</style\v>"; }',
+ 'a::before { content: "</style\xa0>"; }',
+ 'a::before { content: "</ſtyle>"; }',
+ ])
+ def test_style_content(self, content):
+ s = f'<style>{content}</style>'
+ self._run_check(s, [("starttag", "style", []),
+ ("data", content),
+ ("endtag", "style")])
+
+ @support.subTests('endtag', ['script', 'SCRIPT', 'script ', 'script\n',
+ 'script/', 'script foo=bar', 'script foo=">"'])
+ def test_script_closing_tag(self, endtag):
# see issue #13358
# make sure that HTMLParser calls handle_data only once for each CDATA.
- # The normal event collector normalizes the events in get_events,
- # so we override it to return the original list of events.
- class Collector(EventCollector):
- def get_events(self):
- return self.events
-
content = """<!-- not a comment --> &not-an-entity-ref;
<a href="" /> </p><p> <span></span></style>
'</script' + '>'"""
- for element in [' script', 'script ', ' script ',
- '\nscript', 'script\n', '\nscript\n']:
- element_lower = element.lower().strip()
- s = '<script>{content}</{element}>'.format(element=element,
- content=content)
- self._run_check(s, [("starttag", element_lower, []),
- ("data", content),
- ("endtag", element_lower)],
- collector=Collector(convert_charrefs=False))
+ s = f'<ScrIPt>{content}</{endtag}>'
+ self._run_check(s, [("starttag", "script", []),
+ ("data", content),
+ ("endtag", "script")],
+ collector=EventCollectorNoNormalize(convert_charrefs=False))
+
+ @support.subTests('endtag', ['style', 'STYLE', 'style ', 'style\n',
+ 'style/', 'style foo=bar', 'style foo=">"'])
+ def test_style_closing_tag(self, endtag):
+ content = """
+ b::before { content: "<!-- not a comment -->"; }
+ p::before { content: "&not-an-entity-ref;"; }
+ a::before { content: "<i>"; }
+ a::after { content: "</i>"; }
+ """
+ s = f'<StyLE>{content}</{endtag}>'
+ self._run_check(s, [("starttag", "style", []),
+ ("data", content),
+ ("endtag", "style")],
+ collector=EventCollectorNoNormalize(convert_charrefs=False))
+
+ @support.subTests('tail,end', [
+ ('', False),
+ ('<', False),
+ ('</', False),
+ ('</s', False),
+ ('</script', False),
+ ('</script ', True),
+ ('</script foo=bar', True),
+ ('</script foo=">', True),
+ ])
+ def test_eof_in_script(self, tail, end):
+ content = "a = 123"
+ s = f'<ScrIPt>{content}{tail}'
+ self._run_check(s, [("starttag", "script", []),
+ ("data", content if end else content + tail)],
+ collector=EventCollectorNoNormalize(convert_charrefs=False))
def test_comments(self):
html = ("<!-- I'm a valid comment -->"
@@ -348,18 +394,16 @@ text
collector = lambda: EventCollectorCharrefs()
self.assertTrue(collector().convert_charrefs)
charrefs = ['&quot;', '&#34;', '&#x22;', '&quot', '&#34', '&#x22']
- # check charrefs in the middle of the text/attributes
- expected = [('starttag', 'a', [('href', 'foo"zar')]),
- ('data', 'a"z'), ('endtag', 'a')]
+ # check charrefs in the middle of the text
+ expected = [('starttag', 'a', []), ('data', 'a"z'), ('endtag', 'a')]
for charref in charrefs:
- self._run_check('<a href="foo{0}zar">a{0}z</a>'.format(charref),
+ self._run_check('<a>a{0}z</a>'.format(charref),
expected, collector=collector())
- # check charrefs at the beginning/end of the text/attributes
- expected = [('data', '"'),
- ('starttag', 'a', [('x', '"'), ('y', '"X'), ('z', 'X"')]),
+ # check charrefs at the beginning/end of the text
+ expected = [('data', '"'), ('starttag', 'a', []),
('data', '"'), ('endtag', 'a'), ('data', '"')]
for charref in charrefs:
- self._run_check('{0}<a x="{0}" y="{0}X" z="X{0}">'
+ self._run_check('{0}<a>'
'{0}</a>{0}'.format(charref),
expected, collector=collector())
# check charrefs in <script>/<style> elements
@@ -382,6 +426,35 @@ text
self._run_check('no charrefs here', [('data', 'no charrefs here')],
collector=collector())
+ def test_convert_charrefs_in_attribute_values(self):
+ # default value for convert_charrefs is now True
+ collector = lambda: EventCollectorCharrefs()
+ self.assertTrue(collector().convert_charrefs)
+
+ # always unescape terminated entity refs, numeric and hex char refs:
+ # - regardless whether they are at start, middle, end of attribute
+ # - or followed by alphanumeric, non-alphanumeric, or equals char
+ charrefs = ['&cent;', '&#xa2;', '&#xa2', '&#162;', '&#162']
+ expected = [('starttag', 'a',
+ [('x', '¢'), ('x', 'z¢'), ('x', '¢z'),
+ ('x', 'z¢z'), ('x', '¢ z'), ('x', '¢=z')]),
+ ('endtag', 'a')]
+ for charref in charrefs:
+ self._run_check('<a x="{0}" x="z{0}" x="{0}z" '
+ ' x="z{0}z" x="{0} z" x="{0}=z"></a>'
+ .format(charref), expected, collector=collector())
+
+ # only unescape unterminated entity matches if they are not followed by
+ # an alphanumeric or an equals sign
+ charref = '&cent'
+ expected = [('starttag', 'a',
+ [('x', '¢'), ('x', 'z¢'), ('x', '&centz'),
+ ('x', 'z&centz'), ('x', '¢ z'), ('x', '&cent=z')]),
+ ('endtag', 'a')]
+ self._run_check('<a x="{0}" x="z{0}" x="{0}z" '
+ ' x="z{0}z" x="{0} z" x="{0}=z"></a>'
+ .format(charref), expected, collector=collector())
+
# the remaining tests were for the "tolerant" parser (which is now
# the default), and check various kind of broken markup
def test_tolerant_parsing(self):
@@ -393,28 +466,34 @@ text
('data', '<'),
('starttag', 'bc<', [('a', None)]),
('endtag', 'html'),
- ('data', '\n<img src="URL>'),
- ('comment', '/img'),
- ('endtag', 'html<')])
+ ('data', '\n')])
def test_starttag_junk_chars(self):
+ self._run_check("<", [('data', '<')])
+ self._run_check("<>", [('data', '<>')])
+ self._run_check("< >", [('data', '< >')])
+ self._run_check("< ", [('data', '< ')])
self._run_check("</>", [])
+ self._run_check("<$>", [('data', '<$>')])
self._run_check("</$>", [('comment', '$')])
self._run_check("</", [('data', '</')])
- self._run_check("</a", [('data', '</a')])
+ self._run_check("</a", [])
+ self._run_check("</ a>", [('comment', ' a')])
+ self._run_check("</ a", [('comment', ' a')])
self._run_check("<a<a>", [('starttag', 'a<a', [])])
self._run_check("</a<a>", [('endtag', 'a<a')])
- self._run_check("<!", [('data', '<!')])
- self._run_check("<a", [('data', '<a')])
- self._run_check("<a foo='bar'", [('data', "<a foo='bar'")])
- self._run_check("<a foo='bar", [('data', "<a foo='bar")])
- self._run_check("<a foo='>'", [('data', "<a foo='>'")])
- self._run_check("<a foo='>", [('data', "<a foo='>")])
+ self._run_check("<!", [('comment', '')])
+ self._run_check("<a", [])
+ self._run_check("<a foo='bar'", [])
+ self._run_check("<a foo='bar", [])
+ self._run_check("<a foo='>'", [])
+ self._run_check("<a foo='>", [])
self._run_check("<a$>", [('starttag', 'a$', [])])
self._run_check("<a$b>", [('starttag', 'a$b', [])])
self._run_check("<a$b/>", [('startendtag', 'a$b', [])])
self._run_check("<a$b >", [('starttag', 'a$b', [])])
self._run_check("<a$b />", [('startendtag', 'a$b', [])])
+ self._run_check("</a$b>", [('endtag', 'a$b')])
def test_slashes_in_starttag(self):
self._run_check('<a foo="var"/>', [('startendtag', 'a', [('foo', 'var')])])
@@ -447,6 +526,10 @@ text
]
self._run_check(html, expected)
+ def test_slashes_in_endtag(self):
+ self._run_check('</a/>', [('endtag', 'a')])
+ self._run_check('</a foo="var"/>', [('endtag', 'a')])
+
def test_declaration_junk_chars(self):
self._run_check("<!DOCTYPE foo $ >", [('decl', 'DOCTYPE foo $ ')])
@@ -481,15 +564,11 @@ text
self._run_check(html, expected)
def test_broken_invalid_end_tag(self):
- # This is technically wrong (the "> shouldn't be included in the 'data')
- # but is probably not worth fixing it (in addition to all the cases of
- # the previous test, it would require a full attribute parsing).
- # see #13993
html = '<b>This</b attr=">"> confuses the parser'
expected = [('starttag', 'b', []),
('data', 'This'),
('endtag', 'b'),
- ('data', '"> confuses the parser')]
+ ('data', ' confuses the parser')]
self._run_check(html, expected)
def test_correct_detection_of_start_tags(self):
@@ -516,7 +595,7 @@ text
html = '<div style="", foo = "bar" ><b>The <a href="some_url">rain</a>'
expected = [
- ('starttag', 'div', [('style', ''), (',', None), ('foo', 'bar')]),
+ ('starttag', 'div', [('style', ''), (',', None), ('foo', None), ('=', None), ('"bar"', None)]),
('starttag', 'b', []),
('data', 'The '),
('starttag', 'a', [('href', 'some_url')]),
@@ -539,52 +618,129 @@ text
for html, expected in data:
self._run_check(html, expected)
- def test_broken_comments(self):
- html = ('<! not really a comment >'
+ def test_eof_in_comments(self):
+ data = [
+ ('<!--', [('comment', '')]),
+ ('<!---', [('comment', '')]),
+ ('<!----', [('comment', '')]),
+ ('<!-----', [('comment', '-')]),
+ ('<!------', [('comment', '--')]),
+ ('<!----!', [('comment', '')]),
+ ('<!---!', [('comment', '-!')]),
+ ('<!---!>', [('comment', '-!>')]),
+ ('<!--foo', [('comment', 'foo')]),
+ ('<!--foo-', [('comment', 'foo')]),
+ ('<!--foo--', [('comment', 'foo')]),
+ ('<!--foo--!', [('comment', 'foo')]),
+ ('<!--<!--', [('comment', '<!')]),
+ ('<!--<!--!', [('comment', '<!')]),
+ ]
+ for html, expected in data:
+ self._run_check(html, expected)
+
+ def test_eof_in_declarations(self):
+ data = [
+ ('<!', [('comment', '')]),
+ ('<!-', [('comment', '-')]),
+ ('<![', [('comment', '[')]),
+ ('<![CDATA[', [('unknown decl', 'CDATA[')]),
+ ('<![CDATA[x', [('unknown decl', 'CDATA[x')]),
+ ('<![CDATA[x]', [('unknown decl', 'CDATA[x]')]),
+ ('<![CDATA[x]]', [('unknown decl', 'CDATA[x]]')]),
+ ('<!DOCTYPE', [('decl', 'DOCTYPE')]),
+ ('<!DOCTYPE ', [('decl', 'DOCTYPE ')]),
+ ('<!DOCTYPE html', [('decl', 'DOCTYPE html')]),
+ ('<!DOCTYPE html ', [('decl', 'DOCTYPE html ')]),
+ ('<!DOCTYPE html PUBLIC', [('decl', 'DOCTYPE html PUBLIC')]),
+ ('<!DOCTYPE html PUBLIC "foo', [('decl', 'DOCTYPE html PUBLIC "foo')]),
+ ('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo',
+ [('decl', 'DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo')]),
+ ]
+ for html, expected in data:
+ self._run_check(html, expected)
+
+ def test_bogus_comments(self):
+ html = ('<!ELEMENT br EMPTY>'
+ '<! not really a comment >'
'<! not a comment either -->'
'<! -- close enough -->'
'<!><!<-- this was an empty comment>'
- '<!!! another bogus comment !!!>')
+ '<!!! another bogus comment !!!>'
+ # see #32876
+ '<![with square brackets]!>'
+ '<![\nmultiline\nbogusness\n]!>'
+ '<![more brackets]-[and a hyphen]!>'
+ '<![cdata[should be uppercase]]>'
+ '<![CDATA [whitespaces are not ignored]]>'
+ '<![CDATA]]>' # required '[' after CDATA
+ )
expected = [
+ ('comment', 'ELEMENT br EMPTY'),
('comment', ' not really a comment '),
('comment', ' not a comment either --'),
('comment', ' -- close enough --'),
('comment', ''),
('comment', '<-- this was an empty comment'),
('comment', '!! another bogus comment !!!'),
+ ('comment', '[with square brackets]!'),
+ ('comment', '[\nmultiline\nbogusness\n]!'),
+ ('comment', '[more brackets]-[and a hyphen]!'),
+ ('comment', '[cdata[should be uppercase]]'),
+ ('comment', '[CDATA [whitespaces are not ignored]]'),
+ ('comment', '[CDATA]]'),
]
self._run_check(html, expected)
def test_broken_condcoms(self):
# these condcoms are missing the '--' after '<!' and before the '>'
+ # and they are considered bogus comments according to
+ # "8.2.4.42. Markup declaration open state"
html = ('<![if !(IE)]>broken condcom<![endif]>'
'<![if ! IE]><link href="favicon.tiff"/><![endif]>'
'<![if !IE 6]><img src="firefox.png" /><![endif]>'
'<![if !ie 6]><b>foo</b><![endif]>'
'<![if (!IE)|(lt IE 9)]><img src="mammoth.bmp" /><![endif]>')
- # According to the HTML5 specs sections "8.2.4.44 Bogus comment state"
- # and "8.2.4.45 Markup declaration open state", comment tokens should
- # be emitted instead of 'unknown decl', but calling unknown_decl
- # provides more flexibility.
- # See also Lib/_markupbase.py:parse_declaration
expected = [
- ('unknown decl', 'if !(IE)'),
+ ('comment', '[if !(IE)]'),
('data', 'broken condcom'),
- ('unknown decl', 'endif'),
- ('unknown decl', 'if ! IE'),
+ ('comment', '[endif]'),
+ ('comment', '[if ! IE]'),
('startendtag', 'link', [('href', 'favicon.tiff')]),
- ('unknown decl', 'endif'),
- ('unknown decl', 'if !IE 6'),
+ ('comment', '[endif]'),
+ ('comment', '[if !IE 6]'),
('startendtag', 'img', [('src', 'firefox.png')]),
- ('unknown decl', 'endif'),
- ('unknown decl', 'if !ie 6'),
+ ('comment', '[endif]'),
+ ('comment', '[if !ie 6]'),
('starttag', 'b', []),
('data', 'foo'),
('endtag', 'b'),
- ('unknown decl', 'endif'),
- ('unknown decl', 'if (!IE)|(lt IE 9)'),
+ ('comment', '[endif]'),
+ ('comment', '[if (!IE)|(lt IE 9)]'),
('startendtag', 'img', [('src', 'mammoth.bmp')]),
- ('unknown decl', 'endif')
+ ('comment', '[endif]')
+ ]
+ self._run_check(html, expected)
+
+ def test_cdata_declarations(self):
+ # More tests should be added. See also "8.2.4.42. Markup
+ # declaration open state", "8.2.4.69. CDATA section state",
+ # and issue 32876
+ html = ('<![CDATA[just some plain text]]>')
+ expected = [('unknown decl', 'CDATA[just some plain text')]
+ self._run_check(html, expected)
+
+ def test_cdata_declarations_multiline(self):
+ html = ('<code><![CDATA['
+ ' if (a < b && a > b) {'
+ ' printf("[<marquee>How?</marquee>]");'
+ ' }'
+ ']]></code>')
+ expected = [
+ ('starttag', 'code', []),
+ ('unknown decl',
+ 'CDATA[ if (a < b && a > b) { '
+ 'printf("[<marquee>How?</marquee>]"); }'),
+ ('endtag', 'code')
]
self._run_check(html, expected)
@@ -600,6 +756,26 @@ text
('endtag', 'a'), ('data', ' bar & baz')]
)
+ @support.requires_resource('cpu')
+ def test_eof_no_quadratic_complexity(self):
+ # Each of these examples used to take about an hour.
+ # Now they take a fraction of a second.
+ def check(source):
+ parser = html.parser.HTMLParser()
+ parser.feed(source)
+ parser.close()
+ n = 120_000
+ check("<a " * n)
+ check("<a a=" * n)
+ check("</a " * 14 * n)
+ check("</a a=" * 11 * n)
+ check("<!--" * 4 * n)
+ check("<!" * 60 * n)
+ check("<?" * 19 * n)
+ check("</$" * 15 * n)
+ check("<![CDATA[" * 9 * n)
+ check("<!doctype" * 35 * n)
+
class AttributesTestCase(TestCaseBase):
@@ -608,9 +784,15 @@ class AttributesTestCase(TestCaseBase):
("starttag", "a", [("b", "v"), ("c", "v"), ("d", "v"), ("e", None)])
]
self._run_check("""<a b='v' c="v" d=v e>""", output)
- self._run_check("""<a b = 'v' c = "v" d = v e>""", output)
- self._run_check("""<a\nb\n=\n'v'\nc\n=\n"v"\nd\n=\nv\ne>""", output)
- self._run_check("""<a\tb\t=\t'v'\tc\t=\t"v"\td\t=\tv\te>""", output)
+ self._run_check("<a foo==bar>", [('starttag', 'a', [('foo', '=bar')])])
+ self._run_check("<a foo =bar>", [('starttag', 'a', [('foo', None), ('=bar', None)])])
+ self._run_check("<a foo\t=bar>", [('starttag', 'a', [('foo', None), ('=bar', None)])])
+ self._run_check("<a foo\v=bar>", [('starttag', 'a', [('foo\v', 'bar')])])
+ self._run_check("<a foo\xa0=bar>", [('starttag', 'a', [('foo\xa0', 'bar')])])
+ self._run_check("<a foo= bar>", [('starttag', 'a', [('foo', ''), ('bar', None)])])
+ self._run_check("<a foo=\tbar>", [('starttag', 'a', [('foo', ''), ('bar', None)])])
+ self._run_check("<a foo=\vbar>", [('starttag', 'a', [('foo', '\vbar')])])
+ self._run_check("<a foo=\xa0bar>", [('starttag', 'a', [('foo', '\xa0bar')])])
def test_attr_values(self):
self._run_check("""<a b='xxx\n\txxx' c="yyy\t\nyyy" d='\txyz\n'>""",
@@ -619,6 +801,10 @@ class AttributesTestCase(TestCaseBase):
("d", "\txyz\n")])])
self._run_check("""<a b='' c="">""",
[("starttag", "a", [("b", ""), ("c", "")])])
+ self._run_check("<a b=\t c=\n>",
+ [("starttag", "a", [("b", ""), ("c", "")])])
+ self._run_check("<a b=\v c=\xa0>",
+ [("starttag", "a", [("b", "\v"), ("c", "\xa0")])])
# Regression test for SF patch #669683.
self._run_check("<e a=rgb(1,2,3)>",
[("starttag", "e", [("a", "rgb(1,2,3)")])])
@@ -690,7 +876,7 @@ class AttributesTestCase(TestCaseBase):
('data', 'test - bad2'), ('endtag', 'a'),
('starttag', 'a', [('href', "test'\xa0style='color:red;bad3'")]),
('data', 'test - bad3'), ('endtag', 'a'),
- ('starttag', 'a', [('href', "test'\xa0style='color:red;bad4'")]),
+ ('starttag', 'a', [('href', None), ('=', None), ("test'&nbsp;style", 'color:red;bad4')]),
('data', 'test - bad4'), ('endtag', 'a')
]
self._run_check(html, expected)
diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py
index 6bc33b15ec3..04cb440cd4c 100644
--- a/Lib/test/test_http_cookiejar.py
+++ b/Lib/test/test_http_cookiejar.py
@@ -4,6 +4,7 @@ import os
import stat
import sys
import re
+from test import support
from test.support import os_helper
from test.support import warnings_helper
import time
@@ -105,8 +106,7 @@ class DateTimeTests(unittest.TestCase):
self.assertEqual(http2time(s.lower()), test_t, s.lower())
self.assertEqual(http2time(s.upper()), test_t, s.upper())
- def test_http2time_garbage(self):
- for test in [
+ @support.subTests('test', [
'',
'Garbage',
'Mandag 16. September 1996',
@@ -121,10 +121,9 @@ class DateTimeTests(unittest.TestCase):
'08-01-3697739',
'09 Feb 19942632 22:23:32 GMT',
'Wed, 09 Feb 1994834 22:23:32 GMT',
- ]:
- self.assertIsNone(http2time(test),
- "http2time(%s) is not None\n"
- "http2time(test) %s" % (test, http2time(test)))
+ ])
+ def test_http2time_garbage(self, test):
+ self.assertIsNone(http2time(test))
def test_http2time_redos_regression_actually_completes(self):
# LOOSE_HTTP_DATE_RE was vulnerable to malicious input which caused catastrophic backtracking (REDoS).
@@ -149,9 +148,7 @@ class DateTimeTests(unittest.TestCase):
self.assertEqual(parse_date("1994-02-03 19:45:29 +0530"),
(1994, 2, 3, 14, 15, 29))
- def test_iso2time_formats(self):
- # test iso2time for supported dates.
- tests = [
+ @support.subTests('s', [
'1994-02-03 00:00:00 -0000', # ISO 8601 format
'1994-02-03 00:00:00 +0000', # ISO 8601 format
'1994-02-03 00:00:00', # zone is optional
@@ -164,16 +161,15 @@ class DateTimeTests(unittest.TestCase):
# A few tests with extra space at various places
' 1994-02-03 ',
' 1994-02-03T00:00:00 ',
- ]
-
+ ])
+ def test_iso2time_formats(self, s):
+ # test iso2time for supported dates.
test_t = 760233600 # assume broken POSIX counting of seconds
- for s in tests:
- self.assertEqual(iso2time(s), test_t, s)
- self.assertEqual(iso2time(s.lower()), test_t, s.lower())
- self.assertEqual(iso2time(s.upper()), test_t, s.upper())
+ self.assertEqual(iso2time(s), test_t, s)
+ self.assertEqual(iso2time(s.lower()), test_t, s.lower())
+ self.assertEqual(iso2time(s.upper()), test_t, s.upper())
- def test_iso2time_garbage(self):
- for test in [
+ @support.subTests('test', [
'',
'Garbage',
'Thursday, 03-Feb-94 00:00:00 GMT',
@@ -186,9 +182,9 @@ class DateTimeTests(unittest.TestCase):
'01-01-1980 00:00:62',
'01-01-1980T00:00:62',
'19800101T250000Z',
- ]:
- self.assertIsNone(iso2time(test),
- "iso2time(%r)" % test)
+ ])
+ def test_iso2time_garbage(self, test):
+ self.assertIsNone(iso2time(test))
def test_iso2time_performance_regression(self):
# If ISO_DATE_RE regresses to quadratic complexity, this test will take a very long time to succeed.
@@ -199,24 +195,23 @@ class DateTimeTests(unittest.TestCase):
class HeaderTests(unittest.TestCase):
- def test_parse_ns_headers(self):
- # quotes should be stripped
- expected = [[('foo', 'bar'), ('expires', 2209069412), ('version', '0')]]
- for hdr in [
+ @support.subTests('hdr', [
'foo=bar; expires=01 Jan 2040 22:23:32 GMT',
'foo=bar; expires="01 Jan 2040 22:23:32 GMT"',
- ]:
- self.assertEqual(parse_ns_headers([hdr]), expected)
-
- def test_parse_ns_headers_version(self):
-
+ ])
+ def test_parse_ns_headers(self, hdr):
# quotes should be stripped
- expected = [[('foo', 'bar'), ('version', '1')]]
- for hdr in [
+ expected = [[('foo', 'bar'), ('expires', 2209069412), ('version', '0')]]
+ self.assertEqual(parse_ns_headers([hdr]), expected)
+
+ @support.subTests('hdr', [
'foo=bar; version="1"',
'foo=bar; Version="1"',
- ]:
- self.assertEqual(parse_ns_headers([hdr]), expected)
+ ])
+ def test_parse_ns_headers_version(self, hdr):
+ # quotes should be stripped
+ expected = [[('foo', 'bar'), ('version', '1')]]
+ self.assertEqual(parse_ns_headers([hdr]), expected)
def test_parse_ns_headers_special_names(self):
# names such as 'expires' are not special in first name=value pair
@@ -226,8 +221,7 @@ class HeaderTests(unittest.TestCase):
expected = [[("expires", "01 Jan 2040 22:23:32 GMT"), ("version", "0")]]
self.assertEqual(parse_ns_headers([hdr]), expected)
- def test_join_header_words(self):
- for src, expected in [
+ @support.subTests('src,expected', [
([[("foo", None), ("bar", "baz")]], "foo; bar=baz"),
(([]), ""),
(([[]]), ""),
@@ -237,12 +231,11 @@ class HeaderTests(unittest.TestCase):
'n; foo="foo;_", bar=foo_bar'),
([[("n", "m"), ("foo", None)], [("bar", "foo_bar")]],
'n=m; foo, bar=foo_bar'),
- ]:
- with self.subTest(src=src):
- self.assertEqual(join_header_words(src), expected)
+ ])
+ def test_join_header_words(self, src, expected):
+ self.assertEqual(join_header_words(src), expected)
- def test_split_header_words(self):
- tests = [
+ @support.subTests('arg,expect', [
("foo", [[("foo", None)]]),
("foo=bar", [[("foo", "bar")]]),
(" foo ", [[("foo", None)]]),
@@ -259,24 +252,22 @@ class HeaderTests(unittest.TestCase):
(r'foo; bar=baz, spam=, foo="\,\;\"", bar= ',
[[("foo", None), ("bar", "baz")],
[("spam", "")], [("foo", ',;"')], [("bar", "")]]),
- ]
-
- for arg, expect in tests:
- try:
- result = split_header_words([arg])
- except:
- import traceback, io
- f = io.StringIO()
- traceback.print_exc(None, f)
- result = "(error -- traceback follows)\n\n%s" % f.getvalue()
- self.assertEqual(result, expect, """
+ ])
+ def test_split_header_words(self, arg, expect):
+ try:
+ result = split_header_words([arg])
+ except:
+ import traceback, io
+ f = io.StringIO()
+ traceback.print_exc(None, f)
+ result = "(error -- traceback follows)\n\n%s" % f.getvalue()
+ self.assertEqual(result, expect, """
When parsing: '%s'
Expected: '%s'
Got: '%s'
""" % (arg, expect, result))
- def test_roundtrip(self):
- tests = [
+ @support.subTests('arg,expect', [
("foo", "foo"),
("foo=bar", "foo=bar"),
(" foo ", "foo"),
@@ -309,12 +300,11 @@ Got: '%s'
('n; foo="foo;_", bar="foo,_"',
'n; foo="foo;_", bar="foo,_"'),
- ]
-
- for arg, expect in tests:
- input = split_header_words([arg])
- res = join_header_words(input)
- self.assertEqual(res, expect, """
+ ])
+ def test_roundtrip(self, arg, expect):
+ input = split_header_words([arg])
+ res = join_header_words(input)
+ self.assertEqual(res, expect, """
When parsing: '%s'
Expected: '%s'
Got: '%s'
@@ -516,14 +506,7 @@ class CookieTests(unittest.TestCase):
## just the 7 special TLD's listed in their spec. And folks rely on
## that...
- def test_domain_return_ok(self):
- # test optimization: .domain_return_ok() should filter out most
- # domains in the CookieJar before we try to access them (because that
- # may require disk access -- in particular, with MSIECookieJar)
- # This is only a rough check for performance reasons, so it's not too
- # critical as long as it's sufficiently liberal.
- pol = DefaultCookiePolicy()
- for url, domain, ok in [
+ @support.subTests('url,domain,ok', [
("http://foo.bar.com/", "blah.com", False),
("http://foo.bar.com/", "rhubarb.blah.com", False),
("http://foo.bar.com/", "rhubarb.foo.bar.com", False),
@@ -543,11 +526,18 @@ class CookieTests(unittest.TestCase):
("http://foo/", ".local", True),
("http://barfoo.com", ".foo.com", False),
("http://barfoo.com", "foo.com", False),
- ]:
- request = urllib.request.Request(url)
- r = pol.domain_return_ok(domain, request)
- if ok: self.assertTrue(r)
- else: self.assertFalse(r)
+ ])
+ def test_domain_return_ok(self, url, domain, ok):
+ # test optimization: .domain_return_ok() should filter out most
+ # domains in the CookieJar before we try to access them (because that
+ # may require disk access -- in particular, with MSIECookieJar)
+ # This is only a rough check for performance reasons, so it's not too
+ # critical as long as it's sufficiently liberal.
+ pol = DefaultCookiePolicy()
+ request = urllib.request.Request(url)
+ r = pol.domain_return_ok(domain, request)
+ if ok: self.assertTrue(r)
+ else: self.assertFalse(r)
def test_missing_value(self):
# missing = sign in Cookie: header is regarded by Mozilla as a missing
@@ -581,10 +571,7 @@ class CookieTests(unittest.TestCase):
self.assertEqual(interact_netscape(c, "http://www.acme.com/foo/"),
'"spam"; eggs')
- def test_rfc2109_handling(self):
- # RFC 2109 cookies are handled as RFC 2965 or Netscape cookies,
- # dependent on policy settings
- for rfc2109_as_netscape, rfc2965, version in [
+ @support.subTests('rfc2109_as_netscape,rfc2965,version', [
# default according to rfc2965 if not explicitly specified
(None, False, 0),
(None, True, 1),
@@ -593,24 +580,27 @@ class CookieTests(unittest.TestCase):
(False, True, 1),
(True, False, 0),
(True, True, 0),
- ]:
- policy = DefaultCookiePolicy(
- rfc2109_as_netscape=rfc2109_as_netscape,
- rfc2965=rfc2965)
- c = CookieJar(policy)
- interact_netscape(c, "http://www.example.com/", "ni=ni; Version=1")
- try:
- cookie = c._cookies["www.example.com"]["/"]["ni"]
- except KeyError:
- self.assertIsNone(version) # didn't expect a stored cookie
- else:
- self.assertEqual(cookie.version, version)
- # 2965 cookies are unaffected
- interact_2965(c, "http://www.example.com/",
- "foo=bar; Version=1")
- if rfc2965:
- cookie2965 = c._cookies["www.example.com"]["/"]["foo"]
- self.assertEqual(cookie2965.version, 1)
+ ])
+ def test_rfc2109_handling(self, rfc2109_as_netscape, rfc2965, version):
+ # RFC 2109 cookies are handled as RFC 2965 or Netscape cookies,
+ # dependent on policy settings
+ policy = DefaultCookiePolicy(
+ rfc2109_as_netscape=rfc2109_as_netscape,
+ rfc2965=rfc2965)
+ c = CookieJar(policy)
+ interact_netscape(c, "http://www.example.com/", "ni=ni; Version=1")
+ try:
+ cookie = c._cookies["www.example.com"]["/"]["ni"]
+ except KeyError:
+ self.assertIsNone(version) # didn't expect a stored cookie
+ else:
+ self.assertEqual(cookie.version, version)
+ # 2965 cookies are unaffected
+ interact_2965(c, "http://www.example.com/",
+ "foo=bar; Version=1")
+ if rfc2965:
+ cookie2965 = c._cookies["www.example.com"]["/"]["foo"]
+ self.assertEqual(cookie2965.version, 1)
def test_ns_parser(self):
c = CookieJar()
@@ -778,8 +768,7 @@ class CookieTests(unittest.TestCase):
# Cookie is sent back to the same URI.
self.assertEqual(interact_netscape(cj, uri), value)
- def test_escape_path(self):
- cases = [
+ @support.subTests('arg,result', [
# quoted safe
("/foo%2f/bar", "/foo%2F/bar"),
("/foo%2F/bar", "/foo%2F/bar"),
@@ -799,9 +788,9 @@ class CookieTests(unittest.TestCase):
("/foo/bar\u00fc", "/foo/bar%C3%BC"), # UTF-8 encoded
# unicode
("/foo/bar\uabcd", "/foo/bar%EA%AF%8D"), # UTF-8 encoded
- ]
- for arg, result in cases:
- self.assertEqual(escape_path(arg), result)
+ ])
+ def test_escape_path(self, arg, result):
+ self.assertEqual(escape_path(arg), result)
def test_request_path(self):
# with parameters
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
index 2cafa4e45a1..2548a7c5f29 100644
--- a/Lib/test/test_httpservers.py
+++ b/Lib/test/test_httpservers.py
@@ -3,16 +3,16 @@
Written by Cody A.W. Somerville <cody-somerville@ubuntu.com>,
Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest.
"""
-from collections import OrderedDict
+
from http.server import BaseHTTPRequestHandler, HTTPServer, HTTPSServer, \
- SimpleHTTPRequestHandler, CGIHTTPRequestHandler
+ SimpleHTTPRequestHandler
from http import server, HTTPStatus
+import contextlib
import os
import socket
import sys
import re
-import base64
import ntpath
import pathlib
import shutil
@@ -21,6 +21,7 @@ import email.utils
import html
import http, http.client
import urllib.parse
+import urllib.request
import tempfile
import time
import datetime
@@ -31,8 +32,10 @@ from io import BytesIO, StringIO
import unittest
from test import support
from test.support import (
- is_apple, import_helper, os_helper, requires_subprocess, threading_helper
+ is_apple, import_helper, os_helper, threading_helper
)
+from test.support.script_helper import kill_python, spawn_python
+from test.support.socket_helper import find_unused_port
try:
import ssl
@@ -522,42 +525,120 @@ class SimpleHTTPServerTestCase(BaseTestCase):
reader.close()
return body
+ def check_list_dir_dirname(self, dirname, quotedname=None):
+ fullpath = os.path.join(self.tempdir, dirname)
+ try:
+ os.mkdir(os.path.join(self.tempdir, dirname))
+ except (OSError, UnicodeEncodeError):
+ self.skipTest(f'Can not create directory {dirname!a} '
+ f'on current file system')
+
+ if quotedname is None:
+ quotedname = urllib.parse.quote(dirname, errors='surrogatepass')
+ response = self.request(self.base_url + '/' + quotedname + '/')
+ body = self.check_status_and_reason(response, HTTPStatus.OK)
+ displaypath = html.escape(f'{self.base_url}/{dirname}/', quote=False)
+ enc = sys.getfilesystemencoding()
+ prefix = f'listing for {displaypath}</'.encode(enc, 'surrogateescape')
+ self.assertIn(prefix + b'title>', body)
+ self.assertIn(prefix + b'h1>', body)
+
+ def check_list_dir_filename(self, filename):
+ fullpath = os.path.join(self.tempdir, filename)
+ content = ascii(fullpath).encode() + (os_helper.TESTFN_UNDECODABLE or b'\xff')
+ try:
+ with open(fullpath, 'wb') as f:
+ f.write(content)
+ except OSError:
+ self.skipTest(f'Can not create file {filename!a} '
+ f'on current file system')
+
+ response = self.request(self.base_url + '/')
+ body = self.check_status_and_reason(response, HTTPStatus.OK)
+ quotedname = urllib.parse.quote(filename, errors='surrogatepass')
+ enc = response.headers.get_content_charset()
+ self.assertIsNotNone(enc)
+ self.assertIn((f'href="{quotedname}"').encode('ascii'), body)
+ displayname = html.escape(filename, quote=False)
+ self.assertIn(f'>{displayname}<'.encode(enc, 'surrogateescape'), body)
+
+ response = self.request(self.base_url + '/' + quotedname)
+ self.check_status_and_reason(response, HTTPStatus.OK, data=content)
+
+ @unittest.skipUnless(os_helper.TESTFN_NONASCII,
+ 'need os_helper.TESTFN_NONASCII')
+ def test_list_dir_nonascii_dirname(self):
+ dirname = os_helper.TESTFN_NONASCII + '.dir'
+ self.check_list_dir_dirname(dirname)
+
+ @unittest.skipUnless(os_helper.TESTFN_NONASCII,
+ 'need os_helper.TESTFN_NONASCII')
+ def test_list_dir_nonascii_filename(self):
+ filename = os_helper.TESTFN_NONASCII + '.txt'
+ self.check_list_dir_filename(filename)
+
@unittest.skipIf(is_apple,
'undecodable name cannot always be decoded on Apple platforms')
@unittest.skipIf(sys.platform == 'win32',
'undecodable name cannot be decoded on win32')
@unittest.skipUnless(os_helper.TESTFN_UNDECODABLE,
'need os_helper.TESTFN_UNDECODABLE')
- def test_undecodable_filename(self):
- enc = sys.getfilesystemencoding()
- filename = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.txt'
- with open(os.path.join(self.tempdir, filename), 'wb') as f:
- f.write(os_helper.TESTFN_UNDECODABLE)
- response = self.request(self.base_url + '/')
- if is_apple:
- # On Apple platforms the HFS+ filesystem replaces bytes that
- # aren't valid UTF-8 into a percent-encoded value.
- for name in os.listdir(self.tempdir):
- if name != 'test': # Ignore a filename created in setUp().
- filename = name
- break
- body = self.check_status_and_reason(response, HTTPStatus.OK)
- quotedname = urllib.parse.quote(filename, errors='surrogatepass')
- self.assertIn(('href="%s"' % quotedname)
- .encode(enc, 'surrogateescape'), body)
- self.assertIn(('>%s<' % html.escape(filename, quote=False))
- .encode(enc, 'surrogateescape'), body)
- response = self.request(self.base_url + '/' + quotedname)
- self.check_status_and_reason(response, HTTPStatus.OK,
- data=os_helper.TESTFN_UNDECODABLE)
+ def test_list_dir_undecodable_dirname(self):
+ dirname = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.dir'
+ self.check_list_dir_dirname(dirname)
- def test_undecodable_parameter(self):
- # sanity check using a valid parameter
+ @unittest.skipIf(is_apple,
+ 'undecodable name cannot always be decoded on Apple platforms')
+ @unittest.skipIf(sys.platform == 'win32',
+ 'undecodable name cannot be decoded on win32')
+ @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE,
+ 'need os_helper.TESTFN_UNDECODABLE')
+ def test_list_dir_undecodable_filename(self):
+ filename = os.fsdecode(os_helper.TESTFN_UNDECODABLE) + '.txt'
+ self.check_list_dir_filename(filename)
+
+ def test_list_dir_undecodable_dirname2(self):
+ dirname = '\ufffd.dir'
+ self.check_list_dir_dirname(dirname, quotedname='%ff.dir')
+
+ @unittest.skipUnless(os_helper.TESTFN_UNENCODABLE,
+ 'need os_helper.TESTFN_UNENCODABLE')
+ def test_list_dir_unencodable_dirname(self):
+ dirname = os_helper.TESTFN_UNENCODABLE + '.dir'
+ self.check_list_dir_dirname(dirname)
+
+ @unittest.skipUnless(os_helper.TESTFN_UNENCODABLE,
+ 'need os_helper.TESTFN_UNENCODABLE')
+ def test_list_dir_unencodable_filename(self):
+ filename = os_helper.TESTFN_UNENCODABLE + '.txt'
+ self.check_list_dir_filename(filename)
+
+ def test_list_dir_escape_dirname(self):
+ # Characters that need special treating in URL or HTML.
+ for name in ('q?', 'f#', '&amp;', '&amp', '<i>', '"dq"', "'sq'",
+ '%A4', '%E2%82%AC'):
+ with self.subTest(name=name):
+ dirname = name + '.dir'
+ self.check_list_dir_dirname(dirname,
+ quotedname=urllib.parse.quote(dirname, safe='&<>\'"'))
+
+ def test_list_dir_escape_filename(self):
+ # Characters that need special treating in URL or HTML.
+ for name in ('q?', 'f#', '&amp;', '&amp', '<i>', '"dq"', "'sq'",
+ '%A4', '%E2%82%AC'):
+ with self.subTest(name=name):
+ filename = name + '.txt'
+ self.check_list_dir_filename(filename)
+ os_helper.unlink(os.path.join(self.tempdir, filename))
+
+ def test_list_dir_with_query_and_fragment(self):
+ prefix = f'listing for {self.base_url}/</'.encode('latin1')
+ response = self.request(self.base_url + '/#123').read()
+ self.assertIn(prefix + b'title>', response)
+ self.assertIn(prefix + b'h1>', response)
response = self.request(self.base_url + '/?x=123').read()
- self.assertRegex(response, rf'listing for {self.base_url}/\?x=123'.encode('latin1'))
- # now the bogus encoding
- response = self.request(self.base_url + '/?x=%bb').read()
- self.assertRegex(response, rf'listing for {self.base_url}/\?x=\xef\xbf\xbd'.encode('latin1'))
+ self.assertIn(prefix + b'title>', response)
+ self.assertIn(prefix + b'h1>', response)
def test_get_dir_redirect_location_domain_injection_bug(self):
"""Ensure //evil.co/..%2f../../X does not put //evil.co/ in Location.
@@ -615,10 +696,19 @@ class SimpleHTTPServerTestCase(BaseTestCase):
# check for trailing "/" which should return 404. See Issue17324
response = self.request(self.base_url + '/test/')
self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
+ response = self.request(self.base_url + '/test%2f')
+ self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
+ response = self.request(self.base_url + '/test%2F')
+ self.check_status_and_reason(response, HTTPStatus.NOT_FOUND)
response = self.request(self.base_url + '/')
self.check_status_and_reason(response, HTTPStatus.OK)
+ response = self.request(self.base_url + '%2f')
+ self.check_status_and_reason(response, HTTPStatus.OK)
+ response = self.request(self.base_url + '%2F')
+ self.check_status_and_reason(response, HTTPStatus.OK)
response = self.request(self.base_url)
self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
+ self.assertEqual(response.getheader("Location"), self.base_url + "/")
self.assertEqual(response.getheader("Content-Length"), "0")
response = self.request(self.base_url + '/?hi=2')
self.check_status_and_reason(response, HTTPStatus.OK)
@@ -724,6 +814,8 @@ class SimpleHTTPServerTestCase(BaseTestCase):
self.check_status_and_reason(response, HTTPStatus.OK)
response = self.request(self.tempdir_name)
self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY)
+ self.assertEqual(response.getheader("Location"),
+ self.tempdir_name + "/")
response = self.request(self.tempdir_name + '/?hi=2')
self.check_status_and_reason(response, HTTPStatus.OK)
response = self.request(self.tempdir_name + '?hi=1')
@@ -731,350 +823,6 @@ class SimpleHTTPServerTestCase(BaseTestCase):
self.assertEqual(response.getheader("Location"),
self.tempdir_name + "/?hi=1")
- def test_html_escape_filename(self):
- filename = '<test&>.txt'
- fullpath = os.path.join(self.tempdir, filename)
-
- try:
- open(fullpath, 'wb').close()
- except OSError:
- raise unittest.SkipTest('Can not create file %s on current file '
- 'system' % filename)
-
- try:
- response = self.request(self.base_url + '/')
- body = self.check_status_and_reason(response, HTTPStatus.OK)
- enc = response.headers.get_content_charset()
- finally:
- os.unlink(fullpath) # avoid affecting test_undecodable_filename
-
- self.assertIsNotNone(enc)
- html_text = '>%s<' % html.escape(filename, quote=False)
- self.assertIn(html_text.encode(enc), body)
-
-
-cgi_file1 = """\
-#!%s
-
-print("Content-type: text/html")
-print()
-print("Hello World")
-"""
-
-cgi_file2 = """\
-#!%s
-import os
-import sys
-import urllib.parse
-
-print("Content-type: text/html")
-print()
-
-content_length = int(os.environ["CONTENT_LENGTH"])
-query_string = sys.stdin.buffer.read(content_length)
-params = {key.decode("utf-8"): val.decode("utf-8")
- for key, val in urllib.parse.parse_qsl(query_string)}
-
-print("%%s, %%s, %%s" %% (params["spam"], params["eggs"], params["bacon"]))
-"""
-
-cgi_file4 = """\
-#!%s
-import os
-
-print("Content-type: text/html")
-print()
-
-print(os.environ["%s"])
-"""
-
-cgi_file6 = """\
-#!%s
-import os
-
-print("X-ambv: was here")
-print("Content-type: text/html")
-print()
-print("<pre>")
-for k, v in os.environ.items():
- try:
- k.encode('ascii')
- v.encode('ascii')
- except UnicodeEncodeError:
- continue # see: BPO-44647
- print(f"{k}={v}")
-print("</pre>")
-"""
-
-
-@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
- "This test can't be run reliably as root (issue #13308).")
-@requires_subprocess()
-class CGIHTTPServerTestCase(BaseTestCase):
- class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
- _test_case_self = None # populated by each setUp() method call.
-
- def __init__(self, *args, **kwargs):
- with self._test_case_self.assertWarnsRegex(
- DeprecationWarning,
- r'http\.server\.CGIHTTPRequestHandler'):
- # This context also happens to catch and silence the
- # threading DeprecationWarning from os.fork().
- super().__init__(*args, **kwargs)
-
- linesep = os.linesep.encode('ascii')
-
- def setUp(self):
- self.request_handler._test_case_self = self # practical, but yuck.
- BaseTestCase.setUp(self)
- self.cwd = os.getcwd()
- self.parent_dir = tempfile.mkdtemp()
- self.cgi_dir = os.path.join(self.parent_dir, 'cgi-bin')
- self.cgi_child_dir = os.path.join(self.cgi_dir, 'child-dir')
- self.sub_dir_1 = os.path.join(self.parent_dir, 'sub')
- self.sub_dir_2 = os.path.join(self.sub_dir_1, 'dir')
- self.cgi_dir_in_sub_dir = os.path.join(self.sub_dir_2, 'cgi-bin')
- os.mkdir(self.cgi_dir)
- os.mkdir(self.cgi_child_dir)
- os.mkdir(self.sub_dir_1)
- os.mkdir(self.sub_dir_2)
- os.mkdir(self.cgi_dir_in_sub_dir)
- self.nocgi_path = None
- self.file1_path = None
- self.file2_path = None
- self.file3_path = None
- self.file4_path = None
- self.file5_path = None
-
- # The shebang line should be pure ASCII: use symlink if possible.
- # See issue #7668.
- self._pythonexe_symlink = None
- if os_helper.can_symlink():
- self.pythonexe = os.path.join(self.parent_dir, 'python')
- self._pythonexe_symlink = support.PythonSymlink(self.pythonexe).__enter__()
- else:
- self.pythonexe = sys.executable
-
- try:
- # The python executable path is written as the first line of the
- # CGI Python script. The encoding cookie cannot be used, and so the
- # path should be encodable to the default script encoding (utf-8)
- self.pythonexe.encode('utf-8')
- except UnicodeEncodeError:
- self.tearDown()
- self.skipTest("Python executable path is not encodable to utf-8")
-
- self.nocgi_path = os.path.join(self.parent_dir, 'nocgi.py')
- with open(self.nocgi_path, 'w', encoding='utf-8') as fp:
- fp.write(cgi_file1 % self.pythonexe)
- os.chmod(self.nocgi_path, 0o777)
-
- self.file1_path = os.path.join(self.cgi_dir, 'file1.py')
- with open(self.file1_path, 'w', encoding='utf-8') as file1:
- file1.write(cgi_file1 % self.pythonexe)
- os.chmod(self.file1_path, 0o777)
-
- self.file2_path = os.path.join(self.cgi_dir, 'file2.py')
- with open(self.file2_path, 'w', encoding='utf-8') as file2:
- file2.write(cgi_file2 % self.pythonexe)
- os.chmod(self.file2_path, 0o777)
-
- self.file3_path = os.path.join(self.cgi_child_dir, 'file3.py')
- with open(self.file3_path, 'w', encoding='utf-8') as file3:
- file3.write(cgi_file1 % self.pythonexe)
- os.chmod(self.file3_path, 0o777)
-
- self.file4_path = os.path.join(self.cgi_dir, 'file4.py')
- with open(self.file4_path, 'w', encoding='utf-8') as file4:
- file4.write(cgi_file4 % (self.pythonexe, 'QUERY_STRING'))
- os.chmod(self.file4_path, 0o777)
-
- self.file5_path = os.path.join(self.cgi_dir_in_sub_dir, 'file5.py')
- with open(self.file5_path, 'w', encoding='utf-8') as file5:
- file5.write(cgi_file1 % self.pythonexe)
- os.chmod(self.file5_path, 0o777)
-
- self.file6_path = os.path.join(self.cgi_dir, 'file6.py')
- with open(self.file6_path, 'w', encoding='utf-8') as file6:
- file6.write(cgi_file6 % self.pythonexe)
- os.chmod(self.file6_path, 0o777)
-
- os.chdir(self.parent_dir)
-
- def tearDown(self):
- self.request_handler._test_case_self = None
- try:
- os.chdir(self.cwd)
- if self._pythonexe_symlink:
- self._pythonexe_symlink.__exit__(None, None, None)
- if self.nocgi_path:
- os.remove(self.nocgi_path)
- if self.file1_path:
- os.remove(self.file1_path)
- if self.file2_path:
- os.remove(self.file2_path)
- if self.file3_path:
- os.remove(self.file3_path)
- if self.file4_path:
- os.remove(self.file4_path)
- if self.file5_path:
- os.remove(self.file5_path)
- if self.file6_path:
- os.remove(self.file6_path)
- os.rmdir(self.cgi_child_dir)
- os.rmdir(self.cgi_dir)
- os.rmdir(self.cgi_dir_in_sub_dir)
- os.rmdir(self.sub_dir_2)
- os.rmdir(self.sub_dir_1)
- # The 'gmon.out' file can be written in the current working
- # directory if C-level code profiling with gprof is enabled.
- os_helper.unlink(os.path.join(self.parent_dir, 'gmon.out'))
- os.rmdir(self.parent_dir)
- finally:
- BaseTestCase.tearDown(self)
-
- def test_url_collapse_path(self):
- # verify tail is the last portion and head is the rest on proper urls
- test_vectors = {
- '': '//',
- '..': IndexError,
- '/.//..': IndexError,
- '/': '//',
- '//': '//',
- '/\\': '//\\',
- '/.//': '//',
- 'cgi-bin/file1.py': '/cgi-bin/file1.py',
- '/cgi-bin/file1.py': '/cgi-bin/file1.py',
- 'a': '//a',
- '/a': '//a',
- '//a': '//a',
- './a': '//a',
- './C:/': '/C:/',
- '/a/b': '/a/b',
- '/a/b/': '/a/b/',
- '/a/b/.': '/a/b/',
- '/a/b/c/..': '/a/b/',
- '/a/b/c/../d': '/a/b/d',
- '/a/b/c/../d/e/../f': '/a/b/d/f',
- '/a/b/c/../d/e/../../f': '/a/b/f',
- '/a/b/c/../d/e/.././././..//f': '/a/b/f',
- '../a/b/c/../d/e/.././././..//f': IndexError,
- '/a/b/c/../d/e/../../../f': '/a/f',
- '/a/b/c/../d/e/../../../../f': '//f',
- '/a/b/c/../d/e/../../../../../f': IndexError,
- '/a/b/c/../d/e/../../../../f/..': '//',
- '/a/b/c/../d/e/../../../../f/../.': '//',
- }
- for path, expected in test_vectors.items():
- if isinstance(expected, type) and issubclass(expected, Exception):
- self.assertRaises(expected,
- server._url_collapse_path, path)
- else:
- actual = server._url_collapse_path(path)
- self.assertEqual(expected, actual,
- msg='path = %r\nGot: %r\nWanted: %r' %
- (path, actual, expected))
-
- def test_headers_and_content(self):
- res = self.request('/cgi-bin/file1.py')
- self.assertEqual(
- (res.read(), res.getheader('Content-type'), res.status),
- (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK))
-
- def test_issue19435(self):
- res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh')
- self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
-
- def test_post(self):
- params = urllib.parse.urlencode(
- {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
- headers = {'Content-type' : 'application/x-www-form-urlencoded'}
- res = self.request('/cgi-bin/file2.py', 'POST', params, headers)
-
- self.assertEqual(res.read(), b'1, python, 123456' + self.linesep)
-
- def test_invaliduri(self):
- res = self.request('/cgi-bin/invalid')
- res.read()
- self.assertEqual(res.status, HTTPStatus.NOT_FOUND)
-
- def test_authorization(self):
- headers = {b'Authorization' : b'Basic ' +
- base64.b64encode(b'username:pass')}
- res = self.request('/cgi-bin/file1.py', 'GET', headers=headers)
- self.assertEqual(
- (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
-
- def test_no_leading_slash(self):
- # http://bugs.python.org/issue2254
- res = self.request('cgi-bin/file1.py')
- self.assertEqual(
- (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
-
- def test_os_environ_is_not_altered(self):
- signature = "Test CGI Server"
- os.environ['SERVER_SOFTWARE'] = signature
- res = self.request('/cgi-bin/file1.py')
- self.assertEqual(
- (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
- self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
-
- def test_urlquote_decoding_in_cgi_check(self):
- res = self.request('/cgi-bin%2ffile1.py')
- self.assertEqual(
- (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
-
- def test_nested_cgi_path_issue21323(self):
- res = self.request('/cgi-bin/child-dir/file3.py')
- self.assertEqual(
- (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
-
- def test_query_with_multiple_question_mark(self):
- res = self.request('/cgi-bin/file4.py?a=b?c=d')
- self.assertEqual(
- (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
-
- def test_query_with_continuous_slashes(self):
- res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
- self.assertEqual(
- (b'k=aa%2F%2Fbb&//q//p//=//a//b//' + self.linesep,
- 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
-
- def test_cgi_path_in_sub_directories(self):
- try:
- CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin')
- res = self.request('/sub/dir/cgi-bin/file5.py')
- self.assertEqual(
- (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK),
- (res.read(), res.getheader('Content-type'), res.status))
- finally:
- CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin')
-
- def test_accept(self):
- browser_accept = \
- 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
- tests = (
- ((('Accept', browser_accept),), browser_accept),
- ((), ''),
- # Hack case to get two values for the one header
- ((('Accept', 'text/html'), ('ACCEPT', 'text/plain')),
- 'text/html,text/plain'),
- )
- for headers, expected in tests:
- headers = OrderedDict(headers)
- with self.subTest(headers):
- res = self.request('/cgi-bin/file6.py', 'GET', headers=headers)
- self.assertEqual(http.HTTPStatus.OK, res.status)
- expected = f"HTTP_ACCEPT={expected}".encode('ascii')
- self.assertIn(expected, res.read())
-
class SocketlessRequestHandler(SimpleHTTPRequestHandler):
def __init__(self, directory=None):
@@ -1095,6 +843,7 @@ class SocketlessRequestHandler(SimpleHTTPRequestHandler):
def log_message(self, format, *args):
pass
+
class RejectingSocketlessRequestHandler(SocketlessRequestHandler):
def handle_expect_100(self):
self.send_error(HTTPStatus.EXPECTATION_FAILED)
@@ -1536,6 +1285,243 @@ class ScriptTestCase(unittest.TestCase):
self.assertEqual(mock_server.address_family, socket.AF_INET)
+class CommandLineTestCase(unittest.TestCase):
+ default_port = 8000
+ default_bind = None
+ default_protocol = 'HTTP/1.0'
+ default_handler = SimpleHTTPRequestHandler
+ default_server = unittest.mock.ANY
+ tls_cert = certdata_file('ssl_cert.pem')
+ tls_key = certdata_file('ssl_key.pem')
+ tls_password = 'somepass'
+ tls_cert_options = ['--tls-cert']
+ tls_key_options = ['--tls-key']
+ tls_password_options = ['--tls-password-file']
+ args = {
+ 'HandlerClass': default_handler,
+ 'ServerClass': default_server,
+ 'protocol': default_protocol,
+ 'port': default_port,
+ 'bind': default_bind,
+ 'tls_cert': None,
+ 'tls_key': None,
+ 'tls_password': None,
+ }
+
+ def setUp(self):
+ super().setUp()
+ self.tls_password_file = tempfile.mktemp()
+ with open(self.tls_password_file, 'wb') as f:
+ f.write(self.tls_password.encode())
+ self.addCleanup(os_helper.unlink, self.tls_password_file)
+
+ def invoke_httpd(self, *args, stdout=None, stderr=None):
+ stdout = StringIO() if stdout is None else stdout
+ stderr = StringIO() if stderr is None else stderr
+ with contextlib.redirect_stdout(stdout), \
+ contextlib.redirect_stderr(stderr):
+ server._main(args)
+ return stdout.getvalue(), stderr.getvalue()
+
+ @mock.patch('http.server.test')
+ def test_port_flag(self, mock_func):
+ ports = [8000, 65535]
+ for port in ports:
+ with self.subTest(port=port):
+ self.invoke_httpd(str(port))
+ call_args = self.args | dict(port=port)
+ mock_func.assert_called_once_with(**call_args)
+ mock_func.reset_mock()
+
+ @mock.patch('http.server.test')
+ def test_directory_flag(self, mock_func):
+ options = ['-d', '--directory']
+ directories = ['.', '/foo', '\\bar', '/',
+ 'C:\\', 'C:\\foo', 'C:\\bar',
+ '/home/user', './foo/foo2', 'D:\\foo\\bar']
+ for flag in options:
+ for directory in directories:
+ with self.subTest(flag=flag, directory=directory):
+ self.invoke_httpd(flag, directory)
+ mock_func.assert_called_once_with(**self.args)
+ mock_func.reset_mock()
+
+ @mock.patch('http.server.test')
+ def test_bind_flag(self, mock_func):
+ options = ['-b', '--bind']
+ bind_addresses = ['localhost', '127.0.0.1', '::1',
+ '0.0.0.0', '8.8.8.8']
+ for flag in options:
+ for bind_address in bind_addresses:
+ with self.subTest(flag=flag, bind_address=bind_address):
+ self.invoke_httpd(flag, bind_address)
+ call_args = self.args | dict(bind=bind_address)
+ mock_func.assert_called_once_with(**call_args)
+ mock_func.reset_mock()
+
+ @mock.patch('http.server.test')
+ def test_protocol_flag(self, mock_func):
+ options = ['-p', '--protocol']
+ protocols = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2.0', 'HTTP/3.0']
+ for flag in options:
+ for protocol in protocols:
+ with self.subTest(flag=flag, protocol=protocol):
+ self.invoke_httpd(flag, protocol)
+ call_args = self.args | dict(protocol=protocol)
+ mock_func.assert_called_once_with(**call_args)
+ mock_func.reset_mock()
+
+ @unittest.skipIf(ssl is None, "requires ssl")
+ @mock.patch('http.server.test')
+ def test_tls_cert_and_key_flags(self, mock_func):
+ for tls_cert_option in self.tls_cert_options:
+ for tls_key_option in self.tls_key_options:
+ self.invoke_httpd(tls_cert_option, self.tls_cert,
+ tls_key_option, self.tls_key)
+ call_args = self.args | {
+ 'tls_cert': self.tls_cert,
+ 'tls_key': self.tls_key,
+ }
+ mock_func.assert_called_once_with(**call_args)
+ mock_func.reset_mock()
+
+ @unittest.skipIf(ssl is None, "requires ssl")
+ @mock.patch('http.server.test')
+ def test_tls_cert_and_key_and_password_flags(self, mock_func):
+ for tls_cert_option in self.tls_cert_options:
+ for tls_key_option in self.tls_key_options:
+ for tls_password_option in self.tls_password_options:
+ self.invoke_httpd(tls_cert_option,
+ self.tls_cert,
+ tls_key_option,
+ self.tls_key,
+ tls_password_option,
+ self.tls_password_file)
+ call_args = self.args | {
+ 'tls_cert': self.tls_cert,
+ 'tls_key': self.tls_key,
+ 'tls_password': self.tls_password,
+ }
+ mock_func.assert_called_once_with(**call_args)
+ mock_func.reset_mock()
+
+ @unittest.skipIf(ssl is None, "requires ssl")
+ @mock.patch('http.server.test')
+ def test_missing_tls_cert_flag(self, mock_func):
+ for tls_key_option in self.tls_key_options:
+ with self.assertRaises(SystemExit):
+ self.invoke_httpd(tls_key_option, self.tls_key)
+ mock_func.reset_mock()
+
+ for tls_password_option in self.tls_password_options:
+ with self.assertRaises(SystemExit):
+ self.invoke_httpd(tls_password_option, self.tls_password)
+ mock_func.reset_mock()
+
+ @unittest.skipIf(ssl is None, "requires ssl")
+ @mock.patch('http.server.test')
+ def test_invalid_password_file(self, mock_func):
+ non_existent_file = 'non_existent_file'
+ for tls_password_option in self.tls_password_options:
+ for tls_cert_option in self.tls_cert_options:
+ with self.assertRaises(SystemExit):
+ self.invoke_httpd(tls_cert_option,
+ self.tls_cert,
+ tls_password_option,
+ non_existent_file)
+
+ @mock.patch('http.server.test')
+ def test_no_arguments(self, mock_func):
+ self.invoke_httpd()
+ mock_func.assert_called_once_with(**self.args)
+ mock_func.reset_mock()
+
+ @mock.patch('http.server.test')
+ def test_help_flag(self, _):
+ options = ['-h', '--help']
+ for option in options:
+ stdout, stderr = StringIO(), StringIO()
+ with self.assertRaises(SystemExit):
+ self.invoke_httpd(option, stdout=stdout, stderr=stderr)
+ self.assertIn('usage', stdout.getvalue())
+ self.assertEqual(stderr.getvalue(), '')
+
+ @mock.patch('http.server.test')
+ def test_unknown_flag(self, _):
+ stdout, stderr = StringIO(), StringIO()
+ with self.assertRaises(SystemExit):
+ self.invoke_httpd('--unknown-flag', stdout=stdout, stderr=stderr)
+ self.assertEqual(stdout.getvalue(), '')
+ self.assertIn('error', stderr.getvalue())
+
+
+class CommandLineRunTimeTestCase(unittest.TestCase):
+ served_data = os.urandom(32)
+ served_filename = 'served_filename'
+ tls_cert = certdata_file('ssl_cert.pem')
+ tls_key = certdata_file('ssl_key.pem')
+ tls_password = b'somepass'
+ tls_password_file = 'ssl_key_password'
+
+ def setUp(self):
+ super().setUp()
+ server_dir_context = os_helper.temp_cwd()
+ server_dir = self.enterContext(server_dir_context)
+ with open(self.served_filename, 'wb') as f:
+ f.write(self.served_data)
+ with open(self.tls_password_file, 'wb') as f:
+ f.write(self.tls_password)
+
+ def fetch_file(self, path, context=None):
+ req = urllib.request.Request(path, method='GET')
+ with urllib.request.urlopen(req, context=context) as res:
+ return res.read()
+
+ def parse_cli_output(self, output):
+ match = re.search(r'Serving (HTTP|HTTPS) on (.+) port (\d+)', output)
+ if match is None:
+ return None, None, None
+ return match.group(1).lower(), match.group(2), int(match.group(3))
+
+ def wait_for_server(self, proc, protocol, bind, port):
+ """Check that the server has been successfully started."""
+ line = proc.stdout.readline().strip()
+ if support.verbose:
+ print()
+ print('python -m http.server: ', line)
+ return self.parse_cli_output(line) == (protocol, bind, port)
+
+ def test_http_client(self):
+ bind, port = '127.0.0.1', find_unused_port()
+ proc = spawn_python('-u', '-m', 'http.server', str(port), '-b', bind,
+ bufsize=1, text=True)
+ self.addCleanup(kill_python, proc)
+ self.addCleanup(proc.terminate)
+ self.assertTrue(self.wait_for_server(proc, 'http', bind, port))
+ res = self.fetch_file(f'http://{bind}:{port}/{self.served_filename}')
+ self.assertEqual(res, self.served_data)
+
+ @unittest.skipIf(ssl is None, "requires ssl")
+ def test_https_client(self):
+ context = ssl.create_default_context()
+ # allow self-signed certificates
+ context.check_hostname = False
+ context.verify_mode = ssl.CERT_NONE
+
+ bind, port = '127.0.0.1', find_unused_port()
+ proc = spawn_python('-u', '-m', 'http.server', str(port), '-b', bind,
+ '--tls-cert', self.tls_cert,
+ '--tls-key', self.tls_key,
+ '--tls-password-file', self.tls_password_file,
+ bufsize=1, text=True)
+ self.addCleanup(kill_python, proc)
+ self.addCleanup(proc.terminate)
+ self.assertTrue(self.wait_for_server(proc, 'https', bind, port))
+ url = f'https://{bind}:{port}/{self.served_filename}'
+ res = self.fetch_file(url, context=context)
+ self.assertEqual(res, self.served_data)
+
+
def setUpModule():
unittest.addModuleCleanup(os.chdir, os.getcwd())
diff --git a/Lib/test/test_idle.py b/Lib/test/test_idle.py
index 3d8b7ecc0ec..ebf572ac5ca 100644
--- a/Lib/test/test_idle.py
+++ b/Lib/test/test_idle.py
@@ -16,7 +16,7 @@ idlelib.testing = True
# Unittest.main and test.libregrtest.runtest.runtest_inner
# call load_tests, when present here, to discover tests to run.
-from idlelib.idle_test import load_tests
+from idlelib.idle_test import load_tests # noqa: F401
if __name__ == '__main__':
tk.NoDefaultRoot()
diff --git a/Lib/test/test_importlib/import_/test_relative_imports.py b/Lib/test/test_importlib/import_/test_relative_imports.py
index e535d119763..1549cbe96ce 100644
--- a/Lib/test/test_importlib/import_/test_relative_imports.py
+++ b/Lib/test/test_importlib/import_/test_relative_imports.py
@@ -223,6 +223,21 @@ class RelativeImports:
self.__import__('sys', {'__package__': '', '__spec__': None},
level=1)
+ def test_malicious_relative_import(self):
+ # https://github.com/python/cpython/issues/134100
+ # Test to make sure UAF bug with error msg doesn't come back to life
+ import sys
+ loooong = "".ljust(0x23000, "b")
+ name = f"a.{loooong}.c"
+
+ with util.uncache(name):
+ sys.modules[name] = {}
+ with self.assertRaisesRegex(
+ KeyError,
+ r"'a\.b+' not in sys\.modules as expected"
+ ):
+ __import__(f"{loooong}.c", {"__package__": "a"}, level=1)
+
(Frozen_RelativeImports,
Source_RelativeImports
diff --git a/Lib/test/test_importlib/test_locks.py b/Lib/test/test_importlib/test_locks.py
index befac5d62b0..655e5881a15 100644
--- a/Lib/test/test_importlib/test_locks.py
+++ b/Lib/test/test_importlib/test_locks.py
@@ -34,6 +34,7 @@ class ModuleLockAsRLockTests:
# lock status in repr unsupported
test_repr = None
test_locked_repr = None
+ test_repr_count = None
def tearDown(self):
for splitinit in init.values():
diff --git a/Lib/test/test_importlib/test_threaded_import.py b/Lib/test/test_importlib/test_threaded_import.py
index 9af1e4d505c..f78dc399720 100644
--- a/Lib/test/test_importlib/test_threaded_import.py
+++ b/Lib/test/test_importlib/test_threaded_import.py
@@ -135,10 +135,12 @@ class ThreadedImportTests(unittest.TestCase):
if verbose:
print("OK.")
- def test_parallel_module_init(self):
+ @support.bigmemtest(size=50, memuse=76*2**20, dry_run=False)
+ def test_parallel_module_init(self, size):
self.check_parallel_module_init()
- def test_parallel_meta_path(self):
+ @support.bigmemtest(size=50, memuse=76*2**20, dry_run=False)
+ def test_parallel_meta_path(self, size):
finder = Finder()
sys.meta_path.insert(0, finder)
try:
@@ -148,7 +150,8 @@ class ThreadedImportTests(unittest.TestCase):
finally:
sys.meta_path.remove(finder)
- def test_parallel_path_hooks(self):
+ @support.bigmemtest(size=50, memuse=76*2**20, dry_run=False)
+ def test_parallel_path_hooks(self, size):
# Here the Finder instance is only used to check concurrent calls
# to path_hook().
finder = Finder()
@@ -242,13 +245,15 @@ class ThreadedImportTests(unittest.TestCase):
__import__(TESTFN)
del sys.modules[TESTFN]
- def test_concurrent_futures_circular_import(self):
+ @support.bigmemtest(size=1, memuse=1.8*2**30, dry_run=False)
+ def test_concurrent_futures_circular_import(self, size):
# Regression test for bpo-43515
fn = os.path.join(os.path.dirname(__file__),
'partial', 'cfimport.py')
script_helper.assert_python_ok(fn)
- def test_multiprocessing_pool_circular_import(self):
+ @support.bigmemtest(size=1, memuse=1.8*2**30, dry_run=False)
+ def test_multiprocessing_pool_circular_import(self, size):
# Regression test for bpo-41567
fn = os.path.join(os.path.dirname(__file__),
'partial', 'pool_in_threads.py')
diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py
index 4950af42cfe..79eb103224b 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -786,12 +786,12 @@ class TestRetrievingSourceCode(GetSourceBase):
def test_getfile_builtin_module(self):
with self.assertRaises(TypeError) as e:
inspect.getfile(sys)
- self.assertTrue(str(e.exception).startswith('<module'))
+ self.assertStartsWith(str(e.exception), '<module')
def test_getfile_builtin_class(self):
with self.assertRaises(TypeError) as e:
inspect.getfile(int)
- self.assertTrue(str(e.exception).startswith('<class'))
+ self.assertStartsWith(str(e.exception), '<class')
def test_getfile_builtin_function_or_method(self):
with self.assertRaises(TypeError) as e_abs:
@@ -2949,7 +2949,7 @@ class TestSignatureObject(unittest.TestCase):
pass
sig = inspect.signature(test)
- self.assertTrue(repr(sig).startswith('<Signature'))
+ self.assertStartsWith(repr(sig), '<Signature')
self.assertTrue('(po, /, pk' in repr(sig))
# We need two functions, because it is impossible to represent
@@ -2958,7 +2958,7 @@ class TestSignatureObject(unittest.TestCase):
pass
sig2 = inspect.signature(test2)
- self.assertTrue(repr(sig2).startswith('<Signature'))
+ self.assertStartsWith(repr(sig2), '<Signature')
self.assertTrue('(pod=42, /)' in repr(sig2))
po = sig.parameters['po']
@@ -4997,6 +4997,37 @@ class TestSignatureObject(unittest.TestCase):
with self.assertRaisesRegex(NameError, "undefined"):
signature_func(ida.f)
+ def test_signature_deferred_annotations(self):
+ def f(x: undef):
+ pass
+
+ class C:
+ x: undef
+
+ def __init__(self, x: undef):
+ self.x = x
+
+ sig = inspect.signature(f, annotation_format=Format.FORWARDREF)
+ self.assertEqual(list(sig.parameters), ['x'])
+ sig = inspect.signature(C, annotation_format=Format.FORWARDREF)
+ self.assertEqual(list(sig.parameters), ['x'])
+
+ class CallableWrapper:
+ def __init__(self, func):
+ self.func = func
+ self.__annotate__ = func.__annotate__
+
+ def __call__(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ @property
+ def __annotations__(self):
+ return self.__annotate__(Format.VALUE)
+
+ cw = CallableWrapper(f)
+ sig = inspect.signature(cw, annotation_format=Format.FORWARDREF)
+ self.assertEqual(list(sig.parameters), ['args', 'kwargs'])
+
def test_signature_none_annotation(self):
class funclike:
# Has to be callable, and have correct
@@ -5102,7 +5133,7 @@ class TestParameterObject(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'cannot have default values'):
p.replace(kind=inspect.Parameter.VAR_POSITIONAL)
- self.assertTrue(repr(p).startswith('<Parameter'))
+ self.assertStartsWith(repr(p), '<Parameter')
self.assertTrue('"a=42"' in repr(p))
def test_signature_parameter_hashable(self):
@@ -5846,7 +5877,7 @@ class TestSignatureDefinitions(unittest.TestCase):
def test_os_module_has_signatures(self):
unsupported_signature = {'chmod', 'utime'}
unsupported_signature |= {name for name in
- ['get_terminal_size', 'posix_spawn', 'posix_spawnp',
+ ['get_terminal_size', 'link', 'posix_spawn', 'posix_spawnp',
'register_at_fork', 'startfile']
if hasattr(os, name)}
self._test_module_has_signatures(os, unsupported_signature=unsupported_signature)
@@ -6146,12 +6177,14 @@ class TestRepl(unittest.TestCase):
object.
"""
+ # TODO(picnixz): refactor this as it's used by test_repl.py
+
# To run the REPL without using a terminal, spawn python with the command
# line option '-i' and the process name set to '<stdin>'.
# The directory of argv[0] must match the directory of the Python
# executable for the Popen() call to python to succeed as the directory
- # path may be used by Py_GetPath() to build the default module search
- # path.
+ # path may be used by PyConfig_Get("module_search_paths") to build the
+ # default module search path.
stdin_fname = os.path.join(os.path.dirname(sys.executable), "<stdin>")
cmd_line = [stdin_fname, '-E', '-i']
cmd_line.extend(args)
diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py
index 245528ce57a..7a7cb73f673 100644
--- a/Lib/test/test_int.py
+++ b/Lib/test/test_int.py
@@ -836,7 +836,7 @@ class PyLongModuleTests(unittest.TestCase):
n = hibit | getrandbits(bits - 1)
assert n.bit_length() == bits
sn = str(n)
- self.assertFalse(sn.startswith('0'))
+ self.assertNotStartsWith(sn, '0')
self.assertEqual(n, int(sn))
bits <<= 1
diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py
index 66c7afce88f..0ee4582b5d1 100644
--- a/Lib/test/test_interpreters/test_api.py
+++ b/Lib/test/test_interpreters/test_api.py
@@ -1,18 +1,23 @@
+import contextlib
import os
import pickle
+import sys
from textwrap import dedent
import threading
import types
import unittest
from test import support
+from test.support import os_helper
+from test.support import script_helper
from test.support import import_helper
# Raise SkipTest if subinterpreters not supported.
_interpreters = import_helper.import_module('_interpreters')
+from concurrent import interpreters
from test.support import Py_GIL_DISABLED
-from test.support import interpreters
from test.support import force_not_colorized
-from test.support.interpreters import (
+import test._crossinterp_definitions as defs
+from concurrent.interpreters import (
InterpreterError, InterpreterNotFoundError, ExecutionFailed,
)
from .utils import (
@@ -29,6 +34,59 @@ WHENCE_STR_XI = 'cross-interpreter C-API'
WHENCE_STR_STDLIB = '_interpreters module'
+def is_pickleable(obj):
+ try:
+ pickle.dumps(obj)
+ except Exception:
+ return False
+ return True
+
+
+@contextlib.contextmanager
+def defined_in___main__(name, script, *, remove=False):
+ import __main__ as mainmod
+ mainns = vars(mainmod)
+ assert name not in mainns
+ exec(script, mainns, mainns)
+ if remove:
+ yield mainns.pop(name)
+ else:
+ try:
+ yield mainns[name]
+ finally:
+ mainns.pop(name, None)
+
+
+def build_excinfo(exctype, msg=None, formatted=None, errdisplay=None):
+ if isinstance(exctype, type):
+ assert issubclass(exctype, BaseException), exctype
+ exctype = types.SimpleNamespace(
+ __name__=exctype.__name__,
+ __qualname__=exctype.__qualname__,
+ __module__=exctype.__module__,
+ )
+ elif isinstance(exctype, str):
+ module, _, name = exctype.rpartition(exctype)
+ if not module and name in __builtins__:
+ module = 'builtins'
+ exctype = types.SimpleNamespace(
+ __name__=name,
+ __qualname__=exctype,
+ __module__=module or None,
+ )
+ else:
+ assert isinstance(exctype, types.SimpleNamespace)
+ assert msg is None or isinstance(msg, str), msg
+ assert formatted is None or isinstance(formatted, str), formatted
+ assert errdisplay is None or isinstance(errdisplay, str), errdisplay
+ return types.SimpleNamespace(
+ type=exctype,
+ msg=msg,
+ formatted=formatted,
+ errdisplay=errdisplay,
+ )
+
+
class ModuleTests(TestBase):
def test_queue_aliases(self):
@@ -75,7 +133,7 @@ class CreateTests(TestBase):
main, = interpreters.list_all()
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import interpreters
+ from concurrent import interpreters
interp = interpreters.create()
print(interp.id)
"""))
@@ -138,7 +196,7 @@ class GetCurrentTests(TestBase):
main = interpreters.get_main()
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import interpreters
+ from concurrent import interpreters
cur = interpreters.get_current()
print(cur.id)
"""))
@@ -155,7 +213,7 @@ class GetCurrentTests(TestBase):
with self.subTest('subinterpreter'):
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import interpreters
+ from concurrent import interpreters
cur = interpreters.get_current()
print(id(cur))
cur = interpreters.get_current()
@@ -167,7 +225,7 @@ class GetCurrentTests(TestBase):
with self.subTest('per-interpreter'):
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import interpreters
+ from concurrent import interpreters
cur = interpreters.get_current()
print(id(cur))
"""))
@@ -524,7 +582,7 @@ class TestInterpreterClose(TestBase):
main, = interpreters.list_all()
interp = interpreters.create()
out = _run_output(interp, dedent(f"""
- from test.support import interpreters
+ from concurrent import interpreters
interp = interpreters.Interpreter({interp.id})
try:
interp.close()
@@ -541,7 +599,7 @@ class TestInterpreterClose(TestBase):
self.assertEqual(set(interpreters.list_all()),
{main, interp1, interp2})
interp1.exec(dedent(f"""
- from test.support import interpreters
+ from concurrent import interpreters
interp2 = interpreters.Interpreter({interp2.id})
interp2.close()
interp3 = interpreters.create()
@@ -748,7 +806,7 @@ class TestInterpreterExec(TestBase):
ham()
""")
scriptfile = self.make_script('script.py', tempdir, text="""
- from test.support import interpreters
+ from concurrent import interpreters
def script():
import spam
@@ -769,7 +827,7 @@ class TestInterpreterExec(TestBase):
~~~~~~~~~~~^^^^^^^^
{interpmod_line.strip()}
raise ExecutionFailed(excinfo)
- test.support.interpreters.ExecutionFailed: RuntimeError: uh-oh!
+ concurrent.interpreters.ExecutionFailed: RuntimeError: uh-oh!
Uncaught in the interpreter:
@@ -839,9 +897,16 @@ class TestInterpreterExec(TestBase):
interp.exec(10)
def test_bytes_for_script(self):
+ r, w = self.pipe()
+ RAN = b'R'
+ DONE = b'D'
interp = interpreters.create()
- with self.assertRaises(TypeError):
- interp.exec(b'print("spam")')
+ interp.exec(f"""if True:
+ import os
+ os.write({w}, {RAN!r})
+ """)
+ os.write(w, DONE)
+ self.assertEqual(os.read(r, 1), RAN)
def test_with_background_threads_still_running(self):
r_interp, w_interp = self.pipe()
@@ -879,28 +944,46 @@ class TestInterpreterExec(TestBase):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
interp.exec('raise Exception("it worked!")')
+ def test_list_comprehension(self):
+ # gh-135450: List comprehensions caused an assertion failure
+ # in _PyCode_CheckNoExternalState()
+ import string
+ r_interp, w_interp = self.pipe()
+
+ interp = interpreters.create()
+ interp.exec(f"""if True:
+ import os
+ comp = [str(i) for i in range(10)]
+ os.write({w_interp}, ''.join(comp).encode())
+ """)
+ self.assertEqual(os.read(r_interp, 10).decode(), string.digits)
+ interp.close()
+
+
# test__interpreters covers the remaining
# Interpreter.exec() behavior.
-def call_func_noop():
- pass
+call_func_noop = defs.spam_minimal
+call_func_ident = defs.spam_returns_arg
+call_func_failure = defs.spam_raises
def call_func_return_shareable():
return (1, None)
-def call_func_return_not_shareable():
- return [1, 2, 3]
+def call_func_return_stateless_func():
+ return (lambda x: x)
-def call_func_failure():
- raise Exception('spam!')
+def call_func_return_pickleable():
+ return [1, 2, 3]
-def call_func_ident(value):
- return value
+def call_func_return_unpickleable():
+ x = 42
+ return (lambda: x)
def get_call_func_closure(value):
@@ -909,6 +992,11 @@ def get_call_func_closure(value):
return call_func_closure
+def call_func_exec_wrapper(script, ns):
+ res = exec(script, ns, ns)
+ return res, ns, id(ns)
+
+
class Spam:
@staticmethod
@@ -1005,86 +1093,663 @@ class TestInterpreterCall(TestBase):
# - preserves info (e.g. SyntaxError)
# - matching error display
- def test_call(self):
+ @contextlib.contextmanager
+ def assert_fails(self, expected):
+ with self.assertRaises(ExecutionFailed) as cm:
+ yield cm
+ uncaught = cm.exception.excinfo
+ self.assertEqual(uncaught.type.__name__, expected.__name__)
+
+ def assert_fails_not_shareable(self):
+ return self.assert_fails(interpreters.NotShareableError)
+
+ def assert_code_equal(self, code1, code2):
+ if code1 == code2:
+ return
+ self.assertEqual(code1.co_name, code2.co_name)
+ self.assertEqual(code1.co_flags, code2.co_flags)
+ self.assertEqual(code1.co_consts, code2.co_consts)
+ self.assertEqual(code1.co_varnames, code2.co_varnames)
+ self.assertEqual(code1.co_cellvars, code2.co_cellvars)
+ self.assertEqual(code1.co_freevars, code2.co_freevars)
+ self.assertEqual(code1.co_names, code2.co_names)
+ self.assertEqual(
+ _testinternalcapi.get_code_var_counts(code1),
+ _testinternalcapi.get_code_var_counts(code2),
+ )
+ self.assertEqual(code1.co_code, code2.co_code)
+
+ def assert_funcs_equal(self, func1, func2):
+ if func1 == func2:
+ return
+ self.assertIs(type(func1), type(func2))
+ self.assertEqual(func1.__name__, func2.__name__)
+ self.assertEqual(func1.__defaults__, func2.__defaults__)
+ self.assertEqual(func1.__kwdefaults__, func2.__kwdefaults__)
+ self.assertEqual(func1.__closure__, func2.__closure__)
+ self.assert_code_equal(func1.__code__, func2.__code__)
+ self.assertEqual(
+ _testinternalcapi.get_code_var_counts(func1),
+ _testinternalcapi.get_code_var_counts(func2),
+ )
+
+ def assert_exceptions_equal(self, exc1, exc2):
+ assert isinstance(exc1, Exception)
+ assert isinstance(exc2, Exception)
+ if exc1 == exc2:
+ return
+ self.assertIs(type(exc1), type(exc2))
+ self.assertEqual(exc1.args, exc2.args)
+
+ def test_stateless_funcs(self):
interp = interpreters.create()
- for i, (callable, args, kwargs) in enumerate([
- (call_func_noop, (), {}),
- (call_func_return_shareable, (), {}),
- (call_func_return_not_shareable, (), {}),
- (Spam.noop, (), {}),
+ func = call_func_noop
+ with self.subTest('no args, no return'):
+ res = interp.call(func)
+ self.assertIsNone(res)
+
+ func = call_func_return_shareable
+ with self.subTest('no args, returns shareable'):
+ res = interp.call(func)
+ self.assertEqual(res, (1, None))
+
+ func = call_func_return_stateless_func
+ expected = (lambda x: x)
+ with self.subTest('no args, returns stateless func'):
+ res = interp.call(func)
+ self.assert_funcs_equal(res, expected)
+
+ func = call_func_return_pickleable
+ with self.subTest('no args, returns pickleable'):
+ res = interp.call(func)
+ self.assertEqual(res, [1, 2, 3])
+
+ func = call_func_return_unpickleable
+ with self.subTest('no args, returns unpickleable'):
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(func)
+
+ def test_stateless_func_returns_arg(self):
+ interp = interpreters.create()
+
+ for arg in [
+ None,
+ 10,
+ 'spam!',
+ b'spam!',
+ (1, 2, 'spam!'),
+ memoryview(b'spam!'),
+ ]:
+ with self.subTest(f'shareable {arg!r}'):
+ assert _interpreters.is_shareable(arg)
+ res = interp.call(defs.spam_returns_arg, arg)
+ self.assertEqual(res, arg)
+
+ for arg in defs.STATELESS_FUNCTIONS:
+ with self.subTest(f'stateless func {arg!r}'):
+ res = interp.call(defs.spam_returns_arg, arg)
+ self.assert_funcs_equal(res, arg)
+
+ for arg in defs.TOP_FUNCTIONS:
+ if arg in defs.STATELESS_FUNCTIONS:
+ continue
+ with self.subTest(f'stateful func {arg!r}'):
+ res = interp.call(defs.spam_returns_arg, arg)
+ self.assert_funcs_equal(res, arg)
+ assert is_pickleable(arg)
+
+ for arg in [
+ Ellipsis,
+ NotImplemented,
+ object(),
+ 2**1000,
+ [1, 2, 3],
+ {'a': 1, 'b': 2},
+ types.SimpleNamespace(x=42),
+ # builtin types
+ object,
+ type,
+ Exception,
+ ModuleNotFoundError,
+ # builtin exceptions
+ Exception('uh-oh!'),
+ ModuleNotFoundError('mymodule'),
+ # builtin fnctions
+ len,
+ sys.exit,
+ # user classes
+ *defs.TOP_CLASSES,
+ *(c(*a) for c, a in defs.TOP_CLASSES.items()
+ if c not in defs.CLASSES_WITHOUT_EQUALITY),
+ ]:
+ with self.subTest(f'pickleable {arg!r}'):
+ res = interp.call(defs.spam_returns_arg, arg)
+ if type(arg) is object:
+ self.assertIs(type(res), object)
+ elif isinstance(arg, BaseException):
+ self.assert_exceptions_equal(res, arg)
+ else:
+ self.assertEqual(res, arg)
+ assert is_pickleable(arg)
+
+ for arg in [
+ types.MappingProxyType({}),
+ *(f for f in defs.NESTED_FUNCTIONS
+ if f not in defs.STATELESS_FUNCTIONS),
+ ]:
+ with self.subTest(f'unpickleable {arg!r}'):
+ assert not _interpreters.is_shareable(arg)
+ assert not is_pickleable(arg)
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(defs.spam_returns_arg, arg)
+
+ def test_full_args(self):
+ interp = interpreters.create()
+ expected = (1, 2, 3, 4, 5, 6, ('?',), {'g': 7, 'h': 8})
+ func = defs.spam_full_args
+ res = interp.call(func, 1, 2, 3, 4, '?', e=5, f=6, g=7, h=8)
+ self.assertEqual(res, expected)
+
+ def test_full_defaults(self):
+ # pickleable, but not stateless
+ interp = interpreters.create()
+ expected = (-1, -2, -3, -4, -5, -6, (), {'g': 8, 'h': 9})
+ res = interp.call(defs.spam_full_args_with_defaults, g=8, h=9)
+ self.assertEqual(res, expected)
+
+ def test_modified_arg(self):
+ interp = interpreters.create()
+ script = dedent("""
+ a = 7
+ b = 2
+ c = a ** b
+ """)
+ ns = {}
+ expected = {'a': 7, 'b': 2, 'c': 49}
+ res = interp.call(call_func_exec_wrapper, script, ns)
+ obj, resns, resid = res
+ del resns['__builtins__']
+ self.assertIsNone(obj)
+ self.assertEqual(ns, {})
+ self.assertEqual(resns, expected)
+ self.assertNotEqual(resid, id(ns))
+ self.assertNotEqual(resid, id(resns))
+
+ def test_func_in___main___valid(self):
+ # pickleable, already there'
+
+ with os_helper.temp_dir() as tempdir:
+ def new_mod(name, text):
+ script_helper.make_script(tempdir, name, dedent(text))
+
+ def run(text):
+ name = 'myscript'
+ text = dedent(f"""
+ import sys
+ sys.path.insert(0, {tempdir!r})
+
+ """) + dedent(text)
+ filename = script_helper.make_script(tempdir, name, text)
+ res = script_helper.assert_python_ok(filename)
+ return res.out.decode('utf-8').strip()
+
+ # no module indirection
+ with self.subTest('no indirection'):
+ text = run(f"""
+ from concurrent import interpreters
+
+ def spam():
+ # This a global var...
+ return __name__
+
+ if __name__ == '__main__':
+ interp = interpreters.create()
+ res = interp.call(spam)
+ print(res)
+ """)
+ self.assertEqual(text, '<fake __main__>')
+
+ # indirect as func, direct interp
+ new_mod('mymod', f"""
+ def run(interp, func):
+ return interp.call(func)
+ """)
+ with self.subTest('indirect as func, direct interp'):
+ text = run(f"""
+ from concurrent import interpreters
+ import mymod
+
+ def spam():
+ # This a global var...
+ return __name__
+
+ if __name__ == '__main__':
+ interp = interpreters.create()
+ res = mymod.run(interp, spam)
+ print(res)
+ """)
+ self.assertEqual(text, '<fake __main__>')
+
+ # indirect as func, indirect interp
+ new_mod('mymod', f"""
+ from concurrent import interpreters
+ def run(func):
+ interp = interpreters.create()
+ return interp.call(func)
+ """)
+ with self.subTest('indirect as func, indirect interp'):
+ text = run(f"""
+ import mymod
+
+ def spam():
+ # This a global var...
+ return __name__
+
+ if __name__ == '__main__':
+ res = mymod.run(spam)
+ print(res)
+ """)
+ self.assertEqual(text, '<fake __main__>')
+
+ def test_func_in___main___invalid(self):
+ interp = interpreters.create()
+
+ funcname = f'{__name__.replace(".", "_")}_spam_okay'
+ script = dedent(f"""
+ def {funcname}():
+ # This a global var...
+ return __name__
+ """)
+
+ with self.subTest('pickleable, added dynamically'):
+ with defined_in___main__(funcname, script) as arg:
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(defs.spam_returns_arg, arg)
+
+ with self.subTest('lying about __main__'):
+ with defined_in___main__(funcname, script, remove=True) as arg:
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(defs.spam_returns_arg, arg)
+
+ def test_func_in___main___hidden(self):
+ # When a top-level function that uses global variables is called
+ # through Interpreter.call(), it will be pickled, sent over,
+ # and unpickled. That requires that it be found in the other
+ # interpreter's __main__ module. However, the original script
+ # that defined the function is only run in the main interpreter,
+ # so pickle.loads() would normally fail.
+ #
+ # We work around this by running the script in the other
+ # interpreter. However, this is a one-off solution for the sake
+ # of unpickling, so we avoid modifying that interpreter's
+ # __main__ module by running the script in a hidden module.
+ #
+ # In this test we verify that the function runs with the hidden
+ # module as its __globals__ when called in the other interpreter,
+ # and that the interpreter's __main__ module is unaffected.
+ text = dedent("""
+ eggs = True
+
+ def spam(*, explicit=False):
+ if explicit:
+ import __main__
+ ns = __main__.__dict__
+ else:
+ # For now we have to have a LOAD_GLOBAL in the
+ # function in order for globals() to actually return
+ # spam.__globals__. Maybe it doesn't go through pickle?
+ # XXX We will fix this later.
+ spam
+ ns = globals()
+
+ func = ns.get('spam')
+ return [
+ id(ns),
+ ns.get('__name__'),
+ ns.get('__file__'),
+ id(func),
+ None if func is None else repr(func),
+ ns.get('eggs'),
+ ns.get('ham'),
+ ]
+
+ if __name__ == "__main__":
+ from concurrent import interpreters
+ interp = interpreters.create()
+
+ ham = True
+ print([
+ [
+ spam(explicit=True),
+ spam(),
+ ],
+ [
+ interp.call(spam, explicit=True),
+ interp.call(spam),
+ ],
+ ])
+ """)
+ with os_helper.temp_dir() as tempdir:
+ filename = script_helper.make_script(tempdir, 'my-script', text)
+ res = script_helper.assert_python_ok(filename)
+ stdout = res.out.decode('utf-8').strip()
+ local, remote = eval(stdout)
+
+ # In the main interpreter.
+ main, unpickled = local
+ nsid, _, _, funcid, func, _, _ = main
+ self.assertEqual(main, [
+ nsid,
+ '__main__',
+ filename,
+ funcid,
+ func,
+ True,
+ True,
+ ])
+ self.assertIsNot(func, None)
+ self.assertRegex(func, '^<function spam at 0x.*>$')
+ self.assertEqual(unpickled, main)
+
+ # In the subinterpreter.
+ main, unpickled = remote
+ nsid1, _, _, funcid1, _, _, _ = main
+ self.assertEqual(main, [
+ nsid1,
+ '__main__',
+ None,
+ funcid1,
+ None,
+ None,
+ None,
+ ])
+ nsid2, _, _, funcid2, func, _, _ = unpickled
+ self.assertEqual(unpickled, [
+ nsid2,
+ '<fake __main__>',
+ filename,
+ funcid2,
+ func,
+ True,
+ None,
+ ])
+ self.assertIsNot(func, None)
+ self.assertRegex(func, '^<function spam at 0x.*>$')
+ self.assertNotEqual(nsid2, nsid1)
+ self.assertNotEqual(funcid2, funcid1)
+
+ def test_func_in___main___uses_globals(self):
+ # See the note in test_func_in___main___hidden about pickle
+ # and the __main__ module.
+ #
+ # Additionally, the solution to that problem must provide
+ # for global variables on which a pickled function might rely.
+ #
+ # To check that, we run a script that has two global functions
+ # and a global variable in the __main__ module. One of the
+ # functions sets the global variable and the other returns
+ # the value.
+ #
+ # The script calls those functions multiple times in another
+ # interpreter, to verify the following:
+ #
+ # * the global variable is properly initialized
+ # * the global variable retains state between calls
+ # * the setter modifies that persistent variable
+ # * the getter uses the variable
+ # * the calls in the other interpreter do not modify
+ # the main interpreter
+ # * those calls don't modify the interpreter's __main__ module
+ # * the functions and variable do not actually show up in the
+ # other interpreter's __main__ module
+ text = dedent("""
+ count = 0
+
+ def inc(x=1):
+ global count
+ count += x
+
+ def get_count():
+ return count
+
+ if __name__ == "__main__":
+ counts = []
+ results = [count, counts]
+
+ from concurrent import interpreters
+ interp = interpreters.create()
+
+ val = interp.call(get_count)
+ counts.append(val)
+
+ interp.call(inc)
+ val = interp.call(get_count)
+ counts.append(val)
+
+ interp.call(inc, 3)
+ val = interp.call(get_count)
+ counts.append(val)
+
+ results.append(count)
+
+ modified = {name: interp.call(eval, f'{name!r} in vars()')
+ for name in ('count', 'inc', 'get_count')}
+ results.append(modified)
+
+ print(results)
+ """)
+ with os_helper.temp_dir() as tempdir:
+ filename = script_helper.make_script(tempdir, 'my-script', text)
+ res = script_helper.assert_python_ok(filename)
+ stdout = res.out.decode('utf-8').strip()
+ before, counts, after, modified = eval(stdout)
+ self.assertEqual(modified, {
+ 'count': False,
+ 'inc': False,
+ 'get_count': False,
+ })
+ self.assertEqual(before, 0)
+ self.assertEqual(after, 0)
+ self.assertEqual(counts, [0, 1, 4])
+
+ def test_raises(self):
+ interp = interpreters.create()
+ with self.assertRaises(ExecutionFailed):
+ interp.call(call_func_failure)
+
+ with self.assert_fails(ValueError):
+ interp.call(call_func_complex, '???', exc=ValueError('spam'))
+
+ def test_call_valid(self):
+ interp = interpreters.create()
+
+ for i, (callable, args, kwargs, expected) in enumerate([
+ (call_func_noop, (), {}, None),
+ (call_func_ident, ('spamspamspam',), {}, 'spamspamspam'),
+ (call_func_return_shareable, (), {}, (1, None)),
+ (call_func_return_pickleable, (), {}, [1, 2, 3]),
+ (Spam.noop, (), {}, None),
+ (Spam.from_values, (), {}, Spam(())),
+ (Spam.from_values, (1, 2, 3), {}, Spam((1, 2, 3))),
+ (Spam, ('???',), {}, Spam('???')),
+ (Spam(101), (), {}, (101, (), {})),
+ (Spam(10101).run, (), {}, (10101, (), {})),
+ (call_func_complex, ('ident', 'spam'), {}, 'spam'),
+ (call_func_complex, ('full-ident', 'spam'), {}, ('spam', (), {})),
+ (call_func_complex, ('full-ident', 'spam', 'ham'), {'eggs': '!!!'},
+ ('spam', ('ham',), {'eggs': '!!!'})),
+ (call_func_complex, ('globals',), {}, __name__),
+ (call_func_complex, ('interpid',), {}, interp.id),
+ (call_func_complex, ('custom', 'spam!'), {}, Spam('spam!')),
]):
with self.subTest(f'success case #{i+1}'):
- res = interp.call(callable)
- self.assertIs(res, None)
+ res = interp.call(callable, *args, **kwargs)
+ self.assertEqual(res, expected)
+
+ def test_call_invalid(self):
+ interp = interpreters.create()
+
+ func = get_call_func_closure
+ with self.subTest(func):
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(func, 42)
+
+ func = get_call_func_closure(42)
+ with self.subTest(func):
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(func)
+
+ func = call_func_complex
+ op = 'closure'
+ with self.subTest(f'{func} ({op})'):
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(func, op, value='~~~')
+
+ op = 'custom-inner'
+ with self.subTest(f'{func} ({op})'):
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(func, op, 'eggs!')
+
+ def test_callable_requires_frame(self):
+ # There are various functions that require a current frame.
+ interp = interpreters.create()
+ for call, expected in [
+ ((eval, '[1, 2, 3]'),
+ [1, 2, 3]),
+ ((eval, 'sum([1, 2, 3])'),
+ 6),
+ ((exec, '...'),
+ None),
+ ]:
+ with self.subTest(str(call)):
+ res = interp.call(*call)
+ self.assertEqual(res, expected)
+
+ result_not_pickleable = [
+ globals,
+ locals,
+ vars,
+ ]
+ for func, expectedtype in {
+ globals: dict,
+ locals: dict,
+ vars: dict,
+ dir: list,
+ }.items():
+ with self.subTest(str(func)):
+ if func in result_not_pickleable:
+ with self.assertRaises(interpreters.NotShareableError):
+ interp.call(func)
+ else:
+ res = interp.call(func)
+ self.assertIsInstance(res, expectedtype)
+ self.assertIn('__builtins__', res)
+
+ def test_globals_from_builtins(self):
+ # The builtins exec(), eval(), globals(), locals(), vars(),
+ # and dir() each runs relative to the target interpreter's
+ # __main__ module, when called directly. However,
+ # globals(), locals(), and vars() don't work when called
+ # directly so we don't check them.
+ from _frozen_importlib import BuiltinImporter
+ interp = interpreters.create()
+
+ names = interp.call(dir)
+ self.assertEqual(names, [
+ '__builtins__',
+ '__doc__',
+ '__loader__',
+ '__name__',
+ '__package__',
+ '__spec__',
+ ])
+
+ values = {name: interp.call(eval, name)
+ for name in names if name != '__builtins__'}
+ self.assertEqual(values, {
+ '__name__': '__main__',
+ '__doc__': None,
+ '__spec__': None, # It wasn't imported, so no module spec?
+ '__package__': None,
+ '__loader__': BuiltinImporter,
+ })
+ with self.assertRaises(ExecutionFailed):
+ interp.call(eval, 'spam'),
+
+ interp.call(exec, f'assert dir() == {names}')
+
+ # Update the interpreter's __main__.
+ interp.prepare_main(spam=42)
+ expected = names + ['spam']
+
+ names = interp.call(dir)
+ self.assertEqual(names, expected)
+
+ value = interp.call(eval, 'spam')
+ self.assertEqual(value, 42)
+
+ interp.call(exec, f'assert dir() == {expected}, dir()')
+
+ def test_globals_from_stateless_func(self):
+ # A stateless func, which doesn't depend on any globals,
+ # doesn't go through pickle, so it runs in __main__.
+ def set_global(name, value):
+ globals()[name] = value
+
+ def get_global(name):
+ return globals().get(name)
+
+ interp = interpreters.create()
+
+ modname = interp.call(get_global, '__name__')
+ self.assertEqual(modname, '__main__')
+
+ res = interp.call(get_global, 'spam')
+ self.assertIsNone(res)
+
+ interp.exec('spam = True')
+ res = interp.call(get_global, 'spam')
+ self.assertTrue(res)
+
+ interp.call(set_global, 'spam', 42)
+ res = interp.call(get_global, 'spam')
+ self.assertEqual(res, 42)
+
+ interp.exec('assert spam == 42, repr(spam)')
+
+ def test_call_in_thread(self):
+ interp = interpreters.create()
for i, (callable, args, kwargs) in enumerate([
- (call_func_ident, ('spamspamspam',), {}),
- (get_call_func_closure, (42,), {}),
- (get_call_func_closure(42), (), {}),
+ (call_func_noop, (), {}),
+ (call_func_return_shareable, (), {}),
+ (call_func_return_pickleable, (), {}),
(Spam.from_values, (), {}),
(Spam.from_values, (1, 2, 3), {}),
- (Spam, ('???'), {}),
(Spam(101), (), {}),
(Spam(10101).run, (), {}),
+ (Spam.noop, (), {}),
(call_func_complex, ('ident', 'spam'), {}),
(call_func_complex, ('full-ident', 'spam'), {}),
(call_func_complex, ('full-ident', 'spam', 'ham'), {'eggs': '!!!'}),
(call_func_complex, ('globals',), {}),
(call_func_complex, ('interpid',), {}),
- (call_func_complex, ('closure',), {'value': '~~~'}),
(call_func_complex, ('custom', 'spam!'), {}),
- (call_func_complex, ('custom-inner', 'eggs!'), {}),
- (call_func_complex, ('???',), {'exc': ValueError('spam')}),
- ]):
- with self.subTest(f'invalid case #{i+1}'):
- with self.assertRaises(Exception):
- if args or kwargs:
- raise Exception((args, kwargs))
- interp.call(callable)
-
- with self.assertRaises(ExecutionFailed):
- interp.call(call_func_failure)
-
- def test_call_in_thread(self):
- interp = interpreters.create()
-
- for i, (callable, args, kwargs) in enumerate([
- (call_func_noop, (), {}),
- (call_func_return_shareable, (), {}),
- (call_func_return_not_shareable, (), {}),
- (Spam.noop, (), {}),
]):
with self.subTest(f'success case #{i+1}'):
with self.captured_thread_exception() as ctx:
- t = interp.call_in_thread(callable)
+ t = interp.call_in_thread(callable, *args, **kwargs)
t.join()
self.assertIsNone(ctx.caught)
for i, (callable, args, kwargs) in enumerate([
- (call_func_ident, ('spamspamspam',), {}),
(get_call_func_closure, (42,), {}),
(get_call_func_closure(42), (), {}),
- (Spam.from_values, (), {}),
- (Spam.from_values, (1, 2, 3), {}),
- (Spam, ('???'), {}),
- (Spam(101), (), {}),
- (Spam(10101).run, (), {}),
- (call_func_complex, ('ident', 'spam'), {}),
- (call_func_complex, ('full-ident', 'spam'), {}),
- (call_func_complex, ('full-ident', 'spam', 'ham'), {'eggs': '!!!'}),
- (call_func_complex, ('globals',), {}),
- (call_func_complex, ('interpid',), {}),
- (call_func_complex, ('closure',), {'value': '~~~'}),
- (call_func_complex, ('custom', 'spam!'), {}),
- (call_func_complex, ('custom-inner', 'eggs!'), {}),
- (call_func_complex, ('???',), {'exc': ValueError('spam')}),
]):
with self.subTest(f'invalid case #{i+1}'):
- if args or kwargs:
- continue
with self.captured_thread_exception() as ctx:
- t = interp.call_in_thread(callable)
+ t = interp.call_in_thread(callable, *args, **kwargs)
t.join()
self.assertIsNotNone(ctx.caught)
@@ -1452,6 +2117,14 @@ class LowLevelTests(TestBase):
self.assertFalse(
self.interp_exists(interpid))
+ with self.subTest('basic C-API'):
+ interpid = _testinternalcapi.create_interpreter()
+ self.assertTrue(
+ self.interp_exists(interpid))
+ _testinternalcapi.destroy_interpreter(interpid, basic=True)
+ self.assertFalse(
+ self.interp_exists(interpid))
+
def test_get_config(self):
# This test overlaps with
# test.test_capi.test_misc.InterpreterConfigTests.
@@ -1585,18 +2258,14 @@ class LowLevelTests(TestBase):
with results:
exc = _interpreters.exec(interpid, script)
out = results.stdout()
- self.assertEqual(out, '')
- self.assert_ns_equal(exc, types.SimpleNamespace(
- type=types.SimpleNamespace(
- __name__='Exception',
- __qualname__='Exception',
- __module__='builtins',
- ),
- msg='uh-oh!',
+ expected = build_excinfo(
+ Exception, 'uh-oh!',
# We check these in other tests.
formatted=exc.formatted,
errdisplay=exc.errdisplay,
- ))
+ )
+ self.assertEqual(out, '')
+ self.assert_ns_equal(exc, expected)
with self.subTest('from C-API'):
with self.interpreter_from_capi() as interpid:
@@ -1608,25 +2277,50 @@ class LowLevelTests(TestBase):
self.assertEqual(exc.msg, 'it worked!')
def test_call(self):
- with self.subTest('no args'):
- interpid = _interpreters.create()
- exc = _interpreters.call(interpid, call_func_return_shareable)
- self.assertIs(exc, None)
+ interpid = _interpreters.create()
+
+ # Here we focus on basic args and return values.
+ # See TestInterpreterCall for full operational coverage,
+ # including supported callables.
+
+ with self.subTest('no args, return None'):
+ func = defs.spam_minimal
+ res, exc = _interpreters.call(interpid, func)
+ self.assertIsNone(exc)
+ self.assertIsNone(res)
+
+ with self.subTest('empty args, return None'):
+ func = defs.spam_minimal
+ res, exc = _interpreters.call(interpid, func, (), {})
+ self.assertIsNone(exc)
+ self.assertIsNone(res)
+
+ with self.subTest('no args, return non-None'):
+ func = defs.script_with_return
+ res, exc = _interpreters.call(interpid, func)
+ self.assertIsNone(exc)
+ self.assertIs(res, True)
+
+ with self.subTest('full args, return non-None'):
+ expected = (1, 2, 3, 4, 5, 6, (7, 8), {'g': 9, 'h': 0})
+ func = defs.spam_full_args
+ args = (1, 2, 3, 4, 7, 8)
+ kwargs = dict(e=5, f=6, g=9, h=0)
+ res, exc = _interpreters.call(interpid, func, args, kwargs)
+ self.assertIsNone(exc)
+ self.assertEqual(res, expected)
with self.subTest('uncaught exception'):
- interpid = _interpreters.create()
- exc = _interpreters.call(interpid, call_func_failure)
- self.assertEqual(exc, types.SimpleNamespace(
- type=types.SimpleNamespace(
- __name__='Exception',
- __qualname__='Exception',
- __module__='builtins',
- ),
- msg='spam!',
+ func = defs.spam_raises
+ res, exc = _interpreters.call(interpid, func)
+ expected = build_excinfo(
+ Exception, 'spam!',
# We check these in other tests.
formatted=exc.formatted,
errdisplay=exc.errdisplay,
- ))
+ )
+ self.assertIsNone(res)
+ self.assertEqual(exc, expected)
@requires_test_modules
def test_set___main___attrs(self):
diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py
index eada18f99d0..109ddf34453 100644
--- a/Lib/test/test_interpreters/test_channels.py
+++ b/Lib/test/test_interpreters/test_channels.py
@@ -8,8 +8,8 @@ import time
from test.support import import_helper
# Raise SkipTest if subinterpreters not supported.
_channels = import_helper.import_module('_interpchannels')
-from test.support import interpreters
-from test.support.interpreters import channels
+from concurrent import interpreters
+from test.support import channels
from .utils import _run_output, TestBase
@@ -171,7 +171,7 @@ class TestSendRecv(TestBase):
def test_send_recv_same_interpreter(self):
interp = interpreters.create()
interp.exec(dedent("""
- from test.support.interpreters import channels
+ from test.support import channels
r, s = channels.create()
orig = b'spam'
s.send_nowait(orig)
@@ -244,7 +244,7 @@ class TestSendRecv(TestBase):
def test_send_recv_nowait_same_interpreter(self):
interp = interpreters.create()
interp.exec(dedent("""
- from test.support.interpreters import channels
+ from test.support import channels
r, s = channels.create()
orig = b'spam'
s.send_nowait(orig)
@@ -377,17 +377,17 @@ class TestSendRecv(TestBase):
if not unbound:
extraargs = ''
elif unbound is channels.UNBOUND:
- extraargs = ', unbound=channels.UNBOUND'
+ extraargs = ', unbounditems=channels.UNBOUND'
elif unbound is channels.UNBOUND_ERROR:
- extraargs = ', unbound=channels.UNBOUND_ERROR'
+ extraargs = ', unbounditems=channels.UNBOUND_ERROR'
elif unbound is channels.UNBOUND_REMOVE:
- extraargs = ', unbound=channels.UNBOUND_REMOVE'
+ extraargs = ', unbounditems=channels.UNBOUND_REMOVE'
else:
raise NotImplementedError(repr(unbound))
interp = interpreters.create()
_run_output(interp, dedent(f"""
- from test.support.interpreters import channels
+ from test.support import channels
sch = channels.SendChannel({sch.id})
obj1 = b'spam'
obj2 = b'eggs'
@@ -454,11 +454,11 @@ class TestSendRecv(TestBase):
with self.assertRaises(channels.ChannelEmptyError):
rch.recv_nowait()
- sch.send_nowait(b'ham', unbound=channels.UNBOUND_REMOVE)
+ sch.send_nowait(b'ham', unbounditems=channels.UNBOUND_REMOVE)
self.assertEqual(_channels.get_count(rch.id), 1)
interp = common(rch, sch, channels.UNBOUND_REMOVE, 1)
self.assertEqual(_channels.get_count(rch.id), 3)
- sch.send_nowait(42, unbound=channels.UNBOUND_REMOVE)
+ sch.send_nowait(42, unbounditems=channels.UNBOUND_REMOVE)
self.assertEqual(_channels.get_count(rch.id), 4)
del interp
self.assertEqual(_channels.get_count(rch.id), 2)
@@ -482,13 +482,13 @@ class TestSendRecv(TestBase):
self.assertEqual(_channels.get_count(rch.id), 0)
_run_output(interp, dedent(f"""
- from test.support.interpreters import channels
+ from test.support import channels
sch = channels.SendChannel({sch.id})
- sch.send_nowait(1, unbound=channels.UNBOUND)
- sch.send_nowait(2, unbound=channels.UNBOUND_ERROR)
+ sch.send_nowait(1, unbounditems=channels.UNBOUND)
+ sch.send_nowait(2, unbounditems=channels.UNBOUND_ERROR)
sch.send_nowait(3)
- sch.send_nowait(4, unbound=channels.UNBOUND_REMOVE)
- sch.send_nowait(5, unbound=channels.UNBOUND)
+ sch.send_nowait(4, unbounditems=channels.UNBOUND_REMOVE)
+ sch.send_nowait(5, unbounditems=channels.UNBOUND)
"""))
self.assertEqual(_channels.get_count(rch.id), 5)
@@ -518,15 +518,15 @@ class TestSendRecv(TestBase):
sch.send_nowait(1)
_run_output(interp1, dedent(f"""
- from test.support.interpreters import channels
+ from test.support import channels
rch = channels.RecvChannel({rch.id})
sch = channels.SendChannel({sch.id})
obj1 = rch.recv()
- sch.send_nowait(2, unbound=channels.UNBOUND)
- sch.send_nowait(obj1, unbound=channels.UNBOUND_REMOVE)
+ sch.send_nowait(2, unbounditems=channels.UNBOUND)
+ sch.send_nowait(obj1, unbounditems=channels.UNBOUND_REMOVE)
"""))
_run_output(interp2, dedent(f"""
- from test.support.interpreters import channels
+ from test.support import channels
rch = channels.RecvChannel({rch.id})
sch = channels.SendChannel({sch.id})
obj2 = rch.recv()
@@ -535,21 +535,21 @@ class TestSendRecv(TestBase):
self.assertEqual(_channels.get_count(rch.id), 0)
sch.send_nowait(3)
_run_output(interp1, dedent("""
- sch.send_nowait(4, unbound=channels.UNBOUND)
+ sch.send_nowait(4, unbounditems=channels.UNBOUND)
# interp closed here
- sch.send_nowait(5, unbound=channels.UNBOUND_REMOVE)
- sch.send_nowait(6, unbound=channels.UNBOUND)
+ sch.send_nowait(5, unbounditems=channels.UNBOUND_REMOVE)
+ sch.send_nowait(6, unbounditems=channels.UNBOUND)
"""))
_run_output(interp2, dedent("""
- sch.send_nowait(7, unbound=channels.UNBOUND_ERROR)
+ sch.send_nowait(7, unbounditems=channels.UNBOUND_ERROR)
# interp closed here
- sch.send_nowait(obj1, unbound=channels.UNBOUND_ERROR)
- sch.send_nowait(obj2, unbound=channels.UNBOUND_REMOVE)
- sch.send_nowait(8, unbound=channels.UNBOUND)
+ sch.send_nowait(obj1, unbounditems=channels.UNBOUND_ERROR)
+ sch.send_nowait(obj2, unbounditems=channels.UNBOUND_REMOVE)
+ sch.send_nowait(8, unbounditems=channels.UNBOUND)
"""))
_run_output(interp1, dedent("""
- sch.send_nowait(9, unbound=channels.UNBOUND_REMOVE)
- sch.send_nowait(10, unbound=channels.UNBOUND)
+ sch.send_nowait(9, unbounditems=channels.UNBOUND_REMOVE)
+ sch.send_nowait(10, unbounditems=channels.UNBOUND)
"""))
self.assertEqual(_channels.get_count(rch.id), 10)
diff --git a/Lib/test/test_interpreters/test_lifecycle.py b/Lib/test/test_interpreters/test_lifecycle.py
index ac24f6568ac..15537ac6cc8 100644
--- a/Lib/test/test_interpreters/test_lifecycle.py
+++ b/Lib/test/test_interpreters/test_lifecycle.py
@@ -119,7 +119,7 @@ class StartupTests(TestBase):
# The main interpreter's sys.path[0] should be used by subinterpreters.
script = '''
import sys
- from test.support import interpreters
+ from concurrent import interpreters
orig = sys.path[0]
@@ -170,7 +170,7 @@ class FinalizationTests(TestBase):
# is reported, even when subinterpreters get cleaned up at the end.
import subprocess
argv = [sys.executable, '-c', '''if True:
- from test.support import interpreters
+ from concurrent import interpreters
interp = interpreters.create()
raise Exception
''']
diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py
index 18f83d097eb..cb17340f581 100644
--- a/Lib/test/test_interpreters/test_queues.py
+++ b/Lib/test/test_interpreters/test_queues.py
@@ -7,8 +7,8 @@ import unittest
from test.support import import_helper, Py_DEBUG
# Raise SkipTest if subinterpreters not supported.
_queues = import_helper.import_module('_interpqueues')
-from test.support import interpreters
-from test.support.interpreters import queues, _crossinterp
+from concurrent import interpreters
+from concurrent.interpreters import _queues as queues, _crossinterp
from .utils import _run_output, TestBase as _TestBase
@@ -42,7 +42,7 @@ class LowLevelTests(TestBase):
importlib.reload(queues)
def test_create_destroy(self):
- qid = _queues.create(2, 0, REPLACE)
+ qid = _queues.create(2, REPLACE, -1)
_queues.destroy(qid)
self.assertEqual(get_num_queues(), 0)
with self.assertRaises(queues.QueueNotFoundError):
@@ -56,7 +56,7 @@ class LowLevelTests(TestBase):
'-c',
dedent(f"""
import {_queues.__name__} as _queues
- _queues.create(2, 0, {REPLACE})
+ _queues.create(2, {REPLACE}, -1)
"""),
)
self.assertEqual(stdout, '')
@@ -67,13 +67,13 @@ class LowLevelTests(TestBase):
def test_bind_release(self):
with self.subTest('typical'):
- qid = _queues.create(2, 0, REPLACE)
+ qid = _queues.create(2, REPLACE, -1)
_queues.bind(qid)
_queues.release(qid)
self.assertEqual(get_num_queues(), 0)
with self.subTest('bind too much'):
- qid = _queues.create(2, 0, REPLACE)
+ qid = _queues.create(2, REPLACE, -1)
_queues.bind(qid)
_queues.bind(qid)
_queues.release(qid)
@@ -81,7 +81,7 @@ class LowLevelTests(TestBase):
self.assertEqual(get_num_queues(), 0)
with self.subTest('nested'):
- qid = _queues.create(2, 0, REPLACE)
+ qid = _queues.create(2, REPLACE, -1)
_queues.bind(qid)
_queues.bind(qid)
_queues.release(qid)
@@ -89,7 +89,7 @@ class LowLevelTests(TestBase):
self.assertEqual(get_num_queues(), 0)
with self.subTest('release without binding'):
- qid = _queues.create(2, 0, REPLACE)
+ qid = _queues.create(2, REPLACE, -1)
with self.assertRaises(queues.QueueError):
_queues.release(qid)
@@ -126,19 +126,19 @@ class QueueTests(TestBase):
interp = interpreters.create()
interp.exec(dedent(f"""
- from test.support.interpreters import queues
+ from concurrent.interpreters import _queues as queues
queue1 = queues.Queue({queue1.id})
"""));
with self.subTest('same interpreter'):
queue2 = queues.create()
- queue1.put(queue2, syncobj=True)
+ queue1.put(queue2)
queue3 = queue1.get()
self.assertIs(queue3, queue2)
with self.subTest('from current interpreter'):
queue4 = queues.create()
- queue1.put(queue4, syncobj=True)
+ queue1.put(queue4)
out = _run_output(interp, dedent("""
queue4 = queue1.get()
print(queue4.id)
@@ -149,7 +149,7 @@ class QueueTests(TestBase):
with self.subTest('from subinterpreter'):
out = _run_output(interp, dedent("""
queue5 = queues.create()
- queue1.put(queue5, syncobj=True)
+ queue1.put(queue5)
print(queue5.id)
"""))
qid = int(out)
@@ -198,7 +198,7 @@ class TestQueueOps(TestBase):
def test_empty(self):
queue = queues.create()
before = queue.empty()
- queue.put(None, syncobj=True)
+ queue.put(None)
during = queue.empty()
queue.get()
after = queue.empty()
@@ -208,18 +208,64 @@ class TestQueueOps(TestBase):
self.assertIs(after, True)
def test_full(self):
- expected = [False, False, False, True, False, False, False]
- actual = []
- queue = queues.create(3)
- for _ in range(3):
- actual.append(queue.full())
- queue.put(None, syncobj=True)
- actual.append(queue.full())
- for _ in range(3):
- queue.get()
- actual.append(queue.full())
+ for maxsize in [1, 3, 11]:
+ with self.subTest(f'maxsize={maxsize}'):
+ num_to_add = maxsize
+ expected = [False] * (num_to_add * 2 + 3)
+ expected[maxsize] = True
+ expected[maxsize + 1] = True
+
+ queue = queues.create(maxsize)
+ actual = []
+ empty = [queue.empty()]
+
+ for _ in range(num_to_add):
+ actual.append(queue.full())
+ queue.put_nowait(None)
+ actual.append(queue.full())
+ with self.assertRaises(queues.QueueFull):
+ queue.put_nowait(None)
+ empty.append(queue.empty())
+
+ for _ in range(num_to_add):
+ actual.append(queue.full())
+ queue.get_nowait()
+ actual.append(queue.full())
+ with self.assertRaises(queues.QueueEmpty):
+ queue.get_nowait()
+ actual.append(queue.full())
+ empty.append(queue.empty())
- self.assertEqual(actual, expected)
+ self.assertEqual(actual, expected)
+ self.assertEqual(empty, [True, False, True])
+
+ # no max size
+ for args in [(), (0,), (-1,), (-10,)]:
+ with self.subTest(f'maxsize={args[0]}' if args else '<default>'):
+ num_to_add = 13
+ expected = [False] * (num_to_add * 2 + 3)
+
+ queue = queues.create(*args)
+ actual = []
+ empty = [queue.empty()]
+
+ for _ in range(num_to_add):
+ actual.append(queue.full())
+ queue.put_nowait(None)
+ actual.append(queue.full())
+ empty.append(queue.empty())
+
+ for _ in range(num_to_add):
+ actual.append(queue.full())
+ queue.get_nowait()
+ actual.append(queue.full())
+ with self.assertRaises(queues.QueueEmpty):
+ queue.get_nowait()
+ actual.append(queue.full())
+ empty.append(queue.empty())
+
+ self.assertEqual(actual, expected)
+ self.assertEqual(empty, [True, False, True])
def test_qsize(self):
expected = [0, 1, 2, 3, 2, 3, 2, 1, 0, 1, 0]
@@ -227,16 +273,16 @@ class TestQueueOps(TestBase):
queue = queues.create()
for _ in range(3):
actual.append(queue.qsize())
- queue.put(None, syncobj=True)
+ queue.put(None)
actual.append(queue.qsize())
queue.get()
actual.append(queue.qsize())
- queue.put(None, syncobj=True)
+ queue.put(None)
actual.append(queue.qsize())
for _ in range(3):
queue.get()
actual.append(queue.qsize())
- queue.put(None, syncobj=True)
+ queue.put(None)
actual.append(queue.qsize())
queue.get()
actual.append(queue.qsize())
@@ -245,70 +291,32 @@ class TestQueueOps(TestBase):
def test_put_get_main(self):
expected = list(range(20))
- for syncobj in (True, False):
- kwds = dict(syncobj=syncobj)
- with self.subTest(f'syncobj={syncobj}'):
- queue = queues.create()
- for i in range(20):
- queue.put(i, **kwds)
- actual = [queue.get() for _ in range(20)]
+ queue = queues.create()
+ for i in range(20):
+ queue.put(i)
+ actual = [queue.get() for _ in range(20)]
- self.assertEqual(actual, expected)
+ self.assertEqual(actual, expected)
def test_put_timeout(self):
- for syncobj in (True, False):
- kwds = dict(syncobj=syncobj)
- with self.subTest(f'syncobj={syncobj}'):
- queue = queues.create(2)
- queue.put(None, **kwds)
- queue.put(None, **kwds)
- with self.assertRaises(queues.QueueFull):
- queue.put(None, timeout=0.1, **kwds)
- queue.get()
- queue.put(None, **kwds)
+ queue = queues.create(2)
+ queue.put(None)
+ queue.put(None)
+ with self.assertRaises(queues.QueueFull):
+ queue.put(None, timeout=0.1)
+ queue.get()
+ queue.put(None)
def test_put_nowait(self):
- for syncobj in (True, False):
- kwds = dict(syncobj=syncobj)
- with self.subTest(f'syncobj={syncobj}'):
- queue = queues.create(2)
- queue.put_nowait(None, **kwds)
- queue.put_nowait(None, **kwds)
- with self.assertRaises(queues.QueueFull):
- queue.put_nowait(None, **kwds)
- queue.get()
- queue.put_nowait(None, **kwds)
-
- def test_put_syncobj(self):
- for obj in [
- None,
- True,
- 10,
- 'spam',
- b'spam',
- (0, 'a'),
- ]:
- with self.subTest(repr(obj)):
- queue = queues.create()
-
- queue.put(obj, syncobj=True)
- obj2 = queue.get()
- self.assertEqual(obj2, obj)
-
- queue.put(obj, syncobj=True)
- obj2 = queue.get_nowait()
- self.assertEqual(obj2, obj)
-
- for obj in [
- [1, 2, 3],
- {'a': 13, 'b': 17},
- ]:
- with self.subTest(repr(obj)):
- queue = queues.create()
- with self.assertRaises(interpreters.NotShareableError):
- queue.put(obj, syncobj=True)
+ queue = queues.create(2)
+ queue.put_nowait(None)
+ queue.put_nowait(None)
+ with self.assertRaises(queues.QueueFull):
+ queue.put_nowait(None)
+ queue.get()
+ queue.put_nowait(None)
- def test_put_not_syncobj(self):
+ def test_put_full_fallback(self):
for obj in [
None,
True,
@@ -323,11 +331,11 @@ class TestQueueOps(TestBase):
with self.subTest(repr(obj)):
queue = queues.create()
- queue.put(obj, syncobj=False)
+ queue.put(obj)
obj2 = queue.get()
self.assertEqual(obj2, obj)
- queue.put(obj, syncobj=False)
+ queue.put(obj)
obj2 = queue.get_nowait()
self.assertEqual(obj2, obj)
@@ -341,24 +349,9 @@ class TestQueueOps(TestBase):
with self.assertRaises(queues.QueueEmpty):
queue.get_nowait()
- def test_put_get_default_syncobj(self):
- expected = list(range(20))
- queue = queues.create(syncobj=True)
- for methname in ('get', 'get_nowait'):
- with self.subTest(f'{methname}()'):
- get = getattr(queue, methname)
- for i in range(20):
- queue.put(i)
- actual = [get() for _ in range(20)]
- self.assertEqual(actual, expected)
-
- obj = [1, 2, 3] # lists are not shareable
- with self.assertRaises(interpreters.NotShareableError):
- queue.put(obj)
-
- def test_put_get_default_not_syncobj(self):
+ def test_put_get_full_fallback(self):
expected = list(range(20))
- queue = queues.create(syncobj=False)
+ queue = queues.create()
for methname in ('get', 'get_nowait'):
with self.subTest(f'{methname}()'):
get = getattr(queue, methname)
@@ -377,14 +370,14 @@ class TestQueueOps(TestBase):
def test_put_get_same_interpreter(self):
interp = interpreters.create()
interp.exec(dedent("""
- from test.support.interpreters import queues
+ from concurrent.interpreters import _queues as queues
queue = queues.create()
"""))
for methname in ('get', 'get_nowait'):
with self.subTest(f'{methname}()'):
interp.exec(dedent(f"""
orig = b'spam'
- queue.put(orig, syncobj=True)
+ queue.put(orig)
obj = queue.{methname}()
assert obj == orig, 'expected: obj == orig'
assert obj is not orig, 'expected: obj is not orig'
@@ -399,12 +392,12 @@ class TestQueueOps(TestBase):
for methname in ('get', 'get_nowait'):
with self.subTest(f'{methname}()'):
obj1 = b'spam'
- queue1.put(obj1, syncobj=True)
+ queue1.put(obj1)
out = _run_output(
interp,
dedent(f"""
- from test.support.interpreters import queues
+ from concurrent.interpreters import _queues as queues
queue1 = queues.Queue({queue1.id})
queue2 = queues.Queue({queue2.id})
assert queue1.qsize() == 1, 'expected: queue1.qsize() == 1'
@@ -416,7 +409,7 @@ class TestQueueOps(TestBase):
obj2 = b'eggs'
print(id(obj2))
assert queue2.qsize() == 0, 'expected: queue2.qsize() == 0'
- queue2.put(obj2, syncobj=True)
+ queue2.put(obj2)
assert queue2.qsize() == 1, 'expected: queue2.qsize() == 1'
"""))
self.assertEqual(len(queues.list_all()), 2)
@@ -433,22 +426,22 @@ class TestQueueOps(TestBase):
if not unbound:
extraargs = ''
elif unbound is queues.UNBOUND:
- extraargs = ', unbound=queues.UNBOUND'
+ extraargs = ', unbounditems=queues.UNBOUND'
elif unbound is queues.UNBOUND_ERROR:
- extraargs = ', unbound=queues.UNBOUND_ERROR'
+ extraargs = ', unbounditems=queues.UNBOUND_ERROR'
elif unbound is queues.UNBOUND_REMOVE:
- extraargs = ', unbound=queues.UNBOUND_REMOVE'
+ extraargs = ', unbounditems=queues.UNBOUND_REMOVE'
else:
raise NotImplementedError(repr(unbound))
interp = interpreters.create()
_run_output(interp, dedent(f"""
- from test.support.interpreters import queues
+ from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id})
obj1 = b'spam'
obj2 = b'eggs'
- queue.put(obj1, syncobj=True{extraargs})
- queue.put(obj2, syncobj=True{extraargs})
+ queue.put(obj1{extraargs})
+ queue.put(obj2{extraargs})
"""))
self.assertEqual(queue.qsize(), presize + 2)
@@ -501,11 +494,11 @@ class TestQueueOps(TestBase):
with self.assertRaises(queues.QueueEmpty):
queue.get_nowait()
- queue.put(b'ham', unbound=queues.UNBOUND_REMOVE)
+ queue.put(b'ham', unbounditems=queues.UNBOUND_REMOVE)
self.assertEqual(queue.qsize(), 1)
interp = common(queue, queues.UNBOUND_REMOVE, 1)
self.assertEqual(queue.qsize(), 3)
- queue.put(42, unbound=queues.UNBOUND_REMOVE)
+ queue.put(42, unbounditems=queues.UNBOUND_REMOVE)
self.assertEqual(queue.qsize(), 4)
del interp
self.assertEqual(queue.qsize(), 2)
@@ -521,13 +514,13 @@ class TestQueueOps(TestBase):
queue = queues.create()
interp = interpreters.create()
_run_output(interp, dedent(f"""
- from test.support.interpreters import queues
+ from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id})
- queue.put(1, syncobj=True, unbound=queues.UNBOUND)
- queue.put(2, syncobj=True, unbound=queues.UNBOUND_ERROR)
- queue.put(3, syncobj=True)
- queue.put(4, syncobj=True, unbound=queues.UNBOUND_REMOVE)
- queue.put(5, syncobj=True, unbound=queues.UNBOUND)
+ queue.put(1, unbounditems=queues.UNBOUND)
+ queue.put(2, unbounditems=queues.UNBOUND_ERROR)
+ queue.put(3)
+ queue.put(4, unbounditems=queues.UNBOUND_REMOVE)
+ queue.put(5, unbounditems=queues.UNBOUND)
"""))
self.assertEqual(queue.qsize(), 5)
@@ -555,16 +548,16 @@ class TestQueueOps(TestBase):
interp1 = interpreters.create()
interp2 = interpreters.create()
- queue.put(1, syncobj=True)
+ queue.put(1)
_run_output(interp1, dedent(f"""
- from test.support.interpreters import queues
+ from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id})
obj1 = queue.get()
- queue.put(2, syncobj=True, unbound=queues.UNBOUND)
- queue.put(obj1, syncobj=True, unbound=queues.UNBOUND_REMOVE)
+ queue.put(2, unbounditems=queues.UNBOUND)
+ queue.put(obj1, unbounditems=queues.UNBOUND_REMOVE)
"""))
_run_output(interp2, dedent(f"""
- from test.support.interpreters import queues
+ from concurrent.interpreters import _queues as queues
queue = queues.Queue({queue.id})
obj2 = queue.get()
obj1 = queue.get()
@@ -572,21 +565,21 @@ class TestQueueOps(TestBase):
self.assertEqual(queue.qsize(), 0)
queue.put(3)
_run_output(interp1, dedent("""
- queue.put(4, syncobj=True, unbound=queues.UNBOUND)
+ queue.put(4, unbounditems=queues.UNBOUND)
# interp closed here
- queue.put(5, syncobj=True, unbound=queues.UNBOUND_REMOVE)
- queue.put(6, syncobj=True, unbound=queues.UNBOUND)
+ queue.put(5, unbounditems=queues.UNBOUND_REMOVE)
+ queue.put(6, unbounditems=queues.UNBOUND)
"""))
_run_output(interp2, dedent("""
- queue.put(7, syncobj=True, unbound=queues.UNBOUND_ERROR)
+ queue.put(7, unbounditems=queues.UNBOUND_ERROR)
# interp closed here
- queue.put(obj1, syncobj=True, unbound=queues.UNBOUND_ERROR)
- queue.put(obj2, syncobj=True, unbound=queues.UNBOUND_REMOVE)
- queue.put(8, syncobj=True, unbound=queues.UNBOUND)
+ queue.put(obj1, unbounditems=queues.UNBOUND_ERROR)
+ queue.put(obj2, unbounditems=queues.UNBOUND_REMOVE)
+ queue.put(8, unbounditems=queues.UNBOUND)
"""))
_run_output(interp1, dedent("""
- queue.put(9, syncobj=True, unbound=queues.UNBOUND_REMOVE)
- queue.put(10, syncobj=True, unbound=queues.UNBOUND)
+ queue.put(9, unbounditems=queues.UNBOUND_REMOVE)
+ queue.put(10, unbounditems=queues.UNBOUND)
"""))
self.assertEqual(queue.qsize(), 10)
@@ -642,12 +635,12 @@ class TestQueueOps(TestBase):
break
except queues.QueueEmpty:
continue
- queue2.put(obj, syncobj=True)
+ queue2.put(obj)
t = threading.Thread(target=f)
t.start()
orig = b'spam'
- queue1.put(orig, syncobj=True)
+ queue1.put(orig)
obj = queue2.get()
t.join()
diff --git a/Lib/test/test_interpreters/test_stress.py b/Lib/test/test_interpreters/test_stress.py
index 56bfc172199..e25e67a0d4f 100644
--- a/Lib/test/test_interpreters/test_stress.py
+++ b/Lib/test/test_interpreters/test_stress.py
@@ -6,7 +6,7 @@ from test.support import import_helper
from test.support import threading_helper
# Raise SkipTest if subinterpreters not supported.
import_helper.import_module('_interpreters')
-from test.support import interpreters
+from concurrent import interpreters
from .utils import TestBase
@@ -21,21 +21,29 @@ class StressTests(TestBase):
for _ in range(100):
interp = interpreters.create()
alive.append(interp)
+ del alive
+ support.gc_collect()
- @support.requires_resource('cpu')
- @threading_helper.requires_working_threading()
- def test_create_many_threaded(self):
+ @support.bigmemtest(size=200, memuse=32*2**20, dry_run=False)
+ def test_create_many_threaded(self, size):
alive = []
+ start = threading.Event()
def task():
+ # try to create all interpreters simultaneously
+ if not start.wait(support.SHORT_TIMEOUT):
+ raise TimeoutError
interp = interpreters.create()
alive.append(interp)
- threads = (threading.Thread(target=task) for _ in range(200))
+ threads = [threading.Thread(target=task) for _ in range(size)]
with threading_helper.start_threads(threads):
- pass
+ start.set()
+ del alive
+ support.gc_collect()
- @support.requires_resource('cpu')
@threading_helper.requires_working_threading()
- def test_many_threads_running_interp_in_other_interp(self):
+ @support.bigmemtest(size=200, memuse=34*2**20, dry_run=False)
+ def test_many_threads_running_interp_in_other_interp(self, size):
+ start = threading.Event()
interp = interpreters.create()
script = f"""if True:
@@ -47,6 +55,9 @@ class StressTests(TestBase):
interp = interpreters.create()
alreadyrunning = (f'{interpreters.InterpreterError}: '
'interpreter already running')
+ # try to run all interpreters simultaneously
+ if not start.wait(support.SHORT_TIMEOUT):
+ raise TimeoutError
success = False
while not success:
try:
@@ -58,9 +69,10 @@ class StressTests(TestBase):
else:
success = True
- threads = (threading.Thread(target=run) for _ in range(200))
+ threads = [threading.Thread(target=run) for _ in range(size)]
with threading_helper.start_threads(threads):
- pass
+ start.set()
+ support.gc_collect()
if __name__ == '__main__':
diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py
index fc4ad662e03..ae09aa457b4 100644
--- a/Lib/test/test_interpreters/utils.py
+++ b/Lib/test/test_interpreters/utils.py
@@ -12,7 +12,6 @@ from textwrap import dedent
import threading
import types
import unittest
-import warnings
from test import support
@@ -22,7 +21,7 @@ try:
import _interpreters
except ImportError as exc:
raise unittest.SkipTest(str(exc))
-from test.support import interpreters
+from concurrent import interpreters
try:
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 545643aa455..0c921ffbc25 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -572,7 +572,7 @@ class IOTest(unittest.TestCase):
for [test, abilities] in tests:
with self.subTest(test):
if test == pipe_writer and not threading_helper.can_start_thread:
- skipTest()
+ self.skipTest("Need threads")
with test() as obj:
do_test(test, obj, abilities)
@@ -902,7 +902,7 @@ class IOTest(unittest.TestCase):
self.BytesIO()
)
for obj in test:
- self.assertTrue(hasattr(obj, "__dict__"))
+ self.assertHasAttr(obj, "__dict__")
def test_opener(self):
with self.open(os_helper.TESTFN, "w", encoding="utf-8") as f:
@@ -918,7 +918,7 @@ class IOTest(unittest.TestCase):
def badopener(fname, flags):
return -1
with self.assertRaises(ValueError) as cm:
- open('non-existent', 'r', opener=badopener)
+ self.open('non-existent', 'r', opener=badopener)
self.assertEqual(str(cm.exception), 'opener returned -1')
def test_bad_opener_other_negative(self):
@@ -926,7 +926,7 @@ class IOTest(unittest.TestCase):
def badopener(fname, flags):
return -2
with self.assertRaises(ValueError) as cm:
- open('non-existent', 'r', opener=badopener)
+ self.open('non-existent', 'r', opener=badopener)
self.assertEqual(str(cm.exception), 'opener returned -2')
def test_opener_invalid_fd(self):
@@ -1062,6 +1062,37 @@ class IOTest(unittest.TestCase):
# Silence destructor error
R.flush = lambda self: None
+ @threading_helper.requires_working_threading()
+ def test_write_readline_races(self):
+ # gh-134908: Concurrent iteration over a file caused races
+ thread_count = 2
+ write_count = 100
+ read_count = 100
+
+ def writer(file, barrier):
+ barrier.wait()
+ for _ in range(write_count):
+ file.write("x")
+
+ def reader(file, barrier):
+ barrier.wait()
+ for _ in range(read_count):
+ for line in file:
+ self.assertEqual(line, "")
+
+ with self.open(os_helper.TESTFN, "w+") as f:
+ barrier = threading.Barrier(thread_count + 1)
+ reader = threading.Thread(target=reader, args=(f, barrier))
+ writers = [threading.Thread(target=writer, args=(f, barrier))
+ for _ in range(thread_count)]
+ with threading_helper.catch_threading_exception() as cm:
+ with threading_helper.start_threads(writers + [reader]):
+ pass
+ self.assertIsNone(cm.exc_type)
+
+ self.assertEqual(os.stat(os_helper.TESTFN).st_size,
+ write_count * thread_count)
+
class CIOTest(IOTest):
@@ -1117,7 +1148,7 @@ class TestIOCTypes(unittest.TestCase):
def check_subs(types, base):
for tp in types:
with self.subTest(tp=tp, base=base):
- self.assertTrue(issubclass(tp, base))
+ self.assertIsSubclass(tp, base)
def recursive_check(d):
for k, v in d.items():
@@ -1373,6 +1404,28 @@ class CommonBufferedTests:
with self.assertRaises(AttributeError):
buf.raw = x
+ def test_pickling_subclass(self):
+ global MyBufferedIO
+ class MyBufferedIO(self.tp):
+ def __init__(self, raw, tag):
+ super().__init__(raw)
+ self.tag = tag
+ def __getstate__(self):
+ return self.tag, self.raw.getvalue()
+ def __setstate__(slf, state):
+ tag, value = state
+ slf.__init__(self.BytesIO(value), tag)
+
+ raw = self.BytesIO(b'data')
+ buf = MyBufferedIO(raw, tag='ham')
+ for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+ with self.subTest(protocol=proto):
+ pickled = pickle.dumps(buf, proto)
+ newbuf = pickle.loads(pickled)
+ self.assertEqual(newbuf.raw.getvalue(), b'data')
+ self.assertEqual(newbuf.tag, 'ham')
+ del MyBufferedIO
+
class SizeofTest:
@@ -1848,7 +1901,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
flushed = b"".join(writer._write_stack)
# At least (total - 8) bytes were implicitly flushed, perhaps more
# depending on the implementation.
- self.assertTrue(flushed.startswith(contents[:-8]), flushed)
+ self.assertStartsWith(flushed, contents[:-8])
def check_writes(self, intermediate_func):
# Lots of writes, test the flushed output is as expected.
@@ -1918,7 +1971,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
self.assertEqual(bufio.write(b"ABCDEFGHI"), 9)
s = raw.pop_written()
# Previously buffered bytes were flushed
- self.assertTrue(s.startswith(b"01234567A"), s)
+ self.assertStartsWith(s, b"01234567A")
def test_write_and_rewind(self):
raw = self.BytesIO()
@@ -2214,7 +2267,7 @@ class BufferedRWPairTest(unittest.TestCase):
def test_peek(self):
pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO())
- self.assertTrue(pair.peek(3).startswith(b"abc"))
+ self.assertStartsWith(pair.peek(3), b"abc")
self.assertEqual(pair.read(3), b"abc")
def test_readable(self):
@@ -3950,6 +4003,28 @@ class TextIOWrapperTest(unittest.TestCase):
f.write(res)
self.assertEqual(res + f.readline(), 'foo\nbar\n')
+ def test_pickling_subclass(self):
+ global MyTextIO
+ class MyTextIO(self.TextIOWrapper):
+ def __init__(self, raw, tag):
+ super().__init__(raw)
+ self.tag = tag
+ def __getstate__(self):
+ return self.tag, self.buffer.getvalue()
+ def __setstate__(slf, state):
+ tag, value = state
+ slf.__init__(self.BytesIO(value), tag)
+
+ raw = self.BytesIO(b'data')
+ txt = MyTextIO(raw, 'ham')
+ for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+ with self.subTest(protocol=proto):
+ pickled = pickle.dumps(txt, proto)
+ newtxt = pickle.loads(pickled)
+ self.assertEqual(newtxt.buffer.getvalue(), b'data')
+ self.assertEqual(newtxt.tag, 'ham')
+ del MyTextIO
+
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
def test_read_non_blocking(self):
import os
@@ -4373,7 +4448,7 @@ class MiscIOTest(unittest.TestCase):
self._check_abc_inheritance(io)
def _check_warn_on_dealloc(self, *args, **kwargs):
- f = open(*args, **kwargs)
+ f = self.open(*args, **kwargs)
r = repr(f)
with self.assertWarns(ResourceWarning) as cm:
f = None
@@ -4402,7 +4477,7 @@ class MiscIOTest(unittest.TestCase):
r, w = os.pipe()
fds += r, w
with warnings_helper.check_no_resource_warning(self):
- open(r, *args, closefd=False, **kwargs)
+ self.open(r, *args, closefd=False, **kwargs)
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
def test_warn_on_dealloc_fd(self):
@@ -4574,10 +4649,8 @@ class MiscIOTest(unittest.TestCase):
proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
warnings = proc.err.splitlines()
self.assertEqual(len(warnings), 2)
- self.assertTrue(
- warnings[0].startswith(b"<string>:5: EncodingWarning: "))
- self.assertTrue(
- warnings[1].startswith(b"<string>:8: EncodingWarning: "))
+ self.assertStartsWith(warnings[0], b"<string>:5: EncodingWarning: ")
+ self.assertStartsWith(warnings[1], b"<string>:8: EncodingWarning: ")
def test_text_encoding(self):
# PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"
@@ -4790,7 +4863,7 @@ class SignalsTest(unittest.TestCase):
os.read(r, len(data) * 100)
exc = cm.exception
if isinstance(exc, RuntimeError):
- self.assertTrue(str(exc).startswith("reentrant call"), str(exc))
+ self.assertStartsWith(str(exc), "reentrant call")
finally:
signal.alarm(0)
wio.close()
diff --git a/Lib/test/test_ioctl.py b/Lib/test/test_ioctl.py
index 7a986048bda..277d2fc99ea 100644
--- a/Lib/test/test_ioctl.py
+++ b/Lib/test/test_ioctl.py
@@ -5,7 +5,7 @@ import sys
import threading
import unittest
from test import support
-from test.support import threading_helper
+from test.support import os_helper, threading_helper
from test.support.import_helper import import_module
fcntl = import_module('fcntl')
termios = import_module('termios')
@@ -127,9 +127,8 @@ class IoctlTestsTty(unittest.TestCase):
self._check_ioctl_not_mutate_len(1024)
def test_ioctl_mutate_2048(self):
- # Test with a larger buffer, just for the record.
self._check_ioctl_mutate_len(2048)
- self.assertRaises(ValueError, self._check_ioctl_not_mutate_len, 2048)
+ self._check_ioctl_not_mutate_len(1024)
@unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()")
@@ -202,6 +201,17 @@ class IoctlTestsPty(unittest.TestCase):
new_winsz = struct.unpack("HHHH", result)
self.assertEqual(new_winsz[:2], (20, 40))
+ @unittest.skipUnless(hasattr(fcntl, 'FICLONE'), 'need fcntl.FICLONE')
+ def test_bad_fd(self):
+ # gh-134744: Test error handling
+ fd = os_helper.make_bad_fd()
+ with self.assertRaises(OSError):
+ fcntl.ioctl(fd, fcntl.FICLONE, fd)
+ with self.assertRaises(OSError):
+ fcntl.ioctl(fd, fcntl.FICLONE, b'\0' * 10)
+ with self.assertRaises(OSError):
+ fcntl.ioctl(fd, fcntl.FICLONE, b'\0' * 2048)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index d04012d1afd..db1c38243e2 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -397,6 +397,19 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
# A trailing IPv4 address is two parts
assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42%scope")
+ def test_bad_address_split_v6_too_long(self):
+ def assertBadSplit(addr):
+ msg = r"At most 45 characters expected in '%s"
+ with self.assertAddressError(msg, re.escape(addr[:45])):
+ ipaddress.IPv6Address(addr)
+
+ # Long IPv6 address
+ long_addr = ("0:" * 10000) + "0"
+ assertBadSplit(long_addr)
+ assertBadSplit(long_addr + "%zoneid")
+ assertBadSplit(long_addr + ":255.255.255.255")
+ assertBadSplit(long_addr + ":ffff:255.255.255.255")
+
def test_bad_address_split_v6_too_many_parts(self):
def assertBadSplit(addr):
msg = "Exactly 8 parts expected without '::' in %r"
@@ -2178,6 +2191,11 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1'),
ipaddress.ip_address('FFFF::c000:201'))
+ self.assertEqual(ipaddress.ip_address('0000:0000:0000:0000:0000:FFFF:192.168.255.255'),
+ ipaddress.ip_address('::ffff:c0a8:ffff'))
+ self.assertEqual(ipaddress.ip_address('FFFF:0000:0000:0000:0000:0000:192.168.255.255'),
+ ipaddress.ip_address('ffff::c0a8:ffff'))
+
self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'),
ipaddress.ip_address('::FFFF:c000:201%scope'))
self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'),
@@ -2190,6 +2208,10 @@ class IpaddrUnitTest(unittest.TestCase):
ipaddress.ip_address('::FFFF:c000:201%scope'))
self.assertNotEqual(ipaddress.ip_address('FFFF::192.0.2.1'),
ipaddress.ip_address('FFFF::c000:201%scope'))
+ self.assertEqual(ipaddress.ip_address('0000:0000:0000:0000:0000:FFFF:192.168.255.255%scope'),
+ ipaddress.ip_address('::ffff:c0a8:ffff%scope'))
+ self.assertEqual(ipaddress.ip_address('FFFF:0000:0000:0000:0000:0000:192.168.255.255%scope'),
+ ipaddress.ip_address('ffff::c0a8:ffff%scope'))
def testIPVersion(self):
self.assertEqual(ipaddress.IPv4Address.version, 4)
@@ -2599,6 +2621,10 @@ class IpaddrUnitTest(unittest.TestCase):
'::7:6:5:4:3:2:0': '0:7:6:5:4:3:2:0/128',
'7:6:5:4:3:2:1::': '7:6:5:4:3:2:1:0/128',
'0:6:5:4:3:2:1::': '0:6:5:4:3:2:1:0/128',
+ '0000:0000:0000:0000:0000:0000:255.255.255.255': '::ffff:ffff/128',
+ '0000:0000:0000:0000:0000:ffff:255.255.255.255': '::ffff:255.255.255.255/128',
+ 'ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255':
+ 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128',
}
for uncompressed, compressed in list(test_addresses.items()):
self.assertEqual(compressed, str(ipaddress.IPv6Interface(
@@ -2762,6 +2788,34 @@ class IpaddrUnitTest(unittest.TestCase):
ipv6_address2 = ipaddress.IPv6Interface("2001:658:22a:cafe:200:0:0:2")
self.assertNotEqual(ipv6_address1.__hash__(), ipv6_address2.__hash__())
+ # issue 134062 Hash collisions in IPv4Network and IPv6Network
+ def testNetworkV4HashCollisions(self):
+ self.assertNotEqual(
+ ipaddress.IPv4Network("192.168.1.255/32").__hash__(),
+ ipaddress.IPv4Network("192.168.1.0/24").__hash__()
+ )
+ self.assertNotEqual(
+ ipaddress.IPv4Network("172.24.255.0/24").__hash__(),
+ ipaddress.IPv4Network("172.24.0.0/16").__hash__()
+ )
+ self.assertNotEqual(
+ ipaddress.IPv4Network("192.168.1.87/32").__hash__(),
+ ipaddress.IPv4Network("192.168.1.86/31").__hash__()
+ )
+
+ # issue 134062 Hash collisions in IPv4Network and IPv6Network
+ def testNetworkV6HashCollisions(self):
+ self.assertNotEqual(
+ ipaddress.IPv6Network("fe80::/64").__hash__(),
+ ipaddress.IPv6Network("fe80::ffff:ffff:ffff:0/112").__hash__()
+ )
+ self.assertNotEqual(
+ ipaddress.IPv4Network("10.0.0.0/8").__hash__(),
+ ipaddress.IPv6Network(
+ "ffff:ffff:ffff:ffff:ffff:ffff:aff:0/112"
+ ).__hash__()
+ )
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py
index daad00e8643..f440fc28ee7 100644
--- a/Lib/test/test_isinstance.py
+++ b/Lib/test/test_isinstance.py
@@ -318,6 +318,7 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
self.assertRaises(RecursionError, isinstance, 1, X())
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_infinite_recursion_via_bases_tuple(self):
"""Regression test for bpo-30570."""
class Failure(object):
@@ -328,6 +329,7 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
issubclass(Failure(), int)
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_infinite_cycle_in_bases(self):
"""Regression test for bpo-30570."""
class X:
diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py
index 1b9f3cf7624..18e4b676c53 100644
--- a/Lib/test/test_iter.py
+++ b/Lib/test/test_iter.py
@@ -1147,7 +1147,7 @@ class TestCase(unittest.TestCase):
def test_exception_locations(self):
# The location of an exception raised from __init__ or
- # __next__ should should be the iterator expression
+ # __next__ should be the iterator expression
def init_raises():
try:
diff --git a/Lib/test/test_json/test_dump.py b/Lib/test/test_json/test_dump.py
index 13b40020781..39470754003 100644
--- a/Lib/test/test_json/test_dump.py
+++ b/Lib/test/test_json/test_dump.py
@@ -22,6 +22,14 @@ class TestDump:
self.assertIn('valid_key', o)
self.assertNotIn(b'invalid_key', o)
+ def test_dump_skipkeys_indent_empty(self):
+ v = {b'invalid_key': False}
+ self.assertEqual(self.json.dumps(v, skipkeys=True, indent=4), '{}')
+
+ def test_skipkeys_indent(self):
+ v = {b'invalid_key': False, 'valid_key': True}
+ self.assertEqual(self.json.dumps(v, skipkeys=True, indent=4), '{\n "valid_key": true\n}')
+
def test_encode_truefalse(self):
self.assertEqual(self.dumps(
{True: False, False: True}, sort_keys=True),
diff --git a/Lib/test/test_json/test_fail.py b/Lib/test/test_json/test_fail.py
index 7c1696cc66d..79c44af2fbf 100644
--- a/Lib/test/test_json/test_fail.py
+++ b/Lib/test/test_json/test_fail.py
@@ -102,7 +102,7 @@ class TestFail:
with self.assertRaisesRegex(TypeError,
'Object of type module is not JSON serializable') as cm:
self.dumps(sys)
- self.assertFalse(hasattr(cm.exception, '__notes__'))
+ self.assertNotHasAttr(cm.exception, '__notes__')
with self.assertRaises(TypeError) as cm:
self.dumps([1, [2, 3, sys]])
diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py
index d82093f3895..5d7b56ff9ad 100644
--- a/Lib/test/test_json/test_recursion.py
+++ b/Lib/test/test_json/test_recursion.py
@@ -69,6 +69,7 @@ class TestRecursion:
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_highly_nested_objects_decoding(self):
very_deep = 200000
# test that loading highly-nested objects doesn't segfault when C
@@ -85,6 +86,7 @@ class TestRecursion:
@support.skip_wasi_stack_overflow()
@support.skip_emscripten_stack_overflow()
+ @support.requires_resource('cpu')
def test_highly_nested_objects_encoding(self):
# See #12051
l, d = [], {}
@@ -98,6 +100,7 @@ class TestRecursion:
self.dumps(d)
@support.skip_emscripten_stack_overflow()
+ @support.skip_wasi_stack_overflow()
def test_endless_recursion(self):
# See #12051
class EndlessJSONEncoder(self.json.JSONEncoder):
diff --git a/Lib/test/test_json/test_tool.py b/Lib/test/test_json/test_tool.py
index ba9c42f758e..30f9bb33316 100644
--- a/Lib/test/test_json/test_tool.py
+++ b/Lib/test/test_json/test_tool.py
@@ -6,9 +6,11 @@ import unittest
import subprocess
from test import support
-from test.support import force_not_colorized, os_helper
+from test.support import force_colorized, force_not_colorized, os_helper
from test.support.script_helper import assert_python_ok
+from _colorize import get_theme
+
@support.requires_subprocess()
class TestMain(unittest.TestCase):
@@ -158,7 +160,7 @@ class TestMain(unittest.TestCase):
rc, out, err = assert_python_ok('-m', self.module, '-h',
PYTHON_COLORS='0')
self.assertEqual(rc, 0)
- self.assertTrue(out.startswith(b'usage: '))
+ self.assertStartsWith(out, b'usage: ')
self.assertEqual(err, b'')
def test_sort_keys_flag(self):
@@ -246,34 +248,39 @@ class TestMain(unittest.TestCase):
proc.communicate(b'"{}"')
self.assertEqual(proc.returncode, errno.EPIPE)
+ @force_colorized
def test_colors(self):
infile = os_helper.TESTFN
self.addCleanup(os.remove, infile)
+ t = get_theme().syntax
+ ob = "{"
+ cb = "}"
+
cases = (
- ('{}', b'{}'),
- ('[]', b'[]'),
- ('null', b'\x1b[1;36mnull\x1b[0m'),
- ('true', b'\x1b[1;36mtrue\x1b[0m'),
- ('false', b'\x1b[1;36mfalse\x1b[0m'),
- ('NaN', b'NaN'),
- ('Infinity', b'Infinity'),
- ('-Infinity', b'-Infinity'),
- ('"foo"', b'\x1b[1;32m"foo"\x1b[0m'),
- (r'" \"foo\" "', b'\x1b[1;32m" \\"foo\\" "\x1b[0m'),
- ('"α"', b'\x1b[1;32m"\\u03b1"\x1b[0m'),
- ('123', b'123'),
- ('-1.2345e+23', b'-1.2345e+23'),
+ ('{}', '{}'),
+ ('[]', '[]'),
+ ('null', f'{t.keyword}null{t.reset}'),
+ ('true', f'{t.keyword}true{t.reset}'),
+ ('false', f'{t.keyword}false{t.reset}'),
+ ('NaN', f'{t.number}NaN{t.reset}'),
+ ('Infinity', f'{t.number}Infinity{t.reset}'),
+ ('-Infinity', f'{t.number}-Infinity{t.reset}'),
+ ('"foo"', f'{t.string}"foo"{t.reset}'),
+ (r'" \"foo\" "', f'{t.string}" \\"foo\\" "{t.reset}'),
+ ('"α"', f'{t.string}"\\u03b1"{t.reset}'),
+ ('123', f'{t.number}123{t.reset}'),
+ ('-1.25e+23', f'{t.number}-1.25e+23{t.reset}'),
(r'{"\\": ""}',
- b'''\
-{
- \x1b[94m"\\\\"\x1b[0m: \x1b[1;32m""\x1b[0m
-}'''),
+ f'''\
+{ob}
+ {t.definition}"\\\\"{t.reset}: {t.string}""{t.reset}
+{cb}'''),
(r'{"\\\\": ""}',
- b'''\
-{
- \x1b[94m"\\\\\\\\"\x1b[0m: \x1b[1;32m""\x1b[0m
-}'''),
+ f'''\
+{ob}
+ {t.definition}"\\\\\\\\"{t.reset}: {t.string}""{t.reset}
+{cb}'''),
('''\
{
"foo": "bar",
@@ -281,30 +288,32 @@ class TestMain(unittest.TestCase):
"qux": [true, false, null],
"xyz": [NaN, -Infinity, Infinity]
}''',
- b'''\
-{
- \x1b[94m"foo"\x1b[0m: \x1b[1;32m"bar"\x1b[0m,
- \x1b[94m"baz"\x1b[0m: 1234,
- \x1b[94m"qux"\x1b[0m: [
- \x1b[1;36mtrue\x1b[0m,
- \x1b[1;36mfalse\x1b[0m,
- \x1b[1;36mnull\x1b[0m
+ f'''\
+{ob}
+ {t.definition}"foo"{t.reset}: {t.string}"bar"{t.reset},
+ {t.definition}"baz"{t.reset}: {t.number}1234{t.reset},
+ {t.definition}"qux"{t.reset}: [
+ {t.keyword}true{t.reset},
+ {t.keyword}false{t.reset},
+ {t.keyword}null{t.reset}
],
- \x1b[94m"xyz"\x1b[0m: [
- NaN,
- -Infinity,
- Infinity
+ {t.definition}"xyz"{t.reset}: [
+ {t.number}NaN{t.reset},
+ {t.number}-Infinity{t.reset},
+ {t.number}Infinity{t.reset}
]
-}'''),
+{cb}'''),
)
for input_, expected in cases:
with self.subTest(input=input_):
with open(infile, "w", encoding="utf-8") as fp:
fp.write(input_)
- _, stdout, _ = assert_python_ok('-m', self.module, infile,
- PYTHON_COLORS='1')
- stdout = stdout.replace(b'\r\n', b'\n') # normalize line endings
+ _, stdout_b, _ = assert_python_ok(
+ '-m', self.module, infile, FORCE_COLOR='1', __isolated='1'
+ )
+ stdout = stdout_b.decode()
+ stdout = stdout.replace('\r\n', '\n') # normalize line endings
stdout = stdout.strip()
self.assertEqual(stdout, expected)
diff --git a/Lib/test/test_launcher.py b/Lib/test/test_launcher.py
index 173fc743cf6..caa1603c78e 100644
--- a/Lib/test/test_launcher.py
+++ b/Lib/test/test_launcher.py
@@ -443,7 +443,7 @@ class TestLauncher(unittest.TestCase, RunPyMixin):
except subprocess.CalledProcessError:
raise unittest.SkipTest("requires at least one Python 3.x install")
self.assertEqual("PythonCore", data["env.company"])
- self.assertTrue(data["env.tag"].startswith("3."), data["env.tag"])
+ self.assertStartsWith(data["env.tag"], "3.")
def test_search_major_3_32(self):
try:
@@ -453,8 +453,8 @@ class TestLauncher(unittest.TestCase, RunPyMixin):
raise unittest.SkipTest("requires at least one 32-bit Python 3.x install")
raise
self.assertEqual("PythonCore", data["env.company"])
- self.assertTrue(data["env.tag"].startswith("3."), data["env.tag"])
- self.assertTrue(data["env.tag"].endswith("-32"), data["env.tag"])
+ self.assertStartsWith(data["env.tag"], "3.")
+ self.assertEndsWith(data["env.tag"], "-32")
def test_search_major_2(self):
try:
@@ -463,7 +463,7 @@ class TestLauncher(unittest.TestCase, RunPyMixin):
if not is_installed("2.7"):
raise unittest.SkipTest("requires at least one Python 2.x install")
self.assertEqual("PythonCore", data["env.company"])
- self.assertTrue(data["env.tag"].startswith("2."), data["env.tag"])
+ self.assertStartsWith(data["env.tag"], "2.")
def test_py_default(self):
with self.py_ini(TEST_PY_DEFAULTS):
diff --git a/Lib/test/test_linecache.py b/Lib/test/test_linecache.py
index e4aa41ebb43..02f65338428 100644
--- a/Lib/test/test_linecache.py
+++ b/Lib/test/test_linecache.py
@@ -4,10 +4,12 @@ import linecache
import unittest
import os.path
import tempfile
+import threading
import tokenize
from importlib.machinery import ModuleSpec
from test import support
from test.support import os_helper
+from test.support import threading_helper
from test.support.script_helper import assert_python_ok
@@ -374,5 +376,40 @@ class LineCacheInvalidationTests(unittest.TestCase):
self.assertIn(self.unchanged_file, linecache.cache)
+class MultiThreadingTest(unittest.TestCase):
+ @threading_helper.reap_threads
+ @threading_helper.requires_working_threading()
+ def test_read_write_safety(self):
+
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ filenames = []
+ for i in range(10):
+ name = os.path.join(tmpdirname, f"test_{i}.py")
+ with open(name, "w") as h:
+ h.write("import time\n")
+ h.write("import system\n")
+ filenames.append(name)
+
+ def linecache_get_line(b):
+ b.wait()
+ for _ in range(100):
+ for name in filenames:
+ linecache.getline(name, 1)
+
+ def check(funcs):
+ barrier = threading.Barrier(len(funcs))
+ threads = []
+
+ for func in funcs:
+ thread = threading.Thread(target=func, args=(barrier,))
+
+ threads.append(thread)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+ check([linecache_get_line] * 20)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py
index 6894fba2ad1..223f34fb696 100644
--- a/Lib/test/test_list.py
+++ b/Lib/test/test_list.py
@@ -365,5 +365,20 @@ class ListTest(list_tests.CommonTest):
rc, _, _ = assert_python_ok("-c", code)
self.assertEqual(rc, 0)
+ def test_list_overwrite_local(self):
+ """Test that overwriting the last reference to the
+ iterable doesn't prematurely free the iterable"""
+
+ def foo(x):
+ self.assertEqual(sys.getrefcount(x), 1)
+ r = 0
+ for i in x:
+ r += i
+ x = None
+ return r
+
+ self.assertEqual(foo(list(range(10))), 45)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index cffdeeacc5d..70148dc30fc 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -716,7 +716,7 @@ class ListComprehensionTest(unittest.TestCase):
def test_exception_locations(self):
# The location of an exception raised from __init__ or
- # __next__ should should be the iterator expression
+ # __next__ should be the iterator expression
def init_raises():
try:
diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py
index 528ceef5281..55b502e52ca 100644
--- a/Lib/test/test_locale.py
+++ b/Lib/test/test_locale.py
@@ -1,13 +1,18 @@
from decimal import Decimal
-from test.support import verbose, is_android, linked_to_musl, os_helper
+from test.support import cpython_only, verbose, is_android, linked_to_musl, os_helper
from test.support.warnings_helper import check_warnings
-from test.support.import_helper import import_fresh_module
+from test.support.import_helper import ensure_lazy_imports, import_fresh_module
from unittest import mock
import unittest
import locale
import sys
import codecs
+class LazyImportTest(unittest.TestCase):
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("locale", {"re", "warnings"})
+
class BaseLocalizedTest(unittest.TestCase):
#
@@ -382,6 +387,10 @@ class NormalizeTest(unittest.TestCase):
self.check('c', 'C')
self.check('posix', 'C')
+ def test_c_utf8(self):
+ self.check('c.utf8', 'C.UTF-8')
+ self.check('C.UTF-8', 'C.UTF-8')
+
def test_english(self):
self.check('en', 'en_US.ISO8859-1')
self.check('EN', 'en_US.ISO8859-1')
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index 3f113ec1be4..3819965ed2c 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -61,7 +61,7 @@ import warnings
import weakref
from http.server import HTTPServer, BaseHTTPRequestHandler
-from unittest.mock import patch
+from unittest.mock import call, Mock, patch
from urllib.parse import urlparse, parse_qs
from socketserver import (ThreadingUDPServer, DatagramRequestHandler,
ThreadingTCPServer, StreamRequestHandler)
@@ -1036,7 +1036,7 @@ class TestTCPServer(ControlMixin, ThreadingTCPServer):
"""
allow_reuse_address = True
- allow_reuse_port = True
+ allow_reuse_port = False
def __init__(self, addr, handler, poll_interval=0.5,
bind_and_activate=True):
@@ -5572,12 +5572,19 @@ class BasicConfigTest(unittest.TestCase):
assertRaises = self.assertRaises
handlers = [logging.StreamHandler()]
stream = sys.stderr
+ formatter = logging.Formatter()
assertRaises(ValueError, logging.basicConfig, filename='test.log',
stream=stream)
assertRaises(ValueError, logging.basicConfig, filename='test.log',
handlers=handlers)
assertRaises(ValueError, logging.basicConfig, stream=stream,
handlers=handlers)
+ assertRaises(ValueError, logging.basicConfig, formatter=formatter,
+ format='%(message)s')
+ assertRaises(ValueError, logging.basicConfig, formatter=formatter,
+ datefmt='%H:%M:%S')
+ assertRaises(ValueError, logging.basicConfig, formatter=formatter,
+ style='%')
# Issue 23207: test for invalid kwargs
assertRaises(ValueError, logging.basicConfig, loglevel=logging.INFO)
# Should pop both filename and filemode even if filename is None
@@ -5712,6 +5719,20 @@ class BasicConfigTest(unittest.TestCase):
# didn't write anything due to the encoding error
self.assertEqual(data, r'')
+ def test_formatter_given(self):
+ mock_formatter = Mock()
+ mock_handler = Mock(formatter=None)
+ with patch("logging.Formatter") as mock_formatter_init:
+ logging.basicConfig(formatter=mock_formatter, handlers=[mock_handler])
+ self.assertEqual(mock_handler.setFormatter.call_args_list, [call(mock_formatter)])
+ self.assertEqual(mock_formatter_init.call_count, 0)
+
+ def test_formatter_not_given(self):
+ mock_handler = Mock(formatter=None)
+ with patch("logging.Formatter") as mock_formatter_init:
+ logging.basicConfig(handlers=[mock_handler])
+ self.assertEqual(mock_formatter_init.call_count, 1)
+
@support.requires_working_socket()
def test_log_taskName(self):
async def log_record():
diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py
index 9ffb93e797d..e93c3c37354 100644
--- a/Lib/test/test_lzma.py
+++ b/Lib/test/test_lzma.py
@@ -1025,12 +1025,12 @@ class FileTestCase(unittest.TestCase):
with LZMAFile(BytesIO(COMPRESSED_XZ)) as f:
result = f.peek()
self.assertGreater(len(result), 0)
- self.assertTrue(INPUT.startswith(result))
+ self.assertStartsWith(INPUT, result)
self.assertEqual(f.read(), INPUT)
with LZMAFile(BytesIO(COMPRESSED_XZ)) as f:
result = f.peek(10)
self.assertGreater(len(result), 0)
- self.assertTrue(INPUT.startswith(result))
+ self.assertStartsWith(INPUT, result)
self.assertEqual(f.read(), INPUT)
def test_peek_bad_args(self):
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index 913a60bf9e0..46cb54647b1 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -475,6 +475,19 @@ class MathTests(unittest.TestCase):
# similarly, copysign(2., NAN) could be 2. or -2.
self.assertEqual(abs(math.copysign(2., NAN)), 2.)
+ def test_signbit(self):
+ self.assertRaises(TypeError, math.signbit)
+ self.assertRaises(TypeError, math.signbit, '1.0')
+
+ # C11, §7.12.3.6 requires signbit() to return a nonzero value
+ # if and only if the sign of its argument value is negative,
+ # but in practice, we are only interested in a boolean value.
+ self.assertIsInstance(math.signbit(1.0), bool)
+
+ for arg in [0., 1., INF, NAN]:
+ self.assertFalse(math.signbit(arg))
+ self.assertTrue(math.signbit(-arg))
+
def testCos(self):
self.assertRaises(TypeError, math.cos)
self.ftest('cos(-pi/2)', math.cos(-math.pi/2), 0, abs_tol=math.ulp(1))
@@ -1214,6 +1227,12 @@ class MathTests(unittest.TestCase):
self.assertEqual(math.ldexp(NINF, n), NINF)
self.assertTrue(math.isnan(math.ldexp(NAN, n)))
+ @requires_IEEE_754
+ def testLdexp_denormal(self):
+ # Denormal output incorrectly rounded (truncated)
+ # on some Windows.
+ self.assertEqual(math.ldexp(6993274598585239, -1126), 1e-323)
+
def testLog(self):
self.assertRaises(TypeError, math.log)
self.assertRaises(TypeError, math.log, 1, 2, 3)
@@ -1381,7 +1400,6 @@ class MathTests(unittest.TestCase):
args = ((-5, -5, 10), (1.5, 4611686018427387904, 2305843009213693952))
self.assertEqual(sumprod(*args), 0.0)
-
@requires_IEEE_754
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
"sumprod() accuracy not guaranteed on machines with double rounding")
@@ -1967,6 +1985,28 @@ class MathTests(unittest.TestCase):
self.assertFalse(math.isfinite(float("inf")))
self.assertFalse(math.isfinite(float("-inf")))
+ def testIsnormal(self):
+ self.assertTrue(math.isnormal(1.25))
+ self.assertTrue(math.isnormal(-1.0))
+ self.assertFalse(math.isnormal(0.0))
+ self.assertFalse(math.isnormal(-0.0))
+ self.assertFalse(math.isnormal(INF))
+ self.assertFalse(math.isnormal(NINF))
+ self.assertFalse(math.isnormal(NAN))
+ self.assertFalse(math.isnormal(FLOAT_MIN/2))
+ self.assertFalse(math.isnormal(-FLOAT_MIN/2))
+
+ def testIssubnormal(self):
+ self.assertFalse(math.issubnormal(1.25))
+ self.assertFalse(math.issubnormal(-1.0))
+ self.assertFalse(math.issubnormal(0.0))
+ self.assertFalse(math.issubnormal(-0.0))
+ self.assertFalse(math.issubnormal(INF))
+ self.assertFalse(math.issubnormal(NINF))
+ self.assertFalse(math.issubnormal(NAN))
+ self.assertTrue(math.issubnormal(FLOAT_MIN/2))
+ self.assertTrue(math.issubnormal(-FLOAT_MIN/2))
+
def testIsnan(self):
self.assertTrue(math.isnan(float("nan")))
self.assertTrue(math.isnan(float("-nan")))
@@ -2458,7 +2498,6 @@ class MathTests(unittest.TestCase):
with self.assertRaises(ValueError):
math.nextafter(1.0, INF, steps=-1)
-
@requires_IEEE_754
def test_ulp(self):
self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py
index 95629ed862d..63998a86c45 100644
--- a/Lib/test/test_memoryio.py
+++ b/Lib/test/test_memoryio.py
@@ -265,8 +265,8 @@ class MemoryTestMixin:
memio = self.ioclass(buf * 10)
self.assertEqual(iter(memio), memio)
- self.assertTrue(hasattr(memio, '__iter__'))
- self.assertTrue(hasattr(memio, '__next__'))
+ self.assertHasAttr(memio, '__iter__')
+ self.assertHasAttr(memio, '__next__')
i = 0
for line in memio:
self.assertEqual(line, buf)
diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index 61b068c630c..64f440f180b 100644
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -743,19 +743,21 @@ class RacingTest(unittest.TestCase):
from multiprocessing.managers import SharedMemoryManager
except ImportError:
self.skipTest("Test requires multiprocessing")
- from threading import Thread
+ from threading import Thread, Event
- n = 100
+ start = Event()
with SharedMemoryManager() as smm:
obj = smm.ShareableList(range(100))
- threads = []
- for _ in range(n):
- # Issue gh-127085, the `ShareableList.count` is just a convenient way to mess the `exports`
- # counter of `memoryview`, this issue has no direct relation with `ShareableList`.
- threads.append(Thread(target=obj.count, args=(1,)))
-
+ def test():
+ # Issue gh-127085, the `ShareableList.count` is just a
+ # convenient way to mess the `exports` counter of `memoryview`,
+ # this issue has no direct relation with `ShareableList`.
+ start.wait(support.SHORT_TIMEOUT)
+ for i in range(10):
+ obj.count(1)
+ threads = [Thread(target=test) for _ in range(10)]
with threading_helper.start_threads(threads):
- pass
+ start.set()
del obj
diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py
index dad5dbde7cd..fb57d5e5544 100644
--- a/Lib/test/test_mimetypes.py
+++ b/Lib/test/test_mimetypes.py
@@ -6,7 +6,8 @@ import sys
import unittest.mock
from platform import win32_edition
from test import support
-from test.support import os_helper
+from test.support import cpython_only, force_not_colorized, os_helper
+from test.support.import_helper import ensure_lazy_imports
try:
import _winapi
@@ -435,8 +436,13 @@ class MiscTestCase(unittest.TestCase):
def test__all__(self):
support.check__all__(self, mimetypes)
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("mimetypes", {"os", "posixpath", "urllib.parse", "argparse"})
+
class CommandLineTest(unittest.TestCase):
+ @force_not_colorized
def test_parse_args(self):
args, help_text = mimetypes._parse_args("-h")
self.assertTrue(help_text.startswith("usage: "))
diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py
index 6679c0a4fbe..4f25e9c2a03 100644
--- a/Lib/test/test_minidom.py
+++ b/Lib/test/test_minidom.py
@@ -102,41 +102,38 @@ class MinidomTest(unittest.TestCase):
elem = root.childNodes[0]
nelem = dom.createElement("element")
root.insertBefore(nelem, elem)
- self.confirm(len(root.childNodes) == 2
- and root.childNodes.length == 2
- and root.childNodes[0] is nelem
- and root.childNodes.item(0) is nelem
- and root.childNodes[1] is elem
- and root.childNodes.item(1) is elem
- and root.firstChild is nelem
- and root.lastChild is elem
- and root.toxml() == "<doc><element/><foo/></doc>"
- , "testInsertBefore -- node properly placed in tree")
+ self.assertEqual(len(root.childNodes), 2)
+ self.assertEqual(root.childNodes.length, 2)
+ self.assertIs(root.childNodes[0], nelem)
+ self.assertIs(root.childNodes.item(0), nelem)
+ self.assertIs(root.childNodes[1], elem)
+ self.assertIs(root.childNodes.item(1), elem)
+ self.assertIs(root.firstChild, nelem)
+ self.assertIs(root.lastChild, elem)
+ self.assertEqual(root.toxml(), "<doc><element/><foo/></doc>")
nelem = dom.createElement("element")
root.insertBefore(nelem, None)
- self.confirm(len(root.childNodes) == 3
- and root.childNodes.length == 3
- and root.childNodes[1] is elem
- and root.childNodes.item(1) is elem
- and root.childNodes[2] is nelem
- and root.childNodes.item(2) is nelem
- and root.lastChild is nelem
- and nelem.previousSibling is elem
- and root.toxml() == "<doc><element/><foo/><element/></doc>"
- , "testInsertBefore -- node properly placed in tree")
+ self.assertEqual(len(root.childNodes), 3)
+ self.assertEqual(root.childNodes.length, 3)
+ self.assertIs(root.childNodes[1], elem)
+ self.assertIs(root.childNodes.item(1), elem)
+ self.assertIs(root.childNodes[2], nelem)
+ self.assertIs(root.childNodes.item(2), nelem)
+ self.assertIs(root.lastChild, nelem)
+ self.assertIs(nelem.previousSibling, elem)
+ self.assertEqual(root.toxml(), "<doc><element/><foo/><element/></doc>")
nelem2 = dom.createElement("bar")
root.insertBefore(nelem2, nelem)
- self.confirm(len(root.childNodes) == 4
- and root.childNodes.length == 4
- and root.childNodes[2] is nelem2
- and root.childNodes.item(2) is nelem2
- and root.childNodes[3] is nelem
- and root.childNodes.item(3) is nelem
- and nelem2.nextSibling is nelem
- and nelem.previousSibling is nelem2
- and root.toxml() ==
- "<doc><element/><foo/><bar/><element/></doc>"
- , "testInsertBefore -- node properly placed in tree")
+ self.assertEqual(len(root.childNodes), 4)
+ self.assertEqual(root.childNodes.length, 4)
+ self.assertIs(root.childNodes[2], nelem2)
+ self.assertIs(root.childNodes.item(2), nelem2)
+ self.assertIs(root.childNodes[3], nelem)
+ self.assertIs(root.childNodes.item(3), nelem)
+ self.assertIs(nelem2.nextSibling, nelem)
+ self.assertIs(nelem.previousSibling, nelem2)
+ self.assertEqual(root.toxml(),
+ "<doc><element/><foo/><bar/><element/></doc>")
dom.unlink()
def _create_fragment_test_nodes(self):
@@ -342,8 +339,8 @@ class MinidomTest(unittest.TestCase):
self.assertRaises(xml.dom.NotFoundErr, child.removeAttributeNode,
None)
self.assertIs(node, child.removeAttributeNode(node))
- self.confirm(len(child.attributes) == 0
- and child.getAttributeNode("spam") is None)
+ self.assertEqual(len(child.attributes), 0)
+ self.assertIsNone(child.getAttributeNode("spam"))
dom2 = Document()
child2 = dom2.appendChild(dom2.createElement("foo"))
node2 = child2.getAttributeNode("spam")
@@ -366,33 +363,34 @@ class MinidomTest(unittest.TestCase):
# Set this attribute to be an ID and make sure that doesn't change
# when changing the value:
el.setIdAttribute("spam")
- self.confirm(len(el.attributes) == 1
- and el.attributes["spam"].value == "bam"
- and el.attributes["spam"].nodeValue == "bam"
- and el.getAttribute("spam") == "bam"
- and el.getAttributeNode("spam").isId)
+ self.assertEqual(len(el.attributes), 1)
+ self.assertEqual(el.attributes["spam"].value, "bam")
+ self.assertEqual(el.attributes["spam"].nodeValue, "bam")
+ self.assertEqual(el.getAttribute("spam"), "bam")
+ self.assertTrue(el.getAttributeNode("spam").isId)
el.attributes["spam"] = "ham"
- self.confirm(len(el.attributes) == 1
- and el.attributes["spam"].value == "ham"
- and el.attributes["spam"].nodeValue == "ham"
- and el.getAttribute("spam") == "ham"
- and el.attributes["spam"].isId)
+ self.assertEqual(len(el.attributes), 1)
+ self.assertEqual(el.attributes["spam"].value, "ham")
+ self.assertEqual(el.attributes["spam"].nodeValue, "ham")
+ self.assertEqual(el.getAttribute("spam"), "ham")
+ self.assertTrue(el.attributes["spam"].isId)
el.setAttribute("spam2", "bam")
- self.confirm(len(el.attributes) == 2
- and el.attributes["spam"].value == "ham"
- and el.attributes["spam"].nodeValue == "ham"
- and el.getAttribute("spam") == "ham"
- and el.attributes["spam2"].value == "bam"
- and el.attributes["spam2"].nodeValue == "bam"
- and el.getAttribute("spam2") == "bam")
+ self.assertEqual(len(el.attributes), 2)
+ self.assertEqual(el.attributes["spam"].value, "ham")
+ self.assertEqual(el.attributes["spam"].nodeValue, "ham")
+ self.assertEqual(el.getAttribute("spam"), "ham")
+ self.assertEqual(el.attributes["spam2"].value, "bam")
+ self.assertEqual(el.attributes["spam2"].nodeValue, "bam")
+ self.assertEqual(el.getAttribute("spam2"), "bam")
el.attributes["spam2"] = "bam2"
- self.confirm(len(el.attributes) == 2
- and el.attributes["spam"].value == "ham"
- and el.attributes["spam"].nodeValue == "ham"
- and el.getAttribute("spam") == "ham"
- and el.attributes["spam2"].value == "bam2"
- and el.attributes["spam2"].nodeValue == "bam2"
- and el.getAttribute("spam2") == "bam2")
+
+ self.assertEqual(len(el.attributes), 2)
+ self.assertEqual(el.attributes["spam"].value, "ham")
+ self.assertEqual(el.attributes["spam"].nodeValue, "ham")
+ self.assertEqual(el.getAttribute("spam"), "ham")
+ self.assertEqual(el.attributes["spam2"].value, "bam2")
+ self.assertEqual(el.attributes["spam2"].nodeValue, "bam2")
+ self.assertEqual(el.getAttribute("spam2"), "bam2")
dom.unlink()
def testGetAttrList(self):
@@ -448,12 +446,12 @@ class MinidomTest(unittest.TestCase):
dom = parseString(d)
elems = dom.getElementsByTagNameNS("http://pyxml.sf.net/minidom",
"myelem")
- self.confirm(len(elems) == 1
- and elems[0].namespaceURI == "http://pyxml.sf.net/minidom"
- and elems[0].localName == "myelem"
- and elems[0].prefix == "minidom"
- and elems[0].tagName == "minidom:myelem"
- and elems[0].nodeName == "minidom:myelem")
+ self.assertEqual(len(elems), 1)
+ self.assertEqual(elems[0].namespaceURI, "http://pyxml.sf.net/minidom")
+ self.assertEqual(elems[0].localName, "myelem")
+ self.assertEqual(elems[0].prefix, "minidom")
+ self.assertEqual(elems[0].tagName, "minidom:myelem")
+ self.assertEqual(elems[0].nodeName, "minidom:myelem")
dom.unlink()
def get_empty_nodelist_from_elements_by_tagName_ns_helper(self, doc, nsuri,
@@ -602,17 +600,17 @@ class MinidomTest(unittest.TestCase):
def testProcessingInstruction(self):
dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
pi = dom.documentElement.firstChild
- self.confirm(pi.target == "mypi"
- and pi.data == "data \t\n "
- and pi.nodeName == "mypi"
- and pi.nodeType == Node.PROCESSING_INSTRUCTION_NODE
- and pi.attributes is None
- and not pi.hasChildNodes()
- and len(pi.childNodes) == 0
- and pi.firstChild is None
- and pi.lastChild is None
- and pi.localName is None
- and pi.namespaceURI == xml.dom.EMPTY_NAMESPACE)
+ self.assertEqual(pi.target, "mypi")
+ self.assertEqual(pi.data, "data \t\n ")
+ self.assertEqual(pi.nodeName, "mypi")
+ self.assertEqual(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE)
+ self.assertIsNone(pi.attributes)
+ self.assertFalse(pi.hasChildNodes())
+ self.assertEqual(len(pi.childNodes), 0)
+ self.assertIsNone(pi.firstChild)
+ self.assertIsNone(pi.lastChild)
+ self.assertIsNone(pi.localName)
+ self.assertEqual(pi.namespaceURI, xml.dom.EMPTY_NAMESPACE)
def testProcessingInstructionRepr(self):
dom = parseString('<e><?mypi \t\n data \t\n ?></e>')
@@ -718,19 +716,16 @@ class MinidomTest(unittest.TestCase):
keys2 = list(attrs2.keys())
keys1.sort()
keys2.sort()
- self.assertEqual(keys1, keys2,
- "clone of element has same attribute keys")
+ self.assertEqual(keys1, keys2)
for i in range(len(keys1)):
a1 = attrs1.item(i)
a2 = attrs2.item(i)
- self.confirm(a1 is not a2
- and a1.value == a2.value
- and a1.nodeValue == a2.nodeValue
- and a1.namespaceURI == a2.namespaceURI
- and a1.localName == a2.localName
- , "clone of attribute node has proper attribute values")
- self.assertIs(a2.ownerElement, e2,
- "clone of attribute node correctly owned")
+ self.assertIsNot(a1, a2)
+ self.assertEqual(a1.value, a2.value)
+ self.assertEqual(a1.nodeValue, a2.nodeValue)
+ self.assertEqual(a1.namespaceURI,a2.namespaceURI)
+ self.assertEqual(a1.localName, a2.localName)
+ self.assertIs(a2.ownerElement, e2)
def _setupCloneElement(self, deep):
dom = parseString("<doc attr='value'><foo/></doc>")
@@ -746,20 +741,19 @@ class MinidomTest(unittest.TestCase):
def testCloneElementShallow(self):
dom, clone = self._setupCloneElement(0)
- self.confirm(len(clone.childNodes) == 0
- and clone.childNodes.length == 0
- and clone.parentNode is None
- and clone.toxml() == '<doc attr="value"/>'
- , "testCloneElementShallow")
+ self.assertEqual(len(clone.childNodes), 0)
+ self.assertEqual(clone.childNodes.length, 0)
+ self.assertIsNone(clone.parentNode)
+ self.assertEqual(clone.toxml(), '<doc attr="value"/>')
+
dom.unlink()
def testCloneElementDeep(self):
dom, clone = self._setupCloneElement(1)
- self.confirm(len(clone.childNodes) == 1
- and clone.childNodes.length == 1
- and clone.parentNode is None
- and clone.toxml() == '<doc attr="value"><foo/></doc>'
- , "testCloneElementDeep")
+ self.assertEqual(len(clone.childNodes), 1)
+ self.assertEqual(clone.childNodes.length, 1)
+ self.assertIsNone(clone.parentNode)
+ self.assertTrue(clone.toxml(), '<doc attr="value"><foo/></doc>')
dom.unlink()
def testCloneDocumentShallow(self):
diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py
index 263e4e6f394..a932ac80117 100644
--- a/Lib/test/test_monitoring.py
+++ b/Lib/test/test_monitoring.py
@@ -2157,6 +2157,21 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
sys.monitoring.restart_events()
sys.monitoring.set_events(0, 0)
+ def test_134879(self):
+ # gh-134789
+ # Specialized FOR_ITER not incrementing index
+ def foo():
+ t = 0
+ for i in [1,2,3,4]:
+ t += i
+ self.assertEqual(t, 10)
+
+ sys.monitoring.use_tool_id(0, "test")
+ self.addCleanup(sys.monitoring.free_tool_id, 0)
+ sys.monitoring.set_local_events(0, foo.__code__, E.BRANCH_LEFT | E.BRANCH_RIGHT)
+ foo()
+ sys.monitoring.set_local_events(0, foo.__code__, 0)
+
class TestOptimizer(MonitoringTestBase, unittest.TestCase):
diff --git a/Lib/test/test_netrc.py b/Lib/test/test_netrc.py
index 81e11a293cc..9d720f62710 100644
--- a/Lib/test/test_netrc.py
+++ b/Lib/test/test_netrc.py
@@ -1,11 +1,7 @@
import netrc, os, unittest, sys, textwrap
+from test import support
from test.support import os_helper
-try:
- import pwd
-except ImportError:
- pwd = None
-
temp_filename = os_helper.TESTFN
class NetrcTestCase(unittest.TestCase):
@@ -269,9 +265,14 @@ class NetrcTestCase(unittest.TestCase):
machine bar.domain.com login foo password pass
""", '#pass')
+ @unittest.skipUnless(support.is_wasi, 'WASI only test')
+ def test_security_on_WASI(self):
+ self.assertFalse(netrc._can_security_check())
+ self.assertEqual(netrc._getpwuid(0), 'uid 0')
+ self.assertEqual(netrc._getpwuid(123456), 'uid 123456')
@unittest.skipUnless(os.name == 'posix', 'POSIX only test')
- @unittest.skipIf(pwd is None, 'security check requires pwd module')
+ @unittest.skipUnless(hasattr(os, 'getuid'), "os.getuid is required")
@os_helper.skip_unless_working_chmod
def test_security(self):
# This test is incomplete since we are normally not run as root and
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index c10387b58e3..22f6403d482 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -6,8 +6,9 @@ import subprocess
import sys
import unittest
import warnings
-from test.support import cpython_only, os_helper
-from test.support import TestFailed, is_emscripten
+from ntpath import ALLOW_MISSING
+from test import support
+from test.support import TestFailed, cpython_only, os_helper
from test.support.os_helper import FakePath
from test import test_genericpath
from tempfile import TemporaryFile
@@ -77,6 +78,10 @@ def tester(fn, wantResult):
%(str(fn), str(wantResult), repr(gotResult)))
+def _parameterize(*parameters):
+ return support.subTests('kwargs', parameters, _do_cleanups=True)
+
+
class NtpathTestCase(unittest.TestCase):
def assertPathEqual(self, path1, path2):
if path1 == path2 or _norm(path1) == _norm(path2):
@@ -124,6 +129,22 @@ class TestNtpath(NtpathTestCase):
tester('ntpath.splitdrive("//?/UNC/server/share/dir")',
("//?/UNC/server/share", "/dir"))
+ def test_splitdrive_invalid_paths(self):
+ splitdrive = ntpath.splitdrive
+ self.assertEqual(splitdrive('\\\\ser\x00ver\\sha\x00re\\di\x00r'),
+ ('\\\\ser\x00ver\\sha\x00re', '\\di\x00r'))
+ self.assertEqual(splitdrive(b'\\\\ser\x00ver\\sha\x00re\\di\x00r'),
+ (b'\\\\ser\x00ver\\sha\x00re', b'\\di\x00r'))
+ self.assertEqual(splitdrive("\\\\\udfff\\\udffe\\\udffd"),
+ ('\\\\\udfff\\\udffe', '\\\udffd'))
+ if sys.platform == 'win32':
+ self.assertRaises(UnicodeDecodeError, splitdrive, b'\\\\\xff\\share\\dir')
+ self.assertRaises(UnicodeDecodeError, splitdrive, b'\\\\server\\\xff\\dir')
+ self.assertRaises(UnicodeDecodeError, splitdrive, b'\\\\server\\share\\\xff')
+ else:
+ self.assertEqual(splitdrive(b'\\\\\xff\\\xfe\\\xfd'),
+ (b'\\\\\xff\\\xfe', b'\\\xfd'))
+
def test_splitroot(self):
tester("ntpath.splitroot('')", ('', '', ''))
tester("ntpath.splitroot('foo')", ('', '', 'foo'))
@@ -214,6 +235,22 @@ class TestNtpath(NtpathTestCase):
tester('ntpath.splitroot(" :/foo")', (" :", "/", "foo"))
tester('ntpath.splitroot("/:/foo")', ("", "/", ":/foo"))
+ def test_splitroot_invalid_paths(self):
+ splitroot = ntpath.splitroot
+ self.assertEqual(splitroot('\\\\ser\x00ver\\sha\x00re\\di\x00r'),
+ ('\\\\ser\x00ver\\sha\x00re', '\\', 'di\x00r'))
+ self.assertEqual(splitroot(b'\\\\ser\x00ver\\sha\x00re\\di\x00r'),
+ (b'\\\\ser\x00ver\\sha\x00re', b'\\', b'di\x00r'))
+ self.assertEqual(splitroot("\\\\\udfff\\\udffe\\\udffd"),
+ ('\\\\\udfff\\\udffe', '\\', '\udffd'))
+ if sys.platform == 'win32':
+ self.assertRaises(UnicodeDecodeError, splitroot, b'\\\\\xff\\share\\dir')
+ self.assertRaises(UnicodeDecodeError, splitroot, b'\\\\server\\\xff\\dir')
+ self.assertRaises(UnicodeDecodeError, splitroot, b'\\\\server\\share\\\xff')
+ else:
+ self.assertEqual(splitroot(b'\\\\\xff\\\xfe\\\xfd'),
+ (b'\\\\\xff\\\xfe', b'\\', b'\xfd'))
+
def test_split(self):
tester('ntpath.split("c:\\foo\\bar")', ('c:\\foo', 'bar'))
tester('ntpath.split("\\\\conky\\mountpoint\\foo\\bar")',
@@ -226,6 +263,21 @@ class TestNtpath(NtpathTestCase):
tester('ntpath.split("c:/")', ('c:/', ''))
tester('ntpath.split("//conky/mountpoint/")', ('//conky/mountpoint/', ''))
+ def test_split_invalid_paths(self):
+ split = ntpath.split
+ self.assertEqual(split('c:\\fo\x00o\\ba\x00r'),
+ ('c:\\fo\x00o', 'ba\x00r'))
+ self.assertEqual(split(b'c:\\fo\x00o\\ba\x00r'),
+ (b'c:\\fo\x00o', b'ba\x00r'))
+ self.assertEqual(split('c:\\\udfff\\\udffe'),
+ ('c:\\\udfff', '\udffe'))
+ if sys.platform == 'win32':
+ self.assertRaises(UnicodeDecodeError, split, b'c:\\\xff\\bar')
+ self.assertRaises(UnicodeDecodeError, split, b'c:\\foo\\\xff')
+ else:
+ self.assertEqual(split(b'c:\\\xff\\\xfe'),
+ (b'c:\\\xff', b'\xfe'))
+
def test_isabs(self):
tester('ntpath.isabs("foo\\bar")', 0)
tester('ntpath.isabs("foo/bar")', 0)
@@ -333,6 +385,30 @@ class TestNtpath(NtpathTestCase):
tester("ntpath.join('D:a', './c:b')", 'D:a\\.\\c:b')
tester("ntpath.join('D:/a', './c:b')", 'D:\\a\\.\\c:b')
+ def test_normcase(self):
+ normcase = ntpath.normcase
+ self.assertEqual(normcase(''), '')
+ self.assertEqual(normcase(b''), b'')
+ self.assertEqual(normcase('ABC'), 'abc')
+ self.assertEqual(normcase(b'ABC'), b'abc')
+ self.assertEqual(normcase('\xc4\u0141\u03a8'), '\xe4\u0142\u03c8')
+ expected = '\u03c9\u2126' if sys.platform == 'win32' else '\u03c9\u03c9'
+ self.assertEqual(normcase('\u03a9\u2126'), expected)
+ if sys.platform == 'win32' or sys.getfilesystemencoding() == 'utf-8':
+ self.assertEqual(normcase('\xc4\u0141\u03a8'.encode()),
+ '\xe4\u0142\u03c8'.encode())
+ self.assertEqual(normcase('\u03a9\u2126'.encode()),
+ expected.encode())
+
+ def test_normcase_invalid_paths(self):
+ normcase = ntpath.normcase
+ self.assertEqual(normcase('abc\x00def'), 'abc\x00def')
+ self.assertEqual(normcase(b'abc\x00def'), b'abc\x00def')
+ self.assertEqual(normcase('\udfff'), '\udfff')
+ if sys.platform == 'win32':
+ path = b'ABC' + bytes(range(128, 256))
+ self.assertEqual(normcase(path), path.lower())
+
def test_normpath(self):
tester("ntpath.normpath('A//////././//.//B')", r'A\B')
tester("ntpath.normpath('A/./B')", r'A\B')
@@ -381,6 +457,21 @@ class TestNtpath(NtpathTestCase):
tester("ntpath.normpath('\\\\')", '\\\\')
tester("ntpath.normpath('//?/UNC/server/share/..')", '\\\\?\\UNC\\server\\share\\')
+ def test_normpath_invalid_paths(self):
+ normpath = ntpath.normpath
+ self.assertEqual(normpath('fo\x00o'), 'fo\x00o')
+ self.assertEqual(normpath(b'fo\x00o'), b'fo\x00o')
+ self.assertEqual(normpath('fo\x00o\\..\\bar'), 'bar')
+ self.assertEqual(normpath(b'fo\x00o\\..\\bar'), b'bar')
+ self.assertEqual(normpath('\udfff'), '\udfff')
+ self.assertEqual(normpath('\udfff\\..\\foo'), 'foo')
+ if sys.platform == 'win32':
+ self.assertRaises(UnicodeDecodeError, normpath, b'\xff')
+ self.assertRaises(UnicodeDecodeError, normpath, b'\xff\\..\\foo')
+ else:
+ self.assertEqual(normpath(b'\xff'), b'\xff')
+ self.assertEqual(normpath(b'\xff\\..\\foo'), b'foo')
+
def test_realpath_curdir(self):
expected = ntpath.normpath(os.getcwd())
tester("ntpath.realpath('.')", expected)
@@ -389,6 +480,27 @@ class TestNtpath(NtpathTestCase):
tester("ntpath.realpath('.\\.')", expected)
tester("ntpath.realpath('\\'.join(['.'] * 100))", expected)
+ def test_realpath_curdir_strict(self):
+ expected = ntpath.normpath(os.getcwd())
+ tester("ntpath.realpath('.', strict=True)", expected)
+ tester("ntpath.realpath('./.', strict=True)", expected)
+ tester("ntpath.realpath('/'.join(['.'] * 100), strict=True)", expected)
+ tester("ntpath.realpath('.\\.', strict=True)", expected)
+ tester("ntpath.realpath('\\'.join(['.'] * 100), strict=True)", expected)
+
+ def test_realpath_curdir_missing_ok(self):
+ expected = ntpath.normpath(os.getcwd())
+ tester("ntpath.realpath('.', strict=ALLOW_MISSING)",
+ expected)
+ tester("ntpath.realpath('./.', strict=ALLOW_MISSING)",
+ expected)
+ tester("ntpath.realpath('/'.join(['.'] * 100), strict=ALLOW_MISSING)",
+ expected)
+ tester("ntpath.realpath('.\\.', strict=ALLOW_MISSING)",
+ expected)
+ tester("ntpath.realpath('\\'.join(['.'] * 100), strict=ALLOW_MISSING)",
+ expected)
+
def test_realpath_pardir(self):
expected = ntpath.normpath(os.getcwd())
tester("ntpath.realpath('..')", ntpath.dirname(expected))
@@ -401,28 +513,59 @@ class TestNtpath(NtpathTestCase):
tester("ntpath.realpath('\\'.join(['..'] * 50))",
ntpath.splitdrive(expected)[0] + '\\')
+ def test_realpath_pardir_strict(self):
+ expected = ntpath.normpath(os.getcwd())
+ tester("ntpath.realpath('..', strict=True)", ntpath.dirname(expected))
+ tester("ntpath.realpath('../..', strict=True)",
+ ntpath.dirname(ntpath.dirname(expected)))
+ tester("ntpath.realpath('/'.join(['..'] * 50), strict=True)",
+ ntpath.splitdrive(expected)[0] + '\\')
+ tester("ntpath.realpath('..\\..', strict=True)",
+ ntpath.dirname(ntpath.dirname(expected)))
+ tester("ntpath.realpath('\\'.join(['..'] * 50), strict=True)",
+ ntpath.splitdrive(expected)[0] + '\\')
+
+ def test_realpath_pardir_missing_ok(self):
+ expected = ntpath.normpath(os.getcwd())
+ tester("ntpath.realpath('..', strict=ALLOW_MISSING)",
+ ntpath.dirname(expected))
+ tester("ntpath.realpath('../..', strict=ALLOW_MISSING)",
+ ntpath.dirname(ntpath.dirname(expected)))
+ tester("ntpath.realpath('/'.join(['..'] * 50), strict=ALLOW_MISSING)",
+ ntpath.splitdrive(expected)[0] + '\\')
+ tester("ntpath.realpath('..\\..', strict=ALLOW_MISSING)",
+ ntpath.dirname(ntpath.dirname(expected)))
+ tester("ntpath.realpath('\\'.join(['..'] * 50), strict=ALLOW_MISSING)",
+ ntpath.splitdrive(expected)[0] + '\\')
+
@os_helper.skip_unless_symlink
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
- def test_realpath_basic(self):
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_basic(self, kwargs):
ABSTFN = ntpath.abspath(os_helper.TESTFN)
open(ABSTFN, "wb").close()
self.addCleanup(os_helper.unlink, ABSTFN)
self.addCleanup(os_helper.unlink, ABSTFN + "1")
os.symlink(ABSTFN, ABSTFN + "1")
- self.assertPathEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)
- self.assertPathEqual(ntpath.realpath(os.fsencode(ABSTFN + "1")),
+ self.assertPathEqual(ntpath.realpath(ABSTFN + "1", **kwargs), ABSTFN)
+ self.assertPathEqual(ntpath.realpath(os.fsencode(ABSTFN + "1"), **kwargs),
os.fsencode(ABSTFN))
# gh-88013: call ntpath.realpath with binary drive name may raise a
# TypeError. The drive should not exist to reproduce the bug.
drives = {f"{c}:\\" for c in string.ascii_uppercase} - set(os.listdrives())
d = drives.pop().encode()
- self.assertEqual(ntpath.realpath(d), d)
+ self.assertEqual(ntpath.realpath(d, strict=False), d)
# gh-106242: Embedded nulls and non-strict fallback to abspath
- self.assertEqual(ABSTFN + "\0spam",
- ntpath.realpath(os_helper.TESTFN + "\0spam", strict=False))
+ if kwargs:
+ with self.assertRaises(OSError):
+ ntpath.realpath(os_helper.TESTFN + "\0spam",
+ **kwargs)
+ else:
+ self.assertEqual(ABSTFN + "\0spam",
+ ntpath.realpath(os_helper.TESTFN + "\0spam", **kwargs))
@os_helper.skip_unless_symlink
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
@@ -434,19 +577,77 @@ class TestNtpath(NtpathTestCase):
self.addCleanup(os_helper.unlink, ABSTFN)
self.assertRaises(FileNotFoundError, ntpath.realpath, ABSTFN, strict=True)
self.assertRaises(FileNotFoundError, ntpath.realpath, ABSTFN + "2", strict=True)
+
+ @unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
+ def test_realpath_invalid_paths(self):
+ realpath = ntpath.realpath
+ ABSTFN = ntpath.abspath(os_helper.TESTFN)
+ ABSTFNb = os.fsencode(ABSTFN)
+ path = ABSTFN + '\x00'
+ # gh-106242: Embedded nulls and non-strict fallback to abspath
+ self.assertEqual(realpath(path, strict=False), path)
# gh-106242: Embedded nulls should raise OSError (not ValueError)
- self.assertRaises(OSError, ntpath.realpath, ABSTFN + "\0spam", strict=True)
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
+ path = ABSTFNb + b'\x00'
+ self.assertEqual(realpath(path, strict=False), path)
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
+ path = ABSTFN + '\\nonexistent\\x\x00'
+ self.assertEqual(realpath(path, strict=False), path)
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
+ path = ABSTFNb + b'\\nonexistent\\x\x00'
+ self.assertEqual(realpath(path, strict=False), path)
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
+ path = ABSTFN + '\x00\\..'
+ self.assertEqual(realpath(path, strict=False), os.getcwd())
+ self.assertEqual(realpath(path, strict=True), os.getcwd())
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), os.getcwd())
+ path = ABSTFNb + b'\x00\\..'
+ self.assertEqual(realpath(path, strict=False), os.getcwdb())
+ self.assertEqual(realpath(path, strict=True), os.getcwdb())
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), os.getcwdb())
+ path = ABSTFN + '\\nonexistent\\x\x00\\..'
+ self.assertEqual(realpath(path, strict=False), ABSTFN + '\\nonexistent')
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), ABSTFN + '\\nonexistent')
+ path = ABSTFNb + b'\\nonexistent\\x\x00\\..'
+ self.assertEqual(realpath(path, strict=False), ABSTFNb + b'\\nonexistent')
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), ABSTFNb + b'\\nonexistent')
+
+ @unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_invalid_unicode_paths(self, kwargs):
+ realpath = ntpath.realpath
+ ABSTFN = ntpath.abspath(os_helper.TESTFN)
+ ABSTFNb = os.fsencode(ABSTFN)
+ path = ABSTFNb + b'\xff'
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
+ path = ABSTFNb + b'\\nonexistent\\\xff'
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
+ path = ABSTFNb + b'\xff\\..'
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
+ path = ABSTFNb + b'\\nonexistent\\\xff\\..'
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
+ self.assertRaises(UnicodeDecodeError, realpath, path, **kwargs)
@os_helper.skip_unless_symlink
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
- def test_realpath_relative(self):
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_relative(self, kwargs):
ABSTFN = ntpath.abspath(os_helper.TESTFN)
open(ABSTFN, "wb").close()
self.addCleanup(os_helper.unlink, ABSTFN)
self.addCleanup(os_helper.unlink, ABSTFN + "1")
os.symlink(ABSTFN, ntpath.relpath(ABSTFN + "1"))
- self.assertPathEqual(ntpath.realpath(ABSTFN + "1"), ABSTFN)
+ self.assertPathEqual(ntpath.realpath(ABSTFN + "1", **kwargs), ABSTFN)
@os_helper.skip_unless_symlink
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
@@ -598,7 +799,62 @@ class TestNtpath(NtpathTestCase):
@os_helper.skip_unless_symlink
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
- def test_realpath_symlink_prefix(self):
+ def test_realpath_symlink_loops_raise(self):
+ # Symlink loops raise OSError in ALLOW_MISSING mode
+ ABSTFN = ntpath.abspath(os_helper.TESTFN)
+ self.addCleanup(os_helper.unlink, ABSTFN)
+ self.addCleanup(os_helper.unlink, ABSTFN + "1")
+ self.addCleanup(os_helper.unlink, ABSTFN + "2")
+ self.addCleanup(os_helper.unlink, ABSTFN + "y")
+ self.addCleanup(os_helper.unlink, ABSTFN + "c")
+ self.addCleanup(os_helper.unlink, ABSTFN + "a")
+ self.addCleanup(os_helper.unlink, ABSTFN + "x")
+
+ os.symlink(ABSTFN, ABSTFN)
+ self.assertRaises(OSError, ntpath.realpath, ABSTFN, strict=ALLOW_MISSING)
+
+ os.symlink(ABSTFN + "1", ABSTFN + "2")
+ os.symlink(ABSTFN + "2", ABSTFN + "1")
+ self.assertRaises(OSError, ntpath.realpath, ABSTFN + "1",
+ strict=ALLOW_MISSING)
+ self.assertRaises(OSError, ntpath.realpath, ABSTFN + "2",
+ strict=ALLOW_MISSING)
+ self.assertRaises(OSError, ntpath.realpath, ABSTFN + "1\\x",
+ strict=ALLOW_MISSING)
+
+ # Windows eliminates '..' components before resolving links;
+ # realpath is not expected to raise if this removes the loop.
+ self.assertPathEqual(ntpath.realpath(ABSTFN + "1\\.."),
+ ntpath.dirname(ABSTFN))
+ self.assertPathEqual(ntpath.realpath(ABSTFN + "1\\..\\x"),
+ ntpath.dirname(ABSTFN) + "\\x")
+
+ os.symlink(ABSTFN + "x", ABSTFN + "y")
+ self.assertPathEqual(ntpath.realpath(ABSTFN + "1\\..\\"
+ + ntpath.basename(ABSTFN) + "y"),
+ ABSTFN + "x")
+ self.assertRaises(
+ OSError, ntpath.realpath,
+ ABSTFN + "1\\..\\" + ntpath.basename(ABSTFN) + "1",
+ strict=ALLOW_MISSING)
+
+ os.symlink(ntpath.basename(ABSTFN) + "a\\b", ABSTFN + "a")
+ self.assertRaises(OSError, ntpath.realpath, ABSTFN + "a",
+ strict=ALLOW_MISSING)
+
+ os.symlink("..\\" + ntpath.basename(ntpath.dirname(ABSTFN))
+ + "\\" + ntpath.basename(ABSTFN) + "c", ABSTFN + "c")
+ self.assertRaises(OSError, ntpath.realpath, ABSTFN + "c",
+ strict=ALLOW_MISSING)
+
+ # Test using relative path as well.
+ self.assertRaises(OSError, ntpath.realpath, ntpath.basename(ABSTFN),
+ strict=ALLOW_MISSING)
+
+ @os_helper.skip_unless_symlink
+ @unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_symlink_prefix(self, kwargs):
ABSTFN = ntpath.abspath(os_helper.TESTFN)
self.addCleanup(os_helper.unlink, ABSTFN + "3")
self.addCleanup(os_helper.unlink, "\\\\?\\" + ABSTFN + "3.")
@@ -613,9 +869,9 @@ class TestNtpath(NtpathTestCase):
f.write(b'1')
os.symlink("\\\\?\\" + ABSTFN + "3.", ABSTFN + "3.link")
- self.assertPathEqual(ntpath.realpath(ABSTFN + "3link"),
+ self.assertPathEqual(ntpath.realpath(ABSTFN + "3link", **kwargs),
ABSTFN + "3")
- self.assertPathEqual(ntpath.realpath(ABSTFN + "3.link"),
+ self.assertPathEqual(ntpath.realpath(ABSTFN + "3.link", **kwargs),
"\\\\?\\" + ABSTFN + "3.")
# Resolved paths should be usable to open target files
@@ -625,14 +881,17 @@ class TestNtpath(NtpathTestCase):
self.assertEqual(f.read(), b'1')
# When the prefix is included, it is not stripped
- self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3link"),
+ self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3link", **kwargs),
"\\\\?\\" + ABSTFN + "3")
- self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3.link"),
+ self.assertPathEqual(ntpath.realpath("\\\\?\\" + ABSTFN + "3.link", **kwargs),
"\\\\?\\" + ABSTFN + "3.")
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
def test_realpath_nul(self):
tester("ntpath.realpath('NUL')", r'\\.\NUL')
+ tester("ntpath.realpath('NUL', strict=False)", r'\\.\NUL')
+ tester("ntpath.realpath('NUL', strict=True)", r'\\.\NUL')
+ tester("ntpath.realpath('NUL', strict=ALLOW_MISSING)", r'\\.\NUL')
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
@unittest.skipUnless(HAVE_GETSHORTPATHNAME, 'need _getshortpathname')
@@ -656,12 +915,20 @@ class TestNtpath(NtpathTestCase):
self.assertPathEqual(test_file_long, ntpath.realpath(test_file_short))
- with os_helper.change_cwd(test_dir_long):
- self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
- with os_helper.change_cwd(test_dir_long.lower()):
- self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
- with os_helper.change_cwd(test_dir_short):
- self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
+ for kwargs in {}, {'strict': True}, {'strict': ALLOW_MISSING}:
+ with self.subTest(**kwargs):
+ with os_helper.change_cwd(test_dir_long):
+ self.assertPathEqual(
+ test_file_long,
+ ntpath.realpath("file.txt", **kwargs))
+ with os_helper.change_cwd(test_dir_long.lower()):
+ self.assertPathEqual(
+ test_file_long,
+ ntpath.realpath("file.txt", **kwargs))
+ with os_helper.change_cwd(test_dir_short):
+ self.assertPathEqual(
+ test_file_long,
+ ntpath.realpath("file.txt", **kwargs))
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
def test_realpath_permission(self):
@@ -682,12 +949,15 @@ class TestNtpath(NtpathTestCase):
# Automatic generation of short names may be disabled on
# NTFS volumes for the sake of performance.
# They're not supported at all on ReFS and exFAT.
- subprocess.run(
+ p = subprocess.run(
# Try to set the short name manually.
['fsutil.exe', 'file', 'setShortName', test_file, 'LONGFI~1.TXT'],
creationflags=subprocess.DETACHED_PROCESS
)
+ if p.returncode:
+ raise unittest.SkipTest('failed to set short name')
+
try:
self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
except AssertionError:
@@ -812,8 +1082,6 @@ class TestNtpath(NtpathTestCase):
tester('ntpath.abspath("C:/nul")', "\\\\.\\nul")
tester('ntpath.abspath("C:\\nul")', "\\\\.\\nul")
self.assertTrue(ntpath.isabs(ntpath.abspath("C:spam")))
- self.assertEqual(ntpath.abspath("C:\x00"), ntpath.join(ntpath.abspath("C:"), "\x00"))
- self.assertEqual(ntpath.abspath("\x00:spam"), "\x00:\\spam")
tester('ntpath.abspath("//..")', "\\\\")
tester('ntpath.abspath("//../")', "\\\\..\\")
tester('ntpath.abspath("//../..")', "\\\\..\\")
@@ -847,6 +1115,26 @@ class TestNtpath(NtpathTestCase):
drive, _ = ntpath.splitdrive(cwd_dir)
tester('ntpath.abspath("/abc/")', drive + "\\abc")
+ def test_abspath_invalid_paths(self):
+ abspath = ntpath.abspath
+ if sys.platform == 'win32':
+ self.assertEqual(abspath("C:\x00"), ntpath.join(abspath("C:"), "\x00"))
+ self.assertEqual(abspath(b"C:\x00"), ntpath.join(abspath(b"C:"), b"\x00"))
+ self.assertEqual(abspath("\x00:spam"), "\x00:\\spam")
+ self.assertEqual(abspath(b"\x00:spam"), b"\x00:\\spam")
+ self.assertEqual(abspath('c:\\fo\x00o'), 'c:\\fo\x00o')
+ self.assertEqual(abspath(b'c:\\fo\x00o'), b'c:\\fo\x00o')
+ self.assertEqual(abspath('c:\\fo\x00o\\..\\bar'), 'c:\\bar')
+ self.assertEqual(abspath(b'c:\\fo\x00o\\..\\bar'), b'c:\\bar')
+ self.assertEqual(abspath('c:\\\udfff'), 'c:\\\udfff')
+ self.assertEqual(abspath('c:\\\udfff\\..\\foo'), 'c:\\foo')
+ if sys.platform == 'win32':
+ self.assertRaises(UnicodeDecodeError, abspath, b'c:\\\xff')
+ self.assertRaises(UnicodeDecodeError, abspath, b'c:\\\xff\\..\\foo')
+ else:
+ self.assertEqual(abspath(b'c:\\\xff'), b'c:\\\xff')
+ self.assertEqual(abspath(b'c:\\\xff\\..\\foo'), b'c:\\foo')
+
def test_relpath(self):
tester('ntpath.relpath("a")', 'a')
tester('ntpath.relpath(ntpath.abspath("a"))', 'a')
@@ -989,6 +1277,18 @@ class TestNtpath(NtpathTestCase):
self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$"))
self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$\\"))
+ def test_ismount_invalid_paths(self):
+ ismount = ntpath.ismount
+ self.assertFalse(ismount("c:\\\udfff"))
+ if sys.platform == 'win32':
+ self.assertRaises(ValueError, ismount, "c:\\\x00")
+ self.assertRaises(ValueError, ismount, b"c:\\\x00")
+ self.assertRaises(UnicodeDecodeError, ismount, b"c:\\\xff")
+ else:
+ self.assertFalse(ismount("c:\\\x00"))
+ self.assertFalse(ismount(b"c:\\\x00"))
+ self.assertFalse(ismount(b"c:\\\xff"))
+
def test_isreserved(self):
self.assertFalse(ntpath.isreserved(''))
self.assertFalse(ntpath.isreserved('.'))
@@ -1095,6 +1395,13 @@ class TestNtpath(NtpathTestCase):
self.assertFalse(ntpath.isjunction('tmpdir'))
self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir'))
+ def test_isfile_invalid_paths(self):
+ isfile = ntpath.isfile
+ self.assertIs(isfile('/tmp\udfffabcds'), False)
+ self.assertIs(isfile(b'/tmp\xffabcds'), False)
+ self.assertIs(isfile('/tmp\x00abcds'), False)
+ self.assertIs(isfile(b'/tmp\x00abcds'), False)
+
@unittest.skipIf(sys.platform != 'win32', "drive letters are a windows concept")
def test_isfile_driveletter(self):
drive = os.environ.get('SystemDrive')
@@ -1195,9 +1502,6 @@ class PathLikeTests(NtpathTestCase):
def test_path_normcase(self):
self._check_function(self.path.normcase)
- if sys.platform == 'win32':
- self.assertEqual(ntpath.normcase('\u03a9\u2126'), 'ωΩ')
- self.assertEqual(ntpath.normcase('abc\x00def'), 'abc\x00def')
def test_path_isabs(self):
self._check_function(self.path.isabs)
diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py
index a45aafc63fa..30baa090486 100644
--- a/Lib/test/test_opcache.py
+++ b/Lib/test/test_opcache.py
@@ -1810,20 +1810,6 @@ class TestSpecializer(TestBase):
self.assert_specialized(compare_op_str, "COMPARE_OP_STR")
self.assert_no_opcode(compare_op_str, "COMPARE_OP")
- @cpython_only
- @requires_specialization_ft
- def test_load_const(self):
- def load_const():
- def unused(): pass
- # Currently, the empty tuple is immortal, and the otherwise
- # unused nested function's code object is mortal. This test will
- # have to use different values if either of that changes.
- return ()
-
- load_const()
- self.assert_specialized(load_const, "LOAD_CONST_IMMORTAL")
- self.assert_specialized(load_const, "LOAD_CONST_MORTAL")
- self.assert_no_opcode(load_const, "LOAD_CONST")
@cpython_only
@requires_specialization_ft
diff --git a/Lib/test/test_optparse.py b/Lib/test/test_optparse.py
index 8655a0537a5..e476e472780 100644
--- a/Lib/test/test_optparse.py
+++ b/Lib/test/test_optparse.py
@@ -14,8 +14,9 @@ import unittest
from io import StringIO
from test import support
-from test.support import os_helper
+from test.support import cpython_only, os_helper
from test.support.i18n_helper import TestTranslationsBase, update_translation_snapshots
+from test.support.import_helper import ensure_lazy_imports
import optparse
from optparse import make_option, Option, \
@@ -614,9 +615,9 @@ Options:
self.parser.add_option(
"-p", "--prob",
help="blow up with probability PROB [default: %default]")
- self.parser.set_defaults(prob=0.43)
+ self.parser.set_defaults(prob=0.25)
expected_help = self.help_prefix + \
- " -p PROB, --prob=PROB blow up with probability PROB [default: 0.43]\n"
+ " -p PROB, --prob=PROB blow up with probability PROB [default: 0.25]\n"
self.assertHelp(self.parser, expected_help)
def test_alt_expand(self):
@@ -1655,6 +1656,10 @@ class MiscTestCase(unittest.TestCase):
not_exported = {'check_builtin', 'AmbiguousOptionError', 'NO_DEFAULT'}
support.check__all__(self, optparse, not_exported=not_exported)
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("optparse", {"textwrap"})
+
class TestTranslations(TestTranslationsBase):
def test_translations(self):
diff --git a/Lib/test/test_ordered_dict.py b/Lib/test/test_ordered_dict.py
index 9f131a9110d..4204a6a47d2 100644
--- a/Lib/test/test_ordered_dict.py
+++ b/Lib/test/test_ordered_dict.py
@@ -147,7 +147,7 @@ class OrderedDictTests:
def test_abc(self):
OrderedDict = self.OrderedDict
self.assertIsInstance(OrderedDict(), MutableMapping)
- self.assertTrue(issubclass(OrderedDict, MutableMapping))
+ self.assertIsSubclass(OrderedDict, MutableMapping)
def test_clear(self):
OrderedDict = self.OrderedDict
@@ -314,14 +314,14 @@ class OrderedDictTests:
check(dup)
self.assertIs(dup.x, od.x)
self.assertIs(dup.z, od.z)
- self.assertFalse(hasattr(dup, 'y'))
+ self.assertNotHasAttr(dup, 'y')
dup = copy.deepcopy(od)
check(dup)
self.assertEqual(dup.x, od.x)
self.assertIsNot(dup.x, od.x)
self.assertEqual(dup.z, od.z)
self.assertIsNot(dup.z, od.z)
- self.assertFalse(hasattr(dup, 'y'))
+ self.assertNotHasAttr(dup, 'y')
# pickle directly pulls the module, so we have to fake it
with replaced_module('collections', self.module):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
@@ -330,7 +330,7 @@ class OrderedDictTests:
check(dup)
self.assertEqual(dup.x, od.x)
self.assertEqual(dup.z, od.z)
- self.assertFalse(hasattr(dup, 'y'))
+ self.assertNotHasAttr(dup, 'y')
check(eval(repr(od)))
update_test = OrderedDict()
update_test.update(od)
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 333179a71e3..5217037ae9d 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -13,7 +13,6 @@ import itertools
import locale
import os
import pickle
-import platform
import select
import selectors
import shutil
@@ -818,7 +817,7 @@ class StatAttributeTests(unittest.TestCase):
self.assertEqual(ctx.exception.errno, errno.EBADF)
def check_file_attributes(self, result):
- self.assertTrue(hasattr(result, 'st_file_attributes'))
+ self.assertHasAttr(result, 'st_file_attributes')
self.assertTrue(isinstance(result.st_file_attributes, int))
self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF)
@@ -1919,6 +1918,10 @@ class MakedirTests(unittest.TestCase):
support.is_wasi,
"WASI's umask is a stub."
)
+ @unittest.skipIf(
+ support.is_emscripten,
+ "TODO: Fails in buildbot; see #135783"
+ )
def test_mode(self):
with os_helper.temp_umask(0o002):
base = os_helper.TESTFN
@@ -2181,7 +2184,7 @@ class GetRandomTests(unittest.TestCase):
self.assertEqual(empty, b'')
def test_getrandom_random(self):
- self.assertTrue(hasattr(os, 'GRND_RANDOM'))
+ self.assertHasAttr(os, 'GRND_RANDOM')
# Don't test os.getrandom(1, os.GRND_RANDOM) to not consume the rare
# resource /dev/random
@@ -4291,13 +4294,8 @@ class EventfdTests(unittest.TestCase):
@unittest.skipIf(sys.platform == "android", "gh-124873: Test is flaky on Android")
@support.requires_linux_version(2, 6, 30)
class TimerfdTests(unittest.TestCase):
- # 1 ms accuracy is reliably achievable on every platform except Android
- # emulators, where we allow 10 ms (gh-108277).
- if sys.platform == "android" and platform.android_ver().is_emulator:
- CLOCK_RES_PLACES = 2
- else:
- CLOCK_RES_PLACES = 3
-
+ # gh-126112: Use 10 ms to tolerate slow buildbots
+ CLOCK_RES_PLACES = 2 # 10 ms
CLOCK_RES = 10 ** -CLOCK_RES_PLACES
CLOCK_RES_NS = 10 ** (9 - CLOCK_RES_PLACES)
@@ -5431,8 +5429,8 @@ class TestPEP519(unittest.TestCase):
def test_pathlike(self):
self.assertEqual('#feelthegil', self.fspath(FakePath('#feelthegil')))
- self.assertTrue(issubclass(FakePath, os.PathLike))
- self.assertTrue(isinstance(FakePath('x'), os.PathLike))
+ self.assertIsSubclass(FakePath, os.PathLike)
+ self.assertIsInstance(FakePath('x'), os.PathLike)
def test_garbage_in_exception_out(self):
vapor = type('blah', (), {})
@@ -5458,8 +5456,8 @@ class TestPEP519(unittest.TestCase):
# true on abstract implementation.
class A(os.PathLike):
pass
- self.assertFalse(issubclass(FakePath, A))
- self.assertTrue(issubclass(FakePath, os.PathLike))
+ self.assertNotIsSubclass(FakePath, A)
+ self.assertIsSubclass(FakePath, os.PathLike)
def test_pathlike_class_getitem(self):
self.assertIsInstance(os.PathLike[bytes], types.GenericAlias)
@@ -5469,7 +5467,7 @@ class TestPEP519(unittest.TestCase):
__slots__ = ()
def __fspath__(self):
return ''
- self.assertFalse(hasattr(A(), '__dict__'))
+ self.assertNotHasAttr(A(), '__dict__')
def test_fspath_set_to_None(self):
class Foo:
diff --git a/Lib/test/test_pathlib/support/lexical_path.py b/Lib/test/test_pathlib/support/lexical_path.py
index f29a521af9b..fd7fbf283a6 100644
--- a/Lib/test/test_pathlib/support/lexical_path.py
+++ b/Lib/test/test_pathlib/support/lexical_path.py
@@ -9,9 +9,10 @@ import posixpath
from . import is_pypi
if is_pypi:
- from pathlib_abc import _JoinablePath
+ from pathlib_abc import vfspath, _JoinablePath
else:
from pathlib.types import _JoinablePath
+ from pathlib._os import vfspath
class LexicalPath(_JoinablePath):
@@ -22,20 +23,20 @@ class LexicalPath(_JoinablePath):
self._segments = pathsegments
def __hash__(self):
- return hash(str(self))
+ return hash(vfspath(self))
def __eq__(self, other):
if not isinstance(other, LexicalPath):
return NotImplemented
- return str(self) == str(other)
+ return vfspath(self) == vfspath(other)
- def __str__(self):
+ def __vfspath__(self):
if not self._segments:
return ''
return self.parser.join(*self._segments)
def __repr__(self):
- return f'{type(self).__name__}({str(self)!r})'
+ return f'{type(self).__name__}({vfspath(self)!r})'
def with_segments(self, *pathsegments):
return type(self)(*pathsegments)
diff --git a/Lib/test/test_pathlib/support/local_path.py b/Lib/test/test_pathlib/support/local_path.py
index d481fd45ead..c1423c545bf 100644
--- a/Lib/test/test_pathlib/support/local_path.py
+++ b/Lib/test/test_pathlib/support/local_path.py
@@ -97,7 +97,7 @@ class LocalPathInfo(PathInfo):
__slots__ = ('_path', '_exists', '_is_dir', '_is_file', '_is_symlink')
def __init__(self, path):
- self._path = str(path)
+ self._path = os.fspath(path)
self._exists = None
self._is_dir = None
self._is_file = None
@@ -139,14 +139,12 @@ class ReadableLocalPath(_ReadablePath, LexicalPath):
Simple implementation of a ReadablePath class for local filesystem paths.
"""
__slots__ = ('info',)
+ __fspath__ = LexicalPath.__vfspath__
def __init__(self, *pathsegments):
super().__init__(*pathsegments)
self.info = LocalPathInfo(self)
- def __fspath__(self):
- return str(self)
-
def __open_rb__(self, buffering=-1):
return open(self, 'rb')
@@ -163,9 +161,7 @@ class WritableLocalPath(_WritablePath, LexicalPath):
"""
__slots__ = ()
-
- def __fspath__(self):
- return str(self)
+ __fspath__ = LexicalPath.__vfspath__
def __open_wb__(self, buffering=-1):
return open(self, 'wb')
diff --git a/Lib/test/test_pathlib/support/zip_path.py b/Lib/test/test_pathlib/support/zip_path.py
index 2905260c9df..21e1d07423a 100644
--- a/Lib/test/test_pathlib/support/zip_path.py
+++ b/Lib/test/test_pathlib/support/zip_path.py
@@ -16,9 +16,10 @@ from stat import S_IFMT, S_ISDIR, S_ISREG, S_ISLNK
from . import is_pypi
if is_pypi:
- from pathlib_abc import PathInfo, _ReadablePath, _WritablePath
+ from pathlib_abc import vfspath, PathInfo, _ReadablePath, _WritablePath
else:
from pathlib.types import PathInfo, _ReadablePath, _WritablePath
+ from pathlib._os import vfspath
class ZipPathGround:
@@ -34,16 +35,16 @@ class ZipPathGround:
root.zip_file.close()
def create_file(self, path, data=b''):
- path.zip_file.writestr(str(path), data)
+ path.zip_file.writestr(vfspath(path), data)
def create_dir(self, path):
- zip_info = zipfile.ZipInfo(str(path) + '/')
+ zip_info = zipfile.ZipInfo(vfspath(path) + '/')
zip_info.external_attr |= stat.S_IFDIR << 16
zip_info.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
path.zip_file.writestr(zip_info, '')
def create_symlink(self, path, target):
- zip_info = zipfile.ZipInfo(str(path))
+ zip_info = zipfile.ZipInfo(vfspath(path))
zip_info.external_attr = stat.S_IFLNK << 16
path.zip_file.writestr(zip_info, target.encode())
@@ -62,28 +63,28 @@ class ZipPathGround:
self.create_symlink(p.joinpath('brokenLinkLoop'), 'brokenLinkLoop')
def readtext(self, p):
- with p.zip_file.open(str(p), 'r') as f:
+ with p.zip_file.open(vfspath(p), 'r') as f:
f = io.TextIOWrapper(f, encoding='utf-8')
return f.read()
def readbytes(self, p):
- with p.zip_file.open(str(p), 'r') as f:
+ with p.zip_file.open(vfspath(p), 'r') as f:
return f.read()
readlink = readtext
def isdir(self, p):
- path_str = str(p) + "/"
+ path_str = vfspath(p) + "/"
return path_str in p.zip_file.NameToInfo
def isfile(self, p):
- info = p.zip_file.NameToInfo.get(str(p))
+ info = p.zip_file.NameToInfo.get(vfspath(p))
if info is None:
return False
return not stat.S_ISLNK(info.external_attr >> 16)
def islink(self, p):
- info = p.zip_file.NameToInfo.get(str(p))
+ info = p.zip_file.NameToInfo.get(vfspath(p))
if info is None:
return False
return stat.S_ISLNK(info.external_attr >> 16)
@@ -240,20 +241,20 @@ class ReadableZipPath(_ReadablePath):
zip_file.filelist = ZipFileList(zip_file)
def __hash__(self):
- return hash((str(self), self.zip_file))
+ return hash((vfspath(self), self.zip_file))
def __eq__(self, other):
if not isinstance(other, ReadableZipPath):
return NotImplemented
- return str(self) == str(other) and self.zip_file is other.zip_file
+ return vfspath(self) == vfspath(other) and self.zip_file is other.zip_file
- def __str__(self):
+ def __vfspath__(self):
if not self._segments:
return ''
return self.parser.join(*self._segments)
def __repr__(self):
- return f'{type(self).__name__}({str(self)!r}, zip_file={self.zip_file!r})'
+ return f'{type(self).__name__}({vfspath(self)!r}, zip_file={self.zip_file!r})'
def with_segments(self, *pathsegments):
return type(self)(*pathsegments, zip_file=self.zip_file)
@@ -261,7 +262,7 @@ class ReadableZipPath(_ReadablePath):
@property
def info(self):
tree = self.zip_file.filelist.tree
- return tree.resolve(str(self), follow_symlinks=False)
+ return tree.resolve(vfspath(self), follow_symlinks=False)
def __open_rb__(self, buffering=-1):
info = self.info.resolve()
@@ -301,36 +302,36 @@ class WritableZipPath(_WritablePath):
self.zip_file = zip_file
def __hash__(self):
- return hash((str(self), self.zip_file))
+ return hash((vfspath(self), self.zip_file))
def __eq__(self, other):
if not isinstance(other, WritableZipPath):
return NotImplemented
- return str(self) == str(other) and self.zip_file is other.zip_file
+ return vfspath(self) == vfspath(other) and self.zip_file is other.zip_file
- def __str__(self):
+ def __vfspath__(self):
if not self._segments:
return ''
return self.parser.join(*self._segments)
def __repr__(self):
- return f'{type(self).__name__}({str(self)!r}, zip_file={self.zip_file!r})'
+ return f'{type(self).__name__}({vfspath(self)!r}, zip_file={self.zip_file!r})'
def with_segments(self, *pathsegments):
return type(self)(*pathsegments, zip_file=self.zip_file)
def __open_wb__(self, buffering=-1):
- return self.zip_file.open(str(self), 'w')
+ return self.zip_file.open(vfspath(self), 'w')
def mkdir(self, mode=0o777):
- zinfo = zipfile.ZipInfo(str(self) + '/')
+ zinfo = zipfile.ZipInfo(vfspath(self) + '/')
zinfo.external_attr |= stat.S_IFDIR << 16
zinfo.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
self.zip_file.writestr(zinfo, '')
def symlink_to(self, target, target_is_directory=False):
- zinfo = zipfile.ZipInfo(str(self))
+ zinfo = zipfile.ZipInfo(vfspath(self))
zinfo.external_attr = stat.S_IFLNK << 16
if target_is_directory:
zinfo.external_attr |= 0x10
- self.zip_file.writestr(zinfo, str(target))
+ self.zip_file.writestr(zinfo, vfspath(target))
diff --git a/Lib/test/test_pathlib/test_join_windows.py b/Lib/test/test_pathlib/test_join_windows.py
index 2cc634f25ef..f30c80605f7 100644
--- a/Lib/test/test_pathlib/test_join_windows.py
+++ b/Lib/test/test_pathlib/test_join_windows.py
@@ -8,6 +8,11 @@ import unittest
from .support import is_pypi
from .support.lexical_path import LexicalWindowsPath
+if is_pypi:
+ from pathlib_abc import vfspath
+else:
+ from pathlib._os import vfspath
+
class JoinTestBase:
def test_join(self):
@@ -70,17 +75,17 @@ class JoinTestBase:
self.assertEqual(p / './dd:s', P(r'C:/a/b\./dd:s'))
self.assertEqual(p / 'E:d:s', P('E:d:s'))
- def test_str(self):
+ def test_vfspath(self):
p = self.cls(r'a\b\c')
- self.assertEqual(str(p), 'a\\b\\c')
+ self.assertEqual(vfspath(p), 'a\\b\\c')
p = self.cls(r'c:\a\b\c')
- self.assertEqual(str(p), 'c:\\a\\b\\c')
+ self.assertEqual(vfspath(p), 'c:\\a\\b\\c')
p = self.cls('\\\\a\\b\\')
- self.assertEqual(str(p), '\\\\a\\b\\')
+ self.assertEqual(vfspath(p), '\\\\a\\b\\')
p = self.cls(r'\\a\b\c')
- self.assertEqual(str(p), '\\\\a\\b\\c')
+ self.assertEqual(vfspath(p), '\\\\a\\b\\c')
p = self.cls(r'\\a\b\c\d')
- self.assertEqual(str(p), '\\\\a\\b\\c\\d')
+ self.assertEqual(vfspath(p), '\\\\a\\b\\c\\d')
def test_parts(self):
P = self.cls
diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py
index 41a79d0dceb..b2e2cdb3338 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -16,10 +16,11 @@ from unittest import mock
from urllib.request import pathname2url
from test.support import import_helper
+from test.support import cpython_only
from test.support import is_emscripten, is_wasi
from test.support import infinite_recursion
from test.support import os_helper
-from test.support.os_helper import TESTFN, FakePath
+from test.support.os_helper import TESTFN, FS_NONASCII, FakePath
try:
import fcntl
except ImportError:
@@ -76,8 +77,14 @@ def needs_symlinks(fn):
class UnsupportedOperationTest(unittest.TestCase):
def test_is_notimplemented(self):
- self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError))
- self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError))
+ self.assertIsSubclass(pathlib.UnsupportedOperation, NotImplementedError)
+ self.assertIsInstance(pathlib.UnsupportedOperation(), NotImplementedError)
+
+
+class LazyImportTest(unittest.TestCase):
+ @cpython_only
+ def test_lazy_import(self):
+ import_helper.ensure_lazy_imports("pathlib", {"shutil"})
#
@@ -293,8 +300,8 @@ class PurePathTest(unittest.TestCase):
clsname = p.__class__.__name__
r = repr(p)
# The repr() is in the form ClassName("forward-slashes path").
- self.assertTrue(r.startswith(clsname + '('), r)
- self.assertTrue(r.endswith(')'), r)
+ self.assertStartsWith(r, clsname + '(')
+ self.assertEndsWith(r, ')')
inner = r[len(clsname) + 1 : -1]
self.assertEqual(eval(inner), p.as_posix())
@@ -763,12 +770,16 @@ class PurePathTest(unittest.TestCase):
self.assertEqual(self.make_uri(P('c:/')), 'file:///c:/')
self.assertEqual(self.make_uri(P('c:/a/b.c')), 'file:///c:/a/b.c')
self.assertEqual(self.make_uri(P('c:/a/b%#c')), 'file:///c:/a/b%25%23c')
- self.assertEqual(self.make_uri(P('c:/a/b\xe9')), 'file:///c:/a/b%C3%A9')
self.assertEqual(self.make_uri(P('//some/share/')), 'file://some/share/')
self.assertEqual(self.make_uri(P('//some/share/a/b.c')),
'file://some/share/a/b.c')
- self.assertEqual(self.make_uri(P('//some/share/a/b%#c\xe9')),
- 'file://some/share/a/b%25%23c%C3%A9')
+
+ from urllib.parse import quote_from_bytes
+ QUOTED_FS_NONASCII = quote_from_bytes(os.fsencode(FS_NONASCII))
+ self.assertEqual(self.make_uri(P('c:/a/b' + FS_NONASCII)),
+ 'file:///c:/a/b' + QUOTED_FS_NONASCII)
+ self.assertEqual(self.make_uri(P('//some/share/a/b%#c' + FS_NONASCII)),
+ 'file://some/share/a/b%25%23c' + QUOTED_FS_NONASCII)
@needs_windows
def test_ordering_windows(self):
@@ -2943,7 +2954,13 @@ class PathTest(PurePathTest):
else:
# ".." segments are normalized first on Windows, so this path is stat()able.
self.assertEqual(set(p.glob("xyzzy/..")), { P(self.base, "xyzzy", "..") })
- self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)})
+ if sys.platform == "emscripten":
+ # Emscripten will return ELOOP if there are 49 or more ..'s.
+ # Can remove when https://github.com/emscripten-core/emscripten/pull/24591 is merged.
+ NDOTDOTS = 48
+ else:
+ NDOTDOTS = 50
+ self.assertEqual(set(p.glob("/".join([".."] * NDOTDOTS))), { P(self.base, *[".."] * NDOTDOTS)})
def test_glob_inaccessible(self):
P = self.cls
@@ -3290,7 +3307,6 @@ class PathTest(PurePathTest):
self.assertEqual(P.from_uri('file:////foo/bar'), P('//foo/bar'))
self.assertEqual(P.from_uri('file://localhost/foo/bar'), P('/foo/bar'))
if not is_wasi:
- self.assertEqual(P.from_uri('file://127.0.0.1/foo/bar'), P('/foo/bar'))
self.assertEqual(P.from_uri(f'file://{socket.gethostname()}/foo/bar'),
P('/foo/bar'))
self.assertRaises(ValueError, P.from_uri, 'foo/bar')
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index be365a5a3dd..6b74e21ad73 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -1,7 +1,9 @@
# A test suite for pdb; not very comprehensive at the moment.
+import _colorize
import doctest
import gc
+import io
import os
import pdb
import sys
@@ -18,7 +20,7 @@ from asyncio.events import _set_event_loop_policy
from contextlib import ExitStack, redirect_stdout
from io import StringIO
from test import support
-from test.support import force_not_colorized, has_socket_support, os_helper
+from test.support import has_socket_support, os_helper
from test.support.import_helper import import_module
from test.support.pty_helper import run_pty, FakeInput
from test.support.script_helper import kill_python
@@ -3446,6 +3448,7 @@ def test_pdb_issue_gh_65052():
"""
+@support.force_not_colorized_test_class
@support.requires_subprocess()
class PdbTestCase(unittest.TestCase):
def tearDown(self):
@@ -3740,7 +3743,6 @@ def bœr():
self.assertNotIn(b'Error', stdout,
"Got an error running test script under PDB")
- @force_not_colorized
def test_issue16180(self):
# A syntax error in the debuggee.
script = "def f: pass\n"
@@ -3754,7 +3756,6 @@ def bœr():
'Fail to handle a syntax error in the debuggee.'
.format(expected, stderr))
- @force_not_colorized
def test_issue84583(self):
# A syntax error from ast.literal_eval should not make pdb exit.
script = "import ast; ast.literal_eval('')\n"
@@ -4688,6 +4689,40 @@ class PdbTestInline(unittest.TestCase):
self.assertIn("42", stdout)
+@support.force_colorized_test_class
+class PdbTestColorize(unittest.TestCase):
+ def setUp(self):
+ self._original_can_colorize = _colorize.can_colorize
+ # Force colorize to be enabled because we are sending data
+ # to a StringIO
+ _colorize.can_colorize = lambda *args, **kwargs: True
+
+ def tearDown(self):
+ _colorize.can_colorize = self._original_can_colorize
+
+ def test_code_display(self):
+ output = io.StringIO()
+ p = pdb.Pdb(stdout=output, colorize=True)
+ p.set_trace(commands=['ll', 'c'])
+ self.assertIn("\x1b", output.getvalue())
+
+ output = io.StringIO()
+ p = pdb.Pdb(stdout=output, colorize=False)
+ p.set_trace(commands=['ll', 'c'])
+ self.assertNotIn("\x1b", output.getvalue())
+
+ output = io.StringIO()
+ p = pdb.Pdb(stdout=output)
+ p.set_trace(commands=['ll', 'c'])
+ self.assertNotIn("\x1b", output.getvalue())
+
+ def test_stack_entry(self):
+ output = io.StringIO()
+ p = pdb.Pdb(stdout=output, colorize=True)
+ p.set_trace(commands=['w', 'c'])
+ self.assertIn("\x1b", output.getvalue())
+
+
@support.force_not_colorized_test_class
@support.requires_subprocess()
class TestREPLSession(unittest.TestCase):
@@ -4711,9 +4746,12 @@ class TestREPLSession(unittest.TestCase):
self.assertEqual(p.returncode, 0)
+@support.force_not_colorized_test_class
@support.requires_subprocess()
class PdbTestReadline(unittest.TestCase):
- def setUpClass():
+
+ @classmethod
+ def setUpClass(cls):
# Ensure that the readline module is loaded
# If this fails, the test is skipped because SkipTest will be raised
readline = import_module('readline')
@@ -4812,14 +4850,37 @@ class PdbTestReadline(unittest.TestCase):
self.assertIn(b'I love Python', output)
+ @unittest.skipIf(sys.platform.startswith('freebsd'),
+ '\\x08 is not interpreted as backspace on FreeBSD')
+ def test_multiline_auto_indent(self):
+ script = textwrap.dedent("""
+ import pdb; pdb.Pdb().set_trace()
+ """)
+
+ input = b"def f(x):\n"
+ input += b"if x > 0:\n"
+ input += b"x += 1\n"
+ input += b"return x\n"
+ # We need to do backspaces to remove the auto-indentation
+ input += b"\x08\x08\x08\x08else:\n"
+ input += b"return -x\n"
+ input += b"\n"
+ input += b"f(-21-21)\n"
+ input += b"c\n"
+
+ output = run_pty(script, input)
+
+ self.assertIn(b'42', output)
+
def test_multiline_completion(self):
script = textwrap.dedent("""
import pdb; pdb.Pdb().set_trace()
""")
input = b"def func():\n"
- # Complete: \treturn 40 + 2
- input += b"\tret\t 40 + 2\n"
+ # Auto-indent
+ # Complete: return 40 + 2
+ input += b"ret\t 40 + 2\n"
input += b"\n"
# Complete: func()
input += b"fun\t()\n"
@@ -4829,6 +4890,8 @@ class PdbTestReadline(unittest.TestCase):
self.assertIn(b'42', output)
+ @unittest.skipIf(sys.platform.startswith('freebsd'),
+ '\\x08 is not interpreted as backspace on FreeBSD')
def test_multiline_indent_completion(self):
script = textwrap.dedent("""
import pdb; pdb.Pdb().set_trace()
@@ -4839,12 +4902,13 @@ class PdbTestReadline(unittest.TestCase):
# if the completion is not working as expected
input = textwrap.dedent("""\
def func():
- \ta = 1
- \ta += 1
- \ta += 1
- \tif a > 0:
- a += 1
- \t\treturn a
+ a = 1
+ \x08\ta += 1
+ \x08\x08\ta += 1
+ \x08\x08\x08\ta += 1
+ \x08\x08\x08\x08\tif a > 0:
+ a += 1
+ \x08\x08\x08\x08return a
func()
c
@@ -4852,7 +4916,7 @@ class PdbTestReadline(unittest.TestCase):
output = run_pty(script, input)
- self.assertIn(b'4', output)
+ self.assertIn(b'5', output)
self.assertNotIn(b'Error', output)
def test_interact_completion(self):
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 565e42b04a6..98629df4574 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -1,4 +1,5 @@
import dis
+import gc
from itertools import combinations, product
import opcode
import sys
@@ -11,7 +12,7 @@ except ImportError:
from test import support
from test.support.bytecode_helper import (
- BytecodeTestCase, CfgOptimizationTestCase, CompilationStepTestCase)
+ BytecodeTestCase, CfgOptimizationTestCase)
def compile_pattern_with_fast_locals(pattern):
@@ -291,6 +292,7 @@ class TestTranforms(BytecodeTestCase):
('---x', 'UNARY_NEGATIVE', None, False, None, None),
('~~~x', 'UNARY_INVERT', None, False, None, None),
('+++x', 'CALL_INTRINSIC_1', intrinsic_positive, False, None, None),
+ ('~True', 'UNARY_INVERT', None, False, None, None),
]
for (
@@ -315,7 +317,7 @@ class TestTranforms(BytecodeTestCase):
return -(1.0-1.0)
for instr in dis.get_instructions(negzero):
- self.assertFalse(instr.opname.startswith('UNARY_'))
+ self.assertNotStartsWith(instr.opname, 'UNARY_')
self.check_lnotab(negzero)
def test_constant_folding_binop(self):
@@ -717,9 +719,9 @@ class TestTranforms(BytecodeTestCase):
self.assertEqual(format('x = %d!', 1234), 'x = 1234!')
self.assertEqual(format('x = %x!', 1234), 'x = 4d2!')
self.assertEqual(format('x = %f!', 1234), 'x = 1234.000000!')
- self.assertEqual(format('x = %s!', 1234.5678901), 'x = 1234.5678901!')
- self.assertEqual(format('x = %f!', 1234.5678901), 'x = 1234.567890!')
- self.assertEqual(format('x = %d!', 1234.5678901), 'x = 1234!')
+ self.assertEqual(format('x = %s!', 1234.0000625), 'x = 1234.0000625!')
+ self.assertEqual(format('x = %f!', 1234.0000625), 'x = 1234.000063!')
+ self.assertEqual(format('x = %d!', 1234.0000625), 'x = 1234!')
self.assertEqual(format('x = %s%% %%%%', 1234), 'x = 1234% %%')
self.assertEqual(format('x = %s!', '%% %s'), 'x = %% %s!')
self.assertEqual(format('x = %s, y = %d', 12, 34), 'x = 12, y = 34')
@@ -2472,6 +2474,13 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
]
self.check(insts, insts)
+ insts = [
+ ("LOAD_FAST", 0, 1),
+ ("DELETE_FAST", 0, 2),
+ ("POP_TOP", None, 3),
+ ]
+ self.check(insts, insts)
+
def test_unoptimized_if_aliased(self):
insts = [
("LOAD_FAST", 0, 1),
@@ -2606,6 +2615,114 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
]
self.cfg_optimization_test(insts, expected, consts=[None])
+ def test_format_simple(self):
+ # FORMAT_SIMPLE will leave its operand on the stack if it's a unicode
+ # object. We treat it conservatively and assume that it always leaves
+ # its operand on the stack.
+ insts = [
+ ("LOAD_FAST", 0, 1),
+ ("FORMAT_SIMPLE", None, 2),
+ ("STORE_FAST", 1, 3),
+ ]
+ self.check(insts, insts)
+
+ insts = [
+ ("LOAD_FAST", 0, 1),
+ ("FORMAT_SIMPLE", None, 2),
+ ("POP_TOP", None, 3),
+ ]
+ expected = [
+ ("LOAD_FAST_BORROW", 0, 1),
+ ("FORMAT_SIMPLE", None, 2),
+ ("POP_TOP", None, 3),
+ ]
+ self.check(insts, expected)
+
+ def test_set_function_attribute(self):
+ # SET_FUNCTION_ATTRIBUTE leaves the function on the stack
+ insts = [
+ ("LOAD_CONST", 0, 1),
+ ("LOAD_FAST", 0, 2),
+ ("SET_FUNCTION_ATTRIBUTE", 2, 3),
+ ("STORE_FAST", 1, 4),
+ ("LOAD_CONST", 0, 5),
+ ("RETURN_VALUE", None, 6)
+ ]
+ self.cfg_optimization_test(insts, insts, consts=[None])
+
+ insts = [
+ ("LOAD_CONST", 0, 1),
+ ("LOAD_FAST", 0, 2),
+ ("SET_FUNCTION_ATTRIBUTE", 2, 3),
+ ("RETURN_VALUE", None, 4)
+ ]
+ expected = [
+ ("LOAD_CONST", 0, 1),
+ ("LOAD_FAST_BORROW", 0, 2),
+ ("SET_FUNCTION_ATTRIBUTE", 2, 3),
+ ("RETURN_VALUE", None, 4)
+ ]
+ self.cfg_optimization_test(insts, expected, consts=[None])
+
+ def test_get_yield_from_iter(self):
+ # GET_YIELD_FROM_ITER may leave its operand on the stack
+ insts = [
+ ("LOAD_FAST", 0, 1),
+ ("GET_YIELD_FROM_ITER", None, 2),
+ ("LOAD_CONST", 0, 3),
+ send := self.Label(),
+ ("SEND", end := self.Label(), 5),
+ ("YIELD_VALUE", 1, 6),
+ ("RESUME", 2, 7),
+ ("JUMP", send, 8),
+ end,
+ ("END_SEND", None, 9),
+ ("LOAD_CONST", 0, 10),
+ ("RETURN_VALUE", None, 11),
+ ]
+ self.cfg_optimization_test(insts, insts, consts=[None])
+
+ def test_push_exc_info(self):
+ insts = [
+ ("LOAD_FAST", 0, 1),
+ ("PUSH_EXC_INFO", None, 2),
+ ]
+ self.check(insts, insts)
+
+ def test_load_special(self):
+ # LOAD_SPECIAL may leave self on the stack
+ insts = [
+ ("LOAD_FAST", 0, 1),
+ ("LOAD_SPECIAL", 0, 2),
+ ("STORE_FAST", 1, 3),
+ ]
+ self.check(insts, insts)
+
+
+ def test_del_in_finally(self):
+ # This loads `obj` onto the stack, executes `del obj`, then returns the
+ # `obj` from the stack. See gh-133371 for more details.
+ def create_obj():
+ obj = [42]
+ try:
+ return obj
+ finally:
+ del obj
+
+ obj = create_obj()
+ # The crash in the linked issue happens while running GC during
+ # interpreter finalization, so run it here manually.
+ gc.collect()
+ self.assertEqual(obj, [42])
+
+ def test_format_simple_unicode(self):
+ # Repro from gh-134889
+ def f():
+ var = f"{1}"
+ var = f"{var}"
+ return var
+ self.assertEqual(f(), "1")
+
if __name__ == "__main__":
diff --git a/Lib/test/test_peg_generator/test_c_parser.py b/Lib/test/test_peg_generator/test_c_parser.py
index 1095e7303c1..aa01a9b8f7e 100644
--- a/Lib/test/test_peg_generator/test_c_parser.py
+++ b/Lib/test/test_peg_generator/test_c_parser.py
@@ -387,10 +387,10 @@ class TestCParser(unittest.TestCase):
test_source = """
stmt = "with (\\n a as b,\\n c as d\\n): pass"
the_ast = parse.parse_string(stmt, mode=1)
- self.assertTrue(ast_dump(the_ast).startswith(
+ self.assertStartsWith(ast_dump(the_ast),
"Module(body=[With(items=[withitem(context_expr=Name(id='a', ctx=Load()), optional_vars=Name(id='b', ctx=Store())), "
"withitem(context_expr=Name(id='c', ctx=Load()), optional_vars=Name(id='d', ctx=Store()))]"
- ))
+ )
"""
self.run_test(grammar_source, test_source)
diff --git a/Lib/test/test_peg_generator/test_pegen.py b/Lib/test/test_peg_generator/test_pegen.py
index d8606521345..d912c558123 100644
--- a/Lib/test/test_peg_generator/test_pegen.py
+++ b/Lib/test/test_peg_generator/test_pegen.py
@@ -91,10 +91,8 @@ class TestPegen(unittest.TestCase):
"""
rules = parse_string(grammar, GrammarParser).rules
self.assertEqual(str(rules["start"]), "start: ','.thing+ NEWLINE")
- self.assertTrue(
- repr(rules["start"]).startswith(
- "Rule('start', None, Rhs([Alt([NamedItem(None, Gather(StringLeaf(\"','\"), NameLeaf('thing'"
- )
+ self.assertStartsWith(repr(rules["start"]),
+ "Rule('start', None, Rhs([Alt([NamedItem(None, Gather(StringLeaf(\"','\"), NameLeaf('thing'"
)
self.assertEqual(str(rules["thing"]), "thing: NUMBER")
parser_class = make_parser(grammar)
diff --git a/Lib/test/test_perf_profiler.py b/Lib/test/test_perf_profiler.py
index c176e505155..7529c853f9c 100644
--- a/Lib/test/test_perf_profiler.py
+++ b/Lib/test/test_perf_profiler.py
@@ -93,9 +93,7 @@ class TestPerfTrampoline(unittest.TestCase):
perf_line, f"Could not find {expected_symbol} in perf file"
)
perf_addr = perf_line.split(" ")[0]
- self.assertFalse(
- perf_addr.startswith("0x"), "Address should not be prefixed with 0x"
- )
+ self.assertNotStartsWith(perf_addr, "0x")
self.assertTrue(
set(perf_addr).issubset(string.hexdigits),
"Address should contain only hex characters",
@@ -508,9 +506,12 @@ def _is_perf_version_at_least(major, minor):
# The output of perf --version looks like "perf version 6.7-3" but
# it can also be perf version "perf version 5.15.143", or even include
# a commit hash in the version string, like "6.12.9.g242e6068fd5c"
+ #
+ # PermissionError is raised if perf does not exist on the Windows Subsystem
+ # for Linux, see #134987
try:
output = subprocess.check_output(["perf", "--version"], text=True)
- except (subprocess.CalledProcessError, FileNotFoundError):
+ except (subprocess.CalledProcessError, FileNotFoundError, PermissionError):
return False
version = output.split()[2]
version = version.split("-")[0]
diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py
index 296d4b882e1..e2384b33345 100644
--- a/Lib/test/test_pickle.py
+++ b/Lib/test/test_pickle.py
@@ -15,7 +15,8 @@ from textwrap import dedent
import doctest
import unittest
from test import support
-from test.support import import_helper, os_helper
+from test.support import cpython_only, import_helper, os_helper
+from test.support.import_helper import ensure_lazy_imports
from test.pickletester import AbstractHookTests
from test.pickletester import AbstractUnpickleTests
@@ -36,6 +37,12 @@ except ImportError:
has_c_implementation = False
+class LazyImportTest(unittest.TestCase):
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("pickle", {"re"})
+
+
class PyPickleTests(AbstractPickleModuleTests, unittest.TestCase):
dump = staticmethod(pickle._dump)
dumps = staticmethod(pickle._dumps)
@@ -604,10 +611,10 @@ class CompatPickleTests(unittest.TestCase):
with self.subTest(((module3, name3), (module2, name2))):
if (module2, name2) == ('exceptions', 'OSError'):
attr = getattribute(module3, name3)
- self.assertTrue(issubclass(attr, OSError))
+ self.assertIsSubclass(attr, OSError)
elif (module2, name2) == ('exceptions', 'ImportError'):
attr = getattribute(module3, name3)
- self.assertTrue(issubclass(attr, ImportError))
+ self.assertIsSubclass(attr, ImportError)
else:
module, name = mapping(module2, name2)
if module3[:1] != '_':
@@ -745,6 +752,7 @@ class CommandLineTest(unittest.TestCase):
expect = self.text_normalize(expect)
self.assertListEqual(res.splitlines(), expect.splitlines())
+ @support.force_not_colorized
def test_unknown_flag(self):
stderr = io.StringIO()
with self.assertRaises(SystemExit):
diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
index b90edc05e04..479649053ab 100644
--- a/Lib/test/test_platform.py
+++ b/Lib/test/test_platform.py
@@ -133,6 +133,22 @@ class PlatformTest(unittest.TestCase):
for terse in (False, True):
res = platform.platform(aliased, terse)
+ def test__platform(self):
+ for src, res in [
+ ('foo bar', 'foo_bar'),
+ (
+ '1/2\\3:4;5"6(7)8(7)6"5;4:3\\2/1',
+ '1-2-3-4-5-6-7-8-7-6-5-4-3-2-1'
+ ),
+ ('--', ''),
+ ('-f', '-f'),
+ ('-foo----', '-foo'),
+ ('--foo---', '-foo'),
+ ('---foo--', '-foo'),
+ ]:
+ with self.subTest(src=src):
+ self.assertEqual(platform._platform(src), res)
+
def test_system(self):
res = platform.system()
@@ -383,15 +399,6 @@ class PlatformTest(unittest.TestCase):
finally:
platform._uname_cache = None
- def test_java_ver(self):
- import re
- msg = re.escape(
- "'java_ver' is deprecated and slated for removal in Python 3.15"
- )
- with self.assertWarnsRegex(DeprecationWarning, msg):
- res = platform.java_ver()
- self.assertEqual(len(res), 4)
-
@unittest.skipUnless(support.MS_WINDOWS, 'This test only makes sense on Windows')
def test_win32_ver(self):
release1, version1, csd1, ptype1 = 'a', 'b', 'c', 'd'
@@ -410,7 +417,7 @@ class PlatformTest(unittest.TestCase):
for v in version.split('.'):
int(v) # should not fail
if csd:
- self.assertTrue(csd.startswith('SP'), msg=csd)
+ self.assertStartsWith(csd, 'SP')
if ptype:
if os.cpu_count() > 1:
self.assertIn('Multiprocessor', ptype)
@@ -794,6 +801,7 @@ class CommandLineTest(unittest.TestCase):
self.invoke_platform(*flags)
obj.assert_called_once_with(aliased, terse)
+ @support.force_not_colorized
def test_help(self):
output = io.StringIO()
diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py
index eea0625012d..e412cb1d58d 100644
--- a/Lib/test/test_positional_only_arg.py
+++ b/Lib/test/test_positional_only_arg.py
@@ -37,8 +37,8 @@ class PositionalOnlyTestCase(unittest.TestCase):
check_syntax_error(self, "def f(/): pass")
check_syntax_error(self, "def f(*, a, /): pass")
check_syntax_error(self, "def f(*, /, a): pass")
- check_syntax_error(self, "def f(a, /, a): pass", "duplicate argument 'a' in function definition")
- check_syntax_error(self, "def f(a, /, *, a): pass", "duplicate argument 'a' in function definition")
+ check_syntax_error(self, "def f(a, /, a): pass", "duplicate parameter 'a' in function definition")
+ check_syntax_error(self, "def f(a, /, *, a): pass", "duplicate parameter 'a' in function definition")
check_syntax_error(self, "def f(a, b/2, c): pass")
check_syntax_error(self, "def f(a, /, c, /): pass")
check_syntax_error(self, "def f(a, /, c, /, d): pass")
@@ -59,8 +59,8 @@ class PositionalOnlyTestCase(unittest.TestCase):
check_syntax_error(self, "async def f(/): pass")
check_syntax_error(self, "async def f(*, a, /): pass")
check_syntax_error(self, "async def f(*, /, a): pass")
- check_syntax_error(self, "async def f(a, /, a): pass", "duplicate argument 'a' in function definition")
- check_syntax_error(self, "async def f(a, /, *, a): pass", "duplicate argument 'a' in function definition")
+ check_syntax_error(self, "async def f(a, /, a): pass", "duplicate parameter 'a' in function definition")
+ check_syntax_error(self, "async def f(a, /, *, a): pass", "duplicate parameter 'a' in function definition")
check_syntax_error(self, "async def f(a, b/2, c): pass")
check_syntax_error(self, "async def f(a, /, c, /): pass")
check_syntax_error(self, "async def f(a, /, c, /, d): pass")
@@ -247,8 +247,8 @@ class PositionalOnlyTestCase(unittest.TestCase):
check_syntax_error(self, "lambda /: None")
check_syntax_error(self, "lambda *, a, /: None")
check_syntax_error(self, "lambda *, /, a: None")
- check_syntax_error(self, "lambda a, /, a: None", "duplicate argument 'a' in function definition")
- check_syntax_error(self, "lambda a, /, *, a: None", "duplicate argument 'a' in function definition")
+ check_syntax_error(self, "lambda a, /, a: None", "duplicate parameter 'a' in function definition")
+ check_syntax_error(self, "lambda a, /, *, a: None", "duplicate parameter 'a' in function definition")
check_syntax_error(self, "lambda a, /, b, /: None")
check_syntax_error(self, "lambda a, /, b, /, c: None")
check_syntax_error(self, "lambda a, /, b, /, c, *, d: None")
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index c9cbe1541e7..628920e34b5 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1107,7 +1107,7 @@ class PosixTester(unittest.TestCase):
def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs):
st = os.stat(target_file)
- self.assertTrue(hasattr(st, 'st_flags'))
+ self.assertHasAttr(st, 'st_flags')
# ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE.
flags = st.st_flags | stat.UF_IMMUTABLE
@@ -1143,7 +1143,7 @@ class PosixTester(unittest.TestCase):
def test_lchflags_symlink(self):
testfn_st = os.stat(os_helper.TESTFN)
- self.assertTrue(hasattr(testfn_st, 'st_flags'))
+ self.assertHasAttr(testfn_st, 'st_flags')
self.addCleanup(os_helper.unlink, _DUMMY_SYMLINK)
os.symlink(os_helper.TESTFN, _DUMMY_SYMLINK)
@@ -1521,6 +1521,51 @@ class PosixTester(unittest.TestCase):
self.assertEqual(cm.exception.errno, errno.EINVAL)
os.close(os.pidfd_open(os.getpid(), 0))
+ @os_helper.skip_unless_hardlink
+ @os_helper.skip_unless_symlink
+ def test_link_follow_symlinks(self):
+ default_follow = sys.platform.startswith(
+ ('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5'))
+ default_no_follow = sys.platform.startswith(('win32', 'linux'))
+ orig = os_helper.TESTFN
+ symlink = orig + 'symlink'
+ posix.symlink(orig, symlink)
+ self.addCleanup(os_helper.unlink, symlink)
+
+ with self.subTest('no follow_symlinks'):
+ # no follow_symlinks -> platform depending
+ link = orig + 'link'
+ posix.link(symlink, link)
+ self.addCleanup(os_helper.unlink, link)
+ if os.link in os.supports_follow_symlinks or default_follow:
+ self.assertEqual(posix.lstat(link), posix.lstat(orig))
+ elif default_no_follow:
+ self.assertEqual(posix.lstat(link), posix.lstat(symlink))
+
+ with self.subTest('follow_symlinks=False'):
+ # follow_symlinks=False -> duplicate the symlink itself
+ link = orig + 'link_nofollow'
+ try:
+ posix.link(symlink, link, follow_symlinks=False)
+ except NotImplementedError:
+ if os.link in os.supports_follow_symlinks or default_no_follow:
+ raise
+ else:
+ self.addCleanup(os_helper.unlink, link)
+ self.assertEqual(posix.lstat(link), posix.lstat(symlink))
+
+ with self.subTest('follow_symlinks=True'):
+ # follow_symlinks=True -> duplicate the target file
+ link = orig + 'link_following'
+ try:
+ posix.link(symlink, link, follow_symlinks=True)
+ except NotImplementedError:
+ if os.link in os.supports_follow_symlinks or default_follow:
+ raise
+ else:
+ self.addCleanup(os_helper.unlink, link)
+ self.assertEqual(posix.lstat(link), posix.lstat(orig))
+
# tests for the posix *at functions follow
class TestPosixDirFd(unittest.TestCase):
@@ -2173,12 +2218,12 @@ class TestPosixWeaklinking(unittest.TestCase):
def test_pwritev(self):
self._verify_available("HAVE_PWRITEV")
if self.mac_ver >= (10, 16):
- self.assertTrue(hasattr(os, "pwritev"), "os.pwritev is not available")
- self.assertTrue(hasattr(os, "preadv"), "os.readv is not available")
+ self.assertHasAttr(os, "pwritev")
+ self.assertHasAttr(os, "preadv")
else:
- self.assertFalse(hasattr(os, "pwritev"), "os.pwritev is available")
- self.assertFalse(hasattr(os, "preadv"), "os.readv is available")
+ self.assertNotHasAttr(os, "pwritev")
+ self.assertNotHasAttr(os, "preadv")
def test_stat(self):
self._verify_available("HAVE_FSTATAT")
diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py
index fa19d549c26..21f06712548 100644
--- a/Lib/test/test_posixpath.py
+++ b/Lib/test/test_posixpath.py
@@ -4,12 +4,13 @@ import posixpath
import random
import sys
import unittest
-from posixpath import realpath, abspath, dirname, basename
+from functools import partial
+from posixpath import realpath, abspath, dirname, basename, ALLOW_MISSING
from test import support
from test import test_genericpath
from test.support import import_helper
from test.support import os_helper
-from test.support.os_helper import FakePath
+from test.support.os_helper import FakePath, TESTFN
from unittest import mock
try:
@@ -21,7 +22,7 @@ except ImportError:
# An absolute path to a temporary filename for testing. We can't rely on TESTFN
# being an absolute path, so we need this.
-ABSTFN = abspath(os_helper.TESTFN)
+ABSTFN = abspath(TESTFN)
def skip_if_ABSTFN_contains_backslash(test):
"""
@@ -33,21 +34,16 @@ def skip_if_ABSTFN_contains_backslash(test):
msg = "ABSTFN is not a posix path - tests fail"
return [test, unittest.skip(msg)(test)][found_backslash]
-def safe_rmdir(dirname):
- try:
- os.rmdir(dirname)
- except OSError:
- pass
+
+def _parameterize(*parameters):
+ return support.subTests('kwargs', parameters)
+
class PosixPathTest(unittest.TestCase):
def setUp(self):
- self.tearDown()
-
- def tearDown(self):
for suffix in ["", "1", "2"]:
- os_helper.unlink(os_helper.TESTFN + suffix)
- safe_rmdir(os_helper.TESTFN + suffix)
+ self.assertFalse(posixpath.lexists(ABSTFN + suffix))
def test_join(self):
fn = posixpath.join
@@ -194,25 +190,28 @@ class PosixPathTest(unittest.TestCase):
self.assertEqual(posixpath.dirname(b"//foo//bar"), b"//foo")
def test_islink(self):
- self.assertIs(posixpath.islink(os_helper.TESTFN + "1"), False)
- self.assertIs(posixpath.lexists(os_helper.TESTFN + "2"), False)
+ self.assertIs(posixpath.islink(TESTFN + "1"), False)
+ self.assertIs(posixpath.lexists(TESTFN + "2"), False)
- with open(os_helper.TESTFN + "1", "wb") as f:
+ self.addCleanup(os_helper.unlink, TESTFN + "1")
+ with open(TESTFN + "1", "wb") as f:
f.write(b"foo")
- self.assertIs(posixpath.islink(os_helper.TESTFN + "1"), False)
+ self.assertIs(posixpath.islink(TESTFN + "1"), False)
if os_helper.can_symlink():
- os.symlink(os_helper.TESTFN + "1", os_helper.TESTFN + "2")
- self.assertIs(posixpath.islink(os_helper.TESTFN + "2"), True)
- os.remove(os_helper.TESTFN + "1")
- self.assertIs(posixpath.islink(os_helper.TESTFN + "2"), True)
- self.assertIs(posixpath.exists(os_helper.TESTFN + "2"), False)
- self.assertIs(posixpath.lexists(os_helper.TESTFN + "2"), True)
-
- self.assertIs(posixpath.islink(os_helper.TESTFN + "\udfff"), False)
- self.assertIs(posixpath.islink(os.fsencode(os_helper.TESTFN) + b"\xff"), False)
- self.assertIs(posixpath.islink(os_helper.TESTFN + "\x00"), False)
- self.assertIs(posixpath.islink(os.fsencode(os_helper.TESTFN) + b"\x00"), False)
+ self.addCleanup(os_helper.unlink, TESTFN + "2")
+ os.symlink(TESTFN + "1", TESTFN + "2")
+ self.assertIs(posixpath.islink(TESTFN + "2"), True)
+ os.remove(TESTFN + "1")
+ self.assertIs(posixpath.islink(TESTFN + "2"), True)
+ self.assertIs(posixpath.exists(TESTFN + "2"), False)
+ self.assertIs(posixpath.lexists(TESTFN + "2"), True)
+
+ def test_islink_invalid_paths(self):
+ self.assertIs(posixpath.islink(TESTFN + "\udfff"), False)
+ self.assertIs(posixpath.islink(os.fsencode(TESTFN) + b"\xff"), False)
+ self.assertIs(posixpath.islink(TESTFN + "\x00"), False)
+ self.assertIs(posixpath.islink(os.fsencode(TESTFN) + b"\x00"), False)
def test_ismount(self):
self.assertIs(posixpath.ismount("/"), True)
@@ -227,8 +226,9 @@ class PosixPathTest(unittest.TestCase):
os.mkdir(ABSTFN)
self.assertIs(posixpath.ismount(ABSTFN), False)
finally:
- safe_rmdir(ABSTFN)
+ os_helper.rmdir(ABSTFN)
+ def test_ismount_invalid_paths(self):
self.assertIs(posixpath.ismount('/\udfff'), False)
self.assertIs(posixpath.ismount(b'/\xff'), False)
self.assertIs(posixpath.ismount('/\x00'), False)
@@ -241,7 +241,7 @@ class PosixPathTest(unittest.TestCase):
os.symlink("/", ABSTFN)
self.assertIs(posixpath.ismount(ABSTFN), False)
finally:
- os.unlink(ABSTFN)
+ os_helper.unlink(ABSTFN)
@unittest.skipIf(posix is None, "Test requires posix module")
def test_ismount_different_device(self):
@@ -448,32 +448,35 @@ class PosixPathTest(unittest.TestCase):
self.assertEqual(result, expected)
@skip_if_ABSTFN_contains_backslash
- def test_realpath_curdir(self):
- self.assertEqual(realpath('.'), os.getcwd())
- self.assertEqual(realpath('./.'), os.getcwd())
- self.assertEqual(realpath('/'.join(['.'] * 100)), os.getcwd())
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_curdir(self, kwargs):
+ self.assertEqual(realpath('.', **kwargs), os.getcwd())
+ self.assertEqual(realpath('./.', **kwargs), os.getcwd())
+ self.assertEqual(realpath('/'.join(['.'] * 100), **kwargs), os.getcwd())
- self.assertEqual(realpath(b'.'), os.getcwdb())
- self.assertEqual(realpath(b'./.'), os.getcwdb())
- self.assertEqual(realpath(b'/'.join([b'.'] * 100)), os.getcwdb())
+ self.assertEqual(realpath(b'.', **kwargs), os.getcwdb())
+ self.assertEqual(realpath(b'./.', **kwargs), os.getcwdb())
+ self.assertEqual(realpath(b'/'.join([b'.'] * 100), **kwargs), os.getcwdb())
@skip_if_ABSTFN_contains_backslash
- def test_realpath_pardir(self):
- self.assertEqual(realpath('..'), dirname(os.getcwd()))
- self.assertEqual(realpath('../..'), dirname(dirname(os.getcwd())))
- self.assertEqual(realpath('/'.join(['..'] * 100)), '/')
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_pardir(self, kwargs):
+ self.assertEqual(realpath('..', **kwargs), dirname(os.getcwd()))
+ self.assertEqual(realpath('../..', **kwargs), dirname(dirname(os.getcwd())))
+ self.assertEqual(realpath('/'.join(['..'] * 100), **kwargs), '/')
- self.assertEqual(realpath(b'..'), dirname(os.getcwdb()))
- self.assertEqual(realpath(b'../..'), dirname(dirname(os.getcwdb())))
- self.assertEqual(realpath(b'/'.join([b'..'] * 100)), b'/')
+ self.assertEqual(realpath(b'..', **kwargs), dirname(os.getcwdb()))
+ self.assertEqual(realpath(b'../..', **kwargs), dirname(dirname(os.getcwdb())))
+ self.assertEqual(realpath(b'/'.join([b'..'] * 100), **kwargs), b'/')
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_basic(self):
+ @_parameterize({}, {'strict': ALLOW_MISSING})
+ def test_realpath_basic(self, kwargs):
# Basic operation.
try:
os.symlink(ABSTFN+"1", ABSTFN)
- self.assertEqual(realpath(ABSTFN), ABSTFN+"1")
+ self.assertEqual(realpath(ABSTFN, **kwargs), ABSTFN+"1")
finally:
os_helper.unlink(ABSTFN)
@@ -489,23 +492,121 @@ class PosixPathTest(unittest.TestCase):
finally:
os_helper.unlink(ABSTFN)
+ def test_realpath_invalid_paths(self):
+ path = '/\x00'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(ValueError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+ path = b'/\x00'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(ValueError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+ path = '/nonexistent/x\x00'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+ path = b'/nonexistent/x\x00'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+ path = '/\x00/..'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(ValueError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+ path = b'/\x00/..'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(ValueError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+
+ path = '/nonexistent/x\x00/..'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+ path = b'/nonexistent/x\x00/..'
+ self.assertRaises(ValueError, realpath, path, strict=False)
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
+
+ path = '/\udfff'
+ if sys.platform == 'win32':
+ self.assertEqual(realpath(path, strict=False), path)
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), path)
+ else:
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=True)
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
+ path = '/nonexistent/\udfff'
+ if sys.platform == 'win32':
+ self.assertEqual(realpath(path, strict=False), path)
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), path)
+ else:
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ path = '/\udfff/..'
+ if sys.platform == 'win32':
+ self.assertEqual(realpath(path, strict=False), '/')
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), '/')
+ else:
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=True)
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
+ path = '/nonexistent/\udfff/..'
+ if sys.platform == 'win32':
+ self.assertEqual(realpath(path, strict=False), '/nonexistent')
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), '/nonexistent')
+ else:
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
+ self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+
+ path = b'/\xff'
+ if sys.platform == 'win32':
+ self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
+ self.assertRaises(UnicodeDecodeError, realpath, path, strict=True)
+ self.assertRaises(UnicodeDecodeError, realpath, path, strict=ALLOW_MISSING)
+ else:
+ self.assertEqual(realpath(path, strict=False), path)
+ if support.is_wasi:
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
+ else:
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+ self.assertEqual(realpath(path, strict=ALLOW_MISSING), path)
+ path = b'/nonexistent/\xff'
+ if sys.platform == 'win32':
+ self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
+ self.assertRaises(UnicodeDecodeError, realpath, path, strict=ALLOW_MISSING)
+ else:
+ self.assertEqual(realpath(path, strict=False), path)
+ if support.is_wasi:
+ self.assertRaises(OSError, realpath, path, strict=True)
+ self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
+ else:
+ self.assertRaises(FileNotFoundError, realpath, path, strict=True)
+
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_relative(self):
+ @_parameterize({}, {'strict': ALLOW_MISSING})
+ def test_realpath_relative(self, kwargs):
try:
os.symlink(posixpath.relpath(ABSTFN+"1"), ABSTFN)
- self.assertEqual(realpath(ABSTFN), ABSTFN+"1")
+ self.assertEqual(realpath(ABSTFN, **kwargs), ABSTFN+"1")
finally:
os_helper.unlink(ABSTFN)
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_missing_pardir(self):
+ @_parameterize({}, {'strict': ALLOW_MISSING})
+ def test_realpath_missing_pardir(self, kwargs):
try:
- os.symlink(os_helper.TESTFN + "1", os_helper.TESTFN)
- self.assertEqual(realpath("nonexistent/../" + os_helper.TESTFN), ABSTFN + "1")
+ os.symlink(TESTFN + "1", TESTFN)
+ self.assertEqual(
+ realpath("nonexistent/../" + TESTFN, **kwargs), ABSTFN + "1")
finally:
- os_helper.unlink(os_helper.TESTFN)
+ os_helper.unlink(TESTFN)
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
@@ -550,37 +651,38 @@ class PosixPathTest(unittest.TestCase):
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_symlink_loops_strict(self):
+ @_parameterize({'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_symlink_loops_strict(self, kwargs):
# Bug #43757, raise OSError if we get into an infinite symlink loop in
- # strict mode.
+ # the strict modes.
try:
os.symlink(ABSTFN, ABSTFN)
- self.assertRaises(OSError, realpath, ABSTFN, strict=True)
+ self.assertRaises(OSError, realpath, ABSTFN, **kwargs)
os.symlink(ABSTFN+"1", ABSTFN+"2")
os.symlink(ABSTFN+"2", ABSTFN+"1")
- self.assertRaises(OSError, realpath, ABSTFN+"1", strict=True)
- self.assertRaises(OSError, realpath, ABSTFN+"2", strict=True)
+ self.assertRaises(OSError, realpath, ABSTFN+"1", **kwargs)
+ self.assertRaises(OSError, realpath, ABSTFN+"2", **kwargs)
- self.assertRaises(OSError, realpath, ABSTFN+"1/x", strict=True)
- self.assertRaises(OSError, realpath, ABSTFN+"1/..", strict=True)
- self.assertRaises(OSError, realpath, ABSTFN+"1/../x", strict=True)
+ self.assertRaises(OSError, realpath, ABSTFN+"1/x", **kwargs)
+ self.assertRaises(OSError, realpath, ABSTFN+"1/..", **kwargs)
+ self.assertRaises(OSError, realpath, ABSTFN+"1/../x", **kwargs)
os.symlink(ABSTFN+"x", ABSTFN+"y")
self.assertRaises(OSError, realpath,
- ABSTFN+"1/../" + basename(ABSTFN) + "y", strict=True)
+ ABSTFN+"1/../" + basename(ABSTFN) + "y", **kwargs)
self.assertRaises(OSError, realpath,
- ABSTFN+"1/../" + basename(ABSTFN) + "1", strict=True)
+ ABSTFN+"1/../" + basename(ABSTFN) + "1", **kwargs)
os.symlink(basename(ABSTFN) + "a/b", ABSTFN+"a")
- self.assertRaises(OSError, realpath, ABSTFN+"a", strict=True)
+ self.assertRaises(OSError, realpath, ABSTFN+"a", **kwargs)
os.symlink("../" + basename(dirname(ABSTFN)) + "/" +
basename(ABSTFN) + "c", ABSTFN+"c")
- self.assertRaises(OSError, realpath, ABSTFN+"c", strict=True)
+ self.assertRaises(OSError, realpath, ABSTFN+"c", **kwargs)
# Test using relative path as well.
with os_helper.change_cwd(dirname(ABSTFN)):
- self.assertRaises(OSError, realpath, basename(ABSTFN), strict=True)
+ self.assertRaises(OSError, realpath, basename(ABSTFN), **kwargs)
finally:
os_helper.unlink(ABSTFN)
os_helper.unlink(ABSTFN+"1")
@@ -591,28 +693,30 @@ class PosixPathTest(unittest.TestCase):
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_repeated_indirect_symlinks(self):
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_repeated_indirect_symlinks(self, kwargs):
# Issue #6975.
try:
os.mkdir(ABSTFN)
os.symlink('../' + basename(ABSTFN), ABSTFN + '/self')
os.symlink('self/self/self', ABSTFN + '/link')
- self.assertEqual(realpath(ABSTFN + '/link'), ABSTFN)
+ self.assertEqual(realpath(ABSTFN + '/link', **kwargs), ABSTFN)
finally:
os_helper.unlink(ABSTFN + '/self')
os_helper.unlink(ABSTFN + '/link')
- safe_rmdir(ABSTFN)
+ os_helper.rmdir(ABSTFN)
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_deep_recursion(self):
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_deep_recursion(self, kwargs):
depth = 10
try:
os.mkdir(ABSTFN)
for i in range(depth):
os.symlink('/'.join(['%d' % i] * 10), ABSTFN + '/%d' % (i + 1))
os.symlink('.', ABSTFN + '/0')
- self.assertEqual(realpath(ABSTFN + '/%d' % depth), ABSTFN)
+ self.assertEqual(realpath(ABSTFN + '/%d' % depth, **kwargs), ABSTFN)
# Test using relative path as well.
with os_helper.change_cwd(ABSTFN):
@@ -620,11 +724,12 @@ class PosixPathTest(unittest.TestCase):
finally:
for i in range(depth + 1):
os_helper.unlink(ABSTFN + '/%d' % i)
- safe_rmdir(ABSTFN)
+ os_helper.rmdir(ABSTFN)
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_resolve_parents(self):
+ @_parameterize({}, {'strict': ALLOW_MISSING})
+ def test_realpath_resolve_parents(self, kwargs):
# We also need to resolve any symlinks in the parents of a relative
# path passed to realpath. E.g.: current working directory is
# /usr/doc with 'doc' being a symlink to /usr/share/doc. We call
@@ -635,15 +740,17 @@ class PosixPathTest(unittest.TestCase):
os.symlink(ABSTFN + "/y", ABSTFN + "/k")
with os_helper.change_cwd(ABSTFN + "/k"):
- self.assertEqual(realpath("a"), ABSTFN + "/y/a")
+ self.assertEqual(realpath("a", **kwargs),
+ ABSTFN + "/y/a")
finally:
os_helper.unlink(ABSTFN + "/k")
- safe_rmdir(ABSTFN + "/y")
- safe_rmdir(ABSTFN)
+ os_helper.rmdir(ABSTFN + "/y")
+ os_helper.rmdir(ABSTFN)
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_resolve_before_normalizing(self):
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_resolve_before_normalizing(self, kwargs):
# Bug #990669: Symbolic links should be resolved before we
# normalize the path. E.g.: if we have directories 'a', 'k' and 'y'
# in the following hierarchy:
@@ -658,20 +765,21 @@ class PosixPathTest(unittest.TestCase):
os.symlink(ABSTFN + "/k/y", ABSTFN + "/link-y")
# Absolute path.
- self.assertEqual(realpath(ABSTFN + "/link-y/.."), ABSTFN + "/k")
+ self.assertEqual(realpath(ABSTFN + "/link-y/..", **kwargs), ABSTFN + "/k")
# Relative path.
with os_helper.change_cwd(dirname(ABSTFN)):
- self.assertEqual(realpath(basename(ABSTFN) + "/link-y/.."),
+ self.assertEqual(realpath(basename(ABSTFN) + "/link-y/..", **kwargs),
ABSTFN + "/k")
finally:
os_helper.unlink(ABSTFN + "/link-y")
- safe_rmdir(ABSTFN + "/k/y")
- safe_rmdir(ABSTFN + "/k")
- safe_rmdir(ABSTFN)
+ os_helper.rmdir(ABSTFN + "/k/y")
+ os_helper.rmdir(ABSTFN + "/k")
+ os_helper.rmdir(ABSTFN)
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
- def test_realpath_resolve_first(self):
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_resolve_first(self, kwargs):
# Bug #1213894: The first component of the path, if not absolute,
# must be resolved too.
@@ -681,12 +789,12 @@ class PosixPathTest(unittest.TestCase):
os.symlink(ABSTFN, ABSTFN + "link")
with os_helper.change_cwd(dirname(ABSTFN)):
base = basename(ABSTFN)
- self.assertEqual(realpath(base + "link"), ABSTFN)
- self.assertEqual(realpath(base + "link/k"), ABSTFN + "/k")
+ self.assertEqual(realpath(base + "link", **kwargs), ABSTFN)
+ self.assertEqual(realpath(base + "link/k", **kwargs), ABSTFN + "/k")
finally:
os_helper.unlink(ABSTFN + "link")
- safe_rmdir(ABSTFN + "/k")
- safe_rmdir(ABSTFN)
+ os_helper.rmdir(ABSTFN + "/k")
+ os_helper.rmdir(ABSTFN)
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
@@ -700,27 +808,95 @@ class PosixPathTest(unittest.TestCase):
self.assertEqual(realpath(ABSTFN + '/foo'), ABSTFN + '/foo')
self.assertEqual(realpath(ABSTFN + '/../foo'), dirname(ABSTFN) + '/foo')
self.assertEqual(realpath(ABSTFN + '/foo/..'), ABSTFN)
+ finally:
+ os.chmod(ABSTFN, 0o755, follow_symlinks=False)
+ os_helper.unlink(ABSTFN)
+
+ @os_helper.skip_unless_symlink
+ @skip_if_ABSTFN_contains_backslash
+ @unittest.skipIf(os.chmod not in os.supports_follow_symlinks, "Can't set symlink permissions")
+ @unittest.skipIf(sys.platform != "darwin", "only macOS requires read permission to readlink()")
+ @_parameterize({'strict': True}, {'strict': ALLOW_MISSING})
+ def test_realpath_unreadable_symlink_strict(self, kwargs):
+ try:
+ os.symlink(ABSTFN+"1", ABSTFN)
+ os.chmod(ABSTFN, 0o000, follow_symlinks=False)
+ with self.assertRaises(PermissionError):
+ realpath(ABSTFN, **kwargs)
+ with self.assertRaises(PermissionError):
+ realpath(ABSTFN + '/foo', **kwargs),
with self.assertRaises(PermissionError):
- realpath(ABSTFN, strict=True)
+ realpath(ABSTFN + '/../foo', **kwargs)
+ with self.assertRaises(PermissionError):
+ realpath(ABSTFN + '/foo/..', **kwargs)
finally:
os.chmod(ABSTFN, 0o755, follow_symlinks=False)
os.unlink(ABSTFN)
@skip_if_ABSTFN_contains_backslash
+ @os_helper.skip_unless_symlink
+ def test_realpath_unreadable_directory(self):
+ try:
+ os.mkdir(ABSTFN)
+ os.mkdir(ABSTFN + '/k')
+ os.chmod(ABSTFN, 0o000)
+ self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN)
+ self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN)
+ self.assertEqual(realpath(ABSTFN, strict=ALLOW_MISSING), ABSTFN)
+
+ try:
+ os.stat(ABSTFN)
+ except PermissionError:
+ pass
+ else:
+ self.skipTest('Cannot block permissions')
+
+ self.assertEqual(realpath(ABSTFN + '/k', strict=False),
+ ABSTFN + '/k')
+ self.assertRaises(PermissionError, realpath, ABSTFN + '/k',
+ strict=True)
+ self.assertRaises(PermissionError, realpath, ABSTFN + '/k',
+ strict=ALLOW_MISSING)
+
+ self.assertEqual(realpath(ABSTFN + '/missing', strict=False),
+ ABSTFN + '/missing')
+ self.assertRaises(PermissionError, realpath, ABSTFN + '/missing',
+ strict=True)
+ self.assertRaises(PermissionError, realpath, ABSTFN + '/missing',
+ strict=ALLOW_MISSING)
+ finally:
+ os.chmod(ABSTFN, 0o755)
+ os_helper.rmdir(ABSTFN + '/k')
+ os_helper.rmdir(ABSTFN)
+
+ @skip_if_ABSTFN_contains_backslash
def test_realpath_nonterminal_file(self):
try:
with open(ABSTFN, 'w') as f:
f.write('test_posixpath wuz ere')
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN)
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN)
+ self.assertEqual(realpath(ABSTFN, strict=ALLOW_MISSING), ABSTFN)
+
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN)
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN)
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "/subdir")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir",
+ strict=ALLOW_MISSING)
finally:
os_helper.unlink(ABSTFN)
@@ -733,16 +909,30 @@ class PosixPathTest(unittest.TestCase):
os.symlink(ABSTFN + "1", ABSTFN)
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN + "1")
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN + "1")
+ self.assertEqual(realpath(ABSTFN, strict=ALLOW_MISSING), ABSTFN + "1")
+
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN + "1")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN + "1")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "1/subdir")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir",
+ strict=ALLOW_MISSING)
finally:
os_helper.unlink(ABSTFN)
+ os_helper.unlink(ABSTFN + "1")
@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
@@ -754,16 +944,31 @@ class PosixPathTest(unittest.TestCase):
os.symlink(ABSTFN + "1", ABSTFN)
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN + "2")
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN + "2")
+ self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN + "2")
+
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN + "2")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN + "2")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..",
+ strict=ALLOW_MISSING)
+
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "2/subdir")
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
+ self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir",
+ strict=ALLOW_MISSING)
finally:
os_helper.unlink(ABSTFN)
+ os_helper.unlink(ABSTFN + "1")
+ os_helper.unlink(ABSTFN + "2")
def test_relpath(self):
(real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
@@ -889,8 +1094,8 @@ class PathLikeTests(unittest.TestCase):
path = posixpath
def setUp(self):
- self.file_name = os_helper.TESTFN
- self.file_path = FakePath(os_helper.TESTFN)
+ self.file_name = TESTFN
+ self.file_path = FakePath(TESTFN)
self.addCleanup(os_helper.unlink, self.file_name)
with open(self.file_name, 'xb', 0) as file:
file.write(b"test_posixpath.PathLikeTests")
@@ -947,9 +1152,12 @@ class PathLikeTests(unittest.TestCase):
def test_path_abspath(self):
self.assertPathEqual(self.path.abspath)
- def test_path_realpath(self):
+ @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
+ def test_path_realpath(self, kwargs):
self.assertPathEqual(self.path.realpath)
+ self.assertPathEqual(partial(self.path.realpath, **kwargs))
+
def test_path_relpath(self):
self.assertPathEqual(self.path.relpath)
diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py
index dfbc2a06e73..41c337ade7e 100644
--- a/Lib/test/test_pprint.py
+++ b/Lib/test/test_pprint.py
@@ -10,6 +10,10 @@ import random
import re
import types
import unittest
+from collections.abc import ItemsView, KeysView, Mapping, MappingView, ValuesView
+
+from test.support import cpython_only
+from test.support.import_helper import ensure_lazy_imports
# list, tuple and dict subclasses that do or don't overwrite __repr__
class list2(list):
@@ -67,6 +71,14 @@ class dict_custom_repr(dict):
def __repr__(self):
return '*'*len(dict.__repr__(self))
+class mappingview_custom_repr(MappingView):
+ def __repr__(self):
+ return '*'*len(MappingView.__repr__(self))
+
+class keysview_custom_repr(KeysView):
+ def __repr__(self):
+ return '*'*len(KeysView.__repr__(self))
+
@dataclasses.dataclass
class dataclass1:
field1: str
@@ -129,6 +141,10 @@ class QueryTestCase(unittest.TestCase):
self.b = list(range(200))
self.a[-12] = self.b
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("pprint", {"dataclasses", "re"})
+
def test_init(self):
pp = pprint.PrettyPrinter()
pp = pprint.PrettyPrinter(indent=4, width=40, depth=5,
@@ -173,10 +189,17 @@ class QueryTestCase(unittest.TestCase):
# Messy dict.
self.d = {}
self.d[0] = self.d[1] = self.d[2] = self.d
+ self.e = {}
+ self.v = ValuesView(self.e)
+ self.m = MappingView(self.e)
+ self.dv = self.e.values()
+ self.e["v"] = self.v
+ self.e["m"] = self.m
+ self.e["dv"] = self.dv
pp = pprint.PrettyPrinter()
- for icky in self.a, self.b, self.d, (self.d, self.d):
+ for icky in self.a, self.b, self.d, (self.d, self.d), self.e, self.v, self.m, self.dv:
self.assertTrue(pprint.isrecursive(icky), "expected isrecursive")
self.assertFalse(pprint.isreadable(icky), "expected not isreadable")
self.assertTrue(pp.isrecursive(icky), "expected isrecursive")
@@ -184,10 +207,11 @@ class QueryTestCase(unittest.TestCase):
# Break the cycles.
self.d.clear()
+ self.e.clear()
del self.a[:]
del self.b[:]
- for safe in self.a, self.b, self.d, (self.d, self.d):
+ for safe in self.a, self.b, self.d, (self.d, self.d), self.e, self.v, self.m, self.dv:
# module-level convenience functions
self.assertFalse(pprint.isrecursive(safe),
"expected not isrecursive for %r" % (safe,))
@@ -230,6 +254,8 @@ class QueryTestCase(unittest.TestCase):
set(), set2(), set3(),
frozenset(), frozenset2(), frozenset3(),
{}, dict2(), dict3(),
+ {}.keys(), {}.values(), {}.items(),
+ MappingView({}), KeysView({}), ItemsView({}), ValuesView({}),
self.assertTrue, pprint,
-6, -6, -6-6j, -1.5, "x", b"x", bytearray(b"x"),
(3,), [3], {3: 6},
@@ -239,6 +265,9 @@ class QueryTestCase(unittest.TestCase):
set({7}), set2({7}), set3({7}),
frozenset({8}), frozenset2({8}), frozenset3({8}),
dict2({5: 6}), dict3({5: 6}),
+ {5: 6}.keys(), {5: 6}.values(), {5: 6}.items(),
+ MappingView({5: 6}), KeysView({5: 6}),
+ ItemsView({5: 6}), ValuesView({5: 6}),
range(10, -11, -1),
True, False, None, ...,
):
@@ -268,6 +297,12 @@ class QueryTestCase(unittest.TestCase):
dict_custom_repr(),
dict_custom_repr({5: 6}),
dict_custom_repr(zip(range(N),range(N))),
+ mappingview_custom_repr({}),
+ mappingview_custom_repr({5: 6}),
+ mappingview_custom_repr(dict(zip(range(N),range(N)))),
+ keysview_custom_repr({}),
+ keysview_custom_repr({5: 6}),
+ keysview_custom_repr(dict(zip(range(N),range(N)))),
):
native = repr(cont)
expected = '*' * len(native)
@@ -296,6 +331,56 @@ class QueryTestCase(unittest.TestCase):
self.assertEqual(pprint.pformat(type(o)), exp)
o = range(100)
+ exp = 'dict_keys([%s])' % ',\n '.join(map(str, o))
+ keys = dict.fromkeys(o).keys()
+ self.assertEqual(pprint.pformat(keys), exp)
+
+ o = range(100)
+ exp = 'dict_values([%s])' % ',\n '.join(map(str, o))
+ values = {v: v for v in o}.values()
+ self.assertEqual(pprint.pformat(values), exp)
+
+ o = range(100)
+ exp = 'dict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o)
+ items = {v: v for v in o}.items()
+ self.assertEqual(pprint.pformat(items), exp)
+
+ o = range(100)
+ exp = 'odict_keys([%s])' % ',\n '.join(map(str, o))
+ keys = collections.OrderedDict.fromkeys(o).keys()
+ self.assertEqual(pprint.pformat(keys), exp)
+
+ o = range(100)
+ exp = 'odict_values([%s])' % ',\n '.join(map(str, o))
+ values = collections.OrderedDict({v: v for v in o}).values()
+ self.assertEqual(pprint.pformat(values), exp)
+
+ o = range(100)
+ exp = 'odict_items([%s])' % ',\n '.join("(%s, %s)" % (i, i) for i in o)
+ items = collections.OrderedDict({v: v for v in o}).items()
+ self.assertEqual(pprint.pformat(items), exp)
+
+ o = range(100)
+ exp = 'KeysView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+ keys_view = KeysView(dict.fromkeys(o))
+ self.assertEqual(pprint.pformat(keys_view), exp)
+
+ o = range(100)
+ exp = 'ItemsView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+ items_view = ItemsView(dict.fromkeys(o))
+ self.assertEqual(pprint.pformat(items_view), exp)
+
+ o = range(100)
+ exp = 'MappingView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+ mapping_view = MappingView(dict.fromkeys(o))
+ self.assertEqual(pprint.pformat(mapping_view), exp)
+
+ o = range(100)
+ exp = 'ValuesView({%s})' % (': None,\n '.join(map(str, o)) + ': None')
+ values_view = ValuesView(dict.fromkeys(o))
+ self.assertEqual(pprint.pformat(values_view), exp)
+
+ o = range(100)
exp = '[%s]' % ',\n '.join(map(str, o))
for type in [list, list2]:
self.assertEqual(pprint.pformat(type(o)), exp)
@@ -373,7 +458,7 @@ class QueryTestCase(unittest.TestCase):
return super().__new__(Temperature, celsius_degrees)
def __repr__(self):
kelvin_degrees = self + 273.15
- return f"{kelvin_degrees}°K"
+ return f"{kelvin_degrees:.2f}°K"
self.assertEqual(pprint.pformat(Temperature(1000)), '1273.15°K')
def test_sorted_dict(self):
@@ -418,6 +503,30 @@ OrderedDict([('the', 0),
('a', 6),
('lazy', 7),
('dog', 8)])""")
+ self.assertEqual(pprint.pformat(d.keys(), sort_dicts=False),
+"""\
+odict_keys(['the',
+ 'quick',
+ 'brown',
+ 'fox',
+ 'jumped',
+ 'over',
+ 'a',
+ 'lazy',
+ 'dog'])""")
+ self.assertEqual(pprint.pformat(d.items(), sort_dicts=False),
+"""\
+odict_items([('the', 0),
+ ('quick', 1),
+ ('brown', 2),
+ ('fox', 3),
+ ('jumped', 4),
+ ('over', 5),
+ ('a', 6),
+ ('lazy', 7),
+ ('dog', 8)])""")
+ self.assertEqual(pprint.pformat(d.values(), sort_dicts=False),
+ "odict_values([0, 1, 2, 3, 4, 5, 6, 7, 8])")
def test_mapping_proxy(self):
words = 'the quick brown fox jumped over a lazy dog'.split()
@@ -446,6 +555,152 @@ mappingproxy(OrderedDict([('the', 0),
('lazy', 7),
('dog', 8)]))""")
+ def test_dict_views(self):
+ for dict_class in (dict, collections.OrderedDict, collections.Counter):
+ empty = dict_class({})
+ short = dict_class(dict(zip('edcba', 'edcba')))
+ long = dict_class(dict((chr(x), chr(x)) for x in range(90, 64, -1)))
+ lengths = {"empty": empty, "short": short, "long": long}
+ prefix = "odict" if dict_class is collections.OrderedDict else "dict"
+ for name, d in lengths.items():
+ with self.subTest(length=name, prefix=prefix):
+ is_short = len(d) < 6
+ joiner = ", " if is_short else ",\n "
+ k = d.keys()
+ v = d.values()
+ i = d.items()
+ self.assertEqual(pprint.pformat(k, sort_dicts=True),
+ prefix + "_keys([%s])" %
+ joiner.join(repr(key) for key in sorted(k)))
+ self.assertEqual(pprint.pformat(v, sort_dicts=True),
+ prefix + "_values([%s])" %
+ joiner.join(repr(val) for val in sorted(v)))
+ self.assertEqual(pprint.pformat(i, sort_dicts=True),
+ prefix + "_items([%s])" %
+ joiner.join(repr(item) for item in sorted(i)))
+ self.assertEqual(pprint.pformat(k, sort_dicts=False),
+ prefix + "_keys([%s])" %
+ joiner.join(repr(key) for key in k))
+ self.assertEqual(pprint.pformat(v, sort_dicts=False),
+ prefix + "_values([%s])" %
+ joiner.join(repr(val) for val in v))
+ self.assertEqual(pprint.pformat(i, sort_dicts=False),
+ prefix + "_items([%s])" %
+ joiner.join(repr(item) for item in i))
+
+ def test_abc_views(self):
+ empty = {}
+ short = dict(zip('edcba', 'edcba'))
+ long = dict((chr(x), chr(x)) for x in range(90, 64, -1))
+ lengths = {"empty": empty, "short": short, "long": long}
+ # Test that a subclass that doesn't replace __repr__ works with different lengths
+ class MV(MappingView): pass
+
+ for name, d in lengths.items():
+ with self.subTest(length=name, name="Views"):
+ is_short = len(d) < 6
+ joiner = ", " if is_short else ",\n "
+ i = d.items()
+ s = sorted(i)
+ joined_items = "({%s})" % joiner.join(["%r: %r" % (k, v) for (k, v) in i])
+ sorted_items = "({%s})" % joiner.join(["%r: %r" % (k, v) for (k, v) in s])
+ self.assertEqual(pprint.pformat(KeysView(d), sort_dicts=True),
+ KeysView.__name__ + sorted_items)
+ self.assertEqual(pprint.pformat(ItemsView(d), sort_dicts=True),
+ ItemsView.__name__ + sorted_items)
+ self.assertEqual(pprint.pformat(MappingView(d), sort_dicts=True),
+ MappingView.__name__ + sorted_items)
+ self.assertEqual(pprint.pformat(MV(d), sort_dicts=True),
+ MV.__name__ + sorted_items)
+ self.assertEqual(pprint.pformat(ValuesView(d), sort_dicts=True),
+ ValuesView.__name__ + sorted_items)
+ self.assertEqual(pprint.pformat(KeysView(d), sort_dicts=False),
+ KeysView.__name__ + joined_items)
+ self.assertEqual(pprint.pformat(ItemsView(d), sort_dicts=False),
+ ItemsView.__name__ + joined_items)
+ self.assertEqual(pprint.pformat(MappingView(d), sort_dicts=False),
+ MappingView.__name__ + joined_items)
+ self.assertEqual(pprint.pformat(MV(d), sort_dicts=False),
+ MV.__name__ + joined_items)
+ self.assertEqual(pprint.pformat(ValuesView(d), sort_dicts=False),
+ ValuesView.__name__ + joined_items)
+
+ def test_nested_views(self):
+ d = {1: MappingView({1: MappingView({1: MappingView({1: 2})})})}
+ self.assertEqual(repr(d),
+ "{1: MappingView({1: MappingView({1: MappingView({1: 2})})})}")
+ self.assertEqual(pprint.pformat(d),
+ "{1: MappingView({1: MappingView({1: MappingView({1: 2})})})}")
+ self.assertEqual(pprint.pformat(d, depth=2),
+ "{1: MappingView({1: {...}})}")
+ d = {}
+ d1 = {1: d.values()}
+ d2 = {1: d1.values()}
+ d3 = {1: d2.values()}
+ self.assertEqual(pprint.pformat(d3),
+ "{1: dict_values([dict_values([dict_values([])])])}")
+ self.assertEqual(pprint.pformat(d3, depth=2),
+ "{1: dict_values([{...}])}")
+
+ def test_unorderable_items_views(self):
+ """Check that views with unorderable items have stable sorting."""
+ d = dict((((3+1j), 3), ((1+1j), (1+0j)), (1j, 0j), (500, None), (499, None)))
+ iv = ItemsView(d)
+ self.assertEqual(pprint.pformat(iv),
+ pprint.pformat(iv))
+ self.assertTrue(pprint.pformat(iv).endswith(", 499: None, 500: None})"),
+ pprint.pformat(iv))
+ self.assertEqual(pprint.pformat(d.items()), # Won't be equal unless _safe_tuple
+ pprint.pformat(d.items())) # is used in _safe_repr
+ self.assertTrue(pprint.pformat(d.items()).endswith(", (499, None), (500, None)])"))
+
+ def test_mapping_view_subclass_no_mapping(self):
+ class BMV(MappingView):
+ def __init__(self, d):
+ super().__init__(d)
+ self.mapping = self._mapping
+ del self._mapping
+
+ self.assertRaises(AttributeError, pprint.pformat, BMV({}))
+
+ def test_mapping_subclass_repr(self):
+ """Test that mapping ABC views use their ._mapping's __repr__."""
+ class MyMapping(Mapping):
+ def __init__(self, keys=None):
+ self._keys = {} if keys is None else dict.fromkeys(keys)
+
+ def __getitem__(self, item):
+ return self._keys[item]
+
+ def __len__(self):
+ return len(self._keys)
+
+ def __iter__(self):
+ return iter(self._keys)
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}([{', '.join(map(repr, self._keys.keys()))}])"
+
+ m = MyMapping(["test", 1])
+ self.assertEqual(repr(m), "MyMapping(['test', 1])")
+ short_view_repr = "%s(MyMapping(['test', 1]))"
+ self.assertEqual(repr(m.keys()), short_view_repr % "KeysView")
+ self.assertEqual(pprint.pformat(m.items()), short_view_repr % "ItemsView")
+ self.assertEqual(pprint.pformat(m.keys()), short_view_repr % "KeysView")
+ self.assertEqual(pprint.pformat(MappingView(m)), short_view_repr % "MappingView")
+ self.assertEqual(pprint.pformat(m.values()), short_view_repr % "ValuesView")
+
+ alpha = "abcdefghijklmnopqrstuvwxyz"
+ m = MyMapping(alpha)
+ alpha_repr = ", ".join(map(repr, list(alpha)))
+ long_view_repr = "%%s(MyMapping([%s]))" % alpha_repr
+ self.assertEqual(repr(m), "MyMapping([%s])" % alpha_repr)
+ self.assertEqual(repr(m.keys()), long_view_repr % "KeysView")
+ self.assertEqual(pprint.pformat(m.items()), long_view_repr % "ItemsView")
+ self.assertEqual(pprint.pformat(m.keys()), long_view_repr % "KeysView")
+ self.assertEqual(pprint.pformat(MappingView(m)), long_view_repr % "MappingView")
+ self.assertEqual(pprint.pformat(m.values()), long_view_repr % "ValuesView")
+
def test_empty_simple_namespace(self):
ns = types.SimpleNamespace()
formatted = pprint.pformat(ns)
@@ -761,6 +1016,10 @@ frozenset2({0,
'frozenset({' + ','.join(map(repr, skeys)) + '})')
self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys))),
'{' + ','.join('%r:None' % k for k in skeys) + '}')
+ self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys).keys())),
+ 'dict_keys([' + ','.join('%r' % k for k in skeys) + '])')
+ self.assertEqual(clean(pprint.pformat(dict.fromkeys(keys).items())),
+ 'dict_items([' + ','.join('(%r,None)' % k for k in skeys) + '])')
# Issue 10017: TypeError on user-defined types as dict keys.
self.assertEqual(pprint.pformat({Unorderable: 0, 1: 0}),
@@ -1042,6 +1301,66 @@ ChainMap({'a': 6,
('a', 6),
('lazy', 7),
('dog', 8)]))""")
+ self.assertEqual(pprint.pformat(d.keys()),
+"""\
+KeysView(ChainMap({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0},
+ OrderedDict([('the', 0),
+ ('quick', 1),
+ ('brown', 2),
+ ('fox', 3),
+ ('jumped', 4),
+ ('over', 5),
+ ('a', 6),
+ ('lazy', 7),
+ ('dog', 8)])))""")
+ self.assertEqual(pprint.pformat(d.items()),
+ """\
+ItemsView(ChainMap({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0},
+ OrderedDict([('the', 0),
+ ('quick', 1),
+ ('brown', 2),
+ ('fox', 3),
+ ('jumped', 4),
+ ('over', 5),
+ ('a', 6),
+ ('lazy', 7),
+ ('dog', 8)])))""")
+ self.assertEqual(pprint.pformat(d.values()),
+ """\
+ValuesView(ChainMap({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0},
+ OrderedDict([('the', 0),
+ ('quick', 1),
+ ('brown', 2),
+ ('fox', 3),
+ ('jumped', 4),
+ ('over', 5),
+ ('a', 6),
+ ('lazy', 7),
+ ('dog', 8)])))""")
def test_deque(self):
d = collections.deque()
@@ -1089,6 +1408,36 @@ deque([('brown', 2),
'over': 5,
'quick': 1,
'the': 0}""")
+ self.assertEqual(pprint.pformat(d.keys()), """\
+KeysView({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0})""")
+ self.assertEqual(pprint.pformat(d.items()), """\
+ItemsView({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0})""")
+ self.assertEqual(pprint.pformat(d.values()), """\
+ValuesView({'a': 6,
+ 'brown': 2,
+ 'dog': 8,
+ 'fox': 3,
+ 'jumped': 4,
+ 'lazy': 7,
+ 'over': 5,
+ 'quick': 1,
+ 'the': 0})""")
def test_user_list(self):
d = collections.UserList()
diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py
index cea241b0f20..26aefdbf042 100644
--- a/Lib/test/test_property.py
+++ b/Lib/test/test_property.py
@@ -87,8 +87,8 @@ class PropertyTests(unittest.TestCase):
self.assertEqual(base.spam, 10)
self.assertEqual(base._spam, 10)
delattr(base, "spam")
- self.assertTrue(not hasattr(base, "spam"))
- self.assertTrue(not hasattr(base, "_spam"))
+ self.assertNotHasAttr(base, "spam")
+ self.assertNotHasAttr(base, "_spam")
base.spam = 20
self.assertEqual(base.spam, 20)
self.assertEqual(base._spam, 20)
diff --git a/Lib/test/test_pstats.py b/Lib/test/test_pstats.py
index d5a5a9738c2..a26a8c1d522 100644
--- a/Lib/test/test_pstats.py
+++ b/Lib/test/test_pstats.py
@@ -1,6 +1,7 @@
import unittest
from test import support
+from test.support.import_helper import ensure_lazy_imports
from io import StringIO
from pstats import SortKey
from enum import StrEnum, _test_simple_enum
@@ -10,6 +11,12 @@ import pstats
import tempfile
import cProfile
+class LazyImportTest(unittest.TestCase):
+ @support.cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("pstats", {"typing"})
+
+
class AddCallersTestCase(unittest.TestCase):
"""Tests for pstats.add_callers helper."""
diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py
index c1728f5019d..4836f38c388 100644
--- a/Lib/test/test_pty.py
+++ b/Lib/test/test_pty.py
@@ -20,7 +20,6 @@ import select
import signal
import socket
import io # readline
-import warnings
TEST_STRING_1 = b"I wish to buy a fish license.\n"
TEST_STRING_2 = b"For my pet fish, Eric.\n"
diff --git a/Lib/test/test_pulldom.py b/Lib/test/test_pulldom.py
index 6dc51e4371d..3c8ed251aca 100644
--- a/Lib/test/test_pulldom.py
+++ b/Lib/test/test_pulldom.py
@@ -46,7 +46,7 @@ class PullDOMTestCase(unittest.TestCase):
items = pulldom.parseString(SMALL_SAMPLE)
evt, node = next(items)
# Just check the node is a Document:
- self.assertTrue(hasattr(node, "createElement"))
+ self.assertHasAttr(node, "createElement")
self.assertEqual(pulldom.START_DOCUMENT, evt)
evt, node = next(items)
self.assertEqual(pulldom.START_ELEMENT, evt)
@@ -192,7 +192,7 @@ class ThoroughTestCase(unittest.TestCase):
evt, node = next(pd)
self.assertEqual(pulldom.START_DOCUMENT, evt)
# Just check the node is a Document:
- self.assertTrue(hasattr(node, "createElement"))
+ self.assertHasAttr(node, "createElement")
if before_root:
evt, node = next(pd)
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index df05cd07d7e..bce68e6cd7a 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -11,7 +11,6 @@ from types import FunctionType, MethodType, BuiltinFunctionType
import pyclbr
from unittest import TestCase, main as unittest_main
from test.test_importlib import util as test_importlib_util
-import warnings
StaticMethodType = type(staticmethod(lambda: None))
@@ -103,7 +102,7 @@ class PyclbrTest(TestCase):
for name, value in dict.items():
if name in ignore:
continue
- self.assertHasAttr(module, name, ignore)
+ self.assertHasAttr(module, name)
py_item = getattr(module, name)
if isinstance(value, pyclbr.Function):
self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType))
@@ -246,9 +245,6 @@ class PyclbrTest(TestCase):
# These were once some of the longest modules.
cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator
cm('pickle', ignore=('partial', 'PickleBuffer'))
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', DeprecationWarning)
- cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property
with temporary_main_spec():
cm(
'pdb',
diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py
index ac88b3c6f13..d1d6f4987de 100644
--- a/Lib/test/test_pydoc/test_pydoc.py
+++ b/Lib/test/test_pydoc/test_pydoc.py
@@ -553,7 +553,7 @@ class PydocDocTest(unittest.TestCase):
# of the known subclasses of object. (doc.docclass() used to
# fail if HeapType was imported before running this test, like
# when running tests sequentially.)
- from _testcapi import HeapType
+ from _testcapi import HeapType # noqa: F401
except ImportError:
pass
text = doc.docclass(object)
@@ -1380,7 +1380,7 @@ class PydocImportTest(PydocBaseTest):
helper('modules garbage')
result = help_io.getvalue()
- self.assertTrue(result.startswith(expected))
+ self.assertStartsWith(result, expected)
def test_importfile(self):
try:
diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py
index 1d56ccd71cf..d4b4f60be98 100644
--- a/Lib/test/test_pyexpat.py
+++ b/Lib/test/test_pyexpat.py
@@ -9,12 +9,11 @@ import traceback
from io import BytesIO
from test import support
from test.support import os_helper
-
+from test.support import sortdict
+from unittest import mock
from xml.parsers import expat
from xml.parsers.expat import errors
-from test.support import sortdict
-
class SetAttributeTest(unittest.TestCase):
def setUp(self):
@@ -436,6 +435,19 @@ class BufferTextTest(unittest.TestCase):
"<!--abc-->", "4", "<!--def-->", "5", "</a>"],
"buffered text not properly split")
+ def test_change_character_data_handler_in_callback(self):
+ # Test that xmlparse_handler_setter() properly handles
+ # the special case "parser.CharacterDataHandler = None".
+ def handler(*args):
+ parser.CharacterDataHandler = None
+
+ handler_wrapper = mock.Mock(wraps=handler)
+ parser = expat.ParserCreate()
+ parser.CharacterDataHandler = handler_wrapper
+ parser.Parse(b"<a>1<b/>2<c></c>3<!--abc-->4<!--def-->5</a> ", True)
+ handler_wrapper.assert_called_once()
+ self.assertIsNone(parser.CharacterDataHandler)
+
# Test handling of exception from callback:
class HandlerExceptionTest(unittest.TestCase):
@@ -595,7 +607,7 @@ class ChardataBufferTest(unittest.TestCase):
def test_disabling_buffer(self):
xml1 = b"<?xml version='1.0' encoding='iso8859'?><a>" + b'a' * 512
xml2 = b'b' * 1024
- xml3 = b'c' * 1024 + b'</a>';
+ xml3 = b'c' * 1024 + b'</a>'
parser = expat.ParserCreate()
parser.CharacterDataHandler = self.counting_handler
parser.buffer_text = 1
diff --git a/Lib/test/test_pyrepl/support.py b/Lib/test/test_pyrepl/support.py
index 3692e164cb9..4f7f9d77933 100644
--- a/Lib/test/test_pyrepl/support.py
+++ b/Lib/test/test_pyrepl/support.py
@@ -113,9 +113,6 @@ handle_events_narrow_console = partial(
prepare_console=partial(prepare_console, width=10),
)
-reader_no_colors = partial(prepare_reader, can_colorize=False)
-reader_force_colors = partial(prepare_reader, can_colorize=True)
-
class FakeConsole(Console):
def __init__(self, events, encoding="utf-8") -> None:
diff --git a/Lib/test/test_pyrepl/test_eventqueue.py b/Lib/test/test_pyrepl/test_eventqueue.py
index afb55710342..edfe6ac4748 100644
--- a/Lib/test/test_pyrepl/test_eventqueue.py
+++ b/Lib/test/test_pyrepl/test_eventqueue.py
@@ -53,7 +53,7 @@ class EventQueueTestBase:
mock_keymap.compile_keymap.return_value = {"a": "b"}
eq = self.make_eventqueue()
eq.keymap = {b"a": "b"}
- eq.push("a")
+ eq.push(b"a")
mock_keymap.compile_keymap.assert_called()
self.assertEqual(eq.events[0].evt, "key")
self.assertEqual(eq.events[0].data, "b")
@@ -63,7 +63,7 @@ class EventQueueTestBase:
mock_keymap.compile_keymap.return_value = {"a": "b"}
eq = self.make_eventqueue()
eq.keymap = {b"c": "d"}
- eq.push("a")
+ eq.push(b"a")
mock_keymap.compile_keymap.assert_called()
self.assertEqual(eq.events[0].evt, "key")
self.assertEqual(eq.events[0].data, "a")
@@ -73,13 +73,13 @@ class EventQueueTestBase:
mock_keymap.compile_keymap.return_value = {"a": "b"}
eq = self.make_eventqueue()
eq.keymap = {b"a": {b"b": "c"}}
- eq.push("a")
+ eq.push(b"a")
mock_keymap.compile_keymap.assert_called()
self.assertTrue(eq.empty())
- eq.push("b")
+ eq.push(b"b")
self.assertEqual(eq.events[0].evt, "key")
self.assertEqual(eq.events[0].data, "c")
- eq.push("d")
+ eq.push(b"d")
self.assertEqual(eq.events[1].evt, "key")
self.assertEqual(eq.events[1].data, "d")
@@ -88,32 +88,32 @@ class EventQueueTestBase:
mock_keymap.compile_keymap.return_value = {"a": "b"}
eq = self.make_eventqueue()
eq.keymap = {b"a": {b"b": "c"}}
- eq.push("a")
+ eq.push(b"a")
mock_keymap.compile_keymap.assert_called()
self.assertTrue(eq.empty())
eq.flush_buf()
- eq.push("\033")
+ eq.push(b"\033")
self.assertEqual(eq.events[0].evt, "key")
self.assertEqual(eq.events[0].data, "\033")
- eq.push("b")
+ eq.push(b"b")
self.assertEqual(eq.events[1].evt, "key")
self.assertEqual(eq.events[1].data, "b")
def test_push_special_key(self):
eq = self.make_eventqueue()
eq.keymap = {}
- eq.push("\x1b")
- eq.push("[")
- eq.push("A")
+ eq.push(b"\x1b")
+ eq.push(b"[")
+ eq.push(b"A")
self.assertEqual(eq.events[0].evt, "key")
self.assertEqual(eq.events[0].data, "\x1b")
def test_push_unrecognized_escape_sequence(self):
eq = self.make_eventqueue()
eq.keymap = {}
- eq.push("\x1b")
- eq.push("[")
- eq.push("Z")
+ eq.push(b"\x1b")
+ eq.push(b"[")
+ eq.push(b"Z")
self.assertEqual(len(eq.events), 3)
self.assertEqual(eq.events[0].evt, "key")
self.assertEqual(eq.events[0].data, "\x1b")
@@ -122,12 +122,54 @@ class EventQueueTestBase:
self.assertEqual(eq.events[2].evt, "key")
self.assertEqual(eq.events[2].data, "Z")
- def test_push_unicode_character(self):
+ def test_push_unicode_character_as_str(self):
eq = self.make_eventqueue()
eq.keymap = {}
- eq.push("ч")
- self.assertEqual(eq.events[0].evt, "key")
- self.assertEqual(eq.events[0].data, "ч")
+ with self.assertRaises(AssertionError):
+ eq.push("ч")
+ with self.assertRaises(AssertionError):
+ eq.push("ñ")
+
+ def test_push_unicode_character_two_bytes(self):
+ eq = self.make_eventqueue()
+ eq.keymap = {}
+
+ encoded = "ч".encode(eq.encoding, "replace")
+ self.assertEqual(len(encoded), 2)
+
+ eq.push(encoded[0])
+ e = eq.get()
+ self.assertIsNone(e)
+
+ eq.push(encoded[1])
+ e = eq.get()
+ self.assertEqual(e.evt, "key")
+ self.assertEqual(e.data, "ч")
+
+ def test_push_single_chars_and_unicode_character_as_str(self):
+ eq = self.make_eventqueue()
+ eq.keymap = {}
+
+ def _event(evt, data, raw=None):
+ r = raw if raw is not None else data.encode(eq.encoding)
+ e = Event(evt, data, r)
+ return e
+
+ def _push(keys):
+ for k in keys:
+ eq.push(k)
+
+ self.assertIsInstance("ñ", str)
+
+ # If an exception happens during push, the existing events must be
+ # preserved and we can continue to push.
+ _push(b"b")
+ with self.assertRaises(AssertionError):
+ _push("ñ")
+ _push(b"a")
+
+ self.assertEqual(eq.get(), _event("key", "b"))
+ self.assertEqual(eq.get(), _event("key", "a"))
@unittest.skipIf(support.MS_WINDOWS, "No Unix event queue on Windows")
diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py
index a20719033fc..8c0eeab6dca 100644
--- a/Lib/test/test_pyrepl/test_interact.py
+++ b/Lib/test/test_pyrepl/test_interact.py
@@ -113,7 +113,7 @@ class TestSimpleInteract(unittest.TestCase):
r = """
def f(x, x): ...
^
-SyntaxError: duplicate argument 'x' in function definition"""
+SyntaxError: duplicate parameter 'x' in function definition"""
self.assertIn(r, f.getvalue())
def test_runsource_shows_syntax_error_for_failed_compilation(self):
diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py
index 93029ab6e08..98bae7dd703 100644
--- a/Lib/test/test_pyrepl/test_pyrepl.py
+++ b/Lib/test/test_pyrepl/test_pyrepl.py
@@ -8,9 +8,10 @@ import select
import subprocess
import sys
import tempfile
+from pkgutil import ModuleInfo
from unittest import TestCase, skipUnless, skipIf
from unittest.mock import patch
-from test.support import force_not_colorized, make_clean_env
+from test.support import force_not_colorized, make_clean_env, Py_DEBUG
from test.support import SHORT_TIMEOUT, STDLIB_DIR
from test.support.import_helper import import_module
from test.support.os_helper import EnvironmentVarGuard, unlink
@@ -452,6 +453,11 @@ class TestPyReplAutoindent(TestCase):
)
# fmt: on
+ events = code_to_events(input_code)
+ reader = self.prepare_reader(events)
+ output = multiline_input(reader)
+ self.assertEqual(output, output_code)
+
def test_auto_indent_continuation(self):
# auto indenting according to previous user indentation
# fmt: off
@@ -912,7 +918,14 @@ class TestPyReplCompleter(TestCase):
class TestPyReplModuleCompleter(TestCase):
def setUp(self):
+ import importlib
+ # Make iter_modules() search only the standard library.
+ # This makes the test more reliable in case there are
+ # other user packages/scripts on PYTHONPATH which can
+ # interfere with the completions.
+ lib_path = os.path.dirname(importlib.__path__[0])
self._saved_sys_path = sys.path
+ sys.path = [lib_path]
def tearDown(self):
sys.path = self._saved_sys_path
@@ -920,19 +933,12 @@ class TestPyReplModuleCompleter(TestCase):
def prepare_reader(self, events, namespace):
console = FakeConsole(events)
config = ReadlineConfig()
+ config.module_completer = ModuleCompleter(namespace)
config.readline_completer = rlcompleter.Completer(namespace).complete
reader = ReadlineAlikeReader(console=console, config=config)
return reader
def test_import_completions(self):
- import importlib
- # Make iter_modules() search only the standard library.
- # This makes the test more reliable in case there are
- # other user packages/scripts on PYTHONPATH which can
- # intefere with the completions.
- lib_path = os.path.dirname(importlib.__path__[0])
- sys.path = [lib_path]
-
cases = (
("import path\t\n", "import pathlib"),
("import importlib.\t\tres\t\n", "import importlib.resources"),
@@ -954,10 +960,38 @@ class TestPyReplModuleCompleter(TestCase):
output = reader.readline()
self.assertEqual(output, expected)
- def test_relative_import_completions(self):
+ @patch("pkgutil.iter_modules", lambda: [ModuleInfo(None, "public", True),
+ ModuleInfo(None, "_private", True)])
+ @patch("sys.builtin_module_names", ())
+ def test_private_completions(self):
+ cases = (
+ # Return public methods by default
+ ("import \t\n", "import public"),
+ ("from \t\n", "from public"),
+ # Return private methods if explicitly specified
+ ("import _\t\n", "import _private"),
+ ("from _\t\n", "from _private"),
+ )
+ for code, expected in cases:
+ with self.subTest(code=code):
+ events = code_to_events(code)
+ reader = self.prepare_reader(events, namespace={})
+ output = reader.readline()
+ self.assertEqual(output, expected)
+
+ @patch(
+ "_pyrepl._module_completer.ModuleCompleter.iter_submodules",
+ lambda *_: [
+ ModuleInfo(None, "public", True),
+ ModuleInfo(None, "_private", True),
+ ],
+ )
+ def test_sub_module_private_completions(self):
cases = (
- ("from .readl\t\n", "from .readline"),
- ("from . import readl\t\n", "from . import readline"),
+ # Return public methods by default
+ ("from foo import \t\n", "from foo import public"),
+ # Return private methods if explicitly specified
+ ("from foo import _\t\n", "from foo import _private"),
)
for code, expected in cases:
with self.subTest(code=code):
@@ -966,8 +1000,42 @@ class TestPyReplModuleCompleter(TestCase):
output = reader.readline()
self.assertEqual(output, expected)
- @patch("pkgutil.iter_modules", lambda: [(None, 'valid_name', None),
- (None, 'invalid-name', None)])
+ def test_builtin_completion_top_level(self):
+ import importlib
+ # Make iter_modules() search only the standard library.
+ # This makes the test more reliable in case there are
+ # other user packages/scripts on PYTHONPATH which can
+ # intefere with the completions.
+ lib_path = os.path.dirname(importlib.__path__[0])
+ sys.path = [lib_path]
+
+ cases = (
+ ("import bui\t\n", "import builtins"),
+ ("from bui\t\n", "from builtins"),
+ )
+ for code, expected in cases:
+ with self.subTest(code=code):
+ events = code_to_events(code)
+ reader = self.prepare_reader(events, namespace={})
+ output = reader.readline()
+ self.assertEqual(output, expected)
+
+ def test_relative_import_completions(self):
+ cases = (
+ (None, "from .readl\t\n", "from .readl"),
+ (None, "from . import readl\t\n", "from . import readl"),
+ ("_pyrepl", "from .readl\t\n", "from .readline"),
+ ("_pyrepl", "from . import readl\t\n", "from . import readline"),
+ )
+ for package, code, expected in cases:
+ with self.subTest(code=code):
+ events = code_to_events(code)
+ reader = self.prepare_reader(events, namespace={"__package__": package})
+ output = reader.readline()
+ self.assertEqual(output, expected)
+
+ @patch("pkgutil.iter_modules", lambda: [ModuleInfo(None, "valid_name", True),
+ ModuleInfo(None, "invalid-name", True)])
def test_invalid_identifiers(self):
# Make sure modules which are not valid identifiers
# are not suggested as those cannot be imported via 'import'.
@@ -983,6 +1051,19 @@ class TestPyReplModuleCompleter(TestCase):
output = reader.readline()
self.assertEqual(output, expected)
+ def test_no_fallback_on_regular_completion(self):
+ cases = (
+ ("import pri\t\n", "import pri"),
+ ("from pri\t\n", "from pri"),
+ ("from typing import Na\t\n", "from typing import Na"),
+ )
+ for code, expected in cases:
+ with self.subTest(code=code):
+ events = code_to_events(code)
+ reader = self.prepare_reader(events, namespace={})
+ output = reader.readline()
+ self.assertEqual(output, expected)
+
def test_get_path_and_prefix(self):
cases = (
('', ('', '')),
@@ -1050,11 +1131,15 @@ class TestPyReplModuleCompleter(TestCase):
self.assertEqual(actual, parsed)
# The parser should not get tripped up by any
# other preceding statements
- code = f'import xyz\n{code}'
- with self.subTest(code=code):
+ _code = f'import xyz\n{code}'
+ parser = ImportParser(_code)
+ actual = parser.parse()
+ with self.subTest(code=_code):
self.assertEqual(actual, parsed)
- code = f'import xyz;{code}'
- with self.subTest(code=code):
+ _code = f'import xyz;{code}'
+ parser = ImportParser(_code)
+ actual = parser.parse()
+ with self.subTest(code=_code):
self.assertEqual(actual, parsed)
def test_parse_error(self):
@@ -1327,7 +1412,7 @@ class TestMain(ReplTestCase):
)
@force_not_colorized
- def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False):
+ def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False, pythonstartup=False):
clean_env = make_clean_env()
clean_env["NO_COLOR"] = "1" # force_not_colorized doesn't touch subprocesses
@@ -1336,9 +1421,13 @@ class TestMain(ReplTestCase):
blue.mkdir()
mod = blue / "calx.py"
mod.write_text("FOO = 42", encoding="utf-8")
+ startup = blue / "startup.py"
+ startup.write_text("BAR = 64", encoding="utf-8")
commands = [
"print(f'^{" + var + "=}')" for var in expectations
] + ["exit()"]
+ if pythonstartup:
+ clean_env["PYTHONSTARTUP"] = str(startup)
if as_file and as_module:
self.fail("as_file and as_module are mutually exclusive")
elif as_file:
@@ -1357,7 +1446,13 @@ class TestMain(ReplTestCase):
skip=True,
)
else:
- self.fail("Choose one of as_file or as_module")
+ output, exit_code = self.run_repl(
+ commands,
+ cmdline_args=[],
+ env=clean_env,
+ cwd=td,
+ skip=True,
+ )
self.assertEqual(exit_code, 0)
for var, expected in expectations.items():
@@ -1370,6 +1465,23 @@ class TestMain(ReplTestCase):
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)
+ def test_globals_initialized_as_default(self):
+ expectations = {
+ "__name__": "'__main__'",
+ "__package__": "None",
+ # "__file__" is missing in -i, like in the basic REPL
+ }
+ self._run_repl_globals_test(expectations)
+
+ def test_globals_initialized_from_pythonstartup(self):
+ expectations = {
+ "BAR": "64",
+ "__name__": "'__main__'",
+ "__package__": "None",
+ # "__file__" is missing in -i, like in the basic REPL
+ }
+ self._run_repl_globals_test(expectations, pythonstartup=True)
+
def test_inspect_keeps_globals_from_inspected_file(self):
expectations = {
"FOO": "42",
@@ -1379,6 +1491,16 @@ class TestMain(ReplTestCase):
}
self._run_repl_globals_test(expectations, as_file=True)
+ def test_inspect_keeps_globals_from_inspected_file_with_pythonstartup(self):
+ expectations = {
+ "FOO": "42",
+ "BAR": "64",
+ "__name__": "'__main__'",
+ "__package__": "None",
+ # "__file__" is missing in -i, like in the basic REPL
+ }
+ self._run_repl_globals_test(expectations, as_file=True, pythonstartup=True)
+
def test_inspect_keeps_globals_from_inspected_module(self):
expectations = {
"FOO": "42",
@@ -1388,26 +1510,32 @@ class TestMain(ReplTestCase):
}
self._run_repl_globals_test(expectations, as_module=True)
+ def test_inspect_keeps_globals_from_inspected_module_with_pythonstartup(self):
+ expectations = {
+ "FOO": "42",
+ "BAR": "64",
+ "__name__": "'__main__'",
+ "__package__": "'blue'",
+ "__file__": re.compile(r"^'.*calx.py'$"),
+ }
+ self._run_repl_globals_test(expectations, as_module=True, pythonstartup=True)
+
@force_not_colorized
def test_python_basic_repl(self):
env = os.environ.copy()
- commands = ("from test.support import initialized_with_pyrepl\n"
- "initialized_with_pyrepl()\n"
- "exit()\n")
-
+ pyrepl_commands = "clear\nexit()\n"
env.pop("PYTHON_BASIC_REPL", None)
- output, exit_code = self.run_repl(commands, env=env, skip=True)
+ output, exit_code = self.run_repl(pyrepl_commands, env=env, skip=True)
self.assertEqual(exit_code, 0)
- self.assertIn("True", output)
- self.assertNotIn("False", output)
self.assertNotIn("Exception", output)
+ self.assertNotIn("NameError", output)
self.assertNotIn("Traceback", output)
+ basic_commands = "help\nexit()\n"
env["PYTHON_BASIC_REPL"] = "1"
- output, exit_code = self.run_repl(commands, env=env)
+ output, exit_code = self.run_repl(basic_commands, env=env)
self.assertEqual(exit_code, 0)
- self.assertIn("False", output)
- self.assertNotIn("True", output)
+ self.assertIn("Type help() for interactive help", output)
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)
@@ -1544,6 +1672,17 @@ class TestMain(ReplTestCase):
self.assertEqual(exit_code, 0)
self.assertNotIn("TypeError", output)
+ @force_not_colorized
+ def test_non_string_suggestion_candidates(self):
+ commands = ("import runpy\n"
+ "runpy._run_module_code('blech', {0: '', 'bluch': ''}, '')\n"
+ "exit()\n")
+
+ output, exit_code = self.run_repl(commands)
+ self.assertEqual(exit_code, 0)
+ self.assertNotIn("all elements in 'candidates' must be strings", output)
+ self.assertIn("bluch", output)
+
def test_readline_history_file(self):
# skip, if readline module is not available
readline = import_module('readline')
@@ -1605,3 +1744,16 @@ class TestMain(ReplTestCase):
# Extra stuff (newline and `exit` rewrites) are necessary
# because of how run_repl works.
self.assertNotIn(">>> \n>>> >>>", cleaned_output)
+
+ @skipUnless(Py_DEBUG, '-X showrefcount requires a Python debug build')
+ def test_showrefcount(self):
+ env = os.environ.copy()
+ env.pop("PYTHON_BASIC_REPL", "")
+ output, _ = self.run_repl("1\n1+2\nexit()\n", cmdline_args=['-Xshowrefcount'], env=env)
+ matches = re.findall(r'\[-?\d+ refs, \d+ blocks\]', output)
+ self.assertEqual(len(matches), 3)
+
+ env["PYTHON_BASIC_REPL"] = "1"
+ output, _ = self.run_repl("1\n1+2\nexit()\n", cmdline_args=['-Xshowrefcount'], env=env)
+ matches = re.findall(r'\[-?\d+ refs, \d+ blocks\]', output)
+ self.assertEqual(len(matches), 3)
diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py
index 8d7fcf538d2..1f655264f1c 100644
--- a/Lib/test/test_pyrepl/test_reader.py
+++ b/Lib/test/test_pyrepl/test_reader.py
@@ -4,20 +4,21 @@ import rlcompleter
from textwrap import dedent
from unittest import TestCase
from unittest.mock import MagicMock
+from test.support import force_colorized_test_class, force_not_colorized_test_class
from .support import handle_all_events, handle_events_narrow_console
from .support import ScreenEqualMixin, code_to_events
-from .support import prepare_console, reader_force_colors
-from .support import reader_no_colors as prepare_reader
+from .support import prepare_reader, prepare_console
from _pyrepl.console import Event
from _pyrepl.reader import Reader
-from _colorize import theme
+from _colorize import default_theme
-overrides = {"RESET": "z", "SOFT_KEYWORD": "K"}
-colors = {overrides.get(k, k[0].lower()): v for k, v in theme.items()}
+overrides = {"reset": "z", "soft_keyword": "K"}
+colors = {overrides.get(k, k[0].lower()): v for k, v in default_theme.syntax.items()}
+@force_not_colorized_test_class
class TestReader(ScreenEqualMixin, TestCase):
def test_calc_screen_wrap_simple(self):
events = code_to_events(10 * "a")
@@ -127,13 +128,6 @@ class TestReader(ScreenEqualMixin, TestCase):
reader.setpos_from_xy(0, 0)
self.assertEqual(reader.pos, 0)
- def test_control_characters(self):
- code = 'flag = "🏳️‍🌈"'
- events = code_to_events(code)
- reader, _ = handle_all_events(events, prepare_reader=reader_force_colors)
- self.assert_screen_equal(reader, 'flag = "🏳️\\u200d🌈"', clean=True)
- self.assert_screen_equal(reader, 'flag {o}={z} {s}"🏳️\\u200d🌈"{z}'.format(**colors))
-
def test_setpos_from_xy_multiple_lines(self):
# fmt: off
code = (
@@ -364,6 +358,8 @@ class TestReader(ScreenEqualMixin, TestCase):
reader.setpos_from_xy(8, 0)
self.assertEqual(reader.pos, 7)
+@force_colorized_test_class
+class TestReaderInColor(ScreenEqualMixin, TestCase):
def test_syntax_highlighting_basic(self):
code = dedent(
"""\
@@ -403,7 +399,7 @@ class TestReader(ScreenEqualMixin, TestCase):
)
expected_sync = expected.format(a="", **colors)
events = code_to_events(code)
- reader, _ = handle_all_events(events, prepare_reader=reader_force_colors)
+ reader, _ = handle_all_events(events)
self.assert_screen_equal(reader, code, clean=True)
self.assert_screen_equal(reader, expected_sync)
self.assertEqual(reader.pos, 2**7 + 2**8)
@@ -416,7 +412,7 @@ class TestReader(ScreenEqualMixin, TestCase):
[Event(evt="key", data="up", raw=bytearray(b"\x1bOA"))] * 13,
code_to_events("async "),
)
- reader, _ = handle_all_events(more_events, prepare_reader=reader_force_colors)
+ reader, _ = handle_all_events(more_events)
self.assert_screen_equal(reader, expected_async)
self.assertEqual(reader.pos, 21)
self.assertEqual(reader.cxy, (6, 1))
@@ -433,7 +429,7 @@ class TestReader(ScreenEqualMixin, TestCase):
"""
).format(**colors)
events = code_to_events(code)
- reader, _ = handle_all_events(events, prepare_reader=reader_force_colors)
+ reader, _ = handle_all_events(events)
self.assert_screen_equal(reader, code, clean=True)
self.assert_screen_equal(reader, expected)
@@ -451,7 +447,7 @@ class TestReader(ScreenEqualMixin, TestCase):
"""
).format(**colors)
events = code_to_events(code)
- reader, _ = handle_all_events(events, prepare_reader=reader_force_colors)
+ reader, _ = handle_all_events(events)
self.assert_screen_equal(reader, code, clean=True)
self.assert_screen_equal(reader, expected)
@@ -471,7 +467,7 @@ class TestReader(ScreenEqualMixin, TestCase):
"""
).format(**colors)
events = code_to_events(code)
- reader, _ = handle_all_events(events, prepare_reader=reader_force_colors)
+ reader, _ = handle_all_events(events)
self.assert_screen_equal(reader, code, clean=True)
self.assert_screen_equal(reader, expected)
@@ -497,6 +493,64 @@ class TestReader(ScreenEqualMixin, TestCase):
"""
).format(OB="{", CB="}", **colors)
events = code_to_events(code)
- reader, _ = handle_all_events(events, prepare_reader=reader_force_colors)
+ reader, _ = handle_all_events(events)
self.assert_screen_equal(reader, code, clean=True)
self.assert_screen_equal(reader, expected)
+
+ def test_syntax_highlighting_indentation_error(self):
+ code = dedent(
+ """\
+ def unfinished_function():
+ var = 1
+ oops
+ """
+ )
+ expected = dedent(
+ """\
+ {k}def{z} {d}unfinished_function{z}{o}({z}{o}){z}{o}:{z}
+ var {o}={z} {n}1{z}
+ oops
+ """
+ ).format(**colors)
+ events = code_to_events(code)
+ reader, _ = handle_all_events(events)
+ self.assert_screen_equal(reader, code, clean=True)
+ self.assert_screen_equal(reader, expected)
+
+ def test_syntax_highlighting_literal_brace_in_fstring_or_tstring(self):
+ code = dedent(
+ """\
+ f"{{"
+ f"}}"
+ f"a{{b"
+ f"a}}b"
+ f"a{{b}}c"
+ t"a{{b}}c"
+ f"{{{0}}}"
+ f"{ {0} }"
+ """
+ )
+ expected = dedent(
+ """\
+ {s}f"{z}{s}<<{z}{s}"{z}
+ {s}f"{z}{s}>>{z}{s}"{z}
+ {s}f"{z}{s}a<<{z}{s}b{z}{s}"{z}
+ {s}f"{z}{s}a>>{z}{s}b{z}{s}"{z}
+ {s}f"{z}{s}a<<{z}{s}b>>{z}{s}c{z}{s}"{z}
+ {s}t"{z}{s}a<<{z}{s}b>>{z}{s}c{z}{s}"{z}
+ {s}f"{z}{s}<<{z}{o}<{z}{n}0{z}{o}>{z}{s}>>{z}{s}"{z}
+ {s}f"{z}{o}<{z} {o}<{z}{n}0{z}{o}>{z} {o}>{z}{s}"{z}
+ """
+ ).format(**colors).replace("<", "{").replace(">", "}")
+ events = code_to_events(code)
+ reader, _ = handle_all_events(events)
+ self.assert_screen_equal(reader, code, clean=True)
+ self.maxDiff=None
+ self.assert_screen_equal(reader, expected)
+
+ def test_control_characters(self):
+ code = 'flag = "🏳️‍🌈"'
+ events = code_to_events(code)
+ reader, _ = handle_all_events(events)
+ self.assert_screen_equal(reader, 'flag = "🏳️\\u200d🌈"', clean=True)
+ self.assert_screen_equal(reader, 'flag {o}={z} {s}"🏳️\\u200d🌈"{z}'.format(**colors))
diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py
index 7acb84a94f7..b3f7dc028fe 100644
--- a/Lib/test/test_pyrepl/test_unix_console.py
+++ b/Lib/test/test_pyrepl/test_unix_console.py
@@ -3,11 +3,12 @@ import os
import sys
import unittest
from functools import partial
-from test.support import os_helper
+from test.support import os_helper, force_not_colorized_test_class
+
from unittest import TestCase
from unittest.mock import MagicMock, call, patch, ANY
-from .support import handle_all_events, code_to_events, reader_no_colors
+from .support import handle_all_events, code_to_events
try:
from _pyrepl.console import Event
@@ -19,6 +20,7 @@ except ImportError:
def unix_console(events, **kwargs):
console = UnixConsole()
console.get_event = MagicMock(side_effect=events)
+ console.getpending = MagicMock(return_value=Event("key", ""))
height = kwargs.get("height", 25)
width = kwargs.get("width", 80)
@@ -33,12 +35,10 @@ def unix_console(events, **kwargs):
handle_events_unix_console = partial(
handle_all_events,
- prepare_reader=reader_no_colors,
prepare_console=unix_console,
)
handle_events_narrow_unix_console = partial(
handle_all_events,
- prepare_reader=reader_no_colors,
prepare_console=partial(unix_console, width=5),
)
handle_events_short_unix_console = partial(
@@ -120,6 +120,7 @@ TERM_CAPABILITIES = {
)
@patch("termios.tcsetattr", lambda a, b, c: None)
@patch("os.write")
+@force_not_colorized_test_class
class TestConsole(TestCase):
def test_simple_addition(self, _os_write):
code = "12+34"
@@ -255,9 +256,7 @@ class TestConsole(TestCase):
# fmt: on
events = itertools.chain(code_to_events(code))
- reader, console = handle_events_short_unix_console(
- events, prepare_reader=reader_no_colors
- )
+ reader, console = handle_events_short_unix_console(events)
console.height = 2
console.getheightwidth = MagicMock(lambda _: (2, 80))
diff --git a/Lib/test/test_pyrepl/test_windows_console.py b/Lib/test/test_pyrepl/test_windows_console.py
index e95fec46a85..f9607e02c60 100644
--- a/Lib/test/test_pyrepl/test_windows_console.py
+++ b/Lib/test/test_pyrepl/test_windows_console.py
@@ -7,12 +7,13 @@ if sys.platform != "win32":
import itertools
from functools import partial
+from test.support import force_not_colorized_test_class
from typing import Iterable
from unittest import TestCase
from unittest.mock import MagicMock, call
from .support import handle_all_events, code_to_events
-from .support import reader_no_colors as default_prepare_reader
+from .support import prepare_reader as default_prepare_reader
try:
from _pyrepl.console import Event, Console
@@ -24,14 +25,17 @@ try:
MOVE_DOWN,
ERASE_IN_LINE,
)
+ import _pyrepl.windows_console as wc
except ImportError:
pass
+@force_not_colorized_test_class
class WindowsConsoleTests(TestCase):
def console(self, events, **kwargs) -> Console:
console = WindowsConsole()
console.get_event = MagicMock(side_effect=events)
+ console.getpending = MagicMock(return_value=Event("key", ""))
console.wait = MagicMock()
console._scroll = MagicMock()
console._hide_cursor = MagicMock()
@@ -350,8 +354,227 @@ class WindowsConsoleTests(TestCase):
Event(evt="key", data='\x1a', raw=bytearray(b'\x1a')),
],
)
- reader, _ = self.handle_events_narrow(events)
+ reader, con = self.handle_events_narrow(events)
self.assertEqual(reader.cxy, (2, 3))
+ con.restore()
+
+
+class WindowsConsoleGetEventTests(TestCase):
+ # Virtual-Key Codes: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
+ VK_BACK = 0x08
+ VK_RETURN = 0x0D
+ VK_LEFT = 0x25
+ VK_7 = 0x37
+ VK_M = 0x4D
+ # Used for miscellaneous characters; it can vary by keyboard.
+ # For the US standard keyboard, the '" key.
+ # For the German keyboard, the Ä key.
+ VK_OEM_7 = 0xDE
+
+ # State of control keys: https://learn.microsoft.com/en-us/windows/console/key-event-record-str
+ RIGHT_ALT_PRESSED = 0x0001
+ RIGHT_CTRL_PRESSED = 0x0004
+ LEFT_ALT_PRESSED = 0x0002
+ LEFT_CTRL_PRESSED = 0x0008
+ ENHANCED_KEY = 0x0100
+ SHIFT_PRESSED = 0x0010
+
+
+ def get_event(self, input_records, **kwargs) -> Console:
+ self.console = WindowsConsole(encoding='utf-8')
+ self.mock = MagicMock(side_effect=input_records)
+ self.console._read_input = self.mock
+ self.console._WindowsConsole__vt_support = kwargs.get("vt_support",
+ False)
+ self.console.wait = MagicMock(return_value=True)
+ event = self.console.get_event(block=False)
+ return event
+
+ def get_input_record(self, unicode_char, vcode=0, control=0):
+ return wc.INPUT_RECORD(
+ wc.KEY_EVENT,
+ wc.ConsoleEvent(KeyEvent=
+ wc.KeyEvent(
+ bKeyDown=True,
+ wRepeatCount=1,
+ wVirtualKeyCode=vcode,
+ wVirtualScanCode=0, # not used
+ uChar=wc.Char(unicode_char),
+ dwControlKeyState=control
+ )))
+
+ def test_EmptyBuffer(self):
+ self.assertEqual(self.get_event([None]), None)
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_WINDOW_BUFFER_SIZE_EVENT(self):
+ ir = wc.INPUT_RECORD(
+ wc.WINDOW_BUFFER_SIZE_EVENT,
+ wc.ConsoleEvent(WindowsBufferSizeEvent=
+ wc.WindowsBufferSizeEvent(
+ wc._COORD(0, 0))))
+ self.assertEqual(self.get_event([ir]), Event("resize", ""))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_KEY_EVENT_up_ignored(self):
+ ir = wc.INPUT_RECORD(
+ wc.KEY_EVENT,
+ wc.ConsoleEvent(KeyEvent=
+ wc.KeyEvent(bKeyDown=False)))
+ self.assertEqual(self.get_event([ir]), None)
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_unhandled_events(self):
+ for event in (wc.FOCUS_EVENT, wc.MENU_EVENT, wc.MOUSE_EVENT):
+ ir = wc.INPUT_RECORD(
+ event,
+ # fake data, nothing is read except bKeyDown
+ wc.ConsoleEvent(KeyEvent=
+ wc.KeyEvent(bKeyDown=False)))
+ self.assertEqual(self.get_event([ir]), None)
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_enter(self):
+ ir = self.get_input_record("\r", self.VK_RETURN)
+ self.assertEqual(self.get_event([ir]), Event("key", "\n"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_backspace(self):
+ ir = self.get_input_record("\x08", self.VK_BACK)
+ self.assertEqual(
+ self.get_event([ir]), Event("key", "backspace"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_m(self):
+ ir = self.get_input_record("m", self.VK_M)
+ self.assertEqual(self.get_event([ir]), Event("key", "m"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_M(self):
+ ir = self.get_input_record("M", self.VK_M, self.SHIFT_PRESSED)
+ self.assertEqual(self.get_event([ir]), Event("key", "M"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_left(self):
+ # VK_LEFT is sent as ENHANCED_KEY
+ ir = self.get_input_record("\x00", self.VK_LEFT, self.ENHANCED_KEY)
+ self.assertEqual(self.get_event([ir]), Event("key", "left"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_left_RIGHT_CTRL_PRESSED(self):
+ ir = self.get_input_record(
+ "\x00", self.VK_LEFT, self.RIGHT_CTRL_PRESSED | self.ENHANCED_KEY)
+ self.assertEqual(
+ self.get_event([ir]), Event("key", "ctrl left"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_left_LEFT_CTRL_PRESSED(self):
+ ir = self.get_input_record(
+ "\x00", self.VK_LEFT, self.LEFT_CTRL_PRESSED | self.ENHANCED_KEY)
+ self.assertEqual(
+ self.get_event([ir]), Event("key", "ctrl left"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_left_RIGHT_ALT_PRESSED(self):
+ ir = self.get_input_record(
+ "\x00", self.VK_LEFT, self.RIGHT_ALT_PRESSED | self.ENHANCED_KEY)
+ self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
+ self.assertEqual(
+ self.console.get_event(), Event("key", "left"))
+ # self.mock is not called again, since the second time we read from the
+ # command queue
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_left_LEFT_ALT_PRESSED(self):
+ ir = self.get_input_record(
+ "\x00", self.VK_LEFT, self.LEFT_ALT_PRESSED | self.ENHANCED_KEY)
+ self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
+ self.assertEqual(
+ self.console.get_event(), Event("key", "left"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_m_LEFT_ALT_PRESSED_and_LEFT_CTRL_PRESSED(self):
+ # For the shift keys, Windows does not send anything when
+ # ALT and CTRL are both pressed, so let's test with VK_M.
+ # get_event() receives this input, but does not
+ # generate an event.
+ # This is for e.g. an English keyboard layout, for a
+ # German layout this returns `µ`, see test_AltGr_m.
+ ir = self.get_input_record(
+ "\x00", self.VK_M, self.LEFT_ALT_PRESSED | self.LEFT_CTRL_PRESSED)
+ self.assertEqual(self.get_event([ir]), None)
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_m_LEFT_ALT_PRESSED(self):
+ ir = self.get_input_record(
+ "m", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED)
+ self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
+ self.assertEqual(self.console.get_event(), Event("key", "m"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_m_RIGHT_ALT_PRESSED(self):
+ ir = self.get_input_record(
+ "m", vcode=self.VK_M, control=self.RIGHT_ALT_PRESSED)
+ self.assertEqual(self.get_event([ir]), Event(evt="key", data="\033"))
+ self.assertEqual(self.console.get_event(), Event("key", "m"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_AltGr_7(self):
+ # E.g. on a German keyboard layout, '{' is entered via
+ # AltGr + 7, where AltGr is the right Alt key on the keyboard.
+ # In this case, Windows automatically sets
+ # RIGHT_ALT_PRESSED = 0x0001 + LEFT_CTRL_PRESSED = 0x0008
+ # This can also be entered like
+ # LeftAlt + LeftCtrl + 7 or
+ # LeftAlt + RightCtrl + 7
+ # See https://learn.microsoft.com/en-us/windows/console/key-event-record-str
+ # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscanw
+ ir = self.get_input_record(
+ "{", vcode=self.VK_7,
+ control=self.RIGHT_ALT_PRESSED | self.LEFT_CTRL_PRESSED)
+ self.assertEqual(self.get_event([ir]), Event("key", "{"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_AltGr_m(self):
+ # E.g. on a German keyboard layout, this yields 'µ'
+ # Let's use LEFT_ALT_PRESSED and RIGHT_CTRL_PRESSED this
+ # time, to cover that, too. See above in test_AltGr_7.
+ ir = self.get_input_record(
+ "µ", vcode=self.VK_M, control=self.LEFT_ALT_PRESSED | self.RIGHT_CTRL_PRESSED)
+ self.assertEqual(self.get_event([ir]), Event("key", "µ"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_umlaut_a_german(self):
+ ir = self.get_input_record("ä", self.VK_OEM_7)
+ self.assertEqual(self.get_event([ir]), Event("key", "ä"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ # virtual terminal tests
+ # Note: wVirtualKeyCode, wVirtualScanCode and dwControlKeyState
+ # are always zero in this case.
+ # "\r" and backspace are handled specially, everything else
+ # is handled in "elif self.__vt_support:" in WindowsConsole.get_event().
+ # Hence, only one regular key ("m") and a terminal sequence
+ # are sufficient to test here, the real tests happen in test_eventqueue
+ # and test_keymap.
+
+ def test_enter_vt(self):
+ ir = self.get_input_record("\r")
+ self.assertEqual(self.get_event([ir], vt_support=True),
+ Event("key", "\n"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_backspace_vt(self):
+ ir = self.get_input_record("\x7f")
+ self.assertEqual(self.get_event([ir], vt_support=True),
+ Event("key", "backspace", b"\x7f"))
+ self.assertEqual(self.mock.call_count, 1)
+
+ def test_up_vt(self):
+ irs = [self.get_input_record(x) for x in "\x1b[A"]
+ self.assertEqual(self.get_event(irs, vt_support=True),
+ Event(evt='key', data='up', raw=bytearray(b'\x1b[A')))
+ self.assertEqual(self.mock.call_count, 3)
if __name__ == "__main__":
diff --git a/Lib/test/test_queue.py b/Lib/test/test_queue.py
index 7f4fe357034..c855fb8fe2b 100644
--- a/Lib/test/test_queue.py
+++ b/Lib/test/test_queue.py
@@ -6,7 +6,7 @@ import threading
import time
import unittest
import weakref
-from test.support import gc_collect
+from test.support import gc_collect, bigmemtest
from test.support import import_helper
from test.support import threading_helper
@@ -963,33 +963,33 @@ class BaseSimpleQueueTest:
# One producer, one consumer => results appended in well-defined order
self.assertEqual(results, inputs)
- def test_many_threads(self):
+ @bigmemtest(size=50, memuse=100*2**20, dry_run=False)
+ def test_many_threads(self, size):
# Test multiple concurrent put() and get()
- N = 50
q = self.q
inputs = list(range(10000))
- results = self.run_threads(N, q, inputs, self.feed, self.consume)
+ results = self.run_threads(size, q, inputs, self.feed, self.consume)
# Multiple consumers without synchronization append the
# results in random order
self.assertEqual(sorted(results), inputs)
- def test_many_threads_nonblock(self):
+ @bigmemtest(size=50, memuse=100*2**20, dry_run=False)
+ def test_many_threads_nonblock(self, size):
# Test multiple concurrent put() and get(block=False)
- N = 50
q = self.q
inputs = list(range(10000))
- results = self.run_threads(N, q, inputs,
+ results = self.run_threads(size, q, inputs,
self.feed, self.consume_nonblock)
self.assertEqual(sorted(results), inputs)
- def test_many_threads_timeout(self):
+ @bigmemtest(size=50, memuse=100*2**20, dry_run=False)
+ def test_many_threads_timeout(self, size):
# Test multiple concurrent put() and get(timeout=...)
- N = 50
q = self.q
inputs = list(range(1000))
- results = self.run_threads(N, q, inputs,
+ results = self.run_threads(size, q, inputs,
self.feed, self.consume_timeout)
self.assertEqual(sorted(results), inputs)
diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py
index 96f6cc86219..0217ebd132b 100644
--- a/Lib/test/test_random.py
+++ b/Lib/test/test_random.py
@@ -14,6 +14,15 @@ from test import support
from fractions import Fraction
from collections import abc, Counter
+
+class MyIndex:
+ def __init__(self, value):
+ self.value = value
+
+ def __index__(self):
+ return self.value
+
+
class TestBasicOps:
# Superclass with tests common to all generators.
# Subclasses must arrange for self.gen to retrieve the Random instance
@@ -142,6 +151,7 @@ class TestBasicOps:
# Exception raised if size of sample exceeds that of population
self.assertRaises(ValueError, self.gen.sample, population, N+1)
self.assertRaises(ValueError, self.gen.sample, [], -1)
+ self.assertRaises(TypeError, self.gen.sample, population, 1.0)
def test_sample_distribution(self):
# For the entire allowable range of 0 <= k <= N, validate that
@@ -259,6 +269,7 @@ class TestBasicOps:
choices(data, range(4), k=5),
choices(k=5, population=data, weights=range(4)),
choices(k=5, population=data, cum_weights=range(4)),
+ choices(data, k=MyIndex(5)),
]:
self.assertEqual(len(sample), 5)
self.assertEqual(type(sample), list)
@@ -369,118 +380,40 @@ class TestBasicOps:
self.assertEqual(x1, x2)
self.assertEqual(y1, y2)
+ @support.requires_IEEE_754
+ def test_53_bits_per_float(self):
+ span = 2 ** 53
+ cum = 0
+ for i in range(100):
+ cum |= int(self.gen.random() * span)
+ self.assertEqual(cum, span-1)
+
def test_getrandbits(self):
+ getrandbits = self.gen.getrandbits
# Verify ranges
for k in range(1, 1000):
- self.assertTrue(0 <= self.gen.getrandbits(k) < 2**k)
- self.assertEqual(self.gen.getrandbits(0), 0)
+ self.assertTrue(0 <= getrandbits(k) < 2**k)
+ self.assertEqual(getrandbits(0), 0)
# Verify all bits active
- getbits = self.gen.getrandbits
for span in [1, 2, 3, 4, 31, 32, 32, 52, 53, 54, 119, 127, 128, 129]:
all_bits = 2**span-1
cum = 0
cpl_cum = 0
for i in range(100):
- v = getbits(span)
+ v = getrandbits(span)
cum |= v
cpl_cum |= all_bits ^ v
self.assertEqual(cum, all_bits)
self.assertEqual(cpl_cum, all_bits)
# Verify argument checking
- self.assertRaises(TypeError, self.gen.getrandbits)
- self.assertRaises(TypeError, self.gen.getrandbits, 1, 2)
- self.assertRaises(ValueError, self.gen.getrandbits, -1)
- self.assertRaises(TypeError, self.gen.getrandbits, 10.1)
-
- def test_pickling(self):
- for proto in range(pickle.HIGHEST_PROTOCOL + 1):
- state = pickle.dumps(self.gen, proto)
- origseq = [self.gen.random() for i in range(10)]
- newgen = pickle.loads(state)
- restoredseq = [newgen.random() for i in range(10)]
- self.assertEqual(origseq, restoredseq)
-
- def test_bug_1727780(self):
- # verify that version-2-pickles can be loaded
- # fine, whether they are created on 32-bit or 64-bit
- # platforms, and that version-3-pickles load fine.
- files = [("randv2_32.pck", 780),
- ("randv2_64.pck", 866),
- ("randv3.pck", 343)]
- for file, value in files:
- with open(support.findfile(file),"rb") as f:
- r = pickle.load(f)
- self.assertEqual(int(r.random()*1000), value)
-
- def test_bug_9025(self):
- # Had problem with an uneven distribution in int(n*random())
- # Verify the fix by checking that distributions fall within expectations.
- n = 100000
- randrange = self.gen.randrange
- k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n))
- self.assertTrue(0.30 < k/n < .37, (k/n))
-
- def test_randbytes(self):
- # Verify ranges
- for n in range(1, 10):
- data = self.gen.randbytes(n)
- self.assertEqual(type(data), bytes)
- self.assertEqual(len(data), n)
-
- self.assertEqual(self.gen.randbytes(0), b'')
-
- # Verify argument checking
- self.assertRaises(TypeError, self.gen.randbytes)
- self.assertRaises(TypeError, self.gen.randbytes, 1, 2)
- self.assertRaises(ValueError, self.gen.randbytes, -1)
- self.assertRaises(TypeError, self.gen.randbytes, 1.0)
-
- def test_mu_sigma_default_args(self):
- self.assertIsInstance(self.gen.normalvariate(), float)
- self.assertIsInstance(self.gen.gauss(), float)
-
-
-try:
- random.SystemRandom().random()
-except NotImplementedError:
- SystemRandom_available = False
-else:
- SystemRandom_available = True
-
-@unittest.skipUnless(SystemRandom_available, "random.SystemRandom not available")
-class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
- gen = random.SystemRandom()
-
- def test_autoseed(self):
- # Doesn't need to do anything except not fail
- self.gen.seed()
-
- def test_saverestore(self):
- self.assertRaises(NotImplementedError, self.gen.getstate)
- self.assertRaises(NotImplementedError, self.gen.setstate, None)
-
- def test_seedargs(self):
- # Doesn't need to do anything except not fail
- self.gen.seed(100)
-
- def test_gauss(self):
- self.gen.gauss_next = None
- self.gen.seed(100)
- self.assertEqual(self.gen.gauss_next, None)
-
- def test_pickling(self):
- for proto in range(pickle.HIGHEST_PROTOCOL + 1):
- self.assertRaises(NotImplementedError, pickle.dumps, self.gen, proto)
-
- def test_53_bits_per_float(self):
- # This should pass whenever a C double has 53 bit precision.
- span = 2 ** 53
- cum = 0
- for i in range(100):
- cum |= int(self.gen.random() * span)
- self.assertEqual(cum, span-1)
+ self.assertRaises(TypeError, getrandbits)
+ self.assertRaises(TypeError, getrandbits, 1, 2)
+ self.assertRaises(ValueError, getrandbits, -1)
+ self.assertRaises(OverflowError, getrandbits, 1<<1000)
+ self.assertRaises(ValueError, getrandbits, -1<<1000)
+ self.assertRaises(TypeError, getrandbits, 10.1)
def test_bigrand(self):
# The randrange routine should build-up the required number of bits
@@ -559,6 +492,10 @@ class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
randrange(1000, step=100)
with self.assertRaises(TypeError):
randrange(1000, None, step=100)
+ with self.assertRaises(TypeError):
+ randrange(1000, step=MyIndex(1))
+ with self.assertRaises(TypeError):
+ randrange(1000, None, step=MyIndex(1))
def test_randbelow_logic(self, _log=log, int=int):
# check bitcount transition points: 2**i and 2**(i+1)-1
@@ -581,6 +518,116 @@ class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(k, numbits) # note the stronger assertion
self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion
+ def test_randrange_index(self):
+ randrange = self.gen.randrange
+ self.assertIn(randrange(MyIndex(5)), range(5))
+ self.assertIn(randrange(MyIndex(2), MyIndex(7)), range(2, 7))
+ self.assertIn(randrange(MyIndex(5), MyIndex(15), MyIndex(2)), range(5, 15, 2))
+
+ def test_randint(self):
+ randint = self.gen.randint
+ self.assertIn(randint(2, 5), (2, 3, 4, 5))
+ self.assertEqual(randint(2, 2), 2)
+ self.assertIn(randint(MyIndex(2), MyIndex(5)), (2, 3, 4, 5))
+ self.assertEqual(randint(MyIndex(2), MyIndex(2)), 2)
+
+ self.assertRaises(ValueError, randint, 5, 2)
+ self.assertRaises(TypeError, randint)
+ self.assertRaises(TypeError, randint, 2)
+ self.assertRaises(TypeError, randint, 2, 5, 1)
+ self.assertRaises(TypeError, randint, 2.0, 5)
+ self.assertRaises(TypeError, randint, 2, 5.0)
+
+ def test_pickling(self):
+ for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+ state = pickle.dumps(self.gen, proto)
+ origseq = [self.gen.random() for i in range(10)]
+ newgen = pickle.loads(state)
+ restoredseq = [newgen.random() for i in range(10)]
+ self.assertEqual(origseq, restoredseq)
+
+ def test_bug_1727780(self):
+ # verify that version-2-pickles can be loaded
+ # fine, whether they are created on 32-bit or 64-bit
+ # platforms, and that version-3-pickles load fine.
+ files = [("randv2_32.pck", 780),
+ ("randv2_64.pck", 866),
+ ("randv3.pck", 343)]
+ for file, value in files:
+ with open(support.findfile(file),"rb") as f:
+ r = pickle.load(f)
+ self.assertEqual(int(r.random()*1000), value)
+
+ def test_bug_9025(self):
+ # Had problem with an uneven distribution in int(n*random())
+ # Verify the fix by checking that distributions fall within expectations.
+ n = 100000
+ randrange = self.gen.randrange
+ k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n))
+ self.assertTrue(0.30 < k/n < .37, (k/n))
+
+ def test_randrange_bug_1590891(self):
+ start = 1000000000000
+ stop = -100000000000000000000
+ step = -200
+ x = self.gen.randrange(start, stop, step)
+ self.assertTrue(stop < x <= start)
+ self.assertEqual((x+stop)%step, 0)
+
+ def test_randbytes(self):
+ # Verify ranges
+ for n in range(1, 10):
+ data = self.gen.randbytes(n)
+ self.assertEqual(type(data), bytes)
+ self.assertEqual(len(data), n)
+
+ self.assertEqual(self.gen.randbytes(0), b'')
+
+ # Verify argument checking
+ self.assertRaises(TypeError, self.gen.randbytes)
+ self.assertRaises(TypeError, self.gen.randbytes, 1, 2)
+ self.assertRaises(ValueError, self.gen.randbytes, -1)
+ self.assertRaises(OverflowError, self.gen.randbytes, 1<<1000)
+ self.assertRaises((ValueError, OverflowError), self.gen.randbytes, -1<<1000)
+ self.assertRaises(TypeError, self.gen.randbytes, 1.0)
+
+ def test_mu_sigma_default_args(self):
+ self.assertIsInstance(self.gen.normalvariate(), float)
+ self.assertIsInstance(self.gen.gauss(), float)
+
+
+try:
+ random.SystemRandom().random()
+except NotImplementedError:
+ SystemRandom_available = False
+else:
+ SystemRandom_available = True
+
+@unittest.skipUnless(SystemRandom_available, "random.SystemRandom not available")
+class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
+ gen = random.SystemRandom()
+
+ def test_autoseed(self):
+ # Doesn't need to do anything except not fail
+ self.gen.seed()
+
+ def test_saverestore(self):
+ self.assertRaises(NotImplementedError, self.gen.getstate)
+ self.assertRaises(NotImplementedError, self.gen.setstate, None)
+
+ def test_seedargs(self):
+ # Doesn't need to do anything except not fail
+ self.gen.seed(100)
+
+ def test_gauss(self):
+ self.gen.gauss_next = None
+ self.gen.seed(100)
+ self.assertEqual(self.gen.gauss_next, None)
+
+ def test_pickling(self):
+ for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+ self.assertRaises(NotImplementedError, pickle.dumps, self.gen, proto)
+
class TestRawMersenneTwister(unittest.TestCase):
@test.support.cpython_only
@@ -766,38 +813,6 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
seed = (1 << (10000 * 8)) - 1 # about 10K bytes
self.gen.seed(seed)
- def test_53_bits_per_float(self):
- # This should pass whenever a C double has 53 bit precision.
- span = 2 ** 53
- cum = 0
- for i in range(100):
- cum |= int(self.gen.random() * span)
- self.assertEqual(cum, span-1)
-
- def test_bigrand(self):
- # The randrange routine should build-up the required number of bits
- # in stages so that all bit positions are active.
- span = 2 ** 500
- cum = 0
- for i in range(100):
- r = self.gen.randrange(span)
- self.assertTrue(0 <= r < span)
- cum |= r
- self.assertEqual(cum, span-1)
-
- def test_bigrand_ranges(self):
- for i in [40,80, 160, 200, 211, 250, 375, 512, 550]:
- start = self.gen.randrange(2 ** (i-2))
- stop = self.gen.randrange(2 ** i)
- if stop <= start:
- continue
- self.assertTrue(start <= self.gen.randrange(start, stop) < stop)
-
- def test_rangelimits(self):
- for start, stop in [(-2,0), (-(2**60)-2,-(2**60)), (2**60,2**60+2)]:
- self.assertEqual(set(range(start,stop)),
- set([self.gen.randrange(start,stop) for i in range(100)]))
-
def test_getrandbits(self):
super().test_getrandbits()
@@ -805,6 +820,25 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
self.gen.seed(1234567)
self.assertEqual(self.gen.getrandbits(100),
97904845777343510404718956115)
+ self.gen.seed(1234567)
+ self.assertEqual(self.gen.getrandbits(MyIndex(100)),
+ 97904845777343510404718956115)
+
+ def test_getrandbits_2G_bits(self):
+ size = 2**31
+ self.gen.seed(1234567)
+ x = self.gen.getrandbits(size)
+ self.assertEqual(x.bit_length(), size)
+ self.assertEqual(x & (2**100-1), 890186470919986886340158459475)
+ self.assertEqual(x >> (size-100), 1226514312032729439655761284440)
+
+ @support.bigmemtest(size=2**32, memuse=1/8+2/15, dry_run=False)
+ def test_getrandbits_4G_bits(self, size):
+ self.gen.seed(1234568)
+ x = self.gen.getrandbits(size)
+ self.assertEqual(x.bit_length(), size)
+ self.assertEqual(x & (2**100-1), 287241425661104632871036099814)
+ self.assertEqual(x >> (size-100), 739728759900339699429794460738)
def test_randrange_uses_getrandbits(self):
# Verify use of getrandbits by randrange
@@ -816,27 +850,6 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(self.gen.randrange(2**99),
97904845777343510404718956115)
- def test_randbelow_logic(self, _log=log, int=int):
- # check bitcount transition points: 2**i and 2**(i+1)-1
- # show that: k = int(1.001 + _log(n, 2))
- # is equal to or one greater than the number of bits in n
- for i in range(1, 1000):
- n = 1 << i # check an exact power of two
- numbits = i+1
- k = int(1.00001 + _log(n, 2))
- self.assertEqual(k, numbits)
- self.assertEqual(n, 2**(k-1))
-
- n += n - 1 # check 1 below the next power of two
- k = int(1.00001 + _log(n, 2))
- self.assertIn(k, [numbits, numbits+1])
- self.assertTrue(2**k > n > 2**(k-2))
-
- n -= n >> 15 # check a little farther below the next power of two
- k = int(1.00001 + _log(n, 2))
- self.assertEqual(k, numbits) # note the stronger assertion
- self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion
-
def test_randbelow_without_getrandbits(self):
# Random._randbelow() can only use random() when the built-in one
# has been overridden but no new getrandbits() method was supplied.
@@ -871,14 +884,6 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
self.gen._randbelow_without_getrandbits(n, maxsize=maxsize)
self.assertEqual(random_mock.call_count, 2)
- def test_randrange_bug_1590891(self):
- start = 1000000000000
- stop = -100000000000000000000
- step = -200
- x = self.gen.randrange(start, stop, step)
- self.assertTrue(stop < x <= start)
- self.assertEqual((x+stop)%step, 0)
-
def test_choices_algorithms(self):
# The various ways of specifying weights should produce the same results
choices = self.gen.choices
@@ -962,6 +967,14 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(self.gen.randbytes(n),
gen2.getrandbits(n * 8).to_bytes(n, 'little'))
+ @support.bigmemtest(size=2**29, memuse=1+16/15, dry_run=False)
+ def test_randbytes_256M(self, size):
+ self.gen.seed(2849427419)
+ x = self.gen.randbytes(size)
+ self.assertEqual(len(x), size)
+ self.assertEqual(x[:12].hex(), 'f6fd9ae63855ab91ea238b4f')
+ self.assertEqual(x[-12:].hex(), '0e7af69a84ee99bf4a11becc')
+
def test_sample_counts_equivalence(self):
# Test the documented strong equivalence to a sample with repeated elements.
# We run this test on random.Random() which makes deterministic selections
@@ -1411,30 +1424,31 @@ class TestModule(unittest.TestCase):
class CommandLineTest(unittest.TestCase):
+ @support.force_not_colorized
def test_parse_args(self):
args, help_text = random._parse_args(shlex.split("--choice a b c"))
self.assertEqual(args.choice, ["a", "b", "c"])
- self.assertTrue(help_text.startswith("usage: "))
+ self.assertStartsWith(help_text, "usage: ")
args, help_text = random._parse_args(shlex.split("--integer 5"))
self.assertEqual(args.integer, 5)
- self.assertTrue(help_text.startswith("usage: "))
+ self.assertStartsWith(help_text, "usage: ")
args, help_text = random._parse_args(shlex.split("--float 2.5"))
self.assertEqual(args.float, 2.5)
- self.assertTrue(help_text.startswith("usage: "))
+ self.assertStartsWith(help_text, "usage: ")
args, help_text = random._parse_args(shlex.split("a b c"))
self.assertEqual(args.input, ["a", "b", "c"])
- self.assertTrue(help_text.startswith("usage: "))
+ self.assertStartsWith(help_text, "usage: ")
args, help_text = random._parse_args(shlex.split("5"))
self.assertEqual(args.input, ["5"])
- self.assertTrue(help_text.startswith("usage: "))
+ self.assertStartsWith(help_text, "usage: ")
args, help_text = random._parse_args(shlex.split("2.5"))
self.assertEqual(args.input, ["2.5"])
- self.assertTrue(help_text.startswith("usage: "))
+ self.assertStartsWith(help_text, "usage: ")
def test_main(self):
for command, expected in [
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index f79a6149078..993652d2e88 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -5,7 +5,6 @@ from test.support import (gc_collect, bigmemtest, _2G,
import locale
import re
import string
-import sys
import unittest
import warnings
from re import Scanner
@@ -2868,11 +2867,11 @@ class PatternReprTests(unittest.TestCase):
pattern = 'Very %spattern' % ('long ' * 1000)
r = repr(re.compile(pattern))
self.assertLess(len(r), 300)
- self.assertEqual(r[:30], "re.compile('Very long long lon")
+ self.assertStartsWith(r, "re.compile('Very long long lon")
r = repr(re.compile(pattern, re.I))
self.assertLess(len(r), 300)
- self.assertEqual(r[:30], "re.compile('Very long long lon")
- self.assertEqual(r[-16:], ", re.IGNORECASE)")
+ self.assertStartsWith(r, "re.compile('Very long long lon")
+ self.assertEndsWith(r, ", re.IGNORECASE)")
def test_flags_repr(self):
self.assertEqual(repr(re.I), "re.IGNORECASE")
@@ -2927,33 +2926,6 @@ class ImplementationTest(unittest.TestCase):
pat = re.compile("")
check_disallow_instantiation(self, type(pat.scanner("")))
- def test_deprecated_modules(self):
- deprecated = {
- 'sre_compile': ['compile', 'error',
- 'SRE_FLAG_IGNORECASE', 'SUBPATTERN',
- '_compile_info'],
- 'sre_constants': ['error', 'SRE_FLAG_IGNORECASE', 'SUBPATTERN',
- '_NamedIntConstant'],
- 'sre_parse': ['SubPattern', 'parse',
- 'SRE_FLAG_IGNORECASE', 'SUBPATTERN',
- '_parse_sub'],
- }
- for name in deprecated:
- with self.subTest(module=name):
- sys.modules.pop(name, None)
- with self.assertWarns(DeprecationWarning) as w:
- __import__(name)
- self.assertEqual(str(w.warning),
- f"module {name!r} is deprecated")
- self.assertEqual(w.filename, __file__)
- self.assertIn(name, sys.modules)
- mod = sys.modules[name]
- self.assertEqual(mod.__name__, name)
- self.assertEqual(mod.__package__, '')
- for attr in deprecated[name]:
- self.assertTrue(hasattr(mod, attr))
- del sys.modules[name]
-
@cpython_only
def test_case_helpers(self):
import _sre
diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py
index b9d082b3597..45192fe5082 100644
--- a/Lib/test/test_readline.py
+++ b/Lib/test/test_readline.py
@@ -1,6 +1,7 @@
"""
Very minimal unittests for parts of the readline module.
"""
+import codecs
import locale
import os
import sys
@@ -231,6 +232,13 @@ print("History length:", readline.get_current_history_length())
# writing and reading non-ASCII bytes into/from a TTY works, but
# readline or ncurses ignores non-ASCII bytes on read.
self.skipTest(f"the LC_CTYPE locale is {loc!r}")
+ if sys.flags.utf8_mode:
+ encoding = locale.getencoding()
+ encoding = codecs.lookup(encoding).name # normalize the name
+ if encoding != "utf-8":
+ # gh-133711: The Python UTF-8 Mode ignores the LC_CTYPE locale
+ # and always use the UTF-8 encoding.
+ self.skipTest(f"the LC_CTYPE encoding is {encoding!r}")
try:
readline.add_history("\xEB\xEF")
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 7e317d5ab94..5bc3c5924b0 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -768,13 +768,16 @@ class BaseTestCase(unittest.TestCase):
self.fail(msg)
return proc
- def run_python(self, args, **kw):
+ def run_python(self, args, isolated=True, **kw):
extraargs = []
if 'uops' in sys._xoptions:
# Pass -X uops along
extraargs.extend(['-X', 'uops'])
- args = [sys.executable, *extraargs, '-X', 'faulthandler', '-I', *args]
- proc = self.run_command(args, **kw)
+ cmd = [sys.executable, *extraargs, '-X', 'faulthandler']
+ if isolated:
+ cmd.append('-I')
+ cmd.extend(args)
+ proc = self.run_command(cmd, **kw)
return proc.stdout
@@ -831,8 +834,8 @@ class ProgramsTestCase(BaseTestCase):
self.check_executed_tests(output, self.tests,
randomize=True, stats=len(self.tests))
- def run_tests(self, args, env=None):
- output = self.run_python(args, env=env)
+ def run_tests(self, args, env=None, isolated=True):
+ output = self.run_python(args, env=env, isolated=isolated)
self.check_output(output)
def test_script_regrtest(self):
@@ -874,7 +877,10 @@ class ProgramsTestCase(BaseTestCase):
self.run_tests(args)
def run_batch(self, *args):
- proc = self.run_command(args)
+ proc = self.run_command(args,
+ # gh-133711: cmd.exe uses the OEM code page
+ # to display the non-ASCII current directory
+ errors="backslashreplace")
self.check_output(proc.stdout)
@unittest.skipUnless(sysconfig.is_python_build(),
@@ -2064,7 +2070,7 @@ class ArgsTestCase(BaseTestCase):
self.check_executed_tests(output, [testname],
failed=[testname],
parallel=True,
- stats=TestStats(1, 1, 0))
+ stats=TestStats(1, 2, 1))
def _check_random_seed(self, run_workers: bool):
# gh-109276: When -r/--randomize is used, random.seed() is called
@@ -2273,7 +2279,6 @@ class ArgsTestCase(BaseTestCase):
def test_xml(self):
code = textwrap.dedent(r"""
import unittest
- from test import support
class VerboseTests(unittest.TestCase):
def test_failed(self):
@@ -2308,6 +2313,50 @@ class ArgsTestCase(BaseTestCase):
for out in testcase.iter('system-out'):
self.assertEqual(out.text, r"abc \x1b def")
+ def test_nonascii(self):
+ code = textwrap.dedent(r"""
+ import unittest
+
+ class NonASCIITests(unittest.TestCase):
+ def test_docstring(self):
+ '''docstring:\u20ac'''
+
+ def test_subtest(self):
+ with self.subTest(param='subtest:\u20ac'):
+ pass
+
+ def test_skip(self):
+ self.skipTest('skipped:\u20ac')
+ """)
+ testname = self.create_test(code=code)
+
+ env = dict(os.environ)
+ env['PYTHONIOENCODING'] = 'ascii'
+
+ def check(output):
+ self.check_executed_tests(output, testname, stats=TestStats(3, 0, 1))
+ self.assertIn(r'docstring:\u20ac', output)
+ self.assertIn(r'skipped:\u20ac', output)
+
+ # Run sequentially
+ output = self.run_tests('-v', testname, env=env, isolated=False)
+ check(output)
+
+ # Run in parallel
+ output = self.run_tests('-j1', '-v', testname, env=env, isolated=False)
+ check(output)
+
+ def test_pgo_exclude(self):
+ # Get PGO tests
+ output = self.run_tests('--pgo', '--list-tests')
+ pgo_tests = output.strip().split()
+
+ # Exclude test_re
+ output = self.run_tests('--pgo', '--list-tests', '-x', 'test_re')
+ tests = output.strip().split()
+ self.assertNotIn('test_re', tests)
+ self.assertEqual(len(tests), len(pgo_tests) - 1)
+
class TestUtils(unittest.TestCase):
def test_format_duration(self):
diff --git a/Lib/test/test_remote_pdb.py b/Lib/test/test_remote_pdb.py
index e4c44c78d4a..a1c50af15f3 100644
--- a/Lib/test/test_remote_pdb.py
+++ b/Lib/test/test_remote_pdb.py
@@ -1,22 +1,19 @@
import io
-import time
import itertools
import json
import os
+import re
import signal
import socket
import subprocess
import sys
-import tempfile
import textwrap
-import threading
import unittest
import unittest.mock
-from contextlib import contextmanager, redirect_stdout, ExitStack
-from pathlib import Path
-from test.support import is_wasi, os_helper, requires_subprocess, SHORT_TIMEOUT
-from test.support.os_helper import temp_dir, TESTFN, unlink
-from typing import Dict, List, Optional, Tuple, Union, Any
+from contextlib import closing, contextmanager, redirect_stdout, redirect_stderr, ExitStack
+from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT
+from test.support.os_helper import TESTFN, unlink
+from typing import List
import pdb
from pdb import _PdbServer, _PdbClient
@@ -79,44 +76,6 @@ class MockSocketFile:
return results
-class MockDebuggerSocket:
- """Mock file-like simulating a connection to a _RemotePdb instance"""
-
- def __init__(self, incoming):
- self.incoming = iter(incoming)
- self.outgoing = []
- self.buffered = bytearray()
-
- def write(self, data: bytes) -> None:
- """Simulate write to socket."""
- self.buffered += data
-
- def flush(self) -> None:
- """Ensure each line is valid JSON."""
- lines = self.buffered.splitlines(keepends=True)
- self.buffered.clear()
- for line in lines:
- assert line.endswith(b"\n")
- self.outgoing.append(json.loads(line))
-
- def readline(self) -> bytes:
- """Read a line from the prepared input queue."""
- # Anything written must be flushed before trying to read,
- # since the read will be dependent upon the last write.
- assert not self.buffered
- try:
- item = next(self.incoming)
- if not isinstance(item, bytes):
- item = json.dumps(item).encode()
- return item + b"\n"
- except StopIteration:
- return b""
-
- def close(self) -> None:
- """No-op close implementation."""
- pass
-
-
class PdbClientTestCase(unittest.TestCase):
"""Tests for the _PdbClient class."""
@@ -124,8 +83,11 @@ class PdbClientTestCase(unittest.TestCase):
self,
*,
incoming,
- simulate_failure=None,
+ simulate_send_failure=False,
+ simulate_sigint_during_stdout_write=False,
+ use_interrupt_socket=False,
expected_outgoing=None,
+ expected_outgoing_signals=None,
expected_completions=None,
expected_exception=None,
expected_stdout="",
@@ -134,6 +96,8 @@ class PdbClientTestCase(unittest.TestCase):
):
if expected_outgoing is None:
expected_outgoing = []
+ if expected_outgoing_signals is None:
+ expected_outgoing_signals = []
if expected_completions is None:
expected_completions = []
if expected_state is None:
@@ -142,16 +106,6 @@ class PdbClientTestCase(unittest.TestCase):
expected_state.setdefault("write_failed", False)
messages = [m for source, m in incoming if source == "server"]
prompts = [m["prompt"] for source, m in incoming if source == "user"]
- sockfile = MockDebuggerSocket(messages)
- stdout = io.StringIO()
-
- if simulate_failure:
- sockfile.write = unittest.mock.Mock()
- sockfile.flush = unittest.mock.Mock()
- if simulate_failure == "write":
- sockfile.write.side_effect = OSError("write failed")
- elif simulate_failure == "flush":
- sockfile.flush.side_effect = OSError("flush failed")
input_iter = (m for source, m in incoming if source == "user")
completions = []
@@ -178,18 +132,60 @@ class PdbClientTestCase(unittest.TestCase):
reply = message["input"]
if isinstance(reply, BaseException):
raise reply
- return reply
+ if isinstance(reply, str):
+ return reply
+ return reply()
with ExitStack() as stack:
+ client_sock, server_sock = socket.socketpair()
+ stack.enter_context(closing(client_sock))
+ stack.enter_context(closing(server_sock))
+
+ server_sock = unittest.mock.Mock(wraps=server_sock)
+
+ client_sock.sendall(
+ b"".join(
+ (m if isinstance(m, bytes) else json.dumps(m).encode()) + b"\n"
+ for m in messages
+ )
+ )
+ client_sock.shutdown(socket.SHUT_WR)
+
+ if simulate_send_failure:
+ server_sock.sendall = unittest.mock.Mock(
+ side_effect=OSError("sendall failed")
+ )
+ client_sock.shutdown(socket.SHUT_RD)
+
+ stdout = io.StringIO()
+
+ if simulate_sigint_during_stdout_write:
+ orig_stdout_write = stdout.write
+
+ def sigint_stdout_write(s):
+ signal.raise_signal(signal.SIGINT)
+ return orig_stdout_write(s)
+
+ stdout.write = sigint_stdout_write
+
input_mock = stack.enter_context(
unittest.mock.patch("pdb.input", side_effect=mock_input)
)
stack.enter_context(redirect_stdout(stdout))
+ if use_interrupt_socket:
+ interrupt_sock = unittest.mock.Mock(spec=socket.socket)
+ mock_kill = None
+ else:
+ interrupt_sock = None
+ mock_kill = stack.enter_context(
+ unittest.mock.patch("os.kill", spec=os.kill)
+ )
+
client = _PdbClient(
- pid=0,
- sockfile=sockfile,
- interrupt_script="/a/b.py",
+ pid=12345,
+ server_socket=server_sock,
+ interrupt_sock=interrupt_sock,
)
if expected_exception is not None:
@@ -199,13 +195,12 @@ class PdbClientTestCase(unittest.TestCase):
client.cmdloop()
- actual_outgoing = sockfile.outgoing
- if simulate_failure:
- actual_outgoing += [
- json.loads(msg.args[0]) for msg in sockfile.write.mock_calls
- ]
+ sent_msgs = [msg.args[0] for msg in server_sock.sendall.mock_calls]
+ for msg in sent_msgs:
+ assert msg.endswith(b"\n")
+ actual_outgoing = [json.loads(msg) for msg in sent_msgs]
- self.assertEqual(sockfile.outgoing, expected_outgoing)
+ self.assertEqual(actual_outgoing, expected_outgoing)
self.assertEqual(completions, expected_completions)
if expected_stdout_substring and not expected_stdout:
self.assertIn(expected_stdout_substring, stdout.getvalue())
@@ -215,6 +210,20 @@ class PdbClientTestCase(unittest.TestCase):
actual_state = {k: getattr(client, k) for k in expected_state}
self.assertEqual(actual_state, expected_state)
+ if use_interrupt_socket:
+ outgoing_signals = [
+ signal.Signals(int.from_bytes(call.args[0]))
+ for call in interrupt_sock.sendall.call_args_list
+ ]
+ else:
+ assert mock_kill is not None
+ outgoing_signals = []
+ for call in mock_kill.call_args_list:
+ pid, signum = call.args
+ self.assertEqual(pid, 12345)
+ outgoing_signals.append(signal.Signals(signum))
+ self.assertEqual(outgoing_signals, expected_outgoing_signals)
+
def test_remote_immediately_closing_the_connection(self):
"""Test the behavior when the remote closes the connection immediately."""
incoming = []
@@ -409,11 +418,38 @@ class PdbClientTestCase(unittest.TestCase):
expected_state={"state": "dumb"},
)
- def test_keyboard_interrupt_at_prompt(self):
- """Test signaling when a prompt gets a KeyboardInterrupt."""
+ def test_sigint_at_prompt(self):
+ """Test signaling when a prompt gets interrupted."""
incoming = [
("server", {"prompt": "(Pdb) ", "state": "pdb"}),
- ("user", {"prompt": "(Pdb) ", "input": KeyboardInterrupt()}),
+ (
+ "user",
+ {
+ "prompt": "(Pdb) ",
+ "input": lambda: signal.raise_signal(signal.SIGINT),
+ },
+ ),
+ ]
+ self.do_test(
+ incoming=incoming,
+ expected_outgoing=[
+ {"signal": "INT"},
+ ],
+ expected_state={"state": "pdb"},
+ )
+
+ def test_sigint_at_continuation_prompt(self):
+ """Test signaling when a continuation prompt gets interrupted."""
+ incoming = [
+ ("server", {"prompt": "(Pdb) ", "state": "pdb"}),
+ ("user", {"prompt": "(Pdb) ", "input": "if True:"}),
+ (
+ "user",
+ {
+ "prompt": "... ",
+ "input": lambda: signal.raise_signal(signal.SIGINT),
+ },
+ ),
]
self.do_test(
incoming=incoming,
@@ -423,6 +459,22 @@ class PdbClientTestCase(unittest.TestCase):
expected_state={"state": "pdb"},
)
+ def test_sigint_when_writing(self):
+ """Test siginaling when sys.stdout.write() gets interrupted."""
+ incoming = [
+ ("server", {"message": "Some message or other\n", "type": "info"}),
+ ]
+ for use_interrupt_socket in [False, True]:
+ with self.subTest(use_interrupt_socket=use_interrupt_socket):
+ self.do_test(
+ incoming=incoming,
+ simulate_sigint_during_stdout_write=True,
+ use_interrupt_socket=use_interrupt_socket,
+ expected_outgoing=[],
+ expected_outgoing_signals=[signal.SIGINT],
+ expected_stdout="Some message or other\n",
+ )
+
def test_eof_at_prompt(self):
"""Test signaling when a prompt gets an EOFError."""
incoming = [
@@ -478,20 +530,7 @@ class PdbClientTestCase(unittest.TestCase):
self.do_test(
incoming=incoming,
expected_outgoing=[{"signal": "INT"}],
- simulate_failure="write",
- expected_state={"write_failed": True},
- )
-
- def test_flush_failing(self):
- """Test terminating if flush fails due to a half closed socket."""
- incoming = [
- ("server", {"prompt": "(Pdb) ", "state": "pdb"}),
- ("user", {"prompt": "(Pdb) ", "input": KeyboardInterrupt()}),
- ]
- self.do_test(
- incoming=incoming,
- expected_outgoing=[{"signal": "INT"}],
- simulate_failure="flush",
+ simulate_send_failure=True,
expected_state={"write_failed": True},
)
@@ -531,6 +570,44 @@ class PdbClientTestCase(unittest.TestCase):
expected_state={"state": "pdb"},
)
+ def test_multiline_completion_in_pdb_state(self):
+ """Test requesting tab completions at a (Pdb) continuation prompt."""
+ # GIVEN
+ incoming = [
+ ("server", {"prompt": "(Pdb) ", "state": "pdb"}),
+ ("user", {"prompt": "(Pdb) ", "input": "if True:"}),
+ (
+ "user",
+ {
+ "prompt": "... ",
+ "completion_request": {
+ "line": " b",
+ "begidx": 4,
+ "endidx": 5,
+ },
+ "input": " bool()",
+ },
+ ),
+ ("server", {"completions": ["bin", "bool", "bytes"]}),
+ ("user", {"prompt": "... ", "input": ""}),
+ ]
+ self.do_test(
+ incoming=incoming,
+ expected_outgoing=[
+ {
+ "complete": {
+ "text": "b",
+ "line": "! b",
+ "begidx": 2,
+ "endidx": 3,
+ }
+ },
+ {"reply": "if True:\n bool()\n"},
+ ],
+ expected_completions=["bin", "bool", "bytes"],
+ expected_state={"state": "pdb"},
+ )
+
def test_completion_in_interact_state(self):
"""Test requesting tab completions at a >>> prompt."""
incoming = [
@@ -622,42 +699,7 @@ class PdbClientTestCase(unittest.TestCase):
},
{"reply": "xyz"},
],
- simulate_failure="write",
- expected_completions=[],
- expected_state={"state": "interact", "write_failed": True},
- )
-
- def test_flush_failure_during_completion(self):
- """Test failing to flush to the socket to request tab completions."""
- incoming = [
- ("server", {"prompt": ">>> ", "state": "interact"}),
- (
- "user",
- {
- "prompt": ">>> ",
- "completion_request": {
- "line": "xy",
- "begidx": 0,
- "endidx": 2,
- },
- "input": "xyz",
- },
- ),
- ]
- self.do_test(
- incoming=incoming,
- expected_outgoing=[
- {
- "complete": {
- "text": "xy",
- "line": "xy",
- "begidx": 0,
- "endidx": 2,
- }
- },
- {"reply": "xyz"},
- ],
- simulate_failure="flush",
+ simulate_send_failure=True,
expected_completions=[],
expected_state={"state": "interact", "write_failed": True},
)
@@ -994,6 +1036,8 @@ class PdbConnectTestCase(unittest.TestCase):
frame=frame,
commands="",
version=pdb._PdbServer.protocol_version(),
+ signal_raising_thread=False,
+ colorize=False,
)
return x # This line won't be reached in debugging
@@ -1051,23 +1095,6 @@ class PdbConnectTestCase(unittest.TestCase):
client_file.write(json.dumps({"reply": command}).encode() + b"\n")
client_file.flush()
- def _send_interrupt(self, pid):
- """Helper to send an interrupt signal to the debugger."""
- # with tempfile.NamedTemporaryFile("w", delete_on_close=False) as interrupt_script:
- interrupt_script = TESTFN + "_interrupt_script.py"
- with open(interrupt_script, 'w') as f:
- f.write(
- 'import pdb, sys\n'
- 'print("Hello, world!")\n'
- 'if inst := pdb.Pdb._last_pdb_instance:\n'
- ' inst.set_trace(sys._getframe(1))\n'
- )
- self.addCleanup(unlink, interrupt_script)
- try:
- sys.remote_exec(pid, interrupt_script)
- except PermissionError:
- self.skipTest("Insufficient permissions to execute code in remote process")
-
def test_connect_and_basic_commands(self):
"""Test connecting to a remote debugger and sending basic commands."""
self._create_script()
@@ -1180,6 +1207,8 @@ class PdbConnectTestCase(unittest.TestCase):
frame=frame,
commands="",
version=pdb._PdbServer.protocol_version(),
+ signal_raising_thread=True,
+ colorize=False,
)
print("Connected to debugger")
iterations = 50
@@ -1195,6 +1224,10 @@ class PdbConnectTestCase(unittest.TestCase):
self._create_script(script=script)
process, client_file = self._connect_and_get_client_file()
+ # Accept a 2nd connection from the subprocess to tell it about signals
+ signal_sock, _ = self.server_sock.accept()
+ self.addCleanup(signal_sock.close)
+
with kill_on_error(process):
# Skip initial messages until we get to the prompt
self._read_until_prompt(client_file)
@@ -1210,7 +1243,7 @@ class PdbConnectTestCase(unittest.TestCase):
break
# Inject a script to interrupt the running process
- self._send_interrupt(process.pid)
+ signal_sock.sendall(signal.SIGINT.to_bytes())
messages = self._read_until_prompt(client_file)
# Verify we got the keyboard interrupt message.
@@ -1266,6 +1299,8 @@ class PdbConnectTestCase(unittest.TestCase):
frame=frame,
commands="",
version=fake_version,
+ signal_raising_thread=False,
+ colorize=False,
)
# This should print if the debugger detaches correctly
@@ -1393,5 +1428,151 @@ class PdbConnectTestCase(unittest.TestCase):
self.assertIn("Function returned: 42", stdout)
self.assertEqual(process.returncode, 0)
+
+def _supports_remote_attaching():
+ PROCESS_VM_READV_SUPPORTED = False
+
+ try:
+ from _remote_debugging import PROCESS_VM_READV_SUPPORTED
+ except ImportError:
+ pass
+
+ return PROCESS_VM_READV_SUPPORTED
+
+
+@unittest.skipIf(not sys.is_remote_debug_enabled(), "Remote debugging is not enabled")
+@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux" and sys.platform != "win32",
+ "Test only runs on Linux, Windows and MacOS")
+@unittest.skipIf(sys.platform == "linux" and not _supports_remote_attaching(),
+ "Testing on Linux requires process_vm_readv support")
+@cpython_only
+@requires_subprocess()
+class PdbAttachTestCase(unittest.TestCase):
+ def setUp(self):
+ # Create a server socket that will wait for the debugger to connect
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.bind(('127.0.0.1', 0)) # Let OS assign port
+ self.sock.listen(1)
+ self.port = self.sock.getsockname()[1]
+ self._create_script()
+
+ def _create_script(self, script=None):
+ # Create a file for subprocess script
+ script = textwrap.dedent(
+ f"""
+ import socket
+ import time
+
+ def foo():
+ return bar()
+
+ def bar():
+ return baz()
+
+ def baz():
+ x = 1
+ # Trigger attach
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect(('127.0.0.1', {self.port}))
+ sock.close()
+ count = 0
+ while x == 1 and count < 100:
+ count += 1
+ time.sleep(0.1)
+ return x
+
+ result = foo()
+ print(f"Function returned: {{result}}")
+ """
+ )
+
+ self.script_path = TESTFN + "_connect_test.py"
+ with open(self.script_path, 'w') as f:
+ f.write(script)
+
+ def tearDown(self):
+ self.sock.close()
+ try:
+ unlink(self.script_path)
+ except OSError:
+ pass
+
+ def do_integration_test(self, client_stdin):
+ process = subprocess.Popen(
+ [sys.executable, self.script_path],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+ self.addCleanup(process.stdout.close)
+ self.addCleanup(process.stderr.close)
+
+ # Wait for the process to reach our attachment point
+ self.sock.settimeout(10)
+ conn, _ = self.sock.accept()
+ conn.close()
+
+ client_stdin = io.StringIO(client_stdin)
+ client_stdout = io.StringIO()
+ client_stderr = io.StringIO()
+
+ self.addCleanup(client_stdin.close)
+ self.addCleanup(client_stdout.close)
+ self.addCleanup(client_stderr.close)
+ self.addCleanup(process.wait)
+
+ with (
+ unittest.mock.patch("sys.stdin", client_stdin),
+ redirect_stdout(client_stdout),
+ redirect_stderr(client_stderr),
+ unittest.mock.patch("sys.argv", ["pdb", "-p", str(process.pid)]),
+ ):
+ try:
+ pdb.main()
+ except PermissionError:
+ self.skipTest("Insufficient permissions for remote execution")
+
+ process.wait()
+ server_stdout = process.stdout.read()
+ server_stderr = process.stderr.read()
+
+ if process.returncode != 0:
+ print("server failed")
+ print(f"server stdout:\n{server_stdout}")
+ print(f"server stderr:\n{server_stderr}")
+
+ self.assertEqual(process.returncode, 0)
+ return {
+ "client": {
+ "stdout": client_stdout.getvalue(),
+ "stderr": client_stderr.getvalue(),
+ },
+ "server": {
+ "stdout": server_stdout,
+ "stderr": server_stderr,
+ },
+ }
+
+ def test_attach_to_process_without_colors(self):
+ with force_color(False):
+ output = self.do_integration_test("ll\nx=42\n")
+ self.assertEqual(output["client"]["stderr"], "")
+ self.assertEqual(output["server"]["stderr"], "")
+
+ self.assertEqual(output["server"]["stdout"], "Function returned: 42\n")
+ self.assertIn("while x == 1", output["client"]["stdout"])
+ self.assertNotIn("\x1b", output["client"]["stdout"])
+
+ def test_attach_to_process_with_colors(self):
+ with force_color(True):
+ output = self.do_integration_test("ll\nx=42\n")
+ self.assertEqual(output["client"]["stderr"], "")
+ self.assertEqual(output["server"]["stderr"], "")
+
+ self.assertEqual(output["server"]["stdout"], "Function returned: 42\n")
+ self.assertIn("\x1b", output["client"]["stdout"])
+ self.assertNotIn("while x == 1", output["client"]["stdout"])
+ self.assertIn("while x == 1", re.sub("\x1b[^m]*m", "", output["client"]["stdout"]))
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py
index 228b326699e..f4a4634fc62 100644
--- a/Lib/test/test_repl.py
+++ b/Lib/test/test_repl.py
@@ -38,8 +38,8 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw):
# line option '-i' and the process name set to '<stdin>'.
# The directory of argv[0] must match the directory of the Python
# executable for the Popen() call to python to succeed as the directory
- # path may be used by Py_GetPath() to build the default module search
- # path.
+ # path may be used by PyConfig_Get("module_search_paths") to build the
+ # default module search path.
stdin_fname = os.path.join(os.path.dirname(sys.executable), "<stdin>")
cmd_line = [stdin_fname, '-I', '-i']
cmd_line.extend(args)
@@ -197,7 +197,7 @@ class TestInteractiveInterpreter(unittest.TestCase):
expected_lines = [
' def f(x, x): ...',
' ^',
- "SyntaxError: duplicate argument 'x' in function definition"
+ "SyntaxError: duplicate parameter 'x' in function definition"
]
self.assertEqual(output.splitlines()[4:-1], expected_lines)
diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py
index ffeb1fba7b8..22a55b57c07 100644
--- a/Lib/test/test_reprlib.py
+++ b/Lib/test/test_reprlib.py
@@ -3,6 +3,7 @@
Nick Mathewson
"""
+import annotationlib
import sys
import os
import shutil
@@ -11,7 +12,7 @@ import importlib.util
import unittest
import textwrap
-from test.support import verbose
+from test.support import verbose, EqualToForwardRef
from test.support.os_helper import create_empty_file
from reprlib import repr as r # Don't shadow builtin repr
from reprlib import Repr
@@ -150,14 +151,38 @@ class ReprTests(unittest.TestCase):
eq(r(frozenset({1, 2, 3, 4, 5, 6, 7})), "frozenset({1, 2, 3, 4, 5, 6, ...})")
def test_numbers(self):
- eq = self.assertEqual
- eq(r(123), repr(123))
- eq(r(123), repr(123))
- eq(r(1.0/3), repr(1.0/3))
-
- n = 10**100
- expected = repr(n)[:18] + "..." + repr(n)[-19:]
- eq(r(n), expected)
+ for x in [123, 1.0 / 3]:
+ self.assertEqual(r(x), repr(x))
+
+ max_digits = sys.get_int_max_str_digits()
+ for k in [100, max_digits - 1]:
+ with self.subTest(f'10 ** {k}', k=k):
+ n = 10 ** k
+ expected = repr(n)[:18] + "..." + repr(n)[-19:]
+ self.assertEqual(r(n), expected)
+
+ def re_msg(n, d):
+ return (rf'<{n.__class__.__name__} instance with roughly {d} '
+ rf'digits \(limit at {max_digits}\) at 0x[a-f0-9]+>')
+
+ k = max_digits
+ with self.subTest(f'10 ** {k}', k=k):
+ n = 10 ** k
+ self.assertRaises(ValueError, repr, n)
+ self.assertRegex(r(n), re_msg(n, k + 1))
+
+ for k in [max_digits + 1, 2 * max_digits]:
+ self.assertGreater(k, 100)
+ with self.subTest(f'10 ** {k}', k=k):
+ n = 10 ** k
+ self.assertRaises(ValueError, repr, n)
+ self.assertRegex(r(n), re_msg(n, k + 1))
+ with self.subTest(f'10 ** {k} - 1', k=k):
+ n = 10 ** k - 1
+ # Here, since math.log10(n) == math.log10(n-1),
+ # the number of digits of n - 1 is overestimated.
+ self.assertRaises(ValueError, repr, n)
+ self.assertRegex(r(n), re_msg(n, k + 1))
def test_instance(self):
eq = self.assertEqual
@@ -172,13 +197,13 @@ class ReprTests(unittest.TestCase):
eq(r(i3), ("<ClassWithFailingRepr instance at %#x>"%id(i3)))
s = r(ClassWithFailingRepr)
- self.assertTrue(s.startswith("<class "))
- self.assertTrue(s.endswith(">"))
+ self.assertStartsWith(s, "<class ")
+ self.assertEndsWith(s, ">")
self.assertIn(s.find("..."), [12, 13])
def test_lambda(self):
r = repr(lambda x: x)
- self.assertTrue(r.startswith("<function ReprTests.test_lambda.<locals>.<lambda"), r)
+ self.assertStartsWith(r, "<function ReprTests.test_lambda.<locals>.<lambda")
# XXX anonymous functions? see func_repr
def test_builtin_function(self):
@@ -186,8 +211,8 @@ class ReprTests(unittest.TestCase):
# Functions
eq(repr(hash), '<built-in function hash>')
# Methods
- self.assertTrue(repr(''.split).startswith(
- '<built-in method split of str object at 0x'))
+ self.assertStartsWith(repr(''.split),
+ '<built-in method split of str object at 0x')
def test_range(self):
eq = self.assertEqual
@@ -372,20 +397,20 @@ class ReprTests(unittest.TestCase):
'object': {
1: 'two',
b'three': [
- (4.5, 6.7),
+ (4.5, 6.25),
[set((8, 9)), frozenset((10, 11))],
],
},
'tests': (
(dict(indent=None), '''\
- {1: 'two', b'three': [(4.5, 6.7), [{8, 9}, frozenset({10, 11})]]}'''),
+ {1: 'two', b'three': [(4.5, 6.25), [{8, 9}, frozenset({10, 11})]]}'''),
(dict(indent=False), '''\
{
1: 'two',
b'three': [
(
4.5,
- 6.7,
+ 6.25,
),
[
{
@@ -405,7 +430,7 @@ class ReprTests(unittest.TestCase):
b'three': [
(
4.5,
- 6.7,
+ 6.25,
),
[
{
@@ -425,7 +450,7 @@ class ReprTests(unittest.TestCase):
b'three': [
(
4.5,
- 6.7,
+ 6.25,
),
[
{
@@ -445,7 +470,7 @@ class ReprTests(unittest.TestCase):
b'three': [
(
4.5,
- 6.7,
+ 6.25,
),
[
{
@@ -465,7 +490,7 @@ class ReprTests(unittest.TestCase):
b'three': [
(
4.5,
- 6.7,
+ 6.25,
),
[
{
@@ -493,7 +518,7 @@ class ReprTests(unittest.TestCase):
b'three': [
(
4.5,
- 6.7,
+ 6.25,
),
[
{
@@ -513,7 +538,7 @@ class ReprTests(unittest.TestCase):
-->b'three': [
-->-->(
-->-->-->4.5,
- -->-->-->6.7,
+ -->-->-->6.25,
-->-->),
-->-->[
-->-->-->{
@@ -533,7 +558,7 @@ class ReprTests(unittest.TestCase):
....b'three': [
........(
............4.5,
- ............6.7,
+ ............6.25,
........),
........[
............{
@@ -729,8 +754,8 @@ class baz:
importlib.invalidate_caches()
from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import baz
ibaz = baz.baz()
- self.assertTrue(repr(ibaz).startswith(
- "<%s.baz object at 0x" % baz.__name__))
+ self.assertStartsWith(repr(ibaz),
+ "<%s.baz object at 0x" % baz.__name__)
def test_method(self):
self._check_path_limitations('qux')
@@ -743,13 +768,13 @@ class aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
from areallylongpackageandmodulenametotestreprtruncation.areallylongpackageandmodulenametotestreprtruncation import qux
# Unbound methods first
r = repr(qux.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod)
- self.assertTrue(r.startswith('<function aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod'), r)
+ self.assertStartsWith(r, '<function aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod')
# Bound method next
iqux = qux.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa()
r = repr(iqux.amethod)
- self.assertTrue(r.startswith(
+ self.assertStartsWith(r,
'<bound method aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.amethod of <%s.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa object at 0x' \
- % (qux.__name__,) ), r)
+ % (qux.__name__,) )
@unittest.skip('needs a built-in function with a really long name')
def test_builtin_function(self):
@@ -829,5 +854,19 @@ class TestRecursiveRepr(unittest.TestCase):
self.assertEqual(type_params[0].__name__, 'T')
self.assertEqual(type_params[0].__bound__, str)
+ def test_annotations(self):
+ class My:
+ @recursive_repr()
+ def __repr__(self, default: undefined = ...):
+ return default
+
+ annotations = annotationlib.get_annotations(
+ My.__repr__, format=annotationlib.Format.FORWARDREF
+ )
+ self.assertEqual(
+ annotations,
+ {'default': EqualToForwardRef("undefined", owner=My.__repr__)}
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_rlcompleter.py b/Lib/test/test_rlcompleter.py
index d403a0fe96b..a8914953ce9 100644
--- a/Lib/test/test_rlcompleter.py
+++ b/Lib/test/test_rlcompleter.py
@@ -88,7 +88,7 @@ class TestRlcompleter(unittest.TestCase):
['CompleteMe._ham'])
matches = self.completer.attr_matches('CompleteMe.__')
for x in matches:
- self.assertTrue(x.startswith('CompleteMe.__'), x)
+ self.assertStartsWith(x, 'CompleteMe.__')
self.assertIn('CompleteMe.__name__', matches)
self.assertIn('CompleteMe.__new__(', matches)
diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py
index ada78ec8e6b..a2a07c04f58 100644
--- a/Lib/test/test_runpy.py
+++ b/Lib/test/test_runpy.py
@@ -796,7 +796,7 @@ class TestExit(unittest.TestCase):
# Use -E to ignore PYTHONSAFEPATH
cmd = [sys.executable, '-E', *cmd]
proc = subprocess.run(cmd, *args, **kwargs, text=True, stderr=subprocess.PIPE)
- self.assertTrue(proc.stderr.endswith("\nKeyboardInterrupt\n"), proc.stderr)
+ self.assertEndsWith(proc.stderr, "\nKeyboardInterrupt\n")
self.assertEqual(proc.returncode, self.EXPECTED_CODE)
def test_pymain_run_file(self):
diff --git a/Lib/test/test_scope.py b/Lib/test/test_scope.py
index 24a366efc6c..520fbc1b662 100644
--- a/Lib/test/test_scope.py
+++ b/Lib/test/test_scope.py
@@ -778,7 +778,7 @@ class ScopeTests(unittest.TestCase):
class X:
locals()["x"] = 43
del x
- self.assertFalse(hasattr(X, "x"))
+ self.assertNotHasAttr(X, "x")
self.assertEqual(x, 42)
@cpython_only
diff --git a/Lib/test/test_script_helper.py b/Lib/test/test_script_helper.py
index f7871fd3b77..eeea6c4842b 100644
--- a/Lib/test/test_script_helper.py
+++ b/Lib/test/test_script_helper.py
@@ -74,8 +74,7 @@ class TestScriptHelperEnvironment(unittest.TestCase):
"""Code coverage for interpreter_requires_environment()."""
def setUp(self):
- self.assertTrue(
- hasattr(script_helper, '__cached_interp_requires_environment'))
+ self.assertHasAttr(script_helper, '__cached_interp_requires_environment')
# Reset the private cached state.
script_helper.__dict__['__cached_interp_requires_environment'] = None
diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py
index c01e323553d..c0df9507bd7 100644
--- a/Lib/test/test_set.py
+++ b/Lib/test/test_set.py
@@ -237,7 +237,7 @@ class TestJointOps:
if type(self.s) not in (set, frozenset):
self.assertEqual(self.s.x, dup.x)
self.assertEqual(self.s.z, dup.z)
- self.assertFalse(hasattr(self.s, 'y'))
+ self.assertNotHasAttr(self.s, 'y')
del self.s.x, self.s.z
def test_iterator_pickling(self):
@@ -876,8 +876,8 @@ class TestBasicOps:
def check_repr_against_values(self):
text = repr(self.set)
- self.assertTrue(text.startswith('{'))
- self.assertTrue(text.endswith('}'))
+ self.assertStartsWith(text, '{')
+ self.assertEndsWith(text, '}')
result = text[1:-1].split(', ')
result.sort()
diff --git a/Lib/test/test_shlex.py b/Lib/test/test_shlex.py
index f35571ea886..a13ddcb76b7 100644
--- a/Lib/test/test_shlex.py
+++ b/Lib/test/test_shlex.py
@@ -3,6 +3,7 @@ import itertools
import shlex
import string
import unittest
+from test.support import cpython_only
from test.support import import_helper
@@ -364,6 +365,7 @@ class ShlexTest(unittest.TestCase):
with self.assertRaises(AttributeError):
shlex_instance.punctuation_chars = False
+ @cpython_only
def test_lazy_imports(self):
import_helper.ensure_lazy_imports('shlex', {'collections', 're', 'os'})
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index ed01163074a..ebb6cf88336 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -427,12 +427,12 @@ class TestRmTree(BaseTest, unittest.TestCase):
else:
self.assertIs(func, os.listdir)
self.assertIn(arg, [TESTFN, self.child_dir_path])
- self.assertTrue(issubclass(exc[0], OSError))
+ self.assertIsSubclass(exc[0], OSError)
self.errorState += 1
else:
self.assertEqual(func, os.rmdir)
self.assertEqual(arg, TESTFN)
- self.assertTrue(issubclass(exc[0], OSError))
+ self.assertIsSubclass(exc[0], OSError)
self.errorState = 3
@unittest.skipIf(sys.platform[:6] == 'cygwin',
@@ -2153,6 +2153,10 @@ class TestArchives(BaseTest, unittest.TestCase):
def test_unpack_archive_bztar(self):
self.check_unpack_tarball('bztar')
+ @support.requires_zstd()
+ def test_unpack_archive_zstdtar(self):
+ self.check_unpack_tarball('zstdtar')
+
@support.requires_lzma()
@unittest.skipIf(AIX and not _maxdataOK(), "AIX MAXDATA must be 0x20000000 or larger")
def test_unpack_archive_xztar(self):
@@ -3475,7 +3479,7 @@ class PublicAPITests(unittest.TestCase):
"""Ensures that the correct values are exposed in the public API."""
def test_module_all_attribute(self):
- self.assertTrue(hasattr(shutil, '__all__'))
+ self.assertHasAttr(shutil, '__all__')
target_api = ['copyfileobj', 'copyfile', 'copymode', 'copystat',
'copy', 'copy2', 'copytree', 'move', 'rmtree', 'Error',
'SpecialFileError', 'make_archive',
@@ -3488,7 +3492,7 @@ class PublicAPITests(unittest.TestCase):
target_api.append('disk_usage')
self.assertEqual(set(shutil.__all__), set(target_api))
with self.assertWarns(DeprecationWarning):
- from shutil import ExecError
+ from shutil import ExecError # noqa: F401
if __name__ == '__main__':
diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py
index a7e9241f44d..d0e32942635 100644
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -307,8 +307,7 @@ class HelperFunctionsTests(unittest.TestCase):
with EnvironmentVarGuard() as environ:
environ['PYTHONUSERBASE'] = 'xoxo'
- self.assertTrue(site.getuserbase().startswith('xoxo'),
- site.getuserbase())
+ self.assertStartsWith(site.getuserbase(), 'xoxo')
@unittest.skipUnless(HAS_USER_SITE, 'need user site')
def test_getusersitepackages(self):
@@ -318,7 +317,7 @@ class HelperFunctionsTests(unittest.TestCase):
# the call sets USER_BASE *and* USER_SITE
self.assertEqual(site.USER_SITE, user_site)
- self.assertTrue(user_site.startswith(site.USER_BASE), user_site)
+ self.assertStartsWith(user_site, site.USER_BASE)
self.assertEqual(site.USER_BASE, site.getuserbase())
def test_getsitepackages(self):
@@ -359,11 +358,10 @@ class HelperFunctionsTests(unittest.TestCase):
environ.unset('PYTHONUSERBASE', 'APPDATA')
user_base = site.getuserbase()
- self.assertTrue(user_base.startswith('~' + os.sep),
- user_base)
+ self.assertStartsWith(user_base, '~' + os.sep)
user_site = site.getusersitepackages()
- self.assertTrue(user_site.startswith(user_base), user_site)
+ self.assertStartsWith(user_site, user_base)
with mock.patch('os.path.isdir', return_value=False) as mock_isdir, \
mock.patch.object(site, 'addsitedir') as mock_addsitedir, \
@@ -495,18 +493,18 @@ class ImportSideEffectTests(unittest.TestCase):
def test_setting_quit(self):
# 'quit' and 'exit' should be injected into builtins
- self.assertTrue(hasattr(builtins, "quit"))
- self.assertTrue(hasattr(builtins, "exit"))
+ self.assertHasAttr(builtins, "quit")
+ self.assertHasAttr(builtins, "exit")
def test_setting_copyright(self):
# 'copyright', 'credits', and 'license' should be in builtins
- self.assertTrue(hasattr(builtins, "copyright"))
- self.assertTrue(hasattr(builtins, "credits"))
- self.assertTrue(hasattr(builtins, "license"))
+ self.assertHasAttr(builtins, "copyright")
+ self.assertHasAttr(builtins, "credits")
+ self.assertHasAttr(builtins, "license")
def test_setting_help(self):
# 'help' should be set in builtins
- self.assertTrue(hasattr(builtins, "help"))
+ self.assertHasAttr(builtins, "help")
def test_sitecustomize_executed(self):
# If sitecustomize is available, it should have been imported.
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index ace97ce0cbe..3dd67b2a2ab 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -2,8 +2,9 @@ import unittest
from unittest import mock
from test import support
from test.support import (
- is_apple, os_helper, refleak_helper, socket_helper, threading_helper
+ cpython_only, is_apple, os_helper, refleak_helper, socket_helper, threading_helper
)
+from test.support.import_helper import ensure_lazy_imports
import _thread as thread
import array
import contextlib
@@ -257,6 +258,12 @@ HAVE_SOCKET_HYPERV = _have_socket_hyperv()
# Size in bytes of the int type
SIZEOF_INT = array.array("i").itemsize
+class TestLazyImport(unittest.TestCase):
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("socket", {"array", "selectors"})
+
+
class SocketTCPTest(unittest.TestCase):
def setUp(self):
@@ -1078,9 +1085,7 @@ class GeneralModuleTests(unittest.TestCase):
'IPV6_USE_MIN_MTU',
}
for opt in opts:
- self.assertTrue(
- hasattr(socket, opt), f"Missing RFC3542 socket option '{opt}'"
- )
+ self.assertHasAttr(socket, opt)
def testHostnameRes(self):
# Testing hostname resolution mechanisms
@@ -1586,11 +1591,11 @@ class GeneralModuleTests(unittest.TestCase):
@unittest.skipUnless(os.name == "nt", "Windows specific")
def test_sock_ioctl(self):
- self.assertTrue(hasattr(socket.socket, 'ioctl'))
- self.assertTrue(hasattr(socket, 'SIO_RCVALL'))
- self.assertTrue(hasattr(socket, 'RCVALL_ON'))
- self.assertTrue(hasattr(socket, 'RCVALL_OFF'))
- self.assertTrue(hasattr(socket, 'SIO_KEEPALIVE_VALS'))
+ self.assertHasAttr(socket.socket, 'ioctl')
+ self.assertHasAttr(socket, 'SIO_RCVALL')
+ self.assertHasAttr(socket, 'RCVALL_ON')
+ self.assertHasAttr(socket, 'RCVALL_OFF')
+ self.assertHasAttr(socket, 'SIO_KEEPALIVE_VALS')
s = socket.socket()
self.addCleanup(s.close)
self.assertRaises(ValueError, s.ioctl, -1, None)
@@ -6075,10 +6080,10 @@ class UDPLITETimeoutTest(SocketUDPLITETest):
class TestExceptions(unittest.TestCase):
def testExceptionTree(self):
- self.assertTrue(issubclass(OSError, Exception))
- self.assertTrue(issubclass(socket.herror, OSError))
- self.assertTrue(issubclass(socket.gaierror, OSError))
- self.assertTrue(issubclass(socket.timeout, OSError))
+ self.assertIsSubclass(OSError, Exception)
+ self.assertIsSubclass(socket.herror, OSError)
+ self.assertIsSubclass(socket.gaierror, OSError)
+ self.assertIsSubclass(socket.timeout, OSError)
self.assertIs(socket.error, OSError)
self.assertIs(socket.timeout, TimeoutError)
diff --git a/Lib/test/test_source_encoding.py b/Lib/test/test_source_encoding.py
index 61b00778f83..1399f3fcd2d 100644
--- a/Lib/test/test_source_encoding.py
+++ b/Lib/test/test_source_encoding.py
@@ -145,8 +145,7 @@ class MiscSourceEncodingTest(unittest.TestCase):
compile(input, "<string>", "exec")
expected = "'ascii' codec can't decode byte 0xe2 in position 16: " \
"ordinal not in range(128)"
- self.assertTrue(c.exception.args[0].startswith(expected),
- msg=c.exception.args[0])
+ self.assertStartsWith(c.exception.args[0], expected)
def test_file_parse_error_multiline(self):
# gh96611:
diff --git a/Lib/test/test_sqlite3/test_cli.py b/Lib/test/test_sqlite3/test_cli.py
index dcd90d11d46..720fa3c4c1e 100644
--- a/Lib/test/test_sqlite3/test_cli.py
+++ b/Lib/test/test_sqlite3/test_cli.py
@@ -1,12 +1,26 @@
"""sqlite3 CLI tests."""
import sqlite3
+import sys
+import textwrap
import unittest
+import unittest.mock
+import os
from sqlite3.__main__ import main as cli
+from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
-from test.support import captured_stdout, captured_stderr, captured_stdin
+from test.support.pty_helper import run_pty
+from test.support import (
+ captured_stdout,
+ captured_stderr,
+ captured_stdin,
+ force_not_colorized_test_class,
+ requires_subprocess,
+ verbose,
+)
+@force_not_colorized_test_class
class CommandLineInterface(unittest.TestCase):
def _do_test(self, *args, expect_success=True):
@@ -63,6 +77,7 @@ class CommandLineInterface(unittest.TestCase):
self.assertIn("(0,)", out)
+@force_not_colorized_test_class
class InteractiveSession(unittest.TestCase):
MEMORY_DB_MSG = "Connected to a transient in-memory database"
PS1 = "sqlite> "
@@ -110,6 +125,38 @@ class InteractiveSession(unittest.TestCase):
self.assertEqual(out.count(self.PS2), 0)
self.assertIn(sqlite3.sqlite_version, out)
+ def test_interact_empty_source(self):
+ out, err = self.run_cli(commands=("", " "))
+ self.assertIn(self.MEMORY_DB_MSG, err)
+ self.assertEndsWith(out, self.PS1)
+ self.assertEqual(out.count(self.PS1), 3)
+ self.assertEqual(out.count(self.PS2), 0)
+
+ def test_interact_dot_commands_unknown(self):
+ out, err = self.run_cli(commands=(".unknown_command", ))
+ self.assertIn(self.MEMORY_DB_MSG, err)
+ self.assertEndsWith(out, self.PS1)
+ self.assertEqual(out.count(self.PS1), 2)
+ self.assertEqual(out.count(self.PS2), 0)
+ self.assertIn('Error: unknown command: "', err)
+ # test "unknown_command" is pointed out in the error message
+ self.assertIn("unknown_command", err)
+
+ def test_interact_dot_commands_empty(self):
+ out, err = self.run_cli(commands=("."))
+ self.assertIn(self.MEMORY_DB_MSG, err)
+ self.assertEndsWith(out, self.PS1)
+ self.assertEqual(out.count(self.PS1), 2)
+ self.assertEqual(out.count(self.PS2), 0)
+
+ def test_interact_dot_commands_with_whitespaces(self):
+ out, err = self.run_cli(commands=(".version ", ". version"))
+ self.assertIn(self.MEMORY_DB_MSG, err)
+ self.assertEqual(out.count(sqlite3.sqlite_version + "\n"), 2)
+ self.assertEndsWith(out, self.PS1)
+ self.assertEqual(out.count(self.PS1), 3)
+ self.assertEqual(out.count(self.PS2), 0)
+
def test_interact_valid_sql(self):
out, err = self.run_cli(commands=("SELECT 1;",))
self.assertIn(self.MEMORY_DB_MSG, err)
@@ -152,6 +199,117 @@ class InteractiveSession(unittest.TestCase):
out, _ = self.run_cli(TESTFN, commands=("SELECT count(t) FROM t;",))
self.assertIn("(0,)\n", out)
+ def test_color(self):
+ with unittest.mock.patch("_colorize.can_colorize", return_value=True):
+ out, err = self.run_cli(commands="TEXT\n")
+ self.assertIn("\x1b[1;35msqlite> \x1b[0m", out)
+ self.assertIn("\x1b[1;35m ... \x1b[0m\x1b", out)
+ out, err = self.run_cli(commands=("sel;",))
+ self.assertIn('\x1b[1;35mOperationalError (SQLITE_ERROR)\x1b[0m: '
+ '\x1b[35mnear "sel": syntax error\x1b[0m', err)
+
+
+@requires_subprocess()
+@force_not_colorized_test_class
+class Completion(unittest.TestCase):
+ PS1 = "sqlite> "
+
+ @classmethod
+ def setUpClass(cls):
+ _sqlite3 = import_module("_sqlite3")
+ if not hasattr(_sqlite3, "SQLITE_KEYWORDS"):
+ raise unittest.SkipTest("unable to determine SQLite keywords")
+
+ readline = import_module("readline")
+ if readline.backend == "editline":
+ raise unittest.SkipTest("libedit readline is not supported")
+
+ def write_input(self, input_, env=None):
+ script = textwrap.dedent("""
+ import readline
+ from sqlite3.__main__ import main
+
+ readline.parse_and_bind("set colored-completion-prefix off")
+ main()
+ """)
+ return run_pty(script, input_, env)
+
+ def test_complete_sql_keywords(self):
+ # List candidates starting with 'S', there should be multiple matches.
+ input_ = b"S\t\tEL\t 1;\n.quit\n"
+ output = self.write_input(input_)
+ self.assertIn(b"SELECT", output)
+ self.assertIn(b"SET", output)
+ self.assertIn(b"SAVEPOINT", output)
+ self.assertIn(b"(1,)", output)
+
+ # Keywords are completed in upper case for even lower case user input.
+ input_ = b"sel\t\t 1;\n.quit\n"
+ output = self.write_input(input_)
+ self.assertIn(b"SELECT", output)
+ self.assertIn(b"(1,)", output)
+
+ @unittest.skipIf(sys.platform.startswith("freebsd"),
+ "Two actual tabs are inserted when there are no matching"
+ " completions in the pseudo-terminal opened by run_pty()"
+ " on FreeBSD")
+ def test_complete_no_match(self):
+ input_ = b"xyzzy\t\t\b\b\b\b\b\b\b.quit\n"
+ # Set NO_COLOR to disable coloring for self.PS1.
+ output = self.write_input(input_, env={**os.environ, "NO_COLOR": "1"})
+ lines = output.decode().splitlines()
+ indices = (
+ i for i, line in enumerate(lines, 1)
+ if line.startswith(f"{self.PS1}xyzzy")
+ )
+ line_num = next(indices, -1)
+ self.assertNotEqual(line_num, -1)
+ # Completions occupy lines, assert no extra lines when there is nothing
+ # to complete.
+ self.assertEqual(line_num, len(lines))
+
+ def test_complete_no_input(self):
+ from _sqlite3 import SQLITE_KEYWORDS
+
+ script = textwrap.dedent("""
+ import readline
+ from sqlite3.__main__ import main
+
+ # Configure readline to ...:
+ # - hide control sequences surrounding each candidate
+ # - hide "Display all xxx possibilities? (y or n)"
+ # - hide "--More--"
+ # - show candidates one per line
+ readline.parse_and_bind("set colored-completion-prefix off")
+ readline.parse_and_bind("set colored-stats off")
+ readline.parse_and_bind("set completion-query-items 0")
+ readline.parse_and_bind("set page-completions off")
+ readline.parse_and_bind("set completion-display-width 0")
+ readline.parse_and_bind("set show-all-if-ambiguous off")
+ readline.parse_and_bind("set show-all-if-unmodified off")
+
+ main()
+ """)
+ input_ = b"\t\t.quit\n"
+ output = run_pty(script, input_, env={**os.environ, "NO_COLOR": "1"})
+ try:
+ lines = output.decode().splitlines()
+ indices = [
+ i for i, line in enumerate(lines)
+ if line.startswith(self.PS1)
+ ]
+ self.assertEqual(len(indices), 2)
+ start, end = indices
+ candidates = [l.strip() for l in lines[start+1:end]]
+ self.assertEqual(candidates, sorted(SQLITE_KEYWORDS))
+ except:
+ if verbose:
+ print(' PTY output: '.center(30, '-'))
+ print(output.decode(errors='replace'))
+ print(' end PTY output '.center(30, '-'))
+ raise
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py
index c3aa3bf2d7b..291e0356253 100644
--- a/Lib/test/test_sqlite3/test_dbapi.py
+++ b/Lib/test/test_sqlite3/test_dbapi.py
@@ -550,17 +550,9 @@ class ConnectionTests(unittest.TestCase):
cx.execute("insert into u values(0)")
def test_connect_positional_arguments(self):
- regex = (
- r"Passing more than 1 positional argument to sqlite3.connect\(\)"
- " is deprecated. Parameters 'timeout', 'detect_types', "
- "'isolation_level', 'check_same_thread', 'factory', "
- "'cached_statements' and 'uri' will become keyword-only "
- "parameters in Python 3.15."
- )
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
- cx = sqlite.connect(":memory:", 1.0)
- cx.close()
- self.assertEqual(cm.filename, __file__)
+ with self.assertRaisesRegex(TypeError,
+ r'connect\(\) takes at most 1 positional arguments'):
+ sqlite.connect(":memory:", 1.0)
def test_connection_resource_warning(self):
with self.assertWarns(ResourceWarning):
diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py
index cc9f1ec5c4b..776659e3b16 100644
--- a/Lib/test/test_sqlite3/test_factory.py
+++ b/Lib/test/test_sqlite3/test_factory.py
@@ -71,18 +71,9 @@ class ConnectionFactoryTests(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(Factory, self).__init__(*args, **kwargs)
- regex = (
- r"Passing more than 1 positional argument to _sqlite3.Connection\(\) "
- r"is deprecated. Parameters 'timeout', 'detect_types', "
- r"'isolation_level', 'check_same_thread', 'factory', "
- r"'cached_statements' and 'uri' will become keyword-only "
- r"parameters in Python 3.15."
- )
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
- with memory_database(5.0, 0, None, True, Factory) as con:
- self.assertIsNone(con.isolation_level)
- self.assertIsInstance(con, Factory)
- self.assertEqual(cm.filename, __file__)
+ with self.assertRaisesRegex(TypeError,
+ r'connect\(\) takes at most 1 positional arguments'):
+ memory_database(5.0, 0, None, True, Factory)
class CursorFactoryTests(MemoryDatabaseMixin, unittest.TestCase):
diff --git a/Lib/test/test_sqlite3/test_hooks.py b/Lib/test/test_sqlite3/test_hooks.py
index 53b8a39bf29..2b907e35131 100644
--- a/Lib/test/test_sqlite3/test_hooks.py
+++ b/Lib/test/test_sqlite3/test_hooks.py
@@ -220,16 +220,9 @@ class ProgressTests(MemoryDatabaseMixin, unittest.TestCase):
""")
def test_progress_handler_keyword_args(self):
- regex = (
- r"Passing keyword argument 'progress_handler' to "
- r"_sqlite3.Connection.set_progress_handler\(\) is deprecated. "
- r"Parameter 'progress_handler' will become positional-only in "
- r"Python 3.15."
- )
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
+ with self.assertRaisesRegex(TypeError,
+ 'takes at least 1 positional argument'):
self.con.set_progress_handler(progress_handler=lambda: None, n=1)
- self.assertEqual(cm.filename, __file__)
class TraceCallbackTests(MemoryDatabaseMixin, unittest.TestCase):
@@ -353,16 +346,9 @@ class TraceCallbackTests(MemoryDatabaseMixin, unittest.TestCase):
cx.execute("select 1")
def test_trace_keyword_args(self):
- regex = (
- r"Passing keyword argument 'trace_callback' to "
- r"_sqlite3.Connection.set_trace_callback\(\) is deprecated. "
- r"Parameter 'trace_callback' will become positional-only in "
- r"Python 3.15."
- )
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
+ with self.assertRaisesRegex(TypeError,
+ 'takes exactly 1 positional argument'):
self.con.set_trace_callback(trace_callback=lambda: None)
- self.assertEqual(cm.filename, __file__)
if __name__ == "__main__":
diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py
index 3abc43a3b1a..11cf877a011 100644
--- a/Lib/test/test_sqlite3/test_userfunctions.py
+++ b/Lib/test/test_sqlite3/test_userfunctions.py
@@ -422,27 +422,9 @@ class FunctionTests(unittest.TestCase):
self.con.execute, "select badreturn()")
def test_func_keyword_args(self):
- regex = (
- r"Passing keyword arguments 'name', 'narg' and 'func' to "
- r"_sqlite3.Connection.create_function\(\) is deprecated. "
- r"Parameters 'name', 'narg' and 'func' will become "
- r"positional-only in Python 3.15."
- )
-
- def noop():
- return None
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
- self.con.create_function("noop", 0, func=noop)
- self.assertEqual(cm.filename, __file__)
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
- self.con.create_function("noop", narg=0, func=noop)
- self.assertEqual(cm.filename, __file__)
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
- self.con.create_function(name="noop", narg=0, func=noop)
- self.assertEqual(cm.filename, __file__)
+ with self.assertRaisesRegex(TypeError,
+ 'takes exactly 3 positional arguments'):
+ self.con.create_function("noop", 0, func=lambda: None)
class WindowSumInt:
@@ -737,25 +719,9 @@ class AggregateTests(unittest.TestCase):
self.assertEqual(val, txt)
def test_agg_keyword_args(self):
- regex = (
- r"Passing keyword arguments 'name', 'n_arg' and 'aggregate_class' to "
- r"_sqlite3.Connection.create_aggregate\(\) is deprecated. "
- r"Parameters 'name', 'n_arg' and 'aggregate_class' will become "
- r"positional-only in Python 3.15."
- )
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
+ with self.assertRaisesRegex(TypeError,
+ 'takes exactly 3 positional arguments'):
self.con.create_aggregate("test", 1, aggregate_class=AggrText)
- self.assertEqual(cm.filename, __file__)
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
- self.con.create_aggregate("test", n_arg=1, aggregate_class=AggrText)
- self.assertEqual(cm.filename, __file__)
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
- self.con.create_aggregate(name="test", n_arg=0,
- aggregate_class=AggrText)
- self.assertEqual(cm.filename, __file__)
class AuthorizerTests(unittest.TestCase):
@@ -800,16 +766,9 @@ class AuthorizerTests(unittest.TestCase):
self.con.execute("select c2 from t1")
def test_authorizer_keyword_args(self):
- regex = (
- r"Passing keyword argument 'authorizer_callback' to "
- r"_sqlite3.Connection.set_authorizer\(\) is deprecated. "
- r"Parameter 'authorizer_callback' will become positional-only in "
- r"Python 3.15."
- )
-
- with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
+ with self.assertRaisesRegex(TypeError,
+ 'takes exactly 1 positional argument'):
self.con.set_authorizer(authorizer_callback=lambda: None)
- self.assertEqual(cm.filename, __file__)
class AuthorizerRaiseExceptionTests(AuthorizerTests):
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 395b2ef88ab..f123f6ece40 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -31,6 +31,7 @@ import weakref
import platform
import sysconfig
import functools
+from contextlib import nullcontext
try:
import ctypes
except ImportError:
@@ -539,9 +540,9 @@ class BasicSocketTests(unittest.TestCase):
openssl_ver = f"OpenSSL {major:d}.{minor:d}.{patch:d}"
else:
openssl_ver = f"OpenSSL {major:d}.{minor:d}.{fix:d}"
- self.assertTrue(
- s.startswith((openssl_ver, libressl_ver, "AWS-LC")),
- (s, t, hex(n))
+ self.assertStartsWith(
+ s, (openssl_ver, libressl_ver, "AWS-LC"),
+ (t, hex(n))
)
@support.cpython_only
@@ -1668,7 +1669,7 @@ class SSLErrorTests(unittest.TestCase):
regex = "(NO_START_LINE|UNSUPPORTED_PUBLIC_KEY_TYPE)"
self.assertRegex(cm.exception.reason, regex)
s = str(cm.exception)
- self.assertTrue("NO_START_LINE" in s, s)
+ self.assertIn("NO_START_LINE", s)
def test_subclass(self):
# Check that the appropriate SSLError subclass is raised
@@ -1683,7 +1684,7 @@ class SSLErrorTests(unittest.TestCase):
with self.assertRaises(ssl.SSLWantReadError) as cm:
c.do_handshake()
s = str(cm.exception)
- self.assertTrue(s.startswith("The operation did not complete (read)"), s)
+ self.assertStartsWith(s, "The operation did not complete (read)")
# For compatibility
self.assertEqual(cm.exception.errno, ssl.SSL_ERROR_WANT_READ)
@@ -2843,6 +2844,7 @@ class ThreadedTests(unittest.TestCase):
# See GH-124984: OpenSSL is not thread safe.
threads = []
+ warnings_filters = sys.flags.context_aware_warnings
global USE_SAME_TEST_CONTEXT
USE_SAME_TEST_CONTEXT = True
try:
@@ -2851,7 +2853,10 @@ class ThreadedTests(unittest.TestCase):
self.test_alpn_protocols,
self.test_getpeercert,
self.test_crl_check,
- self.test_check_hostname_idn,
+ functools.partial(
+ self.test_check_hostname_idn,
+ warnings_filters=warnings_filters,
+ ),
self.test_wrong_cert_tls12,
self.test_wrong_cert_tls13,
):
@@ -3097,7 +3102,7 @@ class ThreadedTests(unittest.TestCase):
cipher = s.cipher()[0].split('-')
self.assertTrue(cipher[:2], ('ECDHE', 'ECDSA'))
- def test_check_hostname_idn(self):
+ def test_check_hostname_idn(self, warnings_filters=True):
if support.verbose:
sys.stdout.write("\n")
@@ -3152,16 +3157,30 @@ class ThreadedTests(unittest.TestCase):
server_hostname="python.example.org") as s:
with self.assertRaises(ssl.CertificateError):
s.connect((HOST, server.port))
- with ThreadedEchoServer(context=server_context, chatty=True) as server:
- with warnings_helper.check_no_resource_warning(self):
- with self.assertRaises(UnicodeError):
- context.wrap_socket(socket.socket(),
- server_hostname='.pythontest.net')
- with ThreadedEchoServer(context=server_context, chatty=True) as server:
- with warnings_helper.check_no_resource_warning(self):
- with self.assertRaises(UnicodeDecodeError):
- context.wrap_socket(socket.socket(),
- server_hostname=b'k\xf6nig.idn.pythontest.net')
+ with (
+ ThreadedEchoServer(context=server_context, chatty=True) as server,
+ (
+ warnings_helper.check_no_resource_warning(self)
+ if warnings_filters
+ else nullcontext()
+ ),
+ self.assertRaises(UnicodeError),
+ ):
+ context.wrap_socket(socket.socket(), server_hostname='.pythontest.net')
+
+ with (
+ ThreadedEchoServer(context=server_context, chatty=True) as server,
+ (
+ warnings_helper.check_no_resource_warning(self)
+ if warnings_filters
+ else nullcontext()
+ ),
+ self.assertRaises(UnicodeDecodeError),
+ ):
+ context.wrap_socket(
+ socket.socket(),
+ server_hostname=b'k\xf6nig.idn.pythontest.net',
+ )
def test_wrong_cert_tls12(self):
"""Connecting when the server rejects the client's certificate
@@ -4488,6 +4507,7 @@ class ThreadedTests(unittest.TestCase):
@requires_tls_version('TLSv1_3')
@unittest.skipUnless(ssl.HAS_PSK, 'TLS-PSK disabled on this OpenSSL build')
+ @unittest.skipUnless(ssl.HAS_PSK_TLS13, 'TLS 1.3 PSK disabled on this OpenSSL build')
def test_psk_tls1_3(self):
psk = bytes.fromhex('deadbeef')
identity_hint = 'identity-hint'
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index 1e6f69d49e9..5a6ba9de337 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -658,7 +658,11 @@ SYMBOL_NAMES = (
"PySys_AuditTuple",
"PySys_FormatStderr",
"PySys_FormatStdout",
+ "PySys_GetAttr",
+ "PySys_GetAttrString",
"PySys_GetObject",
+ "PySys_GetOptionalAttr",
+ "PySys_GetOptionalAttrString",
"PySys_GetXOptions",
"PySys_HasWarnOptions",
"PySys_ResetWarnOptions",
diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py
index 49013a4bcd8..5fd25d5012c 100644
--- a/Lib/test/test_stat.py
+++ b/Lib/test/test_stat.py
@@ -157,7 +157,7 @@ class TestFilemode:
os.chmod(TESTFN, 0o700)
st_mode, modestr = self.get_mode()
- self.assertEqual(modestr[:3], '-rw')
+ self.assertStartsWith(modestr, '-rw')
self.assertS_IS("REG", st_mode)
self.assertEqual(self.statmod.S_IFMT(st_mode),
self.statmod.S_IFREG)
@@ -256,7 +256,7 @@ class TestFilemode:
"FILE_ATTRIBUTE_* constants are Win32 specific")
def test_file_attribute_constants(self):
for key, value in sorted(self.file_attributes.items()):
- self.assertTrue(hasattr(self.statmod, key), key)
+ self.assertHasAttr(self.statmod, key)
modvalue = getattr(self.statmod, key)
self.assertEqual(value, modvalue, key)
@@ -314,7 +314,7 @@ class TestFilemode:
self.assertEqual(self.statmod.S_ISGID, 0o002000)
self.assertEqual(self.statmod.S_ISVTX, 0o001000)
- self.assertFalse(hasattr(self.statmod, "S_ISTXT"))
+ self.assertNotHasAttr(self.statmod, "S_ISTXT")
self.assertEqual(self.statmod.S_IREAD, self.statmod.S_IRUSR)
self.assertEqual(self.statmod.S_IWRITE, self.statmod.S_IWUSR)
self.assertEqual(self.statmod.S_IEXEC, self.statmod.S_IXUSR)
diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py
index c69baa4bf4d..8250b0aef09 100644
--- a/Lib/test/test_statistics.py
+++ b/Lib/test/test_statistics.py
@@ -645,7 +645,7 @@ class TestNumericTestCase(unittest.TestCase):
def test_numerictestcase_is_testcase(self):
# Ensure that NumericTestCase actually is a TestCase.
- self.assertTrue(issubclass(NumericTestCase, unittest.TestCase))
+ self.assertIsSubclass(NumericTestCase, unittest.TestCase)
def test_error_msg_numeric(self):
# Test the error message generated for numeric comparisons.
@@ -683,32 +683,23 @@ class GlobalsTest(unittest.TestCase):
def test_meta(self):
# Test for the existence of metadata.
for meta in self.expected_metadata:
- self.assertTrue(hasattr(self.module, meta),
- "%s not present" % meta)
+ self.assertHasAttr(self.module, meta)
def test_check_all(self):
# Check everything in __all__ exists and is public.
module = self.module
for name in module.__all__:
# No private names in __all__:
- self.assertFalse(name.startswith("_"),
+ self.assertNotStartsWith(name, "_",
'private name "%s" in __all__' % name)
# And anything in __all__ must exist:
- self.assertTrue(hasattr(module, name),
- 'missing name "%s" in __all__' % name)
+ self.assertHasAttr(module, name)
class StatisticsErrorTest(unittest.TestCase):
def test_has_exception(self):
- errmsg = (
- "Expected StatisticsError to be a ValueError, but got a"
- " subclass of %r instead."
- )
- self.assertTrue(hasattr(statistics, 'StatisticsError'))
- self.assertTrue(
- issubclass(statistics.StatisticsError, ValueError),
- errmsg % statistics.StatisticsError.__base__
- )
+ self.assertHasAttr(statistics, 'StatisticsError')
+ self.assertIsSubclass(statistics.StatisticsError, ValueError)
# === Tests for private utility functions ===
@@ -2355,6 +2346,7 @@ class TestGeometricMean(unittest.TestCase):
class TestKDE(unittest.TestCase):
+ @support.requires_resource('cpu')
def test_kde(self):
kde = statistics.kde
StatisticsError = statistics.StatisticsError
@@ -3327,7 +3319,8 @@ class TestNormalDistC(unittest.TestCase, TestNormalDist):
def load_tests(loader, tests, ignore):
"""Used for doctest/unittest integration."""
tests.addTests(doctest.DocTestSuite())
- tests.addTests(doctest.DocTestSuite(statistics))
+ if sys.float_repr_style == 'short':
+ tests.addTests(doctest.DocTestSuite(statistics))
return tests
diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py
index d6a7bd0da59..2584fbf72d3 100644
--- a/Lib/test/test_str.py
+++ b/Lib/test/test_str.py
@@ -1231,10 +1231,10 @@ class StrTest(string_tests.StringLikeTest,
self.assertEqual('{0:\x00^6}'.format(3), '\x00\x003\x00\x00\x00')
self.assertEqual('{0:<6}'.format(3), '3 ')
- self.assertEqual('{0:\x00<6}'.format(3.14), '3.14\x00\x00')
- self.assertEqual('{0:\x01<6}'.format(3.14), '3.14\x01\x01')
- self.assertEqual('{0:\x00^6}'.format(3.14), '\x003.14\x00')
- self.assertEqual('{0:^6}'.format(3.14), ' 3.14 ')
+ self.assertEqual('{0:\x00<6}'.format(3.25), '3.25\x00\x00')
+ self.assertEqual('{0:\x01<6}'.format(3.25), '3.25\x01\x01')
+ self.assertEqual('{0:\x00^6}'.format(3.25), '\x003.25\x00')
+ self.assertEqual('{0:^6}'.format(3.25), ' 3.25 ')
self.assertEqual('{0:\x00<12}'.format(3+2.0j), '(3+2j)\x00\x00\x00\x00\x00\x00')
self.assertEqual('{0:\x01<12}'.format(3+2.0j), '(3+2j)\x01\x01\x01\x01\x01\x01')
diff --git a/Lib/test/test_strftime.py b/Lib/test/test_strftime.py
index 752e31359cf..375f6aaedd8 100644
--- a/Lib/test/test_strftime.py
+++ b/Lib/test/test_strftime.py
@@ -39,7 +39,21 @@ class StrftimeTest(unittest.TestCase):
if now[3] < 12: self.ampm='(AM|am)'
else: self.ampm='(PM|pm)'
- self.jan1 = time.localtime(time.mktime((now[0], 1, 1, 0, 0, 0, 0, 1, 0)))
+ jan1 = time.struct_time(
+ (
+ now.tm_year, # Year
+ 1, # Month (January)
+ 1, # Day (1st)
+ 0, # Hour (0)
+ 0, # Minute (0)
+ 0, # Second (0)
+ -1, # tm_wday (will be determined)
+ 1, # tm_yday (day 1 of the year)
+ -1, # tm_isdst (let the system determine)
+ )
+ )
+ # use mktime to get the correct tm_wday and tm_isdst values
+ self.jan1 = time.localtime(time.mktime(jan1))
try:
if now[8]: self.tz = time.tzname[1]
diff --git a/Lib/test/test_string/_support.py b/Lib/test/test_string/_support.py
index eaa3354a559..abdddaf187b 100644
--- a/Lib/test/test_string/_support.py
+++ b/Lib/test/test_string/_support.py
@@ -1,4 +1,3 @@
-import unittest
from string.templatelib import Interpolation
diff --git a/Lib/test/test_string/test_string.py b/Lib/test/test_string/test_string.py
index f6d112d8a93..5394fe4e12c 100644
--- a/Lib/test/test_string/test_string.py
+++ b/Lib/test/test_string/test_string.py
@@ -2,6 +2,14 @@ import unittest
import string
from string import Template
import types
+from test.support import cpython_only
+from test.support.import_helper import ensure_lazy_imports
+
+
+class LazyImportTest(unittest.TestCase):
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("base64", {"re", "collections"})
class ModuleTest(unittest.TestCase):
diff --git a/Lib/test/test_string/test_templatelib.py b/Lib/test/test_string/test_templatelib.py
index 5b9490c2be6..85fcff486d6 100644
--- a/Lib/test/test_string/test_templatelib.py
+++ b/Lib/test/test_string/test_templatelib.py
@@ -148,6 +148,13 @@ class TemplateIterTests(unittest.TestCase):
self.assertEqual(res[1].format_spec, '')
self.assertEqual(res[2], ' yz')
+ def test_exhausted(self):
+ # See https://github.com/python/cpython/issues/134119.
+ template_iter = iter(t"{1}")
+ self.assertIsInstance(next(template_iter), Interpolation)
+ self.assertRaises(StopIteration, next, template_iter)
+ self.assertRaises(StopIteration, next, template_iter)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py
index 268230f6da7..0241e543cd7 100644
--- a/Lib/test/test_strptime.py
+++ b/Lib/test/test_strptime.py
@@ -221,14 +221,16 @@ class StrptimeTests(unittest.TestCase):
self.assertRaises(ValueError, _strptime._strptime_time, data_string="%d",
format="%A")
for bad_format in ("%", "% ", "%\n"):
- with self.assertRaisesRegex(ValueError, "stray % in format "):
+ with (self.subTest(format=bad_format),
+ self.assertRaisesRegex(ValueError, "stray % in format ")):
_strptime._strptime_time("2005", bad_format)
- for bad_format in ("%e", "%Oe", "%O", "%O ", "%Ee", "%E", "%E ",
- "%.", "%+", "%_", "%~", "%\\",
+ for bad_format in ("%i", "%Oi", "%O", "%O ", "%Ee", "%E", "%E ",
+ "%.", "%+", "%~", "%\\",
"%O.", "%O+", "%O_", "%O~", "%O\\"):
directive = bad_format[1:].rstrip()
- with self.assertRaisesRegex(ValueError,
- f"'{re.escape(directive)}' is a bad directive in format "):
+ with (self.subTest(format=bad_format),
+ self.assertRaisesRegex(ValueError,
+ f"'{re.escape(directive)}' is a bad directive in format ")):
_strptime._strptime_time("2005", bad_format)
msg_week_no_year_or_weekday = r"ISO week directive '%V' must be used with " \
@@ -335,6 +337,15 @@ class StrptimeTests(unittest.TestCase):
self.roundtrip('%B', 1, (1900, m, 1, 0, 0, 0, 0, 1, 0))
self.roundtrip('%b', 1, (1900, m, 1, 0, 0, 0, 0, 1, 0))
+ @run_with_locales('LC_TIME', 'az_AZ', 'ber_DZ', 'ber_MA', 'crh_UA')
+ def test_month_locale2(self):
+ # Test for month directives
+ # Month name contains 'İ' ('\u0130')
+ self.roundtrip('%B', 1, (2025, 6, 1, 0, 0, 0, 6, 152, 0))
+ self.roundtrip('%b', 1, (2025, 6, 1, 0, 0, 0, 6, 152, 0))
+ self.roundtrip('%B', 1, (2025, 7, 1, 0, 0, 0, 1, 182, 0))
+ self.roundtrip('%b', 1, (2025, 7, 1, 0, 0, 0, 1, 182, 0))
+
def test_day(self):
# Test for day directives
self.roundtrip('%d %Y', 2)
@@ -480,13 +491,11 @@ class StrptimeTests(unittest.TestCase):
# * Year is not included: ha_NG.
# * Use non-Gregorian calendar: lo_LA, thai, th_TH.
# On Windows: ar_IN, ar_SA, fa_IR, ps_AF.
- #
- # BUG: Generates regexp that does not match the current date and time
- # for lzh_TW.
@run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP',
'he_IL', 'eu_ES', 'ar_AE', 'mfe_MU', 'yo_NG',
'csb_PL', 'br_FR', 'gez_ET', 'brx_IN',
- 'my_MM', 'or_IN', 'shn_MM', 'az_IR')
+ 'my_MM', 'or_IN', 'shn_MM', 'az_IR',
+ 'byn_ER', 'wal_ET', 'lzh_TW')
def test_date_time_locale(self):
# Test %c directive
loc = locale.getlocale(locale.LC_TIME)[0]
@@ -525,11 +534,9 @@ class StrptimeTests(unittest.TestCase):
# NB: Does not roundtrip because use non-Gregorian calendar:
# lo_LA, thai, th_TH. On Windows: ar_IN, ar_SA, fa_IR, ps_AF.
- # BUG: Generates regexp that does not match the current date
- # for lzh_TW.
@run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP',
'he_IL', 'eu_ES', 'ar_AE',
- 'az_IR', 'my_MM', 'or_IN', 'shn_MM')
+ 'az_IR', 'my_MM', 'or_IN', 'shn_MM', 'lzh_TW')
def test_date_locale(self):
# Test %x directive
now = time.time()
@@ -546,7 +553,7 @@ class StrptimeTests(unittest.TestCase):
# NB: Dates before 1969 do not roundtrip on many locales, including C.
@unittest.skipIf(support.linked_to_musl(), "musl libc issue, bpo-46390")
@run_with_locales('LC_TIME', 'en_US', 'fr_FR', 'de_DE', 'ja_JP',
- 'eu_ES', 'ar_AE', 'my_MM', 'shn_MM')
+ 'eu_ES', 'ar_AE', 'my_MM', 'shn_MM', 'lzh_TW')
def test_date_locale2(self):
# Test %x directive
loc = locale.getlocale(locale.LC_TIME)[0]
@@ -562,11 +569,11 @@ class StrptimeTests(unittest.TestCase):
# norwegian, nynorsk.
# * Hours are in 12-hour notation without AM/PM indication: hy_AM,
# ms_MY, sm_WS.
- # BUG: Generates regexp that does not match the current time for lzh_TW.
@run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP',
'aa_ET', 'am_ET', 'az_IR', 'byn_ER', 'fa_IR', 'gez_ET',
'my_MM', 'om_ET', 'or_IN', 'shn_MM', 'sid_ET', 'so_SO',
- 'ti_ET', 'tig_ER', 'wal_ET')
+ 'ti_ET', 'tig_ER', 'wal_ET', 'lzh_TW',
+ 'ar_SA', 'bg_BG')
def test_time_locale(self):
# Test %X directive
loc = locale.getlocale(locale.LC_TIME)[0]
diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py
index d0bc0bd7b61..9622151143c 100644
--- a/Lib/test/test_structseq.py
+++ b/Lib/test/test_structseq.py
@@ -42,7 +42,7 @@ class StructSeqTest(unittest.TestCase):
# os.stat() gives a complicated struct sequence.
st = os.stat(__file__)
rep = repr(st)
- self.assertTrue(rep.startswith("os.stat_result"))
+ self.assertStartsWith(rep, "os.stat_result")
self.assertIn("st_mode=", rep)
self.assertIn("st_ino=", rep)
self.assertIn("st_dev=", rep)
@@ -307,7 +307,7 @@ class StructSeqTest(unittest.TestCase):
self.assertEqual(t5.tm_mon, 2)
# named invisible fields
- self.assertTrue(hasattr(t, 'tm_zone'), f"{t} has no attribute 'tm_zone'")
+ self.assertHasAttr(t, 'tm_zone')
with self.assertRaisesRegex(AttributeError, 'readonly attribute'):
t.tm_zone = 'some other zone'
self.assertEqual(t2.tm_zone, t.tm_zone)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 3cb755cd56c..f0e350c71f6 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -162,6 +162,20 @@ class ProcessTestCase(BaseTestCase):
[sys.executable, "-c", "while True: pass"],
timeout=0.1)
+ def test_timeout_exception(self):
+ try:
+ subprocess.run([sys.executable, '-c', 'import time;time.sleep(9)'], timeout = -1)
+ except subprocess.TimeoutExpired as e:
+ self.assertIn("-1 seconds", str(e))
+ else:
+ self.fail("Expected TimeoutExpired exception not raised")
+ try:
+ subprocess.run([sys.executable, '-c', 'import time;time.sleep(9)'], timeout = 0)
+ except subprocess.TimeoutExpired as e:
+ self.assertIn("0 seconds", str(e))
+ else:
+ self.fail("Expected TimeoutExpired exception not raised")
+
def test_check_call_zero(self):
# check_call() function with zero return code
rc = subprocess.check_call(ZERO_RETURN_CMD)
@@ -1164,7 +1178,7 @@ class ProcessTestCase(BaseTestCase):
self.assertEqual("line1\nline2\nline3\nline4\nline5\n", stdout)
# Python debug build push something like "[42442 refs]\n"
# to stderr at exit of subprocess.
- self.assertTrue(stderr.startswith("eline2\neline6\neline7\n"))
+ self.assertStartsWith(stderr, "eline2\neline6\neline7\n")
def test_universal_newlines_communicate_encodings(self):
# Check that universal newlines mode works for various encodings,
@@ -1496,7 +1510,7 @@ class ProcessTestCase(BaseTestCase):
"[sys.executable, '-c', 'print(\"Hello World!\")'])",
'assert retcode == 0'))
output = subprocess.check_output([sys.executable, '-c', code])
- self.assertTrue(output.startswith(b'Hello World!'), ascii(output))
+ self.assertStartsWith(output, b'Hello World!')
def test_handles_closed_on_exception(self):
# If CreateProcess exits with an error, ensure the
@@ -1821,8 +1835,8 @@ class RunFuncTestCase(BaseTestCase):
capture_output=True)
lines = cp.stderr.splitlines()
self.assertEqual(len(lines), 2, lines)
- self.assertTrue(lines[0].startswith(b"<string>:2: EncodingWarning: "))
- self.assertTrue(lines[1].startswith(b"<string>:3: EncodingWarning: "))
+ self.assertStartsWith(lines[0], b"<string>:2: EncodingWarning: ")
+ self.assertStartsWith(lines[1], b"<string>:3: EncodingWarning: ")
def _get_test_grp_name():
diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py
index 5cef612a340..193c8b7d7f3 100644
--- a/Lib/test/test_super.py
+++ b/Lib/test/test_super.py
@@ -547,11 +547,11 @@ class TestSuper(unittest.TestCase):
self.assertEqual(s.__reduce__, e.__reduce__)
self.assertEqual(s.__reduce_ex__, e.__reduce_ex__)
self.assertEqual(s.__getstate__, e.__getstate__)
- self.assertFalse(hasattr(s, '__getnewargs__'))
- self.assertFalse(hasattr(s, '__getnewargs_ex__'))
- self.assertFalse(hasattr(s, '__setstate__'))
- self.assertFalse(hasattr(s, '__copy__'))
- self.assertFalse(hasattr(s, '__deepcopy__'))
+ self.assertNotHasAttr(s, '__getnewargs__')
+ self.assertNotHasAttr(s, '__getnewargs_ex__')
+ self.assertNotHasAttr(s, '__setstate__')
+ self.assertNotHasAttr(s, '__copy__')
+ self.assertNotHasAttr(s, '__deepcopy__')
def test_pickling(self):
e = E()
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index 468bac82924..e48a2464ee5 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -407,10 +407,10 @@ class TestSupport(unittest.TestCase):
with support.swap_attr(obj, "y", 5) as y:
self.assertEqual(obj.y, 5)
self.assertIsNone(y)
- self.assertFalse(hasattr(obj, 'y'))
+ self.assertNotHasAttr(obj, 'y')
with support.swap_attr(obj, "y", 5):
del obj.y
- self.assertFalse(hasattr(obj, 'y'))
+ self.assertNotHasAttr(obj, 'y')
def test_swap_item(self):
D = {"x":1}
@@ -561,6 +561,7 @@ class TestSupport(unittest.TestCase):
['-Wignore', '-X', 'dev'],
['-X', 'faulthandler'],
['-X', 'importtime'],
+ ['-X', 'importtime=2'],
['-X', 'showrefcount'],
['-X', 'tracemalloc'],
['-X', 'tracemalloc=3'],
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 0ee17849e28..c52d2421941 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -382,6 +382,13 @@ SyntaxError: invalid syntax
Traceback (most recent call last):
SyntaxError: invalid syntax
+# But prefixes of soft keywords should
+# still raise specialized errors
+
+>>> (mat x)
+Traceback (most recent call last):
+SyntaxError: invalid syntax. Perhaps you forgot a comma?
+
From compiler_complex_args():
>>> def f(None=1):
@@ -419,7 +426,7 @@ SyntaxError: invalid syntax
>>> def foo(/,a,b=,c):
... pass
Traceback (most recent call last):
-SyntaxError: at least one argument must precede /
+SyntaxError: at least one parameter must precede /
>>> def foo(a,/,/,b,c):
... pass
@@ -454,67 +461,67 @@ SyntaxError: / must be ahead of *
>>> def foo(a,*b=3,c):
... pass
Traceback (most recent call last):
-SyntaxError: var-positional argument cannot have default value
+SyntaxError: var-positional parameter cannot have default value
>>> def foo(a,*b: int=,c):
... pass
Traceback (most recent call last):
-SyntaxError: var-positional argument cannot have default value
+SyntaxError: var-positional parameter cannot have default value
>>> def foo(a,**b=3):
... pass
Traceback (most recent call last):
-SyntaxError: var-keyword argument cannot have default value
+SyntaxError: var-keyword parameter cannot have default value
>>> def foo(a,**b: int=3):
... pass
Traceback (most recent call last):
-SyntaxError: var-keyword argument cannot have default value
+SyntaxError: var-keyword parameter cannot have default value
>>> def foo(a,*a, b, **c, d):
... pass
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> def foo(a,*a, b, **c, d=4):
... pass
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> def foo(a,*a, b, **c, *d):
... pass
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> def foo(a,*a, b, **c, **d):
... pass
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> def foo(a=1,/,**b,/,c):
... pass
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> def foo(*b,*d):
... pass
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> def foo(a,*b,c,*d,*e,c):
... pass
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> def foo(a,b,/,c,*b,c,*d,*e,c):
... pass
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> def foo(a,b,/,c,*b,c,*d,**e):
... pass
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> def foo(a=1,/*,b,c):
... pass
@@ -538,7 +545,7 @@ SyntaxError: expected default value expression
>>> lambda /,a,b,c: None
Traceback (most recent call last):
-SyntaxError: at least one argument must precede /
+SyntaxError: at least one parameter must precede /
>>> lambda a,/,/,b,c: None
Traceback (most recent call last):
@@ -570,47 +577,47 @@ SyntaxError: expected comma between / and *
>>> lambda a,*b=3,c: None
Traceback (most recent call last):
-SyntaxError: var-positional argument cannot have default value
+SyntaxError: var-positional parameter cannot have default value
>>> lambda a,**b=3: None
Traceback (most recent call last):
-SyntaxError: var-keyword argument cannot have default value
+SyntaxError: var-keyword parameter cannot have default value
>>> lambda a, *a, b, **c, d: None
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> lambda a,*a, b, **c, d=4: None
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> lambda a,*a, b, **c, *d: None
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> lambda a,*a, b, **c, **d: None
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> lambda a=1,/,**b,/,c: None
Traceback (most recent call last):
-SyntaxError: arguments cannot follow var-keyword argument
+SyntaxError: parameters cannot follow var-keyword parameter
>>> lambda *b,*d: None
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> lambda a,*b,c,*d,*e,c: None
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> lambda a,b,/,c,*b,c,*d,*e,c: None
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> lambda a,b,/,c,*b,c,*d,**e: None
Traceback (most recent call last):
-SyntaxError: * argument may appear only once
+SyntaxError: * may appear only once
>>> lambda a=1,d=,c: None
Traceback (most recent call last):
@@ -1304,7 +1311,7 @@ Missing parens after function definition
Traceback (most recent call last):
SyntaxError: expected '('
-Parenthesized arguments in function definitions
+Parenthesized parameters in function definitions
>>> def f(x, (y, z), w):
... pass
@@ -1431,6 +1438,23 @@ Better error message for using `except as` with not a name:
Traceback (most recent call last):
SyntaxError: cannot use except* statement with literal
+Regression tests for gh-133999:
+
+ >>> try: pass
+ ... except TypeError as name: raise from None
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression between 'raise' and 'from'?
+
+ >>> try: pass
+ ... except* TypeError as name: raise from None
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression between 'raise' and 'from'?
+
+ >>> match 1:
+ ... case 1 | 2 as abc: raise from None
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression between 'raise' and 'from'?
+
Ensure that early = are not matched by the parser as invalid comparisons
>>> f(2, 4, x=34); 1 $ 2
Traceback (most recent call last):
@@ -1678,6 +1702,28 @@ Make sure that the old "raise X, Y[, Z]" form is gone:
...
SyntaxError: invalid syntax
+Better errors for `raise` statement:
+
+ >>> raise ValueError from
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression after 'from'?
+
+ >>> raise mod.ValueError() from
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression after 'from'?
+
+ >>> raise from exc
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression between 'raise' and 'from'?
+
+ >>> raise from None
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression between 'raise' and 'from'?
+
+ >>> raise from
+ Traceback (most recent call last):
+ SyntaxError: did you forget an expression between 'raise' and 'from'?
+
Check that an multiple exception types with missing parentheses
raise a custom exception only when using 'as'
@@ -2178,7 +2224,7 @@ Corner-cases that used to fail to raise the correct error:
>>> with (lambda *:0): pass
Traceback (most recent call last):
- SyntaxError: named arguments must follow bare *
+ SyntaxError: named parameters must follow bare *
Corner-cases that used to crash:
@@ -2826,6 +2872,13 @@ class SyntaxErrorTestCase(unittest.TestCase):
"""
self._check_error(source, "parameter and nonlocal", lineno=3)
+ def test_raise_from_error_message(self):
+ source = """if 1:
+ raise AssertionError() from None
+ print(1,,2)
+ """
+ self._check_error(source, "invalid syntax", lineno=3)
+
def test_yield_outside_function(self):
self._check_error("if 0: yield", "outside function")
self._check_error("if 0: yield\nelse: x=1", "outside function")
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 56413d00823..486bf10a0b5 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -24,7 +24,7 @@ from test.support import import_helper
from test.support import force_not_colorized
from test.support import SHORT_TIMEOUT
try:
- from test.support import interpreters
+ from concurrent import interpreters
except ImportError:
interpreters = None
import textwrap
@@ -57,7 +57,7 @@ class DisplayHookTest(unittest.TestCase):
dh(None)
self.assertEqual(out.getvalue(), "")
- self.assertTrue(not hasattr(builtins, "_"))
+ self.assertNotHasAttr(builtins, "_")
# sys.displayhook() requires arguments
self.assertRaises(TypeError, dh)
@@ -172,7 +172,7 @@ class ExceptHookTest(unittest.TestCase):
with support.captured_stderr() as err:
sys.__excepthook__(*sys.exc_info())
- self.assertTrue(err.getvalue().endswith("ValueError: 42\n"))
+ self.assertEndsWith(err.getvalue(), "ValueError: 42\n")
self.assertRaises(TypeError, sys.__excepthook__)
@@ -192,7 +192,7 @@ class ExceptHookTest(unittest.TestCase):
err = err.getvalue()
self.assertIn(""" File "b'bytes_filename'", line 123\n""", err)
self.assertIn(""" text\n""", err)
- self.assertTrue(err.endswith("SyntaxError: msg\n"))
+ self.assertEndsWith(err, "SyntaxError: msg\n")
def test_excepthook(self):
with test.support.captured_output("stderr") as stderr:
@@ -269,8 +269,7 @@ class SysModuleTest(unittest.TestCase):
rc, out, err = assert_python_failure('-c', code, **env_vars)
self.assertEqual(rc, 1)
self.assertEqual(out, b'')
- self.assertTrue(err.startswith(expected),
- "%s doesn't start with %s" % (ascii(err), ascii(expected)))
+ self.assertStartsWith(err, expected)
# test that stderr buffer is flushed before the exit message is written
# into stderr
@@ -437,7 +436,7 @@ class SysModuleTest(unittest.TestCase):
@unittest.skipUnless(hasattr(sys, "setdlopenflags"),
'test needs sys.setdlopenflags()')
def test_dlopenflags(self):
- self.assertTrue(hasattr(sys, "getdlopenflags"))
+ self.assertHasAttr(sys, "getdlopenflags")
self.assertRaises(TypeError, sys.getdlopenflags, 42)
oldflags = sys.getdlopenflags()
self.assertRaises(TypeError, sys.setdlopenflags)
@@ -623,8 +622,7 @@ class SysModuleTest(unittest.TestCase):
# And the next record must be for g456().
filename, lineno, funcname, sourceline = stack[i+1]
self.assertEqual(funcname, "g456")
- self.assertTrue((sourceline.startswith("if leave_g.wait(") or
- sourceline.startswith("g_raised.set()")))
+ self.assertStartsWith(sourceline, ("if leave_g.wait(", "g_raised.set()"))
finally:
# Reap the spawned thread.
leave_g.set()
@@ -731,7 +729,7 @@ class SysModuleTest(unittest.TestCase):
info = sys.thread_info
self.assertEqual(len(info), 3)
self.assertIn(info.name, ('nt', 'pthread', 'pthread-stubs', 'solaris', None))
- self.assertIn(info.lock, ('semaphore', 'mutex+cond', None))
+ self.assertIn(info.lock, ('pymutex', None))
if sys.platform.startswith(("linux", "android", "freebsd")):
self.assertEqual(info.name, "pthread")
elif sys.platform == "win32":
@@ -860,7 +858,7 @@ class SysModuleTest(unittest.TestCase):
"hash_randomization", "isolated", "dev_mode", "utf8_mode",
"warn_default_encoding", "safe_path", "int_max_str_digits")
for attr in attrs:
- self.assertTrue(hasattr(sys.flags, attr), attr)
+ self.assertHasAttr(sys.flags, attr)
attr_type = bool if attr in ("dev_mode", "safe_path") else int
self.assertEqual(type(getattr(sys.flags, attr)), attr_type, attr)
self.assertTrue(repr(sys.flags))
@@ -871,12 +869,7 @@ class SysModuleTest(unittest.TestCase):
def assert_raise_on_new_sys_type(self, sys_attr):
# Users are intentionally prevented from creating new instances of
# sys.flags, sys.version_info, and sys.getwindowsversion.
- arg = sys_attr
- attr_type = type(sys_attr)
- with self.assertRaises(TypeError):
- attr_type(arg)
- with self.assertRaises(TypeError):
- attr_type.__new__(attr_type, arg)
+ support.check_disallow_instantiation(self, type(sys_attr), sys_attr)
def test_sys_flags_no_instantiation(self):
self.assert_raise_on_new_sys_type(sys.flags)
@@ -1072,10 +1065,11 @@ class SysModuleTest(unittest.TestCase):
levels = {'alpha': 0xA, 'beta': 0xB, 'candidate': 0xC, 'final': 0xF}
- self.assertTrue(hasattr(sys.implementation, 'name'))
- self.assertTrue(hasattr(sys.implementation, 'version'))
- self.assertTrue(hasattr(sys.implementation, 'hexversion'))
- self.assertTrue(hasattr(sys.implementation, 'cache_tag'))
+ self.assertHasAttr(sys.implementation, 'name')
+ self.assertHasAttr(sys.implementation, 'version')
+ self.assertHasAttr(sys.implementation, 'hexversion')
+ self.assertHasAttr(sys.implementation, 'cache_tag')
+ self.assertHasAttr(sys.implementation, 'supports_isolated_interpreters')
version = sys.implementation.version
self.assertEqual(version[:2], (version.major, version.minor))
@@ -1089,6 +1083,15 @@ class SysModuleTest(unittest.TestCase):
self.assertEqual(sys.implementation.name,
sys.implementation.name.lower())
+ # https://peps.python.org/pep-0734
+ sii = sys.implementation.supports_isolated_interpreters
+ self.assertIsInstance(sii, bool)
+ if test.support.check_impl_detail(cpython=True):
+ if test.support.is_emscripten or test.support.is_wasi:
+ self.assertFalse(sii)
+ else:
+ self.assertTrue(sii)
+
@test.support.cpython_only
def test_debugmallocstats(self):
# Test sys._debugmallocstats()
@@ -1137,23 +1140,12 @@ class SysModuleTest(unittest.TestCase):
b = sys.getallocatedblocks()
self.assertLessEqual(b, a)
try:
- # While we could imagine a Python session where the number of
- # multiple buffer objects would exceed the sharing of references,
- # it is unlikely to happen in a normal test run.
- #
- # In free-threaded builds each code object owns an array of
- # pointers to copies of the bytecode. When the number of
- # code objects is a large fraction of the total number of
- # references, this can cause the total number of allocated
- # blocks to exceed the total number of references.
- #
- # For some reason, iOS seems to trigger the "unlikely to happen"
- # case reliably under CI conditions. It's not clear why; but as
- # this test is checking the behavior of getallocatedblock()
- # under garbage collection, we can skip this pre-condition check
- # for now. See GH-130384.
- if not support.Py_GIL_DISABLED and not support.is_apple_mobile:
- self.assertLess(a, sys.gettotalrefcount())
+ # The reported blocks will include immortalized strings, but the
+ # total ref count will not. This will sanity check that among all
+ # other objects (those eligible for garbage collection) there
+ # are more references being tracked than allocated blocks.
+ interned_immortal = sys.getunicodeinternedsize(_only_immortal=True)
+ self.assertLess(a - interned_immortal, sys.gettotalrefcount())
except AttributeError:
# gettotalrefcount() not available
pass
@@ -1301,6 +1293,7 @@ class SysModuleTest(unittest.TestCase):
for name in sys.stdlib_module_names:
self.assertIsInstance(name, str)
+ @unittest.skipUnless(hasattr(sys, '_stdlib_dir'), 'need sys._stdlib_dir')
def test_stdlib_dir(self):
os = import_helper.import_fresh_module('os')
marker = getattr(os, '__file__', None)
@@ -1419,7 +1412,7 @@ class UnraisableHookTest(unittest.TestCase):
else:
self.assertIn("ValueError", report)
self.assertIn("del is broken", report)
- self.assertTrue(report.endswith("\n"))
+ self.assertEndsWith(report, "\n")
def test_original_unraisablehook_exception_qualname(self):
# See bpo-41031, bpo-45083.
@@ -1955,33 +1948,19 @@ class SizeofTest(unittest.TestCase):
self.assertEqual(out, b"")
self.assertEqual(err, b"")
-
-def _supports_remote_attaching():
- PROCESS_VM_READV_SUPPORTED = False
-
- try:
- from _testexternalinspection import PROCESS_VM_READV_SUPPORTED
- except ImportError:
- pass
-
- return PROCESS_VM_READV_SUPPORTED
-
-@unittest.skipIf(not sys.is_remote_debug_enabled(), "Remote debugging is not enabled")
-@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux" and sys.platform != "win32",
- "Test only runs on Linux, Windows and MacOS")
-@unittest.skipIf(sys.platform == "linux" and not _supports_remote_attaching(),
- "Test only runs on Linux with process_vm_readv support")
+@test.support.support_remote_exec_only
@test.support.cpython_only
class TestRemoteExec(unittest.TestCase):
def tearDown(self):
test.support.reap_children()
- def _run_remote_exec_test(self, script_code, python_args=None, env=None, prologue=''):
+ def _run_remote_exec_test(self, script_code, python_args=None, env=None,
+ prologue='',
+ script_path=os_helper.TESTFN + '_remote.py'):
# Create the script that will be remotely executed
- script = os_helper.TESTFN + '_remote.py'
- self.addCleanup(os_helper.unlink, script)
+ self.addCleanup(os_helper.unlink, script_path)
- with open(script, 'w') as f:
+ with open(script_path, 'w') as f:
f.write(script_code)
# Create and run the target process
@@ -2050,7 +2029,7 @@ sock.close()
self.assertEqual(response, b"ready")
# Try remote exec on the target process
- sys.remote_exec(proc.pid, script)
+ sys.remote_exec(proc.pid, script_path)
# Signal script to continue
client_socket.sendall(b"continue")
@@ -2073,14 +2052,32 @@ sock.close()
def test_remote_exec(self):
"""Test basic remote exec functionality"""
- script = '''
-print("Remote script executed successfully!")
-'''
+ script = 'print("Remote script executed successfully!")'
returncode, stdout, stderr = self._run_remote_exec_test(script)
# self.assertEqual(returncode, 0)
self.assertIn(b"Remote script executed successfully!", stdout)
self.assertEqual(stderr, b"")
+ def test_remote_exec_bytes(self):
+ script = 'print("Remote script executed successfully!")'
+ script_path = os.fsencode(os_helper.TESTFN) + b'_bytes_remote.py'
+ returncode, stdout, stderr = self._run_remote_exec_test(script,
+ script_path=script_path)
+ self.assertIn(b"Remote script executed successfully!", stdout)
+ self.assertEqual(stderr, b"")
+
+ @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, 'requires undecodable path')
+ @unittest.skipIf(sys.platform == 'darwin',
+ 'undecodable paths are not supported on macOS')
+ def test_remote_exec_undecodable(self):
+ script = 'print("Remote script executed successfully!")'
+ script_path = os_helper.TESTFN_UNDECODABLE + b'_undecodable_remote.py'
+ for script_path in [script_path, os.fsdecode(script_path)]:
+ returncode, stdout, stderr = self._run_remote_exec_test(script,
+ script_path=script_path)
+ self.assertIn(b"Remote script executed successfully!", stdout)
+ self.assertEqual(stderr, b"")
+
def test_remote_exec_with_self_process(self):
"""Test remote exec with the target process being the same as the test process"""
@@ -2101,7 +2098,7 @@ print("Remote script executed successfully!")
prologue = '''\
import sys
def audit_hook(event, arg):
- print(f"Audit event: {event}, arg: {arg}")
+ print(f"Audit event: {event}, arg: {arg}".encode("ascii", errors="replace"))
sys.addaudithook(audit_hook)
'''
script = '''
@@ -2110,7 +2107,7 @@ print("Remote script executed successfully!")
returncode, stdout, stderr = self._run_remote_exec_test(script, prologue=prologue)
self.assertEqual(returncode, 0)
self.assertIn(b"Remote script executed successfully!", stdout)
- self.assertIn(b"Audit event: remote_debugger_script, arg: ", stdout)
+ self.assertIn(b"Audit event: cpython.remote_debugger_script, arg: ", stdout)
self.assertEqual(stderr, b"")
def test_remote_exec_with_exception(self):
@@ -2157,6 +2154,13 @@ raise Exception("Remote script exception")
with self.assertRaises(OSError):
sys.remote_exec(99999, "print('should not run')")
+ def test_remote_exec_invalid_script(self):
+ """Test remote exec with invalid script type"""
+ with self.assertRaises(TypeError):
+ sys.remote_exec(0, None)
+ with self.assertRaises(TypeError):
+ sys.remote_exec(0, 123)
+
def test_remote_exec_syntax_error(self):
"""Test remote exec with syntax error in script"""
script = '''
@@ -2196,6 +2200,64 @@ this is invalid python code
self.assertIn(b"Remote debugging is not enabled", err)
self.assertEqual(out, b"")
+class TestSysJIT(unittest.TestCase):
+
+ def test_jit_is_available(self):
+ available = sys._jit.is_available()
+ script = f"import sys; assert sys._jit.is_available() is {available}"
+ assert_python_ok("-c", script, PYTHON_JIT="0")
+ assert_python_ok("-c", script, PYTHON_JIT="1")
+
+ def test_jit_is_enabled(self):
+ available = sys._jit.is_available()
+ script = "import sys; assert sys._jit.is_enabled() is {enabled}"
+ assert_python_ok("-c", script.format(enabled=False), PYTHON_JIT="0")
+ assert_python_ok("-c", script.format(enabled=available), PYTHON_JIT="1")
+
+ def test_jit_is_active(self):
+ available = sys._jit.is_available()
+ script = textwrap.dedent(
+ """
+ import _testcapi
+ import _testinternalcapi
+ import sys
+
+ def frame_0_interpreter() -> None:
+ assert sys._jit.is_active() is False
+
+ def frame_1_interpreter() -> None:
+ assert sys._jit.is_active() is False
+ frame_0_interpreter()
+ assert sys._jit.is_active() is False
+
+ def frame_2_jit(expected: bool) -> None:
+ # Inlined into the last loop of frame_3_jit:
+ assert sys._jit.is_active() is expected
+ # Insert C frame:
+ _testcapi.pyobject_vectorcall(frame_1_interpreter, None, None)
+ assert sys._jit.is_active() is expected
+
+ def frame_3_jit() -> None:
+ # JITs just before the last loop:
+ for i in range(_testinternalcapi.TIER2_THRESHOLD + 1):
+ # Careful, doing this in the reverse order breaks tracing:
+ expected = {enabled} and i == _testinternalcapi.TIER2_THRESHOLD
+ assert sys._jit.is_active() is expected
+ frame_2_jit(expected)
+ assert sys._jit.is_active() is expected
+
+ def frame_4_interpreter() -> None:
+ assert sys._jit.is_active() is False
+ frame_3_jit()
+ assert sys._jit.is_active() is False
+
+ assert sys._jit.is_active() is False
+ frame_4_interpreter()
+ assert sys._jit.is_active() is False
+ """
+ )
+ assert_python_ok("-c", script.format(enabled=False), PYTHON_JIT="0")
+ assert_python_ok("-c", script.format(enabled=available), PYTHON_JIT="1")
if __name__ == "__main__":
diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
index 53e55383bf9..2eb8de4b29f 100644
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -32,7 +32,6 @@ from sysconfig import (get_paths, get_platform, get_config_vars,
from sysconfig.__main__ import _main, _parse_makefile, _get_pybuilddir, _get_json_data_name
import _imp
import _osx_support
-import _sysconfig
HAS_USER_BASE = sysconfig._HAS_USER_BASE
@@ -186,7 +185,7 @@ class TestSysConfig(unittest.TestCase, VirtualEnvironmentMixin):
# The include directory on POSIX isn't exactly the same as before,
# but it is "within"
sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv', vars=vars)
- self.assertTrue(sysconfig_includedir.startswith(incpath + os.sep))
+ self.assertStartsWith(sysconfig_includedir, incpath + os.sep)
def test_nt_venv_scheme(self):
# The following directories were hardcoded in the venv module
@@ -531,13 +530,10 @@ class TestSysConfig(unittest.TestCase, VirtualEnvironmentMixin):
Python_h = os.path.join(srcdir, 'Include', 'Python.h')
self.assertTrue(os.path.exists(Python_h), Python_h)
# <srcdir>/PC/pyconfig.h.in always exists even if unused
- pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h.in')
- self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h)
pyconfig_h_in = os.path.join(srcdir, 'pyconfig.h.in')
self.assertTrue(os.path.exists(pyconfig_h_in), pyconfig_h_in)
if os.name == 'nt':
- # <executable dir>/pyconfig.h exists on Windows in a build tree
- pyconfig_h = os.path.join(sys.executable, '..', 'pyconfig.h')
+ pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h')
self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h)
elif os.name == 'posix':
makefile_dir = os.path.dirname(sysconfig.get_makefile_filename())
@@ -572,8 +568,7 @@ class TestSysConfig(unittest.TestCase, VirtualEnvironmentMixin):
expected_suffixes = 'i386-linux-gnu.so', 'x86_64-linux-gnux32.so', 'i386-linux-musl.so'
else: # 8 byte pointer size
expected_suffixes = 'x86_64-linux-gnu.so', 'x86_64-linux-musl.so'
- self.assertTrue(suffix.endswith(expected_suffixes),
- f'unexpected suffix {suffix!r}')
+ self.assertEndsWith(suffix, expected_suffixes)
@unittest.skipUnless(sys.platform == 'android', 'Android-specific test')
def test_android_ext_suffix(self):
@@ -585,13 +580,12 @@ class TestSysConfig(unittest.TestCase, VirtualEnvironmentMixin):
"aarch64": "aarch64-linux-android",
"armv7l": "arm-linux-androideabi",
}[machine]
- self.assertTrue(suffix.endswith(f"-{expected_triplet}.so"),
- f"{machine=}, {suffix=}")
+ self.assertEndsWith(suffix, f"-{expected_triplet}.so")
@unittest.skipUnless(sys.platform == 'darwin', 'OS X-specific test')
def test_osx_ext_suffix(self):
suffix = sysconfig.get_config_var('EXT_SUFFIX')
- self.assertTrue(suffix.endswith('-darwin.so'), suffix)
+ self.assertEndsWith(suffix, '-darwin.so')
def test_always_set_py_debug(self):
self.assertIn('Py_DEBUG', sysconfig.get_config_vars())
@@ -717,8 +711,8 @@ class TestSysConfig(unittest.TestCase, VirtualEnvironmentMixin):
ignore_keys |= {'prefix', 'exec_prefix', 'base', 'platbase', 'installed_base', 'installed_platbase'}
for key in ignore_keys:
- json_config_vars.pop(key)
- system_config_vars.pop(key)
+ json_config_vars.pop(key, None)
+ system_config_vars.pop(key, None)
self.assertEqual(system_config_vars, json_config_vars)
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index fcbaf854cc2..7055e1ed147 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -38,6 +38,10 @@ try:
import lzma
except ImportError:
lzma = None
+try:
+ from compression import zstd
+except ImportError:
+ zstd = None
def sha256sum(data):
return sha256(data).hexdigest()
@@ -48,6 +52,7 @@ tarname = support.findfile("testtar.tar", subdir="archivetestdata")
gzipname = os.path.join(TEMPDIR, "testtar.tar.gz")
bz2name = os.path.join(TEMPDIR, "testtar.tar.bz2")
xzname = os.path.join(TEMPDIR, "testtar.tar.xz")
+zstname = os.path.join(TEMPDIR, "testtar.tar.zst")
tmpname = os.path.join(TEMPDIR, "tmp.tar")
dotlessname = os.path.join(TEMPDIR, "testtar")
@@ -90,6 +95,12 @@ class LzmaTest:
open = lzma.LZMAFile if lzma else None
taropen = tarfile.TarFile.xzopen
+@support.requires_zstd()
+class ZstdTest:
+ tarname = zstname
+ suffix = 'zst'
+ open = zstd.ZstdFile if zstd else None
+ taropen = tarfile.TarFile.zstopen
class ReadTest(TarTest):
@@ -271,6 +282,8 @@ class Bz2UstarReadTest(Bz2Test, UstarReadTest):
class LzmaUstarReadTest(LzmaTest, UstarReadTest):
pass
+class ZstdUstarReadTest(ZstdTest, UstarReadTest):
+ pass
class ListTest(ReadTest, unittest.TestCase):
@@ -375,6 +388,8 @@ class Bz2ListTest(Bz2Test, ListTest):
class LzmaListTest(LzmaTest, ListTest):
pass
+class ZstdListTest(ZstdTest, ListTest):
+ pass
class CommonReadTest(ReadTest):
@@ -837,6 +852,8 @@ class Bz2MiscReadTest(Bz2Test, MiscReadTestBase, unittest.TestCase):
class LzmaMiscReadTest(LzmaTest, MiscReadTestBase, unittest.TestCase):
pass
+class ZstdMiscReadTest(ZstdTest, MiscReadTestBase, unittest.TestCase):
+ pass
class StreamReadTest(CommonReadTest, unittest.TestCase):
@@ -909,6 +926,9 @@ class Bz2StreamReadTest(Bz2Test, StreamReadTest):
class LzmaStreamReadTest(LzmaTest, StreamReadTest):
pass
+class ZstdStreamReadTest(ZstdTest, StreamReadTest):
+ pass
+
class TarStreamModeReadTest(StreamModeTest, unittest.TestCase):
def test_stream_mode_no_cache(self):
@@ -925,6 +945,9 @@ class Bz2StreamModeReadTest(Bz2Test, TarStreamModeReadTest):
class LzmaStreamModeReadTest(LzmaTest, TarStreamModeReadTest):
pass
+class ZstdStreamModeReadTest(ZstdTest, TarStreamModeReadTest):
+ pass
+
class DetectReadTest(TarTest, unittest.TestCase):
def _testfunc_file(self, name, mode):
try:
@@ -986,6 +1009,8 @@ class Bz2DetectReadTest(Bz2Test, DetectReadTest):
class LzmaDetectReadTest(LzmaTest, DetectReadTest):
pass
+class ZstdDetectReadTest(ZstdTest, DetectReadTest):
+ pass
class GzipBrokenHeaderCorrectException(GzipTest, unittest.TestCase):
"""
@@ -1625,7 +1650,7 @@ class WriteTest(WriteTestBase, unittest.TestCase):
try:
for t in tar:
if t.name != ".":
- self.assertTrue(t.name.startswith("./"), t.name)
+ self.assertStartsWith(t.name, "./")
finally:
tar.close()
@@ -1666,6 +1691,8 @@ class Bz2WriteTest(Bz2Test, WriteTest):
class LzmaWriteTest(LzmaTest, WriteTest):
pass
+class ZstdWriteTest(ZstdTest, WriteTest):
+ pass
class StreamWriteTest(WriteTestBase, unittest.TestCase):
@@ -1727,6 +1754,9 @@ class Bz2StreamWriteTest(Bz2Test, StreamWriteTest):
class LzmaStreamWriteTest(LzmaTest, StreamWriteTest):
decompressor = lzma.LZMADecompressor if lzma else None
+class ZstdStreamWriteTest(ZstdTest, StreamWriteTest):
+ decompressor = zstd.ZstdDecompressor if zstd else None
+
class _CompressedWriteTest(TarTest):
# This is not actually a standalone test.
# It does not inherit WriteTest because it only makes sense with gz,bz2
@@ -2042,6 +2072,14 @@ class LzmaCreateTest(LzmaTest, CreateTest):
tobj.add(self.file_path)
+class ZstdCreateTest(ZstdTest, CreateTest):
+
+ # Unlike gz and bz2, zstd uses the level keyword instead of compresslevel.
+ # It does not allow for level to be specified when reading.
+ def test_create_with_level(self):
+ with tarfile.open(tmpname, self.mode, level=1) as tobj:
+ tobj.add(self.file_path)
+
class CreateWithXModeTest(CreateTest):
prefix = "x"
@@ -2523,6 +2561,8 @@ class Bz2AppendTest(Bz2Test, AppendTestBase, unittest.TestCase):
class LzmaAppendTest(LzmaTest, AppendTestBase, unittest.TestCase):
pass
+class ZstdAppendTest(ZstdTest, AppendTestBase, unittest.TestCase):
+ pass
class LimitsTest(unittest.TestCase):
@@ -2675,6 +2715,31 @@ class MiscTest(unittest.TestCase):
str(excinfo.exception),
)
+ @unittest.skipUnless(os_helper.can_symlink(), 'requires symlink support')
+ @unittest.skipUnless(hasattr(os, 'chmod'), "missing os.chmod")
+ @unittest.mock.patch('os.chmod')
+ def test_deferred_directory_attributes_update(self, mock_chmod):
+ # Regression test for gh-127987: setting attributes on arbitrary files
+ tempdir = os.path.join(TEMPDIR, 'test127987')
+ def mock_chmod_side_effect(path, mode, **kwargs):
+ target_path = os.path.realpath(path)
+ if os.path.commonpath([target_path, tempdir]) != tempdir:
+ raise Exception("should not try to chmod anything outside the destination", target_path)
+ mock_chmod.side_effect = mock_chmod_side_effect
+
+ outside_tree_dir = os.path.join(TEMPDIR, 'outside_tree_dir')
+ with ArchiveMaker() as arc:
+ arc.add('x', symlink_to='.')
+ arc.add('x', type=tarfile.DIRTYPE, mode='?rwsrwsrwt')
+ arc.add('x', symlink_to=outside_tree_dir)
+
+ os.makedirs(outside_tree_dir)
+ try:
+ arc.open().extractall(path=tempdir, filter='tar')
+ finally:
+ os_helper.rmtree(outside_tree_dir)
+ os_helper.rmtree(tempdir)
+
class CommandLineTest(unittest.TestCase):
@@ -2835,7 +2900,7 @@ class CommandLineTest(unittest.TestCase):
support.findfile('tokenize_tests-no-coding-cookie-'
'and-utf8-bom-sig-only.txt',
subdir='tokenizedata')]
- for filetype in (GzipTest, Bz2Test, LzmaTest):
+ for filetype in (GzipTest, Bz2Test, LzmaTest, ZstdTest):
if not filetype.open:
continue
try:
@@ -3235,6 +3300,10 @@ class NoneInfoExtractTests(ReadTest):
got_paths = set(
p.relative_to(directory)
for p in pathlib.Path(directory).glob('**/*'))
+ if self.extraction_filter in (None, 'data'):
+ # The 'data' filter is expected to reject special files
+ for path in 'ustar/fifotype', 'ustar/blktype', 'ustar/chrtype':
+ got_paths.discard(pathlib.Path(path))
self.assertEqual(self.control_paths, got_paths)
@contextmanager
@@ -3450,11 +3519,12 @@ class ArchiveMaker:
with t.open() as tar:
... # `tar` is now a TarFile with 'filename' in it!
"""
- def __init__(self):
+ def __init__(self, **kwargs):
self.bio = io.BytesIO()
+ self.tar_kwargs = dict(kwargs)
def __enter__(self):
- self.tar_w = tarfile.TarFile(mode='w', fileobj=self.bio)
+ self.tar_w = tarfile.TarFile(mode='w', fileobj=self.bio, **self.tar_kwargs)
return self
def __exit__(self, *exc):
@@ -3463,12 +3533,28 @@ class ArchiveMaker:
self.bio = None
def add(self, name, *, type=None, symlink_to=None, hardlink_to=None,
- mode=None, size=None, **kwargs):
- """Add a member to the test archive. Call within `with`."""
+ mode=None, size=None, content=None, **kwargs):
+ """Add a member to the test archive. Call within `with`.
+
+ Provides many shortcuts:
+ - default `type` is based on symlink_to, hardlink_to, and trailing `/`
+ in name (which is stripped)
+ - size & content defaults are based on each other
+ - content can be str or bytes
+ - mode should be textual ('-rwxrwxrwx')
+
+ (add more! this is unstable internal test-only API)
+ """
name = str(name)
tarinfo = tarfile.TarInfo(name).replace(**kwargs)
+ if content is not None:
+ if isinstance(content, str):
+ content = content.encode()
+ size = len(content)
if size is not None:
tarinfo.size = size
+ if content is None:
+ content = bytes(tarinfo.size)
if mode:
tarinfo.mode = _filemode_to_int(mode)
if symlink_to is not None:
@@ -3482,7 +3568,7 @@ class ArchiveMaker:
if type is not None:
tarinfo.type = type
if tarinfo.isreg():
- fileobj = io.BytesIO(bytes(tarinfo.size))
+ fileobj = io.BytesIO(content)
else:
fileobj = None
self.tar_w.addfile(tarinfo, fileobj)
@@ -3516,7 +3602,7 @@ class TestExtractionFilters(unittest.TestCase):
destdir = outerdir / 'dest'
@contextmanager
- def check_context(self, tar, filter):
+ def check_context(self, tar, filter, *, check_flag=True):
"""Extracts `tar` to `self.destdir` and allows checking the result
If an error occurs, it must be checked using `expect_exception`
@@ -3525,27 +3611,40 @@ class TestExtractionFilters(unittest.TestCase):
except the destination directory itself and parent directories of
other files.
When checking directories, do so before their contents.
+
+ A file called 'flag' is made in outerdir (i.e. outside destdir)
+ before extraction; it should not be altered nor should its contents
+ be read/copied.
"""
with os_helper.temp_dir(self.outerdir):
+ flag_path = self.outerdir / 'flag'
+ flag_path.write_text('capture me')
try:
tar.extractall(self.destdir, filter=filter)
except Exception as exc:
self.raised_exception = exc
+ self.reraise_exception = True
self.expected_paths = set()
else:
self.raised_exception = None
+ self.reraise_exception = False
self.expected_paths = set(self.outerdir.glob('**/*'))
self.expected_paths.discard(self.destdir)
+ self.expected_paths.discard(flag_path)
try:
- yield
+ yield self
finally:
tar.close()
- if self.raised_exception:
+ if self.reraise_exception:
raise self.raised_exception
self.assertEqual(self.expected_paths, set())
+ if check_flag:
+ self.assertEqual(flag_path.read_text(), 'capture me')
+ else:
+ assert filter == 'fully_trusted'
def expect_file(self, name, type=None, symlink_to=None, mode=None,
- size=None):
+ size=None, content=None):
"""Check a single file. See check_context."""
if self.raised_exception:
raise self.raised_exception
@@ -3564,26 +3663,45 @@ class TestExtractionFilters(unittest.TestCase):
# The symlink might be the same (textually) as what we expect,
# but some systems change the link to an equivalent path, so
# we fall back to samefile().
- if expected != got:
- self.assertTrue(got.samefile(expected))
+ try:
+ if expected != got:
+ self.assertTrue(got.samefile(expected))
+ except Exception as e:
+ # attach a note, so it's shown even if `samefile` fails
+ e.add_note(f'{expected=}, {got=}')
+ raise
elif type == tarfile.REGTYPE or type is None:
self.assertTrue(path.is_file())
elif type == tarfile.DIRTYPE:
self.assertTrue(path.is_dir())
elif type == tarfile.FIFOTYPE:
self.assertTrue(path.is_fifo())
+ elif type == tarfile.SYMTYPE:
+ self.assertTrue(path.is_symlink())
else:
raise NotImplementedError(type)
if size is not None:
self.assertEqual(path.stat().st_size, size)
+ if content is not None:
+ self.assertEqual(path.read_text(), content)
for parent in path.parents:
self.expected_paths.discard(parent)
+ def expect_any_tree(self, name):
+ """Check a directory; forget about its contents."""
+ tree_path = (self.destdir / name).resolve()
+ self.expect_file(tree_path, type=tarfile.DIRTYPE)
+ self.expected_paths = {
+ p for p in self.expected_paths
+ if tree_path not in p.parents
+ }
+
def expect_exception(self, exc_type, message_re='.'):
with self.assertRaisesRegex(exc_type, message_re):
if self.raised_exception is not None:
raise self.raised_exception
- self.raised_exception = None
+ self.reraise_exception = False
+ return self.raised_exception
def test_benign_file(self):
with ArchiveMaker() as arc:
@@ -3669,6 +3787,80 @@ class TestExtractionFilters(unittest.TestCase):
self.expect_file('parent/evil')
@symlink_test
+ @os_helper.skip_unless_symlink
+ def test_realpath_limit_attack(self):
+ # (CVE-2025-4517)
+
+ with ArchiveMaker() as arc:
+ # populate the symlinks and dirs that expand in os.path.realpath()
+ # The component length is chosen so that in common cases, the unexpanded
+ # path fits in PATH_MAX, but it overflows when the final symlink
+ # is expanded
+ steps = "abcdefghijklmnop"
+ if sys.platform == 'win32':
+ component = 'd' * 25
+ elif 'PC_PATH_MAX' in os.pathconf_names:
+ max_path_len = os.pathconf(self.outerdir.parent, "PC_PATH_MAX")
+ path_sep_len = 1
+ dest_len = len(str(self.destdir)) + path_sep_len
+ component_len = (max_path_len - dest_len) // (len(steps) + path_sep_len)
+ component = 'd' * component_len
+ else:
+ raise NotImplementedError("Need to guess component length for {sys.platform}")
+ path = ""
+ step_path = ""
+ for i in steps:
+ arc.add(os.path.join(path, component), type=tarfile.DIRTYPE,
+ mode='drwxrwxrwx')
+ arc.add(os.path.join(path, i), symlink_to=component)
+ path = os.path.join(path, component)
+ step_path = os.path.join(step_path, i)
+ # create the final symlink that exceeds PATH_MAX and simply points
+ # to the top dir.
+ # this link will never be expanded by
+ # os.path.realpath(strict=False), nor anything after it.
+ linkpath = os.path.join(*steps, "l"*254)
+ parent_segments = [".."] * len(steps)
+ arc.add(linkpath, symlink_to=os.path.join(*parent_segments))
+ # make a symlink outside to keep the tar command happy
+ arc.add("escape", symlink_to=os.path.join(linkpath, ".."))
+ # use the symlinks above, that are not checked, to create a hardlink
+ # to a file outside of the destination path
+ arc.add("flaglink", hardlink_to=os.path.join("escape", "flag"))
+ # now that we have the hardlink we can overwrite the file
+ arc.add("flaglink", content='overwrite')
+ # we can also create new files as well!
+ arc.add("escape/newfile", content='new')
+
+ with (self.subTest('fully_trusted'),
+ self.check_context(arc.open(), filter='fully_trusted',
+ check_flag=False)):
+ if sys.platform == 'win32':
+ self.expect_exception((FileNotFoundError, FileExistsError))
+ elif self.raised_exception:
+ # Cannot symlink/hardlink: tarfile falls back to getmember()
+ self.expect_exception(KeyError)
+ # Otherwise, this block should never enter.
+ else:
+ self.expect_any_tree(component)
+ self.expect_file('flaglink', content='overwrite')
+ self.expect_file('../newfile', content='new')
+ self.expect_file('escape', type=tarfile.SYMTYPE)
+ self.expect_file('a', symlink_to=component)
+
+ for filter in 'tar', 'data':
+ with self.subTest(filter), self.check_context(arc.open(), filter=filter):
+ exc = self.expect_exception((OSError, KeyError))
+ if isinstance(exc, OSError):
+ if sys.platform == 'win32':
+ # 3: ERROR_PATH_NOT_FOUND
+ # 5: ERROR_ACCESS_DENIED
+ # 206: ERROR_FILENAME_EXCED_RANGE
+ self.assertIn(exc.winerror, (3, 5, 206))
+ else:
+ self.assertEqual(exc.errno, errno.ENAMETOOLONG)
+
+ @symlink_test
def test_parent_symlink2(self):
# Test interplaying symlinks
# Inspired by 'dirsymlink2b' in jwilk/traversal-archives
@@ -3890,8 +4082,8 @@ class TestExtractionFilters(unittest.TestCase):
arc.add('symlink2', symlink_to=os.path.join(
'linkdir', 'hardlink2'))
arc.add('targetdir/target', size=3)
- arc.add('linkdir/hardlink', hardlink_to='targetdir/target')
- arc.add('linkdir/hardlink2', hardlink_to='linkdir/symlink')
+ arc.add('linkdir/hardlink', hardlink_to=os.path.join('targetdir', 'target'))
+ arc.add('linkdir/hardlink2', hardlink_to=os.path.join('linkdir', 'symlink'))
for filter in 'tar', 'data', 'fully_trusted':
with self.check_context(arc.open(), filter):
@@ -3907,6 +4099,129 @@ class TestExtractionFilters(unittest.TestCase):
self.expect_file('linkdir/symlink', size=3)
self.expect_file('symlink2', size=3)
+ @symlink_test
+ def test_sneaky_hardlink_fallback(self):
+ # (CVE-2025-4330)
+ # Test that when hardlink extraction falls back to extracting members
+ # from the archive, the extracted member is (re-)filtered.
+ with ArchiveMaker() as arc:
+ # Create a directory structure so the c/escape symlink stays
+ # inside the path
+ arc.add("a/t/dummy")
+ # Create b/ directory
+ arc.add("b/")
+ # Point "c" to the bottom of the tree in "a"
+ arc.add("c", symlink_to=os.path.join("a", "t"))
+ # link to non-existant location under "a"
+ arc.add("c/escape", symlink_to=os.path.join("..", "..",
+ "link_here"))
+ # Move "c" to point to "b" ("c/escape" no longer exists)
+ arc.add("c", symlink_to="b")
+ # Attempt to create a hard link to "c/escape". Since it doesn't
+ # exist it will attempt to extract "cescape" but at "boom".
+ arc.add("boom", hardlink_to=os.path.join("c", "escape"))
+
+ with self.check_context(arc.open(), 'data'):
+ if not os_helper.can_symlink():
+ # When 'c/escape' is extracted, 'c' is a regular
+ # directory, and 'c/escape' *would* point outside
+ # the destination if symlinks were allowed.
+ self.expect_exception(
+ tarfile.LinkOutsideDestinationError)
+ elif sys.platform == "win32":
+ # On Windows, 'c/escape' points outside the destination
+ self.expect_exception(tarfile.LinkOutsideDestinationError)
+ else:
+ e = self.expect_exception(
+ tarfile.LinkFallbackError,
+ "link 'boom' would be extracted as a copy of "
+ + "'c/escape', which was rejected")
+ self.assertIsInstance(e.__cause__,
+ tarfile.LinkOutsideDestinationError)
+ for filter in 'tar', 'fully_trusted':
+ with self.subTest(filter), self.check_context(arc.open(), filter):
+ if not os_helper.can_symlink():
+ self.expect_file("a/t/dummy")
+ self.expect_file("b/")
+ self.expect_file("c/")
+ else:
+ self.expect_file("a/t/dummy")
+ self.expect_file("b/")
+ self.expect_file("a/t/escape", symlink_to='../../link_here')
+ self.expect_file("boom", symlink_to='../../link_here')
+ self.expect_file("c", symlink_to='b')
+
+ @symlink_test
+ def test_exfiltration_via_symlink(self):
+ # (CVE-2025-4138)
+ # Test changing symlinks that result in a symlink pointing outside
+ # the extraction directory, unless prevented by 'data' filter's
+ # normalization.
+ with ArchiveMaker() as arc:
+ arc.add("escape", symlink_to=os.path.join('link', 'link', '..', '..', 'link-here'))
+ arc.add("link", symlink_to='./')
+
+ for filter in 'tar', 'data', 'fully_trusted':
+ with self.check_context(arc.open(), filter):
+ if os_helper.can_symlink():
+ self.expect_file("link", symlink_to='./')
+ if filter == 'data':
+ self.expect_file("escape", symlink_to='link-here')
+ else:
+ self.expect_file("escape",
+ symlink_to='link/link/../../link-here')
+ else:
+ # Nothing is extracted.
+ pass
+
+ @symlink_test
+ def test_chmod_outside_dir(self):
+ # (CVE-2024-12718)
+ # Test that members used for delayed updates of directory metadata
+ # are (re-)filtered.
+ with ArchiveMaker() as arc:
+ # "pwn" is a veeeery innocent symlink:
+ arc.add("a/pwn", symlink_to='.')
+ # But now "pwn" is also a directory, so it's scheduled to have its
+ # metadata updated later:
+ arc.add("a/pwn/", mode='drwxrwxrwx')
+ # Oops, "pwn" is not so innocent any more:
+ arc.add("a/pwn", symlink_to='x/../')
+ # Newly created symlink points to the dest dir,
+ # so it's OK for the "data" filter.
+ arc.add('a/x', symlink_to=('../'))
+ # But now "pwn" points outside the dest dir
+
+ for filter in 'tar', 'data', 'fully_trusted':
+ with self.check_context(arc.open(), filter) as cc:
+ if not os_helper.can_symlink():
+ self.expect_file("a/pwn/")
+ elif filter == 'data':
+ self.expect_file("a/x", symlink_to='../')
+ self.expect_file("a/pwn", symlink_to='.')
+ else:
+ self.expect_file("a/x", symlink_to='../')
+ self.expect_file("a/pwn", symlink_to='x/../')
+ if sys.platform != "win32":
+ st_mode = cc.outerdir.stat().st_mode
+ self.assertNotEqual(st_mode & 0o777, 0o777)
+
+ def test_link_fallback_normalizes(self):
+ # Make sure hardlink fallbacks work for non-normalized paths for all
+ # filters
+ with ArchiveMaker() as arc:
+ arc.add("dir/")
+ arc.add("dir/../afile")
+ arc.add("link1", hardlink_to='dir/../afile')
+ arc.add("link2", hardlink_to='dir/../dir/../afile')
+
+ for filter in 'tar', 'data', 'fully_trusted':
+ with self.check_context(arc.open(), filter) as cc:
+ self.expect_file("dir/")
+ self.expect_file("afile")
+ self.expect_file("link1")
+ self.expect_file("link2")
+
def test_modes(self):
# Test how file modes are extracted
# (Note that the modes are ignored on platforms without working chmod)
@@ -4031,24 +4346,64 @@ class TestExtractionFilters(unittest.TestCase):
# The 'tar' filter returns TarInfo objects with the same name/type.
# (It can also fail for particularly "evil" input, but we don't have
# that in the test archive.)
- with tarfile.TarFile.open(tarname) as tar:
+ with tarfile.TarFile.open(tarname, encoding="iso8859-1") as tar:
for tarinfo in tar.getmembers():
- filtered = tarfile.tar_filter(tarinfo, '')
+ try:
+ filtered = tarfile.tar_filter(tarinfo, '')
+ except UnicodeEncodeError:
+ continue
self.assertIs(filtered.name, tarinfo.name)
self.assertIs(filtered.type, tarinfo.type)
def test_data_filter(self):
# The 'data' filter either raises, or returns TarInfo with the same
# name/type.
- with tarfile.TarFile.open(tarname) as tar:
+ with tarfile.TarFile.open(tarname, encoding="iso8859-1") as tar:
for tarinfo in tar.getmembers():
try:
filtered = tarfile.data_filter(tarinfo, '')
- except tarfile.FilterError:
+ except (tarfile.FilterError, UnicodeEncodeError):
continue
self.assertIs(filtered.name, tarinfo.name)
self.assertIs(filtered.type, tarinfo.type)
+ @unittest.skipIf(sys.platform == 'win32', 'requires native bytes paths')
+ def test_filter_unencodable(self):
+ # Sanity check using a valid path.
+ tarinfo = tarfile.TarInfo(os_helper.TESTFN)
+ filtered = tarfile.tar_filter(tarinfo, '')
+ self.assertIs(filtered.name, tarinfo.name)
+ filtered = tarfile.data_filter(tarinfo, '')
+ self.assertIs(filtered.name, tarinfo.name)
+
+ tarinfo = tarfile.TarInfo('test\x00')
+ self.assertRaises(ValueError, tarfile.tar_filter, tarinfo, '')
+ self.assertRaises(ValueError, tarfile.data_filter, tarinfo, '')
+ tarinfo = tarfile.TarInfo('\ud800')
+ self.assertRaises(UnicodeEncodeError, tarfile.tar_filter, tarinfo, '')
+ self.assertRaises(UnicodeEncodeError, tarfile.data_filter, tarinfo, '')
+
+ @unittest.skipIf(sys.platform == 'win32', 'requires native bytes paths')
+ def test_extract_unencodable(self):
+ # Create a member with name \xed\xa0\x80 which is UTF-8 encoded
+ # lone surrogate \ud800.
+ with ArchiveMaker(encoding='ascii', errors='surrogateescape') as arc:
+ arc.add('\udced\udca0\udc80')
+ with os_helper.temp_cwd() as tmp:
+ tar = arc.open(encoding='utf-8', errors='surrogatepass',
+ errorlevel=1)
+ self.assertEqual(tar.getnames(), ['\ud800'])
+ with self.assertRaises(UnicodeEncodeError):
+ tar.extractall()
+ self.assertEqual(os.listdir(), [])
+
+ tar = arc.open(encoding='utf-8', errors='surrogatepass',
+ errorlevel=0, debug=1)
+ with support.captured_stderr() as stderr:
+ tar.extractall()
+ self.assertEqual(os.listdir(), [])
+ self.assertIn('tarfile: UnicodeEncodeError ', stderr.getvalue())
+
def test_change_default_filter_on_instance(self):
tar = tarfile.TarFile(tarname, 'r')
def strict_filter(tarinfo, path):
@@ -4161,13 +4516,13 @@ class TestExtractionFilters(unittest.TestCase):
# If errorlevel is 0, errors affected by errorlevel are ignored
with self.check_context(arc.open(errorlevel=0), extracterror_filter):
- self.expect_file('file')
+ pass
with self.check_context(arc.open(errorlevel=0), filtererror_filter):
- self.expect_file('file')
+ pass
with self.check_context(arc.open(errorlevel=0), oserror_filter):
- self.expect_file('file')
+ pass
with self.check_context(arc.open(errorlevel=0), tarerror_filter):
self.expect_exception(tarfile.TarError)
@@ -4178,7 +4533,7 @@ class TestExtractionFilters(unittest.TestCase):
# If 1, all fatal errors are raised
with self.check_context(arc.open(errorlevel=1), extracterror_filter):
- self.expect_file('file')
+ pass
with self.check_context(arc.open(errorlevel=1), filtererror_filter):
self.expect_exception(tarfile.FilterError)
@@ -4257,7 +4612,7 @@ def setUpModule():
data = fobj.read()
# Create compressed tarfiles.
- for c in GzipTest, Bz2Test, LzmaTest:
+ for c in GzipTest, Bz2Test, LzmaTest, ZstdTest:
if c.open:
os_helper.unlink(c.tarname)
testtarnames.append(c.tarname)
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index d46d3c0f040..52b13b98cbc 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -516,11 +516,11 @@ class TestMkstempInner(TestBadTempdir, BaseTestCase):
_mock_candidate_names('aaa', 'aaa', 'bbb'):
(fd1, name1) = self.make_temp()
os.close(fd1)
- self.assertTrue(name1.endswith('aaa'))
+ self.assertEndsWith(name1, 'aaa')
(fd2, name2) = self.make_temp()
os.close(fd2)
- self.assertTrue(name2.endswith('bbb'))
+ self.assertEndsWith(name2, 'bbb')
def test_collision_with_existing_directory(self):
# _mkstemp_inner tries another name when a directory with
@@ -528,11 +528,11 @@ class TestMkstempInner(TestBadTempdir, BaseTestCase):
with _inside_empty_temp_dir(), \
_mock_candidate_names('aaa', 'aaa', 'bbb'):
dir = tempfile.mkdtemp()
- self.assertTrue(dir.endswith('aaa'))
+ self.assertEndsWith(dir, 'aaa')
(fd, name) = self.make_temp()
os.close(fd)
- self.assertTrue(name.endswith('bbb'))
+ self.assertEndsWith(name, 'bbb')
class TestGetTempPrefix(BaseTestCase):
@@ -828,9 +828,9 @@ class TestMkdtemp(TestBadTempdir, BaseTestCase):
_mock_candidate_names('aaa', 'aaa', 'bbb'):
file = tempfile.NamedTemporaryFile(delete=False)
file.close()
- self.assertTrue(file.name.endswith('aaa'))
+ self.assertEndsWith(file.name, 'aaa')
dir = tempfile.mkdtemp()
- self.assertTrue(dir.endswith('bbb'))
+ self.assertEndsWith(dir, 'bbb')
def test_collision_with_existing_directory(self):
# mkdtemp tries another name when a directory with
@@ -838,9 +838,9 @@ class TestMkdtemp(TestBadTempdir, BaseTestCase):
with _inside_empty_temp_dir(), \
_mock_candidate_names('aaa', 'aaa', 'bbb'):
dir1 = tempfile.mkdtemp()
- self.assertTrue(dir1.endswith('aaa'))
+ self.assertEndsWith(dir1, 'aaa')
dir2 = tempfile.mkdtemp()
- self.assertTrue(dir2.endswith('bbb'))
+ self.assertEndsWith(dir2, 'bbb')
def test_for_tempdir_is_bytes_issue40701_api_warts(self):
orig_tempdir = tempfile.tempdir
diff --git a/Lib/test/test_termios.py b/Lib/test/test_termios.py
index e5d11cf84d2..ce8392a6ccd 100644
--- a/Lib/test/test_termios.py
+++ b/Lib/test/test_termios.py
@@ -290,8 +290,8 @@ class TestModule(unittest.TestCase):
self.assertGreaterEqual(value, 0)
def test_exception(self):
- self.assertTrue(issubclass(termios.error, Exception))
- self.assertFalse(issubclass(termios.error, OSError))
+ self.assertIsSubclass(termios.error, Exception)
+ self.assertNotIsSubclass(termios.error, OSError)
if __name__ == '__main__':
diff --git a/Lib/test/test_threadedtempfile.py b/Lib/test/test_threadedtempfile.py
index 420fc6ec8be..acb427b0c78 100644
--- a/Lib/test/test_threadedtempfile.py
+++ b/Lib/test/test_threadedtempfile.py
@@ -15,6 +15,7 @@ provoking a 2.0 failure under Linux.
import tempfile
+from test import support
from test.support import threading_helper
import unittest
import io
@@ -49,7 +50,8 @@ class TempFileGreedy(threading.Thread):
class ThreadedTempFileTest(unittest.TestCase):
- def test_main(self):
+ @support.bigmemtest(size=NUM_THREADS, memuse=60*2**20, dry_run=False)
+ def test_main(self, size):
threads = [TempFileGreedy() for i in range(NUM_THREADS)]
with threading_helper.start_threads(threads, startEvent.set):
pass
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 814c00ca0fd..00a3037c3e1 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -5,7 +5,7 @@ Tests for the threading module.
import test.support
from test.support import threading_helper, requires_subprocess, requires_gil_enabled
from test.support import verbose, cpython_only, os_helper
-from test.support.import_helper import import_module
+from test.support.import_helper import ensure_lazy_imports, import_module
from test.support.script_helper import assert_python_ok, assert_python_failure
from test.support import force_not_colorized
@@ -28,7 +28,7 @@ from test import lock_tests
from test import support
try:
- from test.support import interpreters
+ from concurrent import interpreters
except ImportError:
interpreters = None
@@ -121,6 +121,10 @@ class ThreadTests(BaseTestCase):
maxDiff = 9999
@cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("threading", {"functools", "warnings"})
+
+ @cpython_only
def test_name(self):
def func(): pass
@@ -526,7 +530,8 @@ class ThreadTests(BaseTestCase):
finally:
sys.setswitchinterval(old_interval)
- def test_join_from_multiple_threads(self):
+ @support.bigmemtest(size=20, memuse=72*2**20, dry_run=False)
+ def test_join_from_multiple_threads(self, size):
# Thread.join() should be thread-safe
errors = []
@@ -1242,13 +1247,68 @@ class ThreadTests(BaseTestCase):
self.assertEqual(err, b"")
self.assertIn(b"all clear", out)
+ @support.subTests('lock_class_name', ['Lock', 'RLock'])
+ def test_acquire_daemon_thread_lock_in_finalization(self, lock_class_name):
+ # gh-123940: Py_Finalize() prevents other threads from running Python
+ # code (and so, releasing locks), so acquiring a locked lock can not
+ # succeed.
+ # We raise an exception rather than hang.
+ code = textwrap.dedent(f"""
+ import threading
+ import time
+
+ thread_started_event = threading.Event()
+
+ lock = threading.{lock_class_name}()
+ def loop():
+ if {lock_class_name!r} == 'RLock':
+ lock.acquire()
+ with lock:
+ thread_started_event.set()
+ while True:
+ time.sleep(1)
+
+ uncontested_lock = threading.{lock_class_name}()
+
+ class Cycle:
+ def __init__(self):
+ self.self_ref = self
+ self.thr = threading.Thread(
+ target=loop, daemon=True)
+ self.thr.start()
+ thread_started_event.wait()
+
+ def __del__(self):
+ assert self.thr.is_alive()
+
+ # We *can* acquire an unlocked lock
+ uncontested_lock.acquire()
+ if {lock_class_name!r} == 'RLock':
+ uncontested_lock.acquire()
+
+ # Acquiring a locked one fails
+ try:
+ lock.acquire()
+ except PythonFinalizationError:
+ assert self.thr.is_alive()
+ print('got the correct exception!')
+
+ # Cycle holds a reference to itself, which ensures it is
+ # cleaned up during the GC that runs after daemon threads
+ # have been forced to exit during finalization.
+ Cycle()
+ """)
+ rc, out, err = assert_python_ok("-c", code)
+ self.assertEqual(err, b"")
+ self.assertIn(b"got the correct exception", out)
+
def test_start_new_thread_failed(self):
# gh-109746: if Python fails to start newly created thread
# due to failure of underlying PyThread_start_new_thread() call,
# its state should be removed from interpreter' thread states list
# to avoid its double cleanup
try:
- from resource import setrlimit, RLIMIT_NPROC
+ from resource import setrlimit, RLIMIT_NPROC # noqa: F401
except ImportError as err:
self.skipTest(err) # RLIMIT_NPROC is specific to Linux and BSD
code = """if 1:
@@ -1279,12 +1339,6 @@ class ThreadTests(BaseTestCase):
@cpython_only
def test_finalize_daemon_thread_hang(self):
- if support.check_sanitizer(thread=True, memory=True):
- # the thread running `time.sleep(100)` below will still be alive
- # at process exit
- self.skipTest(
- "https://github.com/python/cpython/issues/124878 - Known"
- " race condition that TSAN identifies.")
# gh-87135: tests that daemon threads hang during finalization
script = textwrap.dedent('''
import os
@@ -1347,6 +1401,35 @@ class ThreadTests(BaseTestCase):
''')
assert_python_ok("-c", script)
+ @skip_unless_reliable_fork
+ @unittest.skipUnless(hasattr(threading, 'get_native_id'), "test needs threading.get_native_id()")
+ def test_native_id_after_fork(self):
+ script = """if True:
+ import threading
+ import os
+ from test import support
+
+ parent_thread_native_id = threading.current_thread().native_id
+ print(parent_thread_native_id, flush=True)
+ assert parent_thread_native_id == threading.get_native_id()
+ childpid = os.fork()
+ if childpid == 0:
+ print(threading.current_thread().native_id, flush=True)
+ assert threading.current_thread().native_id == threading.get_native_id()
+ else:
+ try:
+ assert parent_thread_native_id == threading.current_thread().native_id
+ assert parent_thread_native_id == threading.get_native_id()
+ finally:
+ support.wait_process(childpid, exitcode=0)
+ """
+ rc, out, err = assert_python_ok('-c', script)
+ self.assertEqual(rc, 0)
+ self.assertEqual(err, b"")
+ native_ids = out.strip().splitlines()
+ self.assertEqual(len(native_ids), 2)
+ self.assertNotEqual(native_ids[0], native_ids[1])
+
class ThreadJoinOnShutdown(BaseTestCase):
def _run_and_join(self, script):
@@ -1427,7 +1510,8 @@ class ThreadJoinOnShutdown(BaseTestCase):
self._run_and_join(script)
@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
- def test_4_daemon_threads(self):
+ @support.bigmemtest(size=40, memuse=70*2**20, dry_run=False)
+ def test_4_daemon_threads(self, size):
# Check that a daemon thread cannot crash the interpreter on shutdown
# by manipulating internal structures that are being disposed of in
# the main thread.
@@ -2131,8 +2215,7 @@ class CRLockTests(lock_tests.RLockTests):
]
for args, kwargs in arg_types:
with self.subTest(args=args, kwargs=kwargs):
- with self.assertWarns(DeprecationWarning):
- threading.RLock(*args, **kwargs)
+ self.assertRaises(TypeError, threading.RLock, *args, **kwargs)
# Subtypes with custom `__init__` are allowed (but, not recommended):
class CustomRLock(self.locktype):
@@ -2150,6 +2233,9 @@ class ConditionAsRLockTests(lock_tests.RLockTests):
# Condition uses an RLock by default and exports its API.
locktype = staticmethod(threading.Condition)
+ def test_constructor_noargs(self):
+ self.skipTest("Condition allows positional arguments")
+
def test_recursion_count(self):
self.skipTest("Condition does not expose _recursion_count()")
diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py
index d06f65270ef..5312faa5077 100644
--- a/Lib/test/test_time.py
+++ b/Lib/test/test_time.py
@@ -761,17 +761,17 @@ class TestPytime(unittest.TestCase):
# Get the localtime and examine it for the offset and zone.
lt = time.localtime()
- self.assertTrue(hasattr(lt, "tm_gmtoff"))
- self.assertTrue(hasattr(lt, "tm_zone"))
+ self.assertHasAttr(lt, "tm_gmtoff")
+ self.assertHasAttr(lt, "tm_zone")
# See if the offset and zone are similar to the module
# attributes.
if lt.tm_gmtoff is None:
- self.assertTrue(not hasattr(time, "timezone"))
+ self.assertNotHasAttr(time, "timezone")
else:
self.assertEqual(lt.tm_gmtoff, -[time.timezone, time.altzone][lt.tm_isdst])
if lt.tm_zone is None:
- self.assertTrue(not hasattr(time, "tzname"))
+ self.assertNotHasAttr(time, "tzname")
else:
self.assertEqual(lt.tm_zone, time.tzname[lt.tm_isdst])
@@ -1184,11 +1184,11 @@ class TestTimeWeaklinking(unittest.TestCase):
if mac_ver >= (10, 12):
for name in clock_names:
- self.assertTrue(hasattr(time, name), f"time.{name} is not available")
+ self.assertHasAttr(time, name)
else:
for name in clock_names:
- self.assertFalse(hasattr(time, name), f"time.{name} is available")
+ self.assertNotHasAttr(time, name)
if __name__ == "__main__":
diff --git a/Lib/test/test_timeit.py b/Lib/test/test_timeit.py
index f5ae0a84eb3..2aeebea9f93 100644
--- a/Lib/test/test_timeit.py
+++ b/Lib/test/test_timeit.py
@@ -222,8 +222,8 @@ class TestTimeit(unittest.TestCase):
def assert_exc_string(self, exc_string, expected_exc_name):
exc_lines = exc_string.splitlines()
self.assertGreater(len(exc_lines), 2)
- self.assertTrue(exc_lines[0].startswith('Traceback'))
- self.assertTrue(exc_lines[-1].startswith(expected_exc_name))
+ self.assertStartsWith(exc_lines[0], 'Traceback')
+ self.assertStartsWith(exc_lines[-1], expected_exc_name)
def test_print_exc(self):
s = io.StringIO()
diff --git a/Lib/test/test_tkinter/support.py b/Lib/test/test_tkinter/support.py
index ebb9e00ff91..46b01e6f131 100644
--- a/Lib/test/test_tkinter/support.py
+++ b/Lib/test/test_tkinter/support.py
@@ -58,7 +58,7 @@ class AbstractDefaultRootTest:
destroy_default_root()
tkinter.NoDefaultRoot()
self.assertRaises(RuntimeError, constructor)
- self.assertFalse(hasattr(tkinter, '_default_root'))
+ self.assertNotHasAttr(tkinter, '_default_root')
def destroy_default_root():
diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py
index 96ea3f0117c..0c76e07066f 100644
--- a/Lib/test/test_tkinter/test_misc.py
+++ b/Lib/test/test_tkinter/test_misc.py
@@ -497,7 +497,7 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
self.assertEqual(vi.serial, 0)
else:
self.assertEqual(vi.micro, 0)
- self.assertTrue(str(vi).startswith(f'{vi.major}.{vi.minor}'))
+ self.assertStartsWith(str(vi), f'{vi.major}.{vi.minor}')
def test_embedded_null(self):
widget = tkinter.Entry(self.root)
@@ -609,7 +609,7 @@ class EventTest(AbstractTkTest, unittest.TestCase):
self.assertIsInstance(e.serial, int)
self.assertEqual(e.time, '??')
self.assertIs(e.send_event, False)
- self.assertFalse(hasattr(e, 'focus'))
+ self.assertNotHasAttr(e, 'focus')
self.assertEqual(e.num, '??')
self.assertEqual(e.state, '??')
self.assertEqual(e.char, '??')
@@ -642,7 +642,7 @@ class EventTest(AbstractTkTest, unittest.TestCase):
self.assertIsInstance(e.serial, int)
self.assertEqual(e.time, '??')
self.assertIs(e.send_event, False)
- self.assertFalse(hasattr(e, 'focus'))
+ self.assertNotHasAttr(e, 'focus')
self.assertEqual(e.num, '??')
self.assertEqual(e.state, '??')
self.assertEqual(e.char, '??')
@@ -676,7 +676,7 @@ class EventTest(AbstractTkTest, unittest.TestCase):
self.assertIsInstance(e.serial, int)
self.assertEqual(e.time, 0)
self.assertIs(e.send_event, False)
- self.assertFalse(hasattr(e, 'focus'))
+ self.assertNotHasAttr(e, 'focus')
self.assertEqual(e.num, '??')
self.assertIsInstance(e.state, int)
self.assertNotEqual(e.state, 0)
@@ -747,7 +747,7 @@ class EventTest(AbstractTkTest, unittest.TestCase):
self.assertIsInstance(e.serial, int)
self.assertEqual(e.time, 0)
self.assertIs(e.send_event, False)
- self.assertFalse(hasattr(e, 'focus'))
+ self.assertNotHasAttr(e, 'focus')
self.assertEqual(e.num, 1)
self.assertEqual(e.state, 0)
self.assertEqual(e.char, '??')
@@ -781,7 +781,7 @@ class EventTest(AbstractTkTest, unittest.TestCase):
self.assertIsInstance(e.serial, int)
self.assertEqual(e.time, 0)
self.assertIs(e.send_event, False)
- self.assertFalse(hasattr(e, 'focus'))
+ self.assertNotHasAttr(e, 'focus')
self.assertEqual(e.num, '??')
self.assertEqual(e.state, 0x100)
self.assertEqual(e.char, '??')
@@ -814,7 +814,7 @@ class EventTest(AbstractTkTest, unittest.TestCase):
self.assertIs(e.widget, f)
self.assertIsInstance(e.serial, int)
self.assertIs(e.send_event, False)
- self.assertFalse(hasattr(e, 'focus'))
+ self.assertNotHasAttr(e, 'focus')
self.assertEqual(e.time, 0)
self.assertEqual(e.num, '??')
self.assertEqual(e.state, 0)
@@ -849,7 +849,7 @@ class EventTest(AbstractTkTest, unittest.TestCase):
self.assertIsInstance(e.serial, int)
self.assertEqual(e.time, 0)
self.assertIs(e.send_event, False)
- self.assertFalse(hasattr(e, 'focus'))
+ self.assertNotHasAttr(e, 'focus')
self.assertEqual(e.num, '??')
self.assertEqual(e.state, 0)
self.assertEqual(e.char, '??')
@@ -1308,17 +1308,17 @@ class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
self.assertIs(tkinter._default_root, root)
tkinter.NoDefaultRoot()
self.assertIs(tkinter._support_default_root, False)
- self.assertFalse(hasattr(tkinter, '_default_root'))
+ self.assertNotHasAttr(tkinter, '_default_root')
# repeated call is no-op
tkinter.NoDefaultRoot()
self.assertIs(tkinter._support_default_root, False)
- self.assertFalse(hasattr(tkinter, '_default_root'))
+ self.assertNotHasAttr(tkinter, '_default_root')
root.destroy()
self.assertIs(tkinter._support_default_root, False)
- self.assertFalse(hasattr(tkinter, '_default_root'))
+ self.assertNotHasAttr(tkinter, '_default_root')
root = tkinter.Tk()
self.assertIs(tkinter._support_default_root, False)
- self.assertFalse(hasattr(tkinter, '_default_root'))
+ self.assertNotHasAttr(tkinter, '_default_root')
root.destroy()
def test_getboolean(self):
diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py
index 2d41a5e5ac0..865e0c5b40d 100644
--- a/Lib/test/test_tokenize.py
+++ b/Lib/test/test_tokenize.py
@@ -1,6 +1,8 @@
import contextlib
+import itertools
import os
import re
+import string
import tempfile
import token
import tokenize
@@ -1975,6 +1977,10 @@ if 1:
for case in cases:
self.check_roundtrip(case)
+ self.check_roundtrip(r"t'{ {}}'")
+ self.check_roundtrip(r"t'{f'{ {}}'}{ {}}'")
+ self.check_roundtrip(r"f'{t'{ {}}'}{ {}}'")
+
def test_continuation(self):
# Balancing continuation
@@ -3234,5 +3240,77 @@ class CommandLineTest(unittest.TestCase):
self.check_output(source, expect, flag)
+class StringPrefixTest(unittest.TestCase):
+ @staticmethod
+ def determine_valid_prefixes():
+ # Try all lengths until we find a length that has zero valid
+ # prefixes. This will miss the case where for example there
+ # are no valid 3 character prefixes, but there are valid 4
+ # character prefixes. That seems unlikely.
+
+ single_char_valid_prefixes = set()
+
+ # Find all of the single character string prefixes. Just get
+ # the lowercase version, we'll deal with combinations of upper
+ # and lower case later. I'm using this logic just in case
+ # some uppercase-only prefix is added.
+ for letter in itertools.chain(string.ascii_lowercase, string.ascii_uppercase):
+ try:
+ eval(f'{letter}""')
+ single_char_valid_prefixes.add(letter.lower())
+ except SyntaxError:
+ pass
+
+ # This logic assumes that all combinations of valid prefixes only use
+ # the characters that are valid single character prefixes. That seems
+ # like a valid assumption, but if it ever changes this will need
+ # adjusting.
+ valid_prefixes = set()
+ for length in itertools.count():
+ num_at_this_length = 0
+ for prefix in (
+ "".join(l)
+ for l in itertools.combinations(single_char_valid_prefixes, length)
+ ):
+ for t in itertools.permutations(prefix):
+ for u in itertools.product(*[(c, c.upper()) for c in t]):
+ p = "".join(u)
+ if p == "not":
+ # 'not' can never be a string prefix,
+ # because it's a valid expression: not ""
+ continue
+ try:
+ eval(f'{p}""')
+
+ # No syntax error, so p is a valid string
+ # prefix.
+
+ valid_prefixes.add(p)
+ num_at_this_length += 1
+ except SyntaxError:
+ pass
+ if num_at_this_length == 0:
+ return valid_prefixes
+
+
+ def test_prefixes(self):
+ # Get the list of defined string prefixes. I don't see an
+ # obvious documented way of doing this, but probably the best
+ # thing is to split apart tokenize.StringPrefix.
+
+ # Make sure StringPrefix begins and ends in parens. We're
+ # assuming it's of the form "(a|b|ab)", if a, b, and cd are
+ # valid string prefixes.
+ self.assertEqual(tokenize.StringPrefix[0], '(')
+ self.assertEqual(tokenize.StringPrefix[-1], ')')
+
+ # Then split apart everything else by '|'.
+ defined_prefixes = set(tokenize.StringPrefix[1:-1].split('|'))
+
+ # Now compute the actual allowed string prefixes and compare
+ # to what is defined in the tokenize module.
+ self.assertEqual(defined_prefixes, self.determine_valid_prefixes())
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_tools/i18n_data/docstrings.py b/Lib/test/test_tools/i18n_data/docstrings.py
index 151a55a4b56..14559a632da 100644
--- a/Lib/test/test_tools/i18n_data/docstrings.py
+++ b/Lib/test/test_tools/i18n_data/docstrings.py
@@ -1,7 +1,7 @@
"""Module docstring"""
# Test docstring extraction
-from gettext import gettext as _
+from gettext import gettext as _ # noqa: F401
# Empty docstring
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 683486e9aca..74b979d0096 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -37,6 +37,12 @@ test_code.co_positions = lambda _: iter([(6, 6, 0, 0)])
test_frame = namedtuple('frame', ['f_code', 'f_globals', 'f_locals'])
test_tb = namedtuple('tb', ['tb_frame', 'tb_lineno', 'tb_next', 'tb_lasti'])
+color_overrides = {"reset": "z", "filename": "fn", "error_highlight": "E"}
+colors = {
+ color_overrides.get(k, k[0].lower()): v
+ for k, v in _colorize.default_theme.traceback.items()
+}
+
LEVENSHTEIN_DATA_FILE = Path(__file__).parent / 'levenshtein_examples.json'
@@ -4182,6 +4188,15 @@ class SuggestionFormattingTestBase:
self.assertNotIn("blech", actual)
self.assertNotIn("oh no!", actual)
+ def test_attribute_error_with_non_string_candidates(self):
+ class T:
+ bluch = 1
+
+ instance = T()
+ instance.__dict__[0] = 1
+ actual = self.get_suggestion(instance, 'blich')
+ self.assertIn("bluch", actual)
+
def test_attribute_error_with_bad_name(self):
def raise_attribute_error_with_bad_name():
raise AttributeError(name=12, obj=23)
@@ -4217,8 +4232,8 @@ class SuggestionFormattingTestBase:
return mod_name
- def get_import_from_suggestion(self, mod_dict, name):
- modname = self.make_module(mod_dict)
+ def get_import_from_suggestion(self, code, name):
+ modname = self.make_module(code)
def callable():
try:
@@ -4295,6 +4310,13 @@ class SuggestionFormattingTestBase:
self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_luch'))
self.assertNotIn("'_bluch'", self.get_import_from_suggestion(code, 'bluch'))
+ def test_import_from_suggestions_non_string(self):
+ modWithNonStringAttr = textwrap.dedent("""\
+ globals()[0] = 1
+ bluch = 1
+ """)
+ self.assertIn("'bluch'", self.get_import_from_suggestion(modWithNonStringAttr, 'blech'))
+
def test_import_from_suggestions_do_not_trigger_for_long_attributes(self):
code = "blech = None"
@@ -4391,6 +4413,15 @@ class SuggestionFormattingTestBase:
actual = self.get_suggestion(func)
self.assertIn("'ZeroDivisionError'?", actual)
+ def test_name_error_suggestions_with_non_string_candidates(self):
+ def func():
+ abc = 1
+ custom_globals = globals().copy()
+ custom_globals[0] = 1
+ print(eval("abv", custom_globals, locals()))
+ actual = self.get_suggestion(func)
+ self.assertIn("abc", actual)
+
def test_name_error_suggestions_do_not_trigger_for_long_names(self):
def func():
somethingverywronghehehehehehe = None
@@ -4721,6 +4752,8 @@ class MiscTest(unittest.TestCase):
class TestColorizedTraceback(unittest.TestCase):
+ maxDiff = None
+
def test_colorized_traceback(self):
def foo(*args):
x = {'a':{'b': None}}
@@ -4743,9 +4776,9 @@ class TestColorizedTraceback(unittest.TestCase):
e, capture_locals=True
)
lines = "".join(exc.format(colorize=True))
- red = _colorize.ANSIColors.RED
- boldr = _colorize.ANSIColors.BOLD_RED
- reset = _colorize.ANSIColors.RESET
+ red = colors["e"]
+ boldr = colors["E"]
+ reset = colors["z"]
self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines)
self.assertIn("return " + red + "(lambda *args: foo(*args))" + reset + boldr + "(1,2,3,4)" + reset, lines)
self.assertIn("return (lambda *args: " + red + "foo" + reset + boldr + "(*args)" + reset + ")(1,2,3,4)", lines)
@@ -4761,18 +4794,16 @@ class TestColorizedTraceback(unittest.TestCase):
e, capture_locals=True
)
actual = "".join(exc.format(colorize=True))
- red = _colorize.ANSIColors.RED
- magenta = _colorize.ANSIColors.MAGENTA
- boldm = _colorize.ANSIColors.BOLD_MAGENTA
- boldr = _colorize.ANSIColors.BOLD_RED
- reset = _colorize.ANSIColors.RESET
- expected = "".join([
- f' File {magenta}"<string>"{reset}, line {magenta}1{reset}\n',
- f' a {boldr}${reset} b\n',
- f' {boldr}^{reset}\n',
- f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n']
- )
- self.assertIn(expected, actual)
+ def expected(t, m, fn, l, f, E, e, z):
+ return "".join(
+ [
+ f' File {fn}"<string>"{z}, line {l}1{z}\n',
+ f' a {E}${z} b\n',
+ f' {E}^{z}\n',
+ f'{t}SyntaxError{z}: {m}invalid syntax{z}\n'
+ ]
+ )
+ self.assertIn(expected(**colors), actual)
def test_colorized_traceback_is_the_default(self):
def foo():
@@ -4788,23 +4819,21 @@ class TestColorizedTraceback(unittest.TestCase):
exception_print(e)
actual = tbstderr.getvalue().splitlines()
- red = _colorize.ANSIColors.RED
- boldr = _colorize.ANSIColors.BOLD_RED
- magenta = _colorize.ANSIColors.MAGENTA
- boldm = _colorize.ANSIColors.BOLD_MAGENTA
- reset = _colorize.ANSIColors.RESET
lno_foo = foo.__code__.co_firstlineno
- expected = ['Traceback (most recent call last):',
- f' File {magenta}"{__file__}"{reset}, '
- f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}',
- f' {red}foo{reset+boldr}(){reset}',
- f' {red}~~~{reset+boldr}^^{reset}',
- f' File {magenta}"{__file__}"{reset}, '
- f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}',
- f' {red}1{reset+boldr}/{reset+red}0{reset}',
- f' {red}~{reset+boldr}^{reset+red}~{reset}',
- f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}']
- self.assertEqual(actual, expected)
+ def expected(t, m, fn, l, f, E, e, z):
+ return [
+ 'Traceback (most recent call last):',
+ f' File {fn}"{__file__}"{z}, '
+ f'line {l}{lno_foo+5}{z}, in {f}test_colorized_traceback_is_the_default{z}',
+ f' {e}foo{z}{E}(){z}',
+ f' {e}~~~{z}{E}^^{z}',
+ f' File {fn}"{__file__}"{z}, '
+ f'line {l}{lno_foo+1}{z}, in {f}foo{z}',
+ f' {e}1{z}{E}/{z}{e}0{z}',
+ f' {e}~{z}{E}^{z}{e}~{z}',
+ f'{t}ZeroDivisionError{z}: {m}division by zero{z}',
+ ]
+ self.assertEqual(actual, expected(**colors))
def test_colorized_traceback_from_exception_group(self):
def foo():
@@ -4822,33 +4851,31 @@ class TestColorizedTraceback(unittest.TestCase):
e, capture_locals=True
)
- red = _colorize.ANSIColors.RED
- boldr = _colorize.ANSIColors.BOLD_RED
- magenta = _colorize.ANSIColors.MAGENTA
- boldm = _colorize.ANSIColors.BOLD_MAGENTA
- reset = _colorize.ANSIColors.RESET
lno_foo = foo.__code__.co_firstlineno
actual = "".join(exc.format(colorize=True)).splitlines()
- expected = [f" + Exception Group Traceback (most recent call last):",
- f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}',
- f' | {red}foo{reset}{boldr}(){reset}',
- f' | {red}~~~{reset}{boldr}^^{reset}',
- f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
- f" | foo = {foo}",
- f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
- f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}',
- f' | raise ExceptionGroup("test", exceptions)',
- f" | exceptions = [ZeroDivisionError('division by zero')]",
- f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset}',
- f' +-+---------------- 1 ----------------',
- f' | Traceback (most recent call last):',
- f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset}',
- f' | {red}1 {reset}{boldr}/{reset}{red} 0{reset}',
- f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset}',
- f" | exceptions = [ZeroDivisionError('division by zero')]",
- f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}',
- f' +------------------------------------']
- self.assertEqual(actual, expected)
+ def expected(t, m, fn, l, f, E, e, z):
+ return [
+ f" + Exception Group Traceback (most recent call last):",
+ f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+9}{z}, in {f}test_colorized_traceback_from_exception_group{z}',
+ f' | {e}foo{z}{E}(){z}',
+ f' | {e}~~~{z}{E}^^{z}',
+ f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])",
+ f" | foo = {foo}",
+ f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>',
+ f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+6}{z}, in {f}foo{z}',
+ f' | raise ExceptionGroup("test", exceptions)',
+ f" | exceptions = [ZeroDivisionError('division by zero')]",
+ f' | {t}ExceptionGroup{z}: {m}test (1 sub-exception){z}',
+ f' +-+---------------- 1 ----------------',
+ f' | Traceback (most recent call last):',
+ f' | File {fn}"{__file__}"{z}, line {l}{lno_foo+3}{z}, in {f}foo{z}',
+ f' | {e}1 {z}{E}/{z}{e} 0{z}',
+ f' | {e}~~{z}{E}^{z}{e}~~{z}',
+ f" | exceptions = [ZeroDivisionError('division by zero')]",
+ f' | {t}ZeroDivisionError{z}: {m}division by zero{z}',
+ f' +------------------------------------',
+ ]
+ self.assertEqual(actual, expected(**colors))
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_tstring.py b/Lib/test/test_tstring.py
index e72a1ea5417..aabae385567 100644
--- a/Lib/test/test_tstring.py
+++ b/Lib/test/test_tstring.py
@@ -219,6 +219,7 @@ class TestTString(unittest.TestCase, TStringBaseCase):
("t'{lambda:1}'", "t-string: lambda expressions are not allowed "
"without parentheses"),
("t'{x:{;}}'", "t-string: expecting a valid expression after '{'"),
+ ("t'{1:d\n}'", "t-string: newlines are not allowed in format specifiers")
):
with self.subTest(case), self.assertRaisesRegex(SyntaxError, err):
eval(case)
diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py
index 2c886bb6d36..c66cb058552 100644
--- a/Lib/test/test_type_annotations.py
+++ b/Lib/test/test_type_annotations.py
@@ -498,6 +498,28 @@ class DeferredEvaluationTests(unittest.TestCase):
self.assertEqual(f.__annotate__(annotationlib.Format.VALUE), annos)
self.assertEqual(f.__annotations__, annos)
+ def test_set_annotations(self):
+ function_code = textwrap.dedent("""
+ def f(x: int):
+ pass
+ """)
+ class_code = textwrap.dedent("""
+ class f:
+ x: int
+ """)
+ for future in (False, True):
+ for label, code in (("function", function_code), ("class", class_code)):
+ with self.subTest(future=future, label=label):
+ if future:
+ code = "from __future__ import annotations\n" + code
+ ns = run_code(code)
+ f = ns["f"]
+ anno = "int" if future else int
+ self.assertEqual(f.__annotations__, {"x": anno})
+
+ f.__annotations__ = {"x": str}
+ self.assertEqual(f.__annotations__, {"x": str})
+
def test_name_clash_with_format(self):
# this test would fail if __annotate__'s parameter was called "format"
# during symbol table construction
diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py
index ee8939f62d0..c40c45594f4 100644
--- a/Lib/test/test_type_comments.py
+++ b/Lib/test/test_type_comments.py
@@ -344,7 +344,7 @@ class TypeCommentTests(unittest.TestCase):
todo = set(t.name[1:])
self.assertEqual(len(t.args.args) + len(t.args.posonlyargs),
len(todo) - bool(t.args.vararg) - bool(t.args.kwarg))
- self.assertTrue(t.name.startswith('f'), t.name)
+ self.assertStartsWith(t.name, 'f')
for index, c in enumerate(t.name[1:]):
todo.remove(c)
if c == 'v':
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index 3552b6b4ef8..fccdcc975e6 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -2,7 +2,7 @@
from test.support import (
run_with_locale, cpython_only, no_rerun,
- MISSING_C_DOCSTRINGS, EqualToForwardRef,
+ MISSING_C_DOCSTRINGS, EqualToForwardRef, check_disallow_instantiation,
)
from test.support.script_helper import assert_python_ok
from test.support.import_helper import import_fresh_module
@@ -21,6 +21,7 @@ import types
import unittest.mock
import weakref
import typing
+import re
c_types = import_fresh_module('types', fresh=['_types'])
py_types = import_fresh_module('types', blocked=['_types'])
@@ -517,8 +518,8 @@ class TypesTests(unittest.TestCase):
# and a number after the decimal. This is tricky, because
# a totally empty format specifier means something else.
# So, just use a sign flag
- test(1e200, '+g', '+1e+200')
- test(1e200, '+', '+1e+200')
+ test(1.25e200, '+g', '+1.25e+200')
+ test(1.25e200, '+', '+1.25e+200')
test(1.1e200, '+g', '+1.1e+200')
test(1.1e200, '+', '+1.1e+200')
@@ -827,15 +828,15 @@ class UnionTests(unittest.TestCase):
self.assertIsInstance(True, x)
self.assertIsInstance('a', x)
self.assertNotIsInstance(None, x)
- self.assertTrue(issubclass(int, x))
- self.assertTrue(issubclass(bool, x))
- self.assertTrue(issubclass(str, x))
- self.assertFalse(issubclass(type(None), x))
+ self.assertIsSubclass(int, x)
+ self.assertIsSubclass(bool, x)
+ self.assertIsSubclass(str, x)
+ self.assertNotIsSubclass(type(None), x)
for x in (int | None, typing.Union[int, None]):
with self.subTest(x=x):
self.assertIsInstance(None, x)
- self.assertTrue(issubclass(type(None), x))
+ self.assertIsSubclass(type(None), x)
for x in (
int | collections.abc.Mapping,
@@ -844,8 +845,8 @@ class UnionTests(unittest.TestCase):
with self.subTest(x=x):
self.assertIsInstance({}, x)
self.assertNotIsInstance((), x)
- self.assertTrue(issubclass(dict, x))
- self.assertFalse(issubclass(list, x))
+ self.assertIsSubclass(dict, x)
+ self.assertNotIsSubclass(list, x)
def test_instancecheck_and_subclasscheck_order(self):
T = typing.TypeVar('T')
@@ -857,7 +858,7 @@ class UnionTests(unittest.TestCase):
for x in will_resolve:
with self.subTest(x=x):
self.assertIsInstance(1, x)
- self.assertTrue(issubclass(int, x))
+ self.assertIsSubclass(int, x)
wont_resolve = (
T | int,
@@ -890,7 +891,7 @@ class UnionTests(unittest.TestCase):
def __subclasscheck__(cls, sub):
1/0
x = int | BadMeta('A', (), {})
- self.assertTrue(issubclass(int, x))
+ self.assertIsSubclass(int, x)
self.assertRaises(ZeroDivisionError, issubclass, list, x)
def test_or_type_operator_with_TypeVar(self):
@@ -1148,8 +1149,7 @@ class UnionTests(unittest.TestCase):
msg='Check for union reference leak.')
def test_instantiation(self):
- with self.assertRaises(TypeError):
- types.UnionType()
+ check_disallow_instantiation(self, types.UnionType)
self.assertIs(int, types.UnionType[int])
self.assertIs(int, types.UnionType[int, int])
self.assertEqual(int | str, types.UnionType[int, str])
@@ -1376,6 +1376,27 @@ class MappingProxyTests(unittest.TestCase):
view = self.mappingproxy(mapping)
self.assertEqual(hash(view), hash(mapping))
+ def test_richcompare(self):
+ mp1 = self.mappingproxy({'a': 1})
+ mp1_2 = self.mappingproxy({'a': 1})
+ mp2 = self.mappingproxy({'a': 2})
+
+ self.assertTrue(mp1 == mp1_2)
+ self.assertFalse(mp1 != mp1_2)
+ self.assertFalse(mp1 == mp2)
+ self.assertTrue(mp1 != mp2)
+
+ msg = "not supported between instances of 'mappingproxy' and 'mappingproxy'"
+
+ with self.assertRaisesRegex(TypeError, msg):
+ mp1 > mp2
+ with self.assertRaisesRegex(TypeError, msg):
+ mp1 < mp1_2
+ with self.assertRaisesRegex(TypeError, msg):
+ mp2 >= mp2
+ with self.assertRaisesRegex(TypeError, msg):
+ mp1_2 <= mp1
+
class ClassCreationTests(unittest.TestCase):
@@ -1399,7 +1420,7 @@ class ClassCreationTests(unittest.TestCase):
def test_new_class_subclass(self):
C = types.new_class("C", (int,))
- self.assertTrue(issubclass(C, int))
+ self.assertIsSubclass(C, int)
def test_new_class_meta(self):
Meta = self.Meta
@@ -1444,7 +1465,7 @@ class ClassCreationTests(unittest.TestCase):
bases=(int,),
kwds=dict(metaclass=Meta, z=2),
exec_body=func)
- self.assertTrue(issubclass(C, int))
+ self.assertIsSubclass(C, int)
self.assertIsInstance(C, Meta)
self.assertEqual(C.x, 0)
self.assertEqual(C.y, 1)
@@ -2010,6 +2031,24 @@ class SimpleNamespaceTests(unittest.TestCase):
self.assertEqual(ns1, ns2)
self.assertNotEqual(ns2, types.SimpleNamespace())
+ def test_richcompare_unsupported(self):
+ ns1 = types.SimpleNamespace(x=1)
+ ns2 = types.SimpleNamespace(y=2)
+
+ msg = re.escape(
+ "not supported between instances of "
+ "'types.SimpleNamespace' and 'types.SimpleNamespace'"
+ )
+
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 > ns2
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 >= ns2
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 < ns2
+ with self.assertRaisesRegex(TypeError, msg):
+ ns1 <= ns2
+
def test_nested(self):
ns1 = types.SimpleNamespace(a=1, b=2)
ns2 = types.SimpleNamespace()
@@ -2513,15 +2552,16 @@ class SubinterpreterTests(unittest.TestCase):
def setUpClass(cls):
global interpreters
try:
- from test.support import interpreters
+ from concurrent import interpreters
except ModuleNotFoundError:
raise unittest.SkipTest('subinterpreters required')
- import test.support.interpreters.channels
+ from test.support import channels # noqa: F401
+ cls.create_channel = staticmethod(channels.create)
@cpython_only
@no_rerun('channels (and queues) might have a refleak; see gh-122199')
def test_static_types_inherited_slots(self):
- rch, sch = interpreters.channels.create()
+ rch, sch = self.create_channel()
script = textwrap.dedent("""
import test.support
@@ -2547,7 +2587,7 @@ class SubinterpreterTests(unittest.TestCase):
main_results = collate_results(raw)
interp = interpreters.create()
- interp.exec('from test.support import interpreters')
+ interp.exec('from concurrent import interpreters')
interp.prepare_main(sch=sch)
interp.exec(script)
raw = rch.recv_nowait()
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 8c55ba4623e..ef02e8202fc 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -46,11 +46,10 @@ import abc
import textwrap
import typing
import weakref
-import warnings
import types
from test.support import (
- captured_stderr, cpython_only, infinite_recursion, requires_docstrings, import_helper, run_code,
+ captured_stderr, cpython_only, requires_docstrings, import_helper, run_code,
EqualToForwardRef,
)
from test.typinganndata import (
@@ -6859,12 +6858,10 @@ class GetTypeHintsTests(BaseTestCase):
self.assertEqual(hints, {'value': Final})
def test_top_level_class_var(self):
- # https://bugs.python.org/issue45166
- with self.assertRaisesRegex(
- TypeError,
- r'typing.ClassVar\[int\] is not valid as type argument',
- ):
- get_type_hints(ann_module6)
+ # This is not meaningful but we don't raise for it.
+ # https://github.com/python/cpython/issues/133959
+ hints = get_type_hints(ann_module6)
+ self.assertEqual(hints, {'wrong': ClassVar[int]})
def test_get_type_hints_typeddict(self):
self.assertEqual(get_type_hints(TotalMovie), {'title': str, 'year': int})
@@ -6967,6 +6964,11 @@ class GetTypeHintsTests(BaseTestCase):
self.assertEqual(get_type_hints(foo, globals(), locals()),
{'a': Callable[..., T]})
+ def test_special_forms_no_forward(self):
+ def f(x: ClassVar[int]):
+ pass
+ self.assertEqual(get_type_hints(f), {'x': ClassVar[int]})
+
def test_special_forms_forward(self):
class C:
@@ -6982,8 +6984,9 @@ class GetTypeHintsTests(BaseTestCase):
self.assertEqual(get_type_hints(C, globals())['b'], Final[int])
self.assertEqual(get_type_hints(C, globals())['x'], ClassVar)
self.assertEqual(get_type_hints(C, globals())['y'], Final)
- with self.assertRaises(TypeError):
- get_type_hints(CF, globals()),
+ lfi = get_type_hints(CF, globals())['b']
+ self.assertIs(get_origin(lfi), list)
+ self.assertEqual(get_args(lfi), (Final[int],))
def test_union_forward_recursion(self):
ValueList = List['Value']
@@ -7216,33 +7219,113 @@ class GetUtilitiesTestCase(TestCase):
class EvaluateForwardRefTests(BaseTestCase):
def test_evaluate_forward_ref(self):
int_ref = ForwardRef('int')
- missing = ForwardRef('missing')
+ self.assertIs(typing.evaluate_forward_ref(int_ref), int)
self.assertIs(
typing.evaluate_forward_ref(int_ref, type_params=()),
int,
)
self.assertIs(
+ typing.evaluate_forward_ref(int_ref, format=annotationlib.Format.VALUE),
+ int,
+ )
+ self.assertIs(
typing.evaluate_forward_ref(
- int_ref, type_params=(), format=annotationlib.Format.FORWARDREF,
+ int_ref, format=annotationlib.Format.FORWARDREF,
),
int,
)
+ self.assertEqual(
+ typing.evaluate_forward_ref(
+ int_ref, format=annotationlib.Format.STRING,
+ ),
+ 'int',
+ )
+
+ def test_evaluate_forward_ref_undefined(self):
+ missing = ForwardRef('missing')
+ with self.assertRaises(NameError):
+ typing.evaluate_forward_ref(missing)
self.assertIs(
typing.evaluate_forward_ref(
- missing, type_params=(), format=annotationlib.Format.FORWARDREF,
+ missing, format=annotationlib.Format.FORWARDREF,
),
missing,
)
self.assertEqual(
typing.evaluate_forward_ref(
- int_ref, type_params=(), format=annotationlib.Format.STRING,
+ missing, format=annotationlib.Format.STRING,
),
- 'int',
+ "missing",
)
- def test_evaluate_forward_ref_no_type_params(self):
- ref = ForwardRef('int')
- self.assertIs(typing.evaluate_forward_ref(ref), int)
+ def test_evaluate_forward_ref_nested(self):
+ ref = ForwardRef("int | list['str']")
+ self.assertEqual(
+ typing.evaluate_forward_ref(ref),
+ int | list[str],
+ )
+ self.assertEqual(
+ typing.evaluate_forward_ref(ref, format=annotationlib.Format.FORWARDREF),
+ int | list[str],
+ )
+ self.assertEqual(
+ typing.evaluate_forward_ref(ref, format=annotationlib.Format.STRING),
+ "int | list['str']",
+ )
+
+ why = ForwardRef('"\'str\'"')
+ self.assertIs(typing.evaluate_forward_ref(why), str)
+
+ def test_evaluate_forward_ref_none(self):
+ none_ref = ForwardRef('None')
+ self.assertIs(typing.evaluate_forward_ref(none_ref), None)
+
+ def test_globals(self):
+ A = "str"
+ ref = ForwardRef('list[A]')
+ with self.assertRaises(NameError):
+ typing.evaluate_forward_ref(ref)
+ self.assertEqual(
+ typing.evaluate_forward_ref(ref, globals={'A': A}),
+ list[str],
+ )
+
+ def test_owner(self):
+ ref = ForwardRef("A")
+
+ with self.assertRaises(NameError):
+ typing.evaluate_forward_ref(ref)
+
+ # We default to the globals of `owner`,
+ # so it no longer raises `NameError`
+ self.assertIs(
+ typing.evaluate_forward_ref(ref, owner=Loop), A
+ )
+
+ def test_inherited_owner(self):
+ # owner passed to evaluate_forward_ref
+ ref = ForwardRef("list['A']")
+ self.assertEqual(
+ typing.evaluate_forward_ref(ref, owner=Loop),
+ list[A],
+ )
+
+ # owner set on the ForwardRef
+ ref = ForwardRef("list['A']", owner=Loop)
+ self.assertEqual(
+ typing.evaluate_forward_ref(ref),
+ list[A],
+ )
+
+ def test_partial_evaluation(self):
+ ref = ForwardRef("list[A]")
+ with self.assertRaises(NameError):
+ typing.evaluate_forward_ref(ref)
+
+ self.assertEqual(
+ typing.evaluate_forward_ref(ref, format=annotationlib.Format.FORWARDREF),
+ list[EqualToForwardRef('A')],
+ )
class CollectionsAbcTests(BaseTestCase):
@@ -8080,78 +8163,13 @@ class NamedTupleTests(BaseTestCase):
self.assertIs(type(a), Group)
self.assertEqual(a, (1, [2]))
- def test_namedtuple_keyword_usage(self):
- with self.assertWarnsRegex(
- DeprecationWarning,
- "Creating NamedTuple classes using keyword arguments is deprecated"
- ):
- LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
-
- nick = LocalEmployee('Nick', 25)
- self.assertIsInstance(nick, tuple)
- self.assertEqual(nick.name, 'Nick')
- self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
- self.assertEqual(LocalEmployee._fields, ('name', 'age'))
- self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int))
-
- with self.assertRaisesRegex(
- TypeError,
- "Either list of fields or keywords can be provided to NamedTuple, not both"
- ):
- NamedTuple('Name', [('x', int)], y=str)
-
- with self.assertRaisesRegex(
- TypeError,
- "Either list of fields or keywords can be provided to NamedTuple, not both"
- ):
- NamedTuple('Name', [], y=str)
-
- with self.assertRaisesRegex(
- TypeError,
- (
- r"Cannot pass `None` as the 'fields' parameter "
- r"and also specify fields using keyword arguments"
- )
- ):
- NamedTuple('Name', None, x=int)
-
- def test_namedtuple_special_keyword_names(self):
- with self.assertWarnsRegex(
- DeprecationWarning,
- "Creating NamedTuple classes using keyword arguments is deprecated"
- ):
- NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list)
-
- self.assertEqual(NT.__name__, 'NT')
- self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields'))
- a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)])
- self.assertEqual(a.cls, str)
- self.assertEqual(a.self, 42)
- self.assertEqual(a.typename, 'foo')
- self.assertEqual(a.fields, [('bar', tuple)])
-
def test_empty_namedtuple(self):
- expected_warning = re.escape(
- "Failing to pass a value for the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- NT1 = NamedTuple('NT1')
+ with self.assertRaisesRegex(TypeError, "missing.*required.*argument"):
+ BAD = NamedTuple('BAD')
- expected_warning = re.escape(
- "Passing `None` as the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- NT2 = NamedTuple('NT2', None)
-
- NT3 = NamedTuple('NT2', [])
+ NT1 = NamedTuple('NT1', {})
+ NT2 = NamedTuple('NT2', ())
+ NT3 = NamedTuple('NT3', [])
class CNT(NamedTuple):
pass # empty body
@@ -8166,16 +8184,18 @@ class NamedTupleTests(BaseTestCase):
def test_namedtuple_errors(self):
with self.assertRaises(TypeError):
NamedTuple.__new__()
+ with self.assertRaisesRegex(TypeError, "object is not iterable"):
+ NamedTuple('Name', None)
with self.assertRaisesRegex(
TypeError,
- "missing 1 required positional argument"
+ "missing 2 required positional arguments"
):
NamedTuple()
with self.assertRaisesRegex(
TypeError,
- "takes from 1 to 2 positional arguments but 3 were given"
+ "takes 2 positional arguments but 3 were given"
):
NamedTuple('Emp', [('name', str)], None)
@@ -8187,10 +8207,22 @@ class NamedTupleTests(BaseTestCase):
with self.assertRaisesRegex(
TypeError,
- "missing 1 required positional argument: 'typename'"
+ "got some positional-only arguments passed as keyword arguments"
):
NamedTuple(typename='Emp', name=str, id=int)
+ with self.assertRaisesRegex(
+ TypeError,
+ "got an unexpected keyword argument"
+ ):
+ NamedTuple('Name', [('x', int)], y=str)
+
+ with self.assertRaisesRegex(
+ TypeError,
+ "got an unexpected keyword argument"
+ ):
+ NamedTuple('Name', [], y=str)
+
def test_copy_and_pickle(self):
global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('cool', int)])
@@ -8538,6 +8570,36 @@ class TypedDictTests(BaseTestCase):
self.assertEqual(Child.__required_keys__, frozenset(['a']))
self.assertEqual(Child.__optional_keys__, frozenset())
+ def test_inheritance_pep563(self):
+ def _make_td(future, class_name, annos, base, extra_names=None):
+ lines = []
+ if future:
+ lines.append('from __future__ import annotations')
+ lines.append('from typing import TypedDict')
+ lines.append(f'class {class_name}({base}):')
+ for name, anno in annos.items():
+ lines.append(f' {name}: {anno}')
+ code = '\n'.join(lines)
+ ns = run_code(code, extra_names)
+ return ns[class_name]
+
+ for base_future in (True, False):
+ for child_future in (True, False):
+ with self.subTest(base_future=base_future, child_future=child_future):
+ base = _make_td(
+ base_future, "Base", {"base": "int"}, "TypedDict"
+ )
+ self.assertIsNotNone(base.__annotate__)
+ child = _make_td(
+ child_future, "Child", {"child": "int"}, "Base", {"Base": base}
+ )
+ base_anno = ForwardRef("int", module="builtins") if base_future else int
+ child_anno = ForwardRef("int", module="builtins") if child_future else int
+ self.assertEqual(base.__annotations__, {'base': base_anno})
+ self.assertEqual(
+ child.__annotations__, {'child': child_anno, 'base': base_anno}
+ )
+
def test_required_notrequired_keys(self):
self.assertEqual(NontotalMovie.__required_keys__,
frozenset({"title"}))
@@ -8904,39 +8966,27 @@ class TypedDictTests(BaseTestCase):
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
def test_zero_fields_typeddicts(self):
- T1 = TypedDict("T1", {})
+ T1a = TypedDict("T1a", {})
+ T1b = TypedDict("T1b", [])
+ T1c = TypedDict("T1c", ())
class T2(TypedDict): pass
class T3[tvar](TypedDict): pass
S = TypeVar("S")
class T4(TypedDict, Generic[S]): pass
- expected_warning = re.escape(
- "Failing to pass a value for the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- T5 = TypedDict('T5')
-
- expected_warning = re.escape(
- "Passing `None` as the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- T6 = TypedDict('T6', None)
-
- for klass in T1, T2, T3, T4, T5, T6:
+ for klass in T1a, T1b, T1c, T2, T3, T4:
with self.subTest(klass=klass.__name__):
self.assertEqual(klass.__annotations__, {})
self.assertEqual(klass.__required_keys__, set())
self.assertEqual(klass.__optional_keys__, set())
self.assertIsInstance(klass(), dict)
+ def test_errors(self):
+ with self.assertRaisesRegex(TypeError, "missing 1 required.*argument"):
+ TypedDict('TD')
+ with self.assertRaisesRegex(TypeError, "object is not iterable"):
+ TypedDict('TD', None)
+
def test_readonly_inheritance(self):
class Base1(TypedDict):
a: ReadOnly[int]
@@ -10731,6 +10781,9 @@ class UnionGenericAliasTests(BaseTestCase):
with self.assertWarns(DeprecationWarning):
self.assertNotEqual(int, typing._UnionGenericAlias)
+ def test_hashable(self):
+ self.assertEqual(hash(typing._UnionGenericAlias), hash(Union))
+
def load_tests(loader, tests, pattern):
import doctest
diff --git a/Lib/test/test_unittest/test_case.py b/Lib/test/test_unittest/test_case.py
index a04af55f3fc..cf10e956bf2 100644
--- a/Lib/test/test_unittest/test_case.py
+++ b/Lib/test/test_unittest/test_case.py
@@ -1920,6 +1920,22 @@ test case
with self.assertLogs():
raise ZeroDivisionError("Unexpected")
+ def testAssertLogsWithFormatter(self):
+ # Check alternative formats will be respected
+ format = "[No.1: the larch] %(levelname)s:%(name)s:%(message)s"
+ formatter = logging.Formatter(format)
+ with self.assertNoStderr():
+ with self.assertLogs() as cm:
+ log_foo.info("1")
+ log_foobar.debug("2")
+ self.assertEqual(cm.output, ["INFO:foo:1"])
+ self.assertLogRecords(cm.records, [{'name': 'foo'}])
+ with self.assertLogs(formatter=formatter) as cm:
+ log_foo.info("1")
+ log_foobar.debug("2")
+ self.assertEqual(cm.output, ["[No.1: the larch] INFO:foo:1"])
+ self.assertLogRecords(cm.records, [{'name': 'foo'}])
+
def testAssertNoLogsDefault(self):
with self.assertRaises(self.failureException) as cm:
with self.assertNoLogs():
@@ -1989,7 +2005,7 @@ test case
pass
self.assertIsNone(value)
- def testAssertStartswith(self):
+ def testAssertStartsWith(self):
self.assertStartsWith('ababahalamaha', 'ababa')
self.assertStartsWith('ababahalamaha', ('x', 'ababa', 'y'))
self.assertStartsWith(UserString('ababahalamaha'), 'ababa')
@@ -2034,7 +2050,7 @@ test case
self.assertStartsWith('ababahalamaha', 'amaha', msg='abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
- def testAssertNotStartswith(self):
+ def testAssertNotStartsWith(self):
self.assertNotStartsWith('ababahalamaha', 'amaha')
self.assertNotStartsWith('ababahalamaha', ('x', 'amaha', 'y'))
self.assertNotStartsWith(UserString('ababahalamaha'), 'amaha')
@@ -2079,7 +2095,7 @@ test case
self.assertNotStartsWith('ababahalamaha', 'ababa', msg='abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
- def testAssertEndswith(self):
+ def testAssertEndsWith(self):
self.assertEndsWith('ababahalamaha', 'amaha')
self.assertEndsWith('ababahalamaha', ('x', 'amaha', 'y'))
self.assertEndsWith(UserString('ababahalamaha'), 'amaha')
@@ -2124,7 +2140,7 @@ test case
self.assertEndsWith('ababahalamaha', 'ababa', msg='abracadabra')
self.assertIn('ababahalamaha', str(cm.exception))
- def testAssertNotEndswith(self):
+ def testAssertNotEndsWith(self):
self.assertNotEndsWith('ababahalamaha', 'ababa')
self.assertNotEndsWith('ababahalamaha', ('x', 'ababa', 'y'))
self.assertNotEndsWith(UserString('ababahalamaha'), 'ababa')
diff --git a/Lib/test/test_unittest/test_result.py b/Lib/test/test_unittest/test_result.py
index 9ac4c52449c..3f44e617303 100644
--- a/Lib/test/test_unittest/test_result.py
+++ b/Lib/test/test_unittest/test_result.py
@@ -1282,14 +1282,22 @@ class TestOutputBuffering(unittest.TestCase):
suite(result)
expected_out = '\nStdout:\ndo cleanup2\ndo cleanup1\n'
self.assertEqual(stdout.getvalue(), expected_out)
- self.assertEqual(len(result.errors), 1)
+ self.assertEqual(len(result.errors), 2)
description = 'tearDownModule (Module)'
test_case, formatted_exc = result.errors[0]
self.assertEqual(test_case.description, description)
self.assertIn('ValueError: bad cleanup2', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
self.assertNotIn('TypeError', formatted_exc)
self.assertIn(expected_out, formatted_exc)
+ test_case, formatted_exc = result.errors[1]
+ self.assertEqual(test_case.description, description)
+ self.assertIn('TypeError: bad cleanup1', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
+ self.assertNotIn('ValueError', formatted_exc)
+ self.assertIn(expected_out, formatted_exc)
+
def testBufferSetUpModule_DoModuleCleanups(self):
with captured_stdout() as stdout:
result = unittest.TestResult()
@@ -1313,22 +1321,34 @@ class TestOutputBuffering(unittest.TestCase):
suite(result)
expected_out = '\nStdout:\nset up module\ndo cleanup2\ndo cleanup1\n'
self.assertEqual(stdout.getvalue(), expected_out)
- self.assertEqual(len(result.errors), 2)
+ self.assertEqual(len(result.errors), 3)
description = 'setUpModule (Module)'
test_case, formatted_exc = result.errors[0]
self.assertEqual(test_case.description, description)
self.assertIn('ZeroDivisionError: division by zero', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
self.assertNotIn('ValueError', formatted_exc)
self.assertNotIn('TypeError', formatted_exc)
self.assertIn('\nStdout:\nset up module\n', formatted_exc)
+
test_case, formatted_exc = result.errors[1]
self.assertIn(expected_out, formatted_exc)
self.assertEqual(test_case.description, description)
self.assertIn('ValueError: bad cleanup2', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
self.assertNotIn('ZeroDivisionError', formatted_exc)
self.assertNotIn('TypeError', formatted_exc)
self.assertIn(expected_out, formatted_exc)
+ test_case, formatted_exc = result.errors[2]
+ self.assertIn(expected_out, formatted_exc)
+ self.assertEqual(test_case.description, description)
+ self.assertIn('TypeError: bad cleanup1', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
+ self.assertNotIn('ZeroDivisionError', formatted_exc)
+ self.assertNotIn('ValueError', formatted_exc)
+ self.assertIn(expected_out, formatted_exc)
+
def testBufferTearDownModule_DoModuleCleanups(self):
with captured_stdout() as stdout:
result = unittest.TestResult()
@@ -1355,21 +1375,32 @@ class TestOutputBuffering(unittest.TestCase):
suite(result)
expected_out = '\nStdout:\ntear down module\ndo cleanup2\ndo cleanup1\n'
self.assertEqual(stdout.getvalue(), expected_out)
- self.assertEqual(len(result.errors), 2)
+ self.assertEqual(len(result.errors), 3)
description = 'tearDownModule (Module)'
test_case, formatted_exc = result.errors[0]
self.assertEqual(test_case.description, description)
self.assertIn('ZeroDivisionError: division by zero', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
self.assertNotIn('ValueError', formatted_exc)
self.assertNotIn('TypeError', formatted_exc)
self.assertIn('\nStdout:\ntear down module\n', formatted_exc)
+
test_case, formatted_exc = result.errors[1]
self.assertEqual(test_case.description, description)
self.assertIn('ValueError: bad cleanup2', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
self.assertNotIn('ZeroDivisionError', formatted_exc)
self.assertNotIn('TypeError', formatted_exc)
self.assertIn(expected_out, formatted_exc)
+ test_case, formatted_exc = result.errors[2]
+ self.assertEqual(test_case.description, description)
+ self.assertIn('TypeError: bad cleanup1', formatted_exc)
+ self.assertNotIn('ExceptionGroup', formatted_exc)
+ self.assertNotIn('ZeroDivisionError', formatted_exc)
+ self.assertNotIn('ValueError', formatted_exc)
+ self.assertIn(expected_out, formatted_exc)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_unittest/test_runner.py b/Lib/test/test_unittest/test_runner.py
index 4d3cfd60b8d..a47e2ebb59d 100644
--- a/Lib/test/test_unittest/test_runner.py
+++ b/Lib/test/test_unittest/test_runner.py
@@ -13,6 +13,7 @@ from test.test_unittest.support import (
LoggingResult,
ResultWithNoStartTestRunStopTestRun,
)
+from test.support.testcase import ExceptionIsLikeMixin
def resultFactory(*_):
@@ -604,7 +605,7 @@ class TestClassCleanup(unittest.TestCase):
@support.force_not_colorized_test_class
-class TestModuleCleanUp(unittest.TestCase):
+class TestModuleCleanUp(ExceptionIsLikeMixin, unittest.TestCase):
def test_add_and_do_ModuleCleanup(self):
module_cleanups = []
@@ -646,11 +647,50 @@ class TestModuleCleanUp(unittest.TestCase):
[(module_cleanup_good, (1, 2, 3),
dict(four='hello', five='goodbye')),
(module_cleanup_bad, (), {})])
- with self.assertRaises(CustomError) as e:
+ with self.assertRaises(Exception) as e:
unittest.case.doModuleCleanups()
- self.assertEqual(str(e.exception), 'CleanUpExc')
+ self.assertExceptionIsLike(e.exception,
+ ExceptionGroup('module cleanup failed',
+ [CustomError('CleanUpExc')]))
self.assertEqual(unittest.case._module_cleanups, [])
+ def test_doModuleCleanup_with_multiple_errors_in_addModuleCleanup(self):
+ def module_cleanup_bad1():
+ raise TypeError('CleanUpExc1')
+
+ def module_cleanup_bad2():
+ raise ValueError('CleanUpExc2')
+
+ class Module:
+ unittest.addModuleCleanup(module_cleanup_bad1)
+ unittest.addModuleCleanup(module_cleanup_bad2)
+ with self.assertRaises(ExceptionGroup) as e:
+ unittest.case.doModuleCleanups()
+ self.assertExceptionIsLike(e.exception,
+ ExceptionGroup('module cleanup failed', [
+ ValueError('CleanUpExc2'),
+ TypeError('CleanUpExc1'),
+ ]))
+
+ def test_doModuleCleanup_with_exception_group_in_addModuleCleanup(self):
+ def module_cleanup_bad():
+ raise ExceptionGroup('CleanUpExc', [
+ ValueError('CleanUpExc2'),
+ TypeError('CleanUpExc1'),
+ ])
+
+ class Module:
+ unittest.addModuleCleanup(module_cleanup_bad)
+ with self.assertRaises(ExceptionGroup) as e:
+ unittest.case.doModuleCleanups()
+ self.assertExceptionIsLike(e.exception,
+ ExceptionGroup('module cleanup failed', [
+ ExceptionGroup('CleanUpExc', [
+ ValueError('CleanUpExc2'),
+ TypeError('CleanUpExc1'),
+ ]),
+ ]))
+
def test_addModuleCleanup_arg_errors(self):
cleanups = []
def cleanup(*args, **kwargs):
@@ -871,9 +911,11 @@ class TestModuleCleanUp(unittest.TestCase):
ordering = []
blowUp = True
suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestableTest)
- with self.assertRaises(CustomError) as cm:
+ with self.assertRaises(Exception) as cm:
suite.debug()
- self.assertEqual(str(cm.exception), 'CleanUpExc')
+ self.assertExceptionIsLike(cm.exception,
+ ExceptionGroup('module cleanup failed',
+ [CustomError('CleanUpExc')]))
self.assertEqual(ordering, ['setUpModule', 'setUpClass', 'test',
'tearDownClass', 'tearDownModule', 'cleanup_exc'])
self.assertEqual(unittest.case._module_cleanups, [])
diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py
index d1e48bde982..0e82c723ec3 100644
--- a/Lib/test/test_unittest/testmock/testhelpers.py
+++ b/Lib/test/test_unittest/testmock/testhelpers.py
@@ -1050,6 +1050,7 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithPostInit()),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithPostInit)
self.assertIsInstance(mock.a, int)
self.assertIsInstance(mock.b, int)
@@ -1072,6 +1073,7 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithDefault(1)),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithDefault)
self.assertIsInstance(mock.a, int)
self.assertIsInstance(mock.b, int)
@@ -1087,6 +1089,7 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithMethod(1)),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithMethod)
self.assertIsInstance(mock.a, int)
mock.b.assert_not_called()
@@ -1102,11 +1105,29 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithNonFields(1)),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithNonFields)
with self.assertRaisesRegex(AttributeError, msg):
mock.a
with self.assertRaisesRegex(AttributeError, msg):
mock.b
+ def test_dataclass_special_attrs(self):
+ @dataclass
+ class Description:
+ name: str
+
+ for mock in [
+ create_autospec(Description, instance=True),
+ create_autospec(Description(1)),
+ ]:
+ with self.subTest(mock=mock):
+ self.assertIsInstance(mock, Description)
+ self.assertIs(mock.__class__, Description)
+ self.assertIsInstance(mock.__dataclass_fields__, MagicMock)
+ self.assertIsInstance(mock.__dataclass_params__, MagicMock)
+ self.assertIsInstance(mock.__match_args__, MagicMock)
+ self.assertIsInstance(mock.__hash__, MagicMock)
+
class TestCallList(unittest.TestCase):
def test_args_list_contains_call_list(self):
diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py
index d3af7a8489e..d4db5e60af7 100644
--- a/Lib/test/test_unparse.py
+++ b/Lib/test/test_unparse.py
@@ -817,6 +817,15 @@ class CosmeticTestCase(ASTTestCase):
self.check_ast_roundtrip("def f[T: int = int, **P = int, *Ts = *int]():\n pass")
self.check_ast_roundtrip("class C[T: int = int, **P = int, *Ts = *int]():\n pass")
+ def test_tstr(self):
+ self.check_ast_roundtrip("t'{a + b}'")
+ self.check_ast_roundtrip("t'{a + b:x}'")
+ self.check_ast_roundtrip("t'{a + b!s}'")
+ self.check_ast_roundtrip("t'{ {a}}'")
+ self.check_ast_roundtrip("t'{ {a}=}'")
+ self.check_ast_roundtrip("t'{{a}}'")
+ self.check_ast_roundtrip("t''")
+
class ManualASTCreationTestCase(unittest.TestCase):
"""Test that AST nodes created without a type_params field unparse correctly."""
diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py
index 90de828cc71..1d889ae7cf4 100644
--- a/Lib/test/test_urllib.py
+++ b/Lib/test/test_urllib.py
@@ -109,7 +109,7 @@ class urlopen_FileTests(unittest.TestCase):
finally:
f.close()
self.pathname = os_helper.TESTFN
- self.quoted_pathname = urllib.parse.quote(self.pathname)
+ self.quoted_pathname = urllib.parse.quote(os.fsencode(self.pathname))
self.returned_obj = urllib.request.urlopen("file:%s" % self.quoted_pathname)
def tearDown(self):
@@ -1551,7 +1551,8 @@ class Pathname_Tests(unittest.TestCase):
urllib.request.url2pathname(url, require_scheme=True),
expected_path)
- error_subtests = [
+ def test_url2pathname_require_scheme_errors(self):
+ subtests = [
'',
':',
'foo',
@@ -1561,13 +1562,21 @@ class Pathname_Tests(unittest.TestCase):
'data:file:foo',
'data:file://foo',
]
- for url in error_subtests:
+ for url in subtests:
with self.subTest(url=url):
self.assertRaises(
urllib.error.URLError,
urllib.request.url2pathname,
url, require_scheme=True)
+ @unittest.skipIf(support.is_emscripten, "Fixed by https://github.com/emscripten-core/emscripten/pull/24593")
+ def test_url2pathname_resolve_host(self):
+ fn = urllib.request.url2pathname
+ sep = os.path.sep
+ self.assertEqual(fn('//127.0.0.1/foo/bar', resolve_host=True), f'{sep}foo{sep}bar')
+ self.assertEqual(fn(f'//{socket.gethostname()}/foo/bar'), f'{sep}foo{sep}bar')
+ self.assertEqual(fn(f'//{socket.gethostname()}/foo/bar', resolve_host=True), f'{sep}foo{sep}bar')
+
@unittest.skipUnless(sys.platform == 'win32',
'test specific to Windows pathnames.')
def test_url2pathname_win(self):
@@ -1598,6 +1607,7 @@ class Pathname_Tests(unittest.TestCase):
self.assertEqual(fn('//server/path/to/file'), '\\\\server\\path\\to\\file')
self.assertEqual(fn('////server/path/to/file'), '\\\\server\\path\\to\\file')
self.assertEqual(fn('/////server/path/to/file'), '\\\\server\\path\\to\\file')
+ self.assertEqual(fn('//127.0.0.1/path/to/file'), '\\\\127.0.0.1\\path\\to\\file')
# Localhost paths
self.assertEqual(fn('//localhost/C:/path/to/file'), 'C:\\path\\to\\file')
self.assertEqual(fn('//localhost/C|/path/to/file'), 'C:\\path\\to\\file')
@@ -1622,8 +1632,7 @@ class Pathname_Tests(unittest.TestCase):
self.assertRaises(urllib.error.URLError, fn, '//:80/foo/bar')
self.assertRaises(urllib.error.URLError, fn, '//:/foo/bar')
self.assertRaises(urllib.error.URLError, fn, '//c:80/foo/bar')
- self.assertEqual(fn('//127.0.0.1/foo/bar'), '/foo/bar')
- self.assertEqual(fn(f'//{socket.gethostname()}/foo/bar'), '/foo/bar')
+ self.assertRaises(urllib.error.URLError, fn, '//127.0.0.1/foo/bar')
@unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII')
def test_url2pathname_nonascii(self):
diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
index aabc360289a..b2bde5a9b1d 100644
--- a/Lib/test/test_urlparse.py
+++ b/Lib/test/test_urlparse.py
@@ -2,6 +2,7 @@ import sys
import unicodedata
import unittest
import urllib.parse
+from test import support
RFC1808_BASE = "http://a/b/c/d;p?q#f"
RFC2396_BASE = "http://a/b/c/d;p?q"
@@ -156,27 +157,25 @@ class UrlParseTestCase(unittest.TestCase):
self.assertEqual(result3.hostname, result.hostname)
self.assertEqual(result3.port, result.port)
- def test_qsl(self):
- for orig, expect in parse_qsl_test_cases:
- result = urllib.parse.parse_qsl(orig, keep_blank_values=True)
- self.assertEqual(result, expect, "Error parsing %r" % orig)
- expect_without_blanks = [v for v in expect if len(v[1])]
- result = urllib.parse.parse_qsl(orig, keep_blank_values=False)
- self.assertEqual(result, expect_without_blanks,
- "Error parsing %r" % orig)
-
- def test_qs(self):
- for orig, expect in parse_qs_test_cases:
- result = urllib.parse.parse_qs(orig, keep_blank_values=True)
- self.assertEqual(result, expect, "Error parsing %r" % orig)
- expect_without_blanks = {v: expect[v]
- for v in expect if len(expect[v][0])}
- result = urllib.parse.parse_qs(orig, keep_blank_values=False)
- self.assertEqual(result, expect_without_blanks,
- "Error parsing %r" % orig)
-
- def test_roundtrips(self):
- str_cases = [
+ @support.subTests('orig,expect', parse_qsl_test_cases)
+ def test_qsl(self, orig, expect):
+ result = urllib.parse.parse_qsl(orig, keep_blank_values=True)
+ self.assertEqual(result, expect)
+ expect_without_blanks = [v for v in expect if len(v[1])]
+ result = urllib.parse.parse_qsl(orig, keep_blank_values=False)
+ self.assertEqual(result, expect_without_blanks)
+
+ @support.subTests('orig,expect', parse_qs_test_cases)
+ def test_qs(self, orig, expect):
+ result = urllib.parse.parse_qs(orig, keep_blank_values=True)
+ self.assertEqual(result, expect)
+ expect_without_blanks = {v: expect[v]
+ for v in expect if len(expect[v][0])}
+ result = urllib.parse.parse_qs(orig, keep_blank_values=False)
+ self.assertEqual(result, expect_without_blanks)
+
+ @support.subTests('bytes', (False, True))
+ @support.subTests('url,parsed,split', [
('path/to/file',
('', '', 'path/to/file', '', '', ''),
('', '', 'path/to/file', '', '')),
@@ -263,23 +262,21 @@ class UrlParseTestCase(unittest.TestCase):
('sch_me:path/to/file',
('', '', 'sch_me:path/to/file', '', '', ''),
('', '', 'sch_me:path/to/file', '', '')),
- ]
- def _encode(t):
- return (t[0].encode('ascii'),
- tuple(x.encode('ascii') for x in t[1]),
- tuple(x.encode('ascii') for x in t[2]))
- bytes_cases = [_encode(x) for x in str_cases]
- str_cases += [
('schème:path/to/file',
('', '', 'schème:path/to/file', '', '', ''),
('', '', 'schème:path/to/file', '', '')),
- ]
- for url, parsed, split in str_cases + bytes_cases:
- with self.subTest(url):
- self.checkRoundtrips(url, parsed, split)
-
- def test_roundtrips_normalization(self):
- str_cases = [
+ ])
+ def test_roundtrips(self, bytes, url, parsed, split):
+ if bytes:
+ if not url.isascii():
+ self.skipTest('non-ASCII bytes')
+ url = str_encode(url)
+ parsed = tuple_encode(parsed)
+ split = tuple_encode(split)
+ self.checkRoundtrips(url, parsed, split)
+
+ @support.subTests('bytes', (False, True))
+ @support.subTests('url,url2,parsed,split', [
('///path/to/file',
'/path/to/file',
('', '', '/path/to/file', '', '', ''),
@@ -300,22 +297,18 @@ class UrlParseTestCase(unittest.TestCase):
'https:///tmp/junk.txt',
('https', '', '/tmp/junk.txt', '', '', ''),
('https', '', '/tmp/junk.txt', '', '')),
- ]
- def _encode(t):
- return (t[0].encode('ascii'),
- t[1].encode('ascii'),
- tuple(x.encode('ascii') for x in t[2]),
- tuple(x.encode('ascii') for x in t[3]))
- bytes_cases = [_encode(x) for x in str_cases]
- for url, url2, parsed, split in str_cases + bytes_cases:
- with self.subTest(url):
- self.checkRoundtrips(url, parsed, split, url2)
-
- def test_http_roundtrips(self):
- # urllib.parse.urlsplit treats 'http:' as an optimized special case,
- # so we test both 'http:' and 'https:' in all the following.
- # Three cheers for white box knowledge!
- str_cases = [
+ ])
+ def test_roundtrips_normalization(self, bytes, url, url2, parsed, split):
+ if bytes:
+ url = str_encode(url)
+ url2 = str_encode(url2)
+ parsed = tuple_encode(parsed)
+ split = tuple_encode(split)
+ self.checkRoundtrips(url, parsed, split, url2)
+
+ @support.subTests('bytes', (False, True))
+ @support.subTests('scheme', ('http', 'https'))
+ @support.subTests('url,parsed,split', [
('://www.python.org',
('www.python.org', '', '', '', ''),
('www.python.org', '', '', '')),
@@ -331,23 +324,20 @@ class UrlParseTestCase(unittest.TestCase):
('://a/b/c/d;p?q#f',
('a', '/b/c/d', 'p', 'q', 'f'),
('a', '/b/c/d;p', 'q', 'f')),
- ]
- def _encode(t):
- return (t[0].encode('ascii'),
- tuple(x.encode('ascii') for x in t[1]),
- tuple(x.encode('ascii') for x in t[2]))
- bytes_cases = [_encode(x) for x in str_cases]
- str_schemes = ('http', 'https')
- bytes_schemes = (b'http', b'https')
- str_tests = str_schemes, str_cases
- bytes_tests = bytes_schemes, bytes_cases
- for schemes, test_cases in (str_tests, bytes_tests):
- for scheme in schemes:
- for url, parsed, split in test_cases:
- url = scheme + url
- parsed = (scheme,) + parsed
- split = (scheme,) + split
- self.checkRoundtrips(url, parsed, split)
+ ])
+ def test_http_roundtrips(self, bytes, scheme, url, parsed, split):
+ # urllib.parse.urlsplit treats 'http:' as an optimized special case,
+ # so we test both 'http:' and 'https:' in all the following.
+ # Three cheers for white box knowledge!
+ if bytes:
+ scheme = str_encode(scheme)
+ url = str_encode(url)
+ parsed = tuple_encode(parsed)
+ split = tuple_encode(split)
+ url = scheme + url
+ parsed = (scheme,) + parsed
+ split = (scheme,) + split
+ self.checkRoundtrips(url, parsed, split)
def checkJoin(self, base, relurl, expected, *, relroundtrip=True):
with self.subTest(base=base, relurl=relurl):
@@ -363,12 +353,13 @@ class UrlParseTestCase(unittest.TestCase):
relurlb = urllib.parse.urlunsplit(urllib.parse.urlsplit(relurlb))
self.assertEqual(urllib.parse.urljoin(baseb, relurlb), expectedb)
- def test_unparse_parse(self):
- str_cases = ['Python', './Python','x-newscheme://foo.com/stuff','x://y','x:/y','x:/','/',]
- bytes_cases = [x.encode('ascii') for x in str_cases]
- for u in str_cases + bytes_cases:
- self.assertEqual(urllib.parse.urlunsplit(urllib.parse.urlsplit(u)), u)
- self.assertEqual(urllib.parse.urlunparse(urllib.parse.urlparse(u)), u)
+ @support.subTests('bytes', (False, True))
+ @support.subTests('u', ['Python', './Python','x-newscheme://foo.com/stuff','x://y','x:/y','x:/','/',])
+ def test_unparse_parse(self, bytes, u):
+ if bytes:
+ u = str_encode(u)
+ self.assertEqual(urllib.parse.urlunsplit(urllib.parse.urlsplit(u)), u)
+ self.assertEqual(urllib.parse.urlunparse(urllib.parse.urlparse(u)), u)
def test_RFC1808(self):
# "normal" cases from RFC 1808:
@@ -695,8 +686,8 @@ class UrlParseTestCase(unittest.TestCase):
self.checkJoin('///b/c', '///w', '///w')
self.checkJoin('///b/c', 'w', '///b/w')
- def test_RFC2732(self):
- str_cases = [
+ @support.subTests('bytes', (False, True))
+ @support.subTests('url,hostname,port', [
('http://Test.python.org:5432/foo/', 'test.python.org', 5432),
('http://12.34.56.78:5432/foo/', '12.34.56.78', 5432),
('http://[::1]:5432/foo/', '::1', 5432),
@@ -727,26 +718,28 @@ class UrlParseTestCase(unittest.TestCase):
('http://[::12.34.56.78]:/foo/', '::12.34.56.78', None),
('http://[::ffff:12.34.56.78]:/foo/',
'::ffff:12.34.56.78', None),
- ]
- def _encode(t):
- return t[0].encode('ascii'), t[1].encode('ascii'), t[2]
- bytes_cases = [_encode(x) for x in str_cases]
- for url, hostname, port in str_cases + bytes_cases:
- urlparsed = urllib.parse.urlparse(url)
- self.assertEqual((urlparsed.hostname, urlparsed.port) , (hostname, port))
-
- str_cases = [
+ ])
+ def test_RFC2732(self, bytes, url, hostname, port):
+ if bytes:
+ url = str_encode(url)
+ hostname = str_encode(hostname)
+ urlparsed = urllib.parse.urlparse(url)
+ self.assertEqual((urlparsed.hostname, urlparsed.port), (hostname, port))
+
+ @support.subTests('bytes', (False, True))
+ @support.subTests('invalid_url', [
'http://::12.34.56.78]/',
'http://[::1/foo/',
'ftp://[::1/foo/bad]/bad',
'http://[::1/foo/bad]/bad',
- 'http://[::ffff:12.34.56.78']
- bytes_cases = [x.encode('ascii') for x in str_cases]
- for invalid_url in str_cases + bytes_cases:
- self.assertRaises(ValueError, urllib.parse.urlparse, invalid_url)
-
- def test_urldefrag(self):
- str_cases = [
+ 'http://[::ffff:12.34.56.78'])
+ def test_RFC2732_invalid(self, bytes, invalid_url):
+ if bytes:
+ invalid_url = str_encode(invalid_url)
+ self.assertRaises(ValueError, urllib.parse.urlparse, invalid_url)
+
+ @support.subTests('bytes', (False, True))
+ @support.subTests('url,defrag,frag', [
('http://python.org#frag', 'http://python.org', 'frag'),
('http://python.org', 'http://python.org', ''),
('http://python.org/#frag', 'http://python.org/', 'frag'),
@@ -770,18 +763,18 @@ class UrlParseTestCase(unittest.TestCase):
('http:?q#f', 'http:?q', 'f'),
('//a/b/c;p?q#f', '//a/b/c;p?q', 'f'),
('://a/b/c;p?q#f', '://a/b/c;p?q', 'f'),
- ]
- def _encode(t):
- return type(t)(x.encode('ascii') for x in t)
- bytes_cases = [_encode(x) for x in str_cases]
- for url, defrag, frag in str_cases + bytes_cases:
- with self.subTest(url):
- result = urllib.parse.urldefrag(url)
- hash = '#' if isinstance(url, str) else b'#'
- self.assertEqual(result.geturl(), url.rstrip(hash))
- self.assertEqual(result, (defrag, frag))
- self.assertEqual(result.url, defrag)
- self.assertEqual(result.fragment, frag)
+ ])
+ def test_urldefrag(self, bytes, url, defrag, frag):
+ if bytes:
+ url = str_encode(url)
+ defrag = str_encode(defrag)
+ frag = str_encode(frag)
+ result = urllib.parse.urldefrag(url)
+ hash = '#' if isinstance(url, str) else b'#'
+ self.assertEqual(result.geturl(), url.rstrip(hash))
+ self.assertEqual(result, (defrag, frag))
+ self.assertEqual(result.url, defrag)
+ self.assertEqual(result.fragment, frag)
def test_urlsplit_scoped_IPv6(self):
p = urllib.parse.urlsplit('http://[FE80::822a:a8ff:fe49:470c%tESt]:1234')
@@ -981,42 +974,35 @@ class UrlParseTestCase(unittest.TestCase):
self.assertEqual(p.scheme, "https")
self.assertEqual(p.geturl(), "https://www.python.org/")
- def test_attributes_bad_port(self):
+ @support.subTests('bytes', (False, True))
+ @support.subTests('parse', (urllib.parse.urlsplit, urllib.parse.urlparse))
+ @support.subTests('port', ("foo", "1.5", "-1", "0x10", "-0", "1_1", " 1", "1 ", "६"))
+ def test_attributes_bad_port(self, bytes, parse, port):
"""Check handling of invalid ports."""
- for bytes in (False, True):
- for parse in (urllib.parse.urlsplit, urllib.parse.urlparse):
- for port in ("foo", "1.5", "-1", "0x10", "-0", "1_1", " 1", "1 ", "६"):
- with self.subTest(bytes=bytes, parse=parse, port=port):
- netloc = "www.example.net:" + port
- url = "http://" + netloc + "/"
- if bytes:
- if netloc.isascii() and port.isascii():
- netloc = netloc.encode("ascii")
- url = url.encode("ascii")
- else:
- continue
- p = parse(url)
- self.assertEqual(p.netloc, netloc)
- with self.assertRaises(ValueError):
- p.port
+ netloc = "www.example.net:" + port
+ url = "http://" + netloc + "/"
+ if bytes:
+ if not (netloc.isascii() and port.isascii()):
+ self.skipTest('non-ASCII bytes')
+ netloc = str_encode(netloc)
+ url = str_encode(url)
+ p = parse(url)
+ self.assertEqual(p.netloc, netloc)
+ with self.assertRaises(ValueError):
+ p.port
- def test_attributes_bad_scheme(self):
+ @support.subTests('bytes', (False, True))
+ @support.subTests('parse', (urllib.parse.urlsplit, urllib.parse.urlparse))
+ @support.subTests('scheme', (".", "+", "-", "0", "http&", "६http"))
+ def test_attributes_bad_scheme(self, bytes, parse, scheme):
"""Check handling of invalid schemes."""
- for bytes in (False, True):
- for parse in (urllib.parse.urlsplit, urllib.parse.urlparse):
- for scheme in (".", "+", "-", "0", "http&", "६http"):
- with self.subTest(bytes=bytes, parse=parse, scheme=scheme):
- url = scheme + "://www.example.net"
- if bytes:
- if url.isascii():
- url = url.encode("ascii")
- else:
- continue
- p = parse(url)
- if bytes:
- self.assertEqual(p.scheme, b"")
- else:
- self.assertEqual(p.scheme, "")
+ url = scheme + "://www.example.net"
+ if bytes:
+ if not url.isascii():
+ self.skipTest('non-ASCII bytes')
+ url = url.encode("ascii")
+ p = parse(url)
+ self.assertEqual(p.scheme, b"" if bytes else "")
def test_attributes_without_netloc(self):
# This example is straight from RFC 3261. It looks like it
@@ -1128,24 +1114,21 @@ class UrlParseTestCase(unittest.TestCase):
self.assertEqual(urllib.parse.urlparse(b"x-newscheme://foo.com/stuff?query"),
(b'x-newscheme', b'foo.com', b'/stuff', b'', b'query', b''))
- def test_default_scheme(self):
+ @support.subTests('func', (urllib.parse.urlparse, urllib.parse.urlsplit))
+ def test_default_scheme(self, func):
# Exercise the scheme parameter of urlparse() and urlsplit()
- for func in (urllib.parse.urlparse, urllib.parse.urlsplit):
- with self.subTest(function=func):
- result = func("http://example.net/", "ftp")
- self.assertEqual(result.scheme, "http")
- result = func(b"http://example.net/", b"ftp")
- self.assertEqual(result.scheme, b"http")
- self.assertEqual(func("path", "ftp").scheme, "ftp")
- self.assertEqual(func("path", scheme="ftp").scheme, "ftp")
- self.assertEqual(func(b"path", scheme=b"ftp").scheme, b"ftp")
- self.assertEqual(func("path").scheme, "")
- self.assertEqual(func(b"path").scheme, b"")
- self.assertEqual(func(b"path", "").scheme, b"")
-
- def test_parse_fragments(self):
- # Exercise the allow_fragments parameter of urlparse() and urlsplit()
- tests = (
+ result = func("http://example.net/", "ftp")
+ self.assertEqual(result.scheme, "http")
+ result = func(b"http://example.net/", b"ftp")
+ self.assertEqual(result.scheme, b"http")
+ self.assertEqual(func("path", "ftp").scheme, "ftp")
+ self.assertEqual(func("path", scheme="ftp").scheme, "ftp")
+ self.assertEqual(func(b"path", scheme=b"ftp").scheme, b"ftp")
+ self.assertEqual(func("path").scheme, "")
+ self.assertEqual(func(b"path").scheme, b"")
+ self.assertEqual(func(b"path", "").scheme, b"")
+
+ @support.subTests('url,attr,expected_frag', (
("http:#frag", "path", "frag"),
("//example.net#frag", "path", "frag"),
("index.html#frag", "path", "frag"),
@@ -1156,24 +1139,24 @@ class UrlParseTestCase(unittest.TestCase):
("//abc#@frag", "path", "@frag"),
("//abc:80#@frag", "path", "@frag"),
("//abc#@frag:80", "path", "@frag:80"),
- )
- for url, attr, expected_frag in tests:
- for func in (urllib.parse.urlparse, urllib.parse.urlsplit):
- if attr == "params" and func is urllib.parse.urlsplit:
- attr = "path"
- with self.subTest(url=url, function=func):
- result = func(url, allow_fragments=False)
- self.assertEqual(result.fragment, "")
- self.assertEndsWith(getattr(result, attr),
- "#" + expected_frag)
- self.assertEqual(func(url, "", False).fragment, "")
-
- result = func(url, allow_fragments=True)
- self.assertEqual(result.fragment, expected_frag)
- self.assertNotEndsWith(getattr(result, attr), expected_frag)
- self.assertEqual(func(url, "", True).fragment,
- expected_frag)
- self.assertEqual(func(url).fragment, expected_frag)
+ ))
+ @support.subTests('func', (urllib.parse.urlparse, urllib.parse.urlsplit))
+ def test_parse_fragments(self, url, attr, expected_frag, func):
+ # Exercise the allow_fragments parameter of urlparse() and urlsplit()
+ if attr == "params" and func is urllib.parse.urlsplit:
+ attr = "path"
+ result = func(url, allow_fragments=False)
+ self.assertEqual(result.fragment, "")
+ self.assertEndsWith(getattr(result, attr),
+ "#" + expected_frag)
+ self.assertEqual(func(url, "", False).fragment, "")
+
+ result = func(url, allow_fragments=True)
+ self.assertEqual(result.fragment, expected_frag)
+ self.assertNotEndsWith(getattr(result, attr), expected_frag)
+ self.assertEqual(func(url, "", True).fragment,
+ expected_frag)
+ self.assertEqual(func(url).fragment, expected_frag)
def test_mixed_types_rejected(self):
# Several functions that process either strings or ASCII encoded bytes
@@ -1199,7 +1182,14 @@ class UrlParseTestCase(unittest.TestCase):
with self.assertRaisesRegex(TypeError, "Cannot mix str"):
urllib.parse.urljoin(b"http://python.org", "http://python.org")
- def _check_result_type(self, str_type):
+ @support.subTests('result_type', [
+ urllib.parse.DefragResult,
+ urllib.parse.SplitResult,
+ urllib.parse.ParseResult,
+ ])
+ def test_result_pairs(self, result_type):
+ # Check encoding and decoding between result pairs
+ str_type = result_type
num_args = len(str_type._fields)
bytes_type = str_type._encoded_counterpart
self.assertIs(bytes_type._decoded_counterpart, str_type)
@@ -1224,16 +1214,6 @@ class UrlParseTestCase(unittest.TestCase):
self.assertEqual(str_result.encode(encoding, errors), bytes_args)
self.assertEqual(str_result.encode(encoding, errors), bytes_result)
- def test_result_pairs(self):
- # Check encoding and decoding between result pairs
- result_types = [
- urllib.parse.DefragResult,
- urllib.parse.SplitResult,
- urllib.parse.ParseResult,
- ]
- for result_type in result_types:
- self._check_result_type(result_type)
-
def test_parse_qs_encoding(self):
result = urllib.parse.parse_qs("key=\u0141%E9", encoding="latin-1")
self.assertEqual(result, {'key': ['\u0141\xE9']})
@@ -1265,8 +1245,7 @@ class UrlParseTestCase(unittest.TestCase):
urllib.parse.parse_qsl('&'.join(['a=a']*11), max_num_fields=10)
urllib.parse.parse_qsl('&'.join(['a=a']*10), max_num_fields=10)
- def test_parse_qs_separator(self):
- parse_qs_semicolon_cases = [
+ @support.subTests('orig,expect', [
(";", {}),
(";;", {}),
(";a=b", {'a': ['b']}),
@@ -1277,17 +1256,14 @@ class UrlParseTestCase(unittest.TestCase):
(b";a=b", {b'a': [b'b']}),
(b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
(b"a=1;a=2", {b'a': [b'1', b'2']}),
- ]
- for orig, expect in parse_qs_semicolon_cases:
- with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"):
- result = urllib.parse.parse_qs(orig, separator=';')
- self.assertEqual(result, expect, "Error parsing %r" % orig)
- result_bytes = urllib.parse.parse_qs(orig, separator=b';')
- self.assertEqual(result_bytes, expect, "Error parsing %r" % orig)
-
-
- def test_parse_qsl_separator(self):
- parse_qsl_semicolon_cases = [
+ ])
+ def test_parse_qs_separator(self, orig, expect):
+ result = urllib.parse.parse_qs(orig, separator=';')
+ self.assertEqual(result, expect)
+ result_bytes = urllib.parse.parse_qs(orig, separator=b';')
+ self.assertEqual(result_bytes, expect)
+
+ @support.subTests('orig,expect', [
(";", []),
(";;", []),
(";a=b", [('a', 'b')]),
@@ -1298,13 +1274,12 @@ class UrlParseTestCase(unittest.TestCase):
(b";a=b", [(b'a', b'b')]),
(b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
(b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]),
- ]
- for orig, expect in parse_qsl_semicolon_cases:
- with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"):
- result = urllib.parse.parse_qsl(orig, separator=';')
- self.assertEqual(result, expect, "Error parsing %r" % orig)
- result_bytes = urllib.parse.parse_qsl(orig, separator=b';')
- self.assertEqual(result_bytes, expect, "Error parsing %r" % orig)
+ ])
+ def test_parse_qsl_separator(self, orig, expect):
+ result = urllib.parse.parse_qsl(orig, separator=';')
+ self.assertEqual(result, expect)
+ result_bytes = urllib.parse.parse_qsl(orig, separator=b';')
+ self.assertEqual(result_bytes, expect)
def test_parse_qsl_bytes(self):
self.assertEqual(urllib.parse.parse_qsl(b'a=b'), [(b'a', b'b')])
@@ -1695,11 +1670,12 @@ class Utility_Tests(unittest.TestCase):
self.assertRaises(UnicodeError, urllib.parse._to_bytes,
'http://www.python.org/medi\u00e6val')
- def test_unwrap(self):
- for wrapped_url in ('<URL:scheme://host/path>', '<scheme://host/path>',
- 'URL:scheme://host/path', 'scheme://host/path'):
- url = urllib.parse.unwrap(wrapped_url)
- self.assertEqual(url, 'scheme://host/path')
+ @support.subTests('wrapped_url',
+ ('<URL:scheme://host/path>', '<scheme://host/path>',
+ 'URL:scheme://host/path', 'scheme://host/path'))
+ def test_unwrap(self, wrapped_url):
+ url = urllib.parse.unwrap(wrapped_url)
+ self.assertEqual(url, 'scheme://host/path')
class DeprecationTest(unittest.TestCase):
@@ -1780,5 +1756,11 @@ class DeprecationTest(unittest.TestCase):
'urllib.parse.to_bytes() is deprecated as of 3.8')
+def str_encode(s):
+ return s.encode('ascii')
+
+def tuple_encode(t):
+ return tuple(str_encode(x) for x in t)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_userdict.py b/Lib/test/test_userdict.py
index ace84ef564d..75de9ea252d 100644
--- a/Lib/test/test_userdict.py
+++ b/Lib/test/test_userdict.py
@@ -166,7 +166,7 @@ class UserDictTest(mapping_tests.TestHashMappingProtocol):
def test_missing(self):
# Make sure UserDict doesn't have a __missing__ method
- self.assertEqual(hasattr(collections.UserDict, "__missing__"), False)
+ self.assertNotHasAttr(collections.UserDict, "__missing__")
# Test several cases:
# (D) subclass defines __missing__ method returning a value
# (E) subclass defines __missing__ method raising RuntimeError
diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py
index 958be5408ce..7ddacf07a2c 100755
--- a/Lib/test/test_uuid.py
+++ b/Lib/test/test_uuid.py
@@ -14,6 +14,7 @@ from unittest import mock
from test import support
from test.support import import_helper
+from test.support.script_helper import assert_python_ok
py_uuid = import_helper.import_fresh_module('uuid', blocked=['_uuid'])
c_uuid = import_helper.import_fresh_module('uuid', fresh=['_uuid'])
@@ -1217,10 +1218,37 @@ class BaseTestUUID:
class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase):
uuid = py_uuid
+
@unittest.skipUnless(c_uuid, 'requires the C _uuid module')
class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase):
uuid = c_uuid
+ def check_has_stable_libuuid_extractable_node(self):
+ if not self.uuid._has_stable_extractable_node:
+ self.skipTest("libuuid cannot deduce MAC address")
+
+ @unittest.skipUnless(os.name == 'posix', 'POSIX only')
+ def test_unix_getnode_from_libuuid(self):
+ self.check_has_stable_libuuid_extractable_node()
+ script = 'import uuid; print(uuid._unix_getnode())'
+ _, n_a, _ = assert_python_ok('-c', script)
+ _, n_b, _ = assert_python_ok('-c', script)
+ n_a, n_b = n_a.decode().strip(), n_b.decode().strip()
+ self.assertTrue(n_a.isdigit())
+ self.assertTrue(n_b.isdigit())
+ self.assertEqual(n_a, n_b)
+
+ @unittest.skipUnless(os.name == 'nt', 'Windows only')
+ def test_windows_getnode_from_libuuid(self):
+ self.check_has_stable_libuuid_extractable_node()
+ script = 'import uuid; print(uuid._windll_getnode())'
+ _, n_a, _ = assert_python_ok('-c', script)
+ _, n_b, _ = assert_python_ok('-c', script)
+ n_a, n_b = n_a.decode().strip(), n_b.decode().strip()
+ self.assertTrue(n_a.isdigit())
+ self.assertTrue(n_b.isdigit())
+ self.assertEqual(n_a, n_b)
+
class BaseTestInternals:
_uuid = py_uuid
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index adc86a49b06..d62f3fba2d1 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -774,7 +774,7 @@ class BasicTest(BaseTest):
with open(script_path, 'rb') as script:
for i, line in enumerate(script, 1):
error_message = f"CR LF found in line {i}"
- self.assertFalse(line.endswith(b'\r\n'), error_message)
+ self.assertNotEndsWith(line, b'\r\n', error_message)
@requireVenvCreate
def test_scm_ignore_files_git(self):
@@ -978,7 +978,7 @@ class EnsurePipTest(BaseTest):
self.assertEqual(err, "")
out = out.decode("latin-1") # Force to text, prevent decoding errors
expected_version = "pip {}".format(ensurepip.version())
- self.assertEqual(out[:len(expected_version)], expected_version)
+ self.assertStartsWith(out, expected_version)
env_dir = os.fsencode(self.env_dir).decode("latin-1")
self.assertIn(env_dir, out)
@@ -1008,7 +1008,7 @@ class EnsurePipTest(BaseTest):
err, flags=re.MULTILINE)
# Ignore warning about missing optional module:
try:
- import ssl
+ import ssl # noqa: F401
except ImportError:
err = re.sub(
"^WARNING: Disabling truststore since ssl support is missing$",
diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py
index 05710c46934..5c3b1250ceb 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -102,7 +102,7 @@ class PublicAPITests(BaseTest):
"""
def test_module_all_attribute(self):
- self.assertTrue(hasattr(self.module, '__all__'))
+ self.assertHasAttr(self.module, '__all__')
target_api = ["warn", "warn_explicit", "showwarning",
"formatwarning", "filterwarnings", "simplefilter",
"resetwarnings", "catch_warnings", "deprecated"]
@@ -735,7 +735,7 @@ class CWarnTests(WarnTests, unittest.TestCase):
# test.import_helper.import_fresh_module utility function
def test_accelerated(self):
self.assertIsNot(original_warnings, self.module)
- self.assertFalse(hasattr(self.module.warn, '__code__'))
+ self.assertNotHasAttr(self.module.warn, '__code__')
class PyWarnTests(WarnTests, unittest.TestCase):
module = py_warnings
@@ -744,7 +744,7 @@ class PyWarnTests(WarnTests, unittest.TestCase):
# test.import_helper.import_fresh_module utility function
def test_pure_python(self):
self.assertIsNot(original_warnings, self.module)
- self.assertTrue(hasattr(self.module.warn, '__code__'))
+ self.assertHasAttr(self.module.warn, '__code__')
class WCmdLineTests(BaseTest):
@@ -1528,12 +1528,12 @@ a=A()
# (_warnings will try to import it)
code = "f = open(%a)" % __file__
rc, out, err = assert_python_ok("-Wd", "-c", code)
- self.assertTrue(err.startswith(expected), ascii(err))
+ self.assertStartsWith(err, expected)
# import the warnings module
code = "import warnings; f = open(%a)" % __file__
rc, out, err = assert_python_ok("-Wd", "-c", code)
- self.assertTrue(err.startswith(expected), ascii(err))
+ self.assertStartsWith(err, expected)
class AsyncTests(BaseTest):
diff --git a/Lib/test/test_wave.py b/Lib/test/test_wave.py
index 5e771c8de96..6c3362857fc 100644
--- a/Lib/test/test_wave.py
+++ b/Lib/test/test_wave.py
@@ -136,32 +136,6 @@ class MiscTestCase(unittest.TestCase):
not_exported = {'WAVE_FORMAT_PCM', 'WAVE_FORMAT_EXTENSIBLE', 'KSDATAFORMAT_SUBTYPE_PCM'}
support.check__all__(self, wave, not_exported=not_exported)
- def test_read_deprecations(self):
- filename = support.findfile('pluck-pcm8.wav', subdir='audiodata')
- with wave.open(filename) as reader:
- with self.assertWarns(DeprecationWarning):
- with self.assertRaises(wave.Error):
- reader.getmark('mark')
- with self.assertWarns(DeprecationWarning):
- self.assertIsNone(reader.getmarkers())
-
- def test_write_deprecations(self):
- with io.BytesIO(b'') as tmpfile:
- with wave.open(tmpfile, 'wb') as writer:
- writer.setnchannels(1)
- writer.setsampwidth(1)
- writer.setframerate(1)
- writer.setcomptype('NONE', 'not compressed')
-
- with self.assertWarns(DeprecationWarning):
- with self.assertRaises(wave.Error):
- writer.setmark(0, 0, 'mark')
- with self.assertWarns(DeprecationWarning):
- with self.assertRaises(wave.Error):
- writer.getmark('mark')
- with self.assertWarns(DeprecationWarning):
- self.assertIsNone(writer.getmarkers())
-
class WaveLowLevelTest(unittest.TestCase):
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index 4faad6629fe..4c7c900eb56 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -432,7 +432,7 @@ class ReferencesTestCase(TestBase):
self.assertEqual(proxy.foo, 2,
"proxy does not reflect attribute modification")
del o.foo
- self.assertFalse(hasattr(proxy, 'foo'),
+ self.assertNotHasAttr(proxy, 'foo',
"proxy does not reflect attribute removal")
proxy.foo = 1
@@ -442,7 +442,7 @@ class ReferencesTestCase(TestBase):
self.assertEqual(o.foo, 2,
"object does not reflect attribute modification via proxy")
del proxy.foo
- self.assertFalse(hasattr(o, 'foo'),
+ self.assertNotHasAttr(o, 'foo',
"object does not reflect attribute removal via proxy")
def test_proxy_deletion(self):
@@ -1108,7 +1108,7 @@ class SubclassableWeakrefTestCase(TestBase):
self.assertEqual(r.slot1, "abc")
self.assertEqual(r.slot2, "def")
self.assertEqual(r.meth(), "abcdef")
- self.assertFalse(hasattr(r, "__dict__"))
+ self.assertNotHasAttr(r, "__dict__")
def test_subclass_refs_with_cycle(self):
"""Confirm https://bugs.python.org/issue3100 is fixed."""
diff --git a/Lib/test/test_weakset.py b/Lib/test/test_weakset.py
index 76e8e5c8ab7..c1e4f9c8366 100644
--- a/Lib/test/test_weakset.py
+++ b/Lib/test/test_weakset.py
@@ -466,7 +466,7 @@ class TestWeakSet(unittest.TestCase):
self.assertIsNot(dup, s)
self.assertIs(dup.x, s.x)
self.assertIs(dup.z, s.z)
- self.assertFalse(hasattr(dup, 'y'))
+ self.assertNotHasAttr(dup, 'y')
dup = copy.deepcopy(s)
self.assertIsInstance(dup, cls)
@@ -476,7 +476,7 @@ class TestWeakSet(unittest.TestCase):
self.assertIsNot(dup.x, s.x)
self.assertEqual(dup.z, s.z)
self.assertIsNot(dup.z, s.z)
- self.assertFalse(hasattr(dup, 'y'))
+ self.assertNotHasAttr(dup, 'y')
if __name__ == "__main__":
diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
index 4c3ea1cd8df..6b577ae100e 100644
--- a/Lib/test/test_webbrowser.py
+++ b/Lib/test/test_webbrowser.py
@@ -6,7 +6,6 @@ import subprocess
import sys
import unittest
import webbrowser
-from functools import partial
from test import support
from test.support import import_helper
from test.support import is_apple_mobile
diff --git a/Lib/test/test_winconsoleio.py b/Lib/test/test_winconsoleio.py
index d9076e77c15..1bae884ed9a 100644
--- a/Lib/test/test_winconsoleio.py
+++ b/Lib/test/test_winconsoleio.py
@@ -17,9 +17,9 @@ ConIO = io._WindowsConsoleIO
class WindowsConsoleIOTests(unittest.TestCase):
def test_abc(self):
- self.assertTrue(issubclass(ConIO, io.RawIOBase))
- self.assertFalse(issubclass(ConIO, io.BufferedIOBase))
- self.assertFalse(issubclass(ConIO, io.TextIOBase))
+ self.assertIsSubclass(ConIO, io.RawIOBase)
+ self.assertNotIsSubclass(ConIO, io.BufferedIOBase)
+ self.assertNotIsSubclass(ConIO, io.TextIOBase)
def test_open_fd(self):
self.assertRaisesRegex(ValueError,
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
index fd7abd1782e..f16611b29a2 100644
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -679,7 +679,7 @@ class AssignmentTargetTestCase(unittest.TestCase):
class C: pass
blah = C()
with mock_contextmanager_generator() as blah.foo:
- self.assertEqual(hasattr(blah, "foo"), True)
+ self.assertHasAttr(blah, "foo")
def testMultipleComplexTargets(self):
class C:
diff --git a/Lib/test/test_wmi.py b/Lib/test/test_wmi.py
index ac7c9cb3a5a..90eb40439d4 100644
--- a/Lib/test/test_wmi.py
+++ b/Lib/test/test_wmi.py
@@ -70,8 +70,8 @@ class WmiTests(unittest.TestCase):
def test_wmi_query_multiple_rows(self):
# Multiple instances should have an extra null separator
r = wmi_exec_query("SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000")
- self.assertFalse(r.startswith("\0"), r)
- self.assertFalse(r.endswith("\0"), r)
+ self.assertNotStartsWith(r, "\0")
+ self.assertNotEndsWith(r, "\0")
it = iter(r.split("\0"))
try:
while True:
diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py
index b047f7b06f8..e04a4d2c221 100644
--- a/Lib/test/test_wsgiref.py
+++ b/Lib/test/test_wsgiref.py
@@ -149,9 +149,9 @@ class IntegrationTests(TestCase):
start_response("200 OK", ('Content-Type','text/plain'))
return ["Hello, world!"]
out, err = run_amock(validator(bad_app))
- self.assertTrue(out.endswith(
+ self.assertEndsWith(out,
b"A server error occurred. Please contact the administrator."
- ))
+ )
self.assertEqual(
err.splitlines()[-2],
"AssertionError: Headers (('Content-Type', 'text/plain')) must"
@@ -174,9 +174,9 @@ class IntegrationTests(TestCase):
for status, exc_message in tests:
with self.subTest(status=status):
out, err = run_amock(create_bad_app(status))
- self.assertTrue(out.endswith(
+ self.assertEndsWith(out,
b"A server error occurred. Please contact the administrator."
- ))
+ )
self.assertEqual(err.splitlines()[-2], exc_message)
def test_wsgi_input(self):
@@ -185,9 +185,9 @@ class IntegrationTests(TestCase):
s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
return [b"data"]
out, err = run_amock(validator(bad_app))
- self.assertTrue(out.endswith(
+ self.assertEndsWith(out,
b"A server error occurred. Please contact the administrator."
- ))
+ )
self.assertEqual(
err.splitlines()[-2], "AssertionError"
)
@@ -200,7 +200,7 @@ class IntegrationTests(TestCase):
])
return [b"data"]
out, err = run_amock(validator(app))
- self.assertTrue(err.endswith('"GET / HTTP/1.0" 200 4\n'))
+ self.assertEndsWith(err, '"GET / HTTP/1.0" 200 4\n')
ver = sys.version.split()[0].encode('ascii')
py = python_implementation().encode('ascii')
pyver = py + b"/" + ver
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 5fe9d688410..bf6d5074fde 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -218,6 +218,33 @@ class ElementTreeTest(unittest.TestCase):
def serialize_check(self, elem, expected):
self.assertEqual(serialize(elem), expected)
+ def test_constructor(self):
+ # Test constructor behavior.
+
+ with self.assertRaises(TypeError):
+ tree = ET.ElementTree("")
+ with self.assertRaises(TypeError):
+ tree = ET.ElementTree(ET.ElementTree())
+
+ def test_setroot(self):
+ # Test _setroot behavior.
+
+ tree = ET.ElementTree()
+ element = ET.Element("tag")
+ tree._setroot(element)
+ self.assertEqual(tree.getroot().tag, "tag")
+ self.assertEqual(tree.getroot(), element)
+
+ # Test behavior with an invalid root element
+
+ tree = ET.ElementTree()
+ with self.assertRaises(TypeError):
+ tree._setroot("")
+ with self.assertRaises(TypeError):
+ tree._setroot(ET.ElementTree())
+ with self.assertRaises(TypeError):
+ tree._setroot(None)
+
def test_interface(self):
# Test element tree interface.
@@ -225,8 +252,7 @@ class ElementTreeTest(unittest.TestCase):
self.assertTrue(ET.iselement(element), msg="not an element")
direlem = dir(element)
for attr in 'tag', 'attrib', 'text', 'tail':
- self.assertTrue(hasattr(element, attr),
- msg='no %s member' % attr)
+ self.assertHasAttr(element, attr)
self.assertIn(attr, direlem,
msg='no %s visible by dir' % attr)
@@ -251,7 +277,7 @@ class ElementTreeTest(unittest.TestCase):
# Make sure all standard element methods exist.
def check_method(method):
- self.assertTrue(hasattr(method, '__call__'),
+ self.assertHasAttr(method, '__call__',
msg="%s not callable" % method)
check_method(element.append)
@@ -2960,6 +2986,50 @@ class BadElementTest(ElementTestCase, unittest.TestCase):
del b
gc_collect()
+ def test_deepcopy_clear(self):
+ # Prevent crashes when __deepcopy__() clears the children list.
+ # See https://github.com/python/cpython/issues/133009.
+ class X(ET.Element):
+ def __deepcopy__(self, memo):
+ root.clear()
+ return self
+
+ root = ET.Element('a')
+ evil = X('x')
+ root.extend([evil, ET.Element('y')])
+ if is_python_implementation():
+ # Mutating a list over which we iterate raises an error.
+ self.assertRaises(RuntimeError, copy.deepcopy, root)
+ else:
+ c = copy.deepcopy(root)
+ # In the C implementation, we can still copy the evil element.
+ self.assertListEqual(list(c), [evil])
+
+ def test_deepcopy_grow(self):
+ # Prevent crashes when __deepcopy__() mutates the children list.
+ # See https://github.com/python/cpython/issues/133009.
+ a = ET.Element('a')
+ b = ET.Element('b')
+ c = ET.Element('c')
+
+ class X(ET.Element):
+ def __deepcopy__(self, memo):
+ root.append(a)
+ root.append(b)
+ return self
+
+ root = ET.Element('top')
+ evil1, evil2 = X('1'), X('2')
+ root.extend([evil1, c, evil2])
+ children = list(copy.deepcopy(root))
+ # mock deep copies
+ self.assertIs(children[0], evil1)
+ self.assertIs(children[2], evil2)
+ # true deep copies
+ self.assertEqual(children[1].tag, c.tag)
+ self.assertEqual([c.tag for c in children[3:]],
+ [a.tag, b.tag, a.tag, b.tag])
+
class MutationDeleteElementPath(str):
def __new__(cls, elem, *args):
diff --git a/Lib/test/test_xxlimited.py b/Lib/test/test_xxlimited.py
index 6dbfb3f4393..b52e78bc4fb 100644
--- a/Lib/test/test_xxlimited.py
+++ b/Lib/test/test_xxlimited.py
@@ -31,7 +31,7 @@ class CommonTests:
self.assertEqual(self.module.foo(1, 2), 3)
def test_str(self):
- self.assertTrue(issubclass(self.module.Str, str))
+ self.assertIsSubclass(self.module.Str, str)
self.assertIsNot(self.module.Str, str)
custom_string = self.module.Str("abcd")
diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py
index d4766c59a10..8fb0a68deba 100644
--- a/Lib/test/test_zipapp.py
+++ b/Lib/test/test_zipapp.py
@@ -259,7 +259,7 @@ class ZipAppTest(unittest.TestCase):
(source / '__main__.py').touch()
target = io.BytesIO()
zipapp.create_archive(str(source), target, interpreter='python')
- self.assertTrue(target.getvalue().startswith(b'#!python\n'))
+ self.assertStartsWith(target.getvalue(), b'#!python\n')
def test_read_shebang(self):
# Test that we can read the shebang line correctly.
@@ -300,7 +300,7 @@ class ZipAppTest(unittest.TestCase):
zipapp.create_archive(str(source), str(target), interpreter='python')
new_target = io.BytesIO()
zipapp.create_archive(str(target), new_target, interpreter='python2.7')
- self.assertTrue(new_target.getvalue().startswith(b'#!python2.7\n'))
+ self.assertStartsWith(new_target.getvalue(), b'#!python2.7\n')
def test_read_from_pathlike_obj(self):
# Test that we can copy an archive using a path-like object
@@ -326,7 +326,7 @@ class ZipAppTest(unittest.TestCase):
new_target = io.BytesIO()
temp_archive.seek(0)
zipapp.create_archive(temp_archive, new_target, interpreter='python2.7')
- self.assertTrue(new_target.getvalue().startswith(b'#!python2.7\n'))
+ self.assertStartsWith(new_target.getvalue(), b'#!python2.7\n')
def test_remove_shebang(self):
# Test that we can remove the shebang from a file.
diff --git a/Lib/test/test_zipfile/__main__.py b/Lib/test/test_zipfile/__main__.py
index e25ac946edf..90da74ade38 100644
--- a/Lib/test/test_zipfile/__main__.py
+++ b/Lib/test/test_zipfile/__main__.py
@@ -1,6 +1,6 @@
import unittest
-from . import load_tests # noqa: F401
+from . import load_tests
if __name__ == "__main__":
diff --git a/Lib/test/test_zipfile/_path/_test_params.py b/Lib/test/test_zipfile/_path/_test_params.py
index bc95b4ebf4a..00a9eaf2f99 100644
--- a/Lib/test/test_zipfile/_path/_test_params.py
+++ b/Lib/test/test_zipfile/_path/_test_params.py
@@ -1,5 +1,5 @@
-import types
import functools
+import types
from ._itertools import always_iterable
diff --git a/Lib/test/test_zipfile/_path/test_complexity.py b/Lib/test/test_zipfile/_path/test_complexity.py
index b505dd7c376..7c108fc6ab8 100644
--- a/Lib/test/test_zipfile/_path/test_complexity.py
+++ b/Lib/test/test_zipfile/_path/test_complexity.py
@@ -8,10 +8,8 @@ import zipfile
from ._functools import compose
from ._itertools import consume
-
from ._support import import_or_skip
-
big_o = import_or_skip('big_o')
pytest = import_or_skip('pytest')
diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py
index 0afabc0c668..696134023a5 100644
--- a/Lib/test/test_zipfile/_path/test_path.py
+++ b/Lib/test/test_zipfile/_path/test_path.py
@@ -1,6 +1,6 @@
+import contextlib
import io
import itertools
-import contextlib
import pathlib
import pickle
import stat
@@ -9,12 +9,11 @@ import unittest
import zipfile
import zipfile._path
-from test.support.os_helper import temp_dir, FakePath
+from test.support.os_helper import FakePath, temp_dir
from ._functools import compose
from ._itertools import Counter
-
-from ._test_params import parameterize, Invoked
+from ._test_params import Invoked, parameterize
class jaraco:
@@ -193,10 +192,10 @@ class TestPath(unittest.TestCase):
"""EncodingWarning must blame the read_text and open calls."""
assert sys.flags.warn_default_encoding
root = zipfile.Path(alpharep)
- with self.assertWarns(EncodingWarning) as wc:
+ with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296)
root.joinpath("a.txt").read_text()
assert __file__ == wc.filename
- with self.assertWarns(EncodingWarning) as wc:
+ with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296)
root.joinpath("a.txt").open("r").close()
assert __file__ == wc.filename
@@ -365,6 +364,17 @@ class TestPath(unittest.TestCase):
assert root.name == 'alpharep.zip' == root.filename.name
@pass_alpharep
+ def test_root_on_disk(self, alpharep):
+ """
+ The name/stem of the root should match the zipfile on disk.
+
+ This condition must hold across platforms.
+ """
+ root = zipfile.Path(self.zipfile_ondisk(alpharep))
+ assert root.name == 'alpharep.zip' == root.filename.name
+ assert root.stem == 'alpharep' == root.filename.stem
+
+ @pass_alpharep
def test_suffix(self, alpharep):
"""
The suffix of the root should be the suffix of the zipfile.
diff --git a/Lib/test/test_zipfile/_path/write-alpharep.py b/Lib/test/test_zipfile/_path/write-alpharep.py
index 48c09b53717..7418391abad 100644
--- a/Lib/test/test_zipfile/_path/write-alpharep.py
+++ b/Lib/test/test_zipfile/_path/write-alpharep.py
@@ -1,4 +1,3 @@
from . import test_path
-
__name__ == '__main__' and test_path.build_alpharep_fixture().extractall('alpharep')
diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py
index 7c8a82d821a..ada96813709 100644
--- a/Lib/test/test_zipfile/test_core.py
+++ b/Lib/test/test_zipfile/test_core.py
@@ -23,11 +23,13 @@ from test import archiver_tests
from test.support import script_helper, os_helper
from test.support import (
findfile, requires_zlib, requires_bz2, requires_lzma,
- captured_stdout, captured_stderr, requires_subprocess,
+ requires_zstd, captured_stdout, captured_stderr, requires_subprocess,
+ cpython_only
)
from test.support.os_helper import (
TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath
)
+from test.support.import_helper import ensure_lazy_imports
TESTFN2 = TESTFN + "2"
@@ -49,6 +51,13 @@ def get_files(test):
yield f
test.assertFalse(f.closed)
+
+class LazyImportTest(unittest.TestCase):
+ @cpython_only
+ def test_lazy_import(self):
+ ensure_lazy_imports("zipfile", {"typing"})
+
+
class AbstractTestsWithSourceFile:
@classmethod
def setUpClass(cls):
@@ -693,6 +702,10 @@ class LzmaTestsWithSourceFile(AbstractTestsWithSourceFile,
unittest.TestCase):
compression = zipfile.ZIP_LZMA
+@requires_zstd()
+class ZstdTestsWithSourceFile(AbstractTestsWithSourceFile,
+ unittest.TestCase):
+ compression = zipfile.ZIP_ZSTANDARD
class AbstractTestZip64InSmallFiles:
# These tests test the ZIP64 functionality without using large files,
@@ -1270,6 +1283,10 @@ class LzmaTestZip64InSmallFiles(AbstractTestZip64InSmallFiles,
unittest.TestCase):
compression = zipfile.ZIP_LZMA
+@requires_zstd()
+class ZstdTestZip64InSmallFiles(AbstractTestZip64InSmallFiles,
+ unittest.TestCase):
+ compression = zipfile.ZIP_ZSTANDARD
class AbstractWriterTests:
@@ -1339,6 +1356,9 @@ class Bzip2WriterTests(AbstractWriterTests, unittest.TestCase):
class LzmaWriterTests(AbstractWriterTests, unittest.TestCase):
compression = zipfile.ZIP_LZMA
+@requires_zstd()
+class ZstdWriterTests(AbstractWriterTests, unittest.TestCase):
+ compression = zipfile.ZIP_ZSTANDARD
class PyZipFileTests(unittest.TestCase):
def assertCompiledIn(self, name, namelist):
@@ -1971,6 +1991,25 @@ class OtherTests(unittest.TestCase):
self.assertFalse(zipfile.is_zipfile(fp))
fp.seek(0, 0)
self.assertFalse(zipfile.is_zipfile(fp))
+ # - passing non-zipfile with ZIP header elements
+ # data created using pyPNG like so:
+ # d = [(ord('P'), ord('K'), 5, 6), (ord('P'), ord('K'), 6, 6)]
+ # w = png.Writer(1,2,alpha=True,compression=0)
+ # f = open('onepix.png', 'wb')
+ # w.write(f, d)
+ # w.close()
+ data = (b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00"
+ b"\x00\x02\x08\x06\x00\x00\x00\x99\x81\xb6'\x00\x00\x00\x15I"
+ b"DATx\x01\x01\n\x00\xf5\xff\x00PK\x05\x06\x00PK\x06\x06\x07"
+ b"\xac\x01N\xc6|a\r\x00\x00\x00\x00IEND\xaeB`\x82")
+ # - passing a filename
+ with open(TESTFN, "wb") as fp:
+ fp.write(data)
+ self.assertFalse(zipfile.is_zipfile(TESTFN))
+ # - passing a file-like object
+ fp = io.BytesIO()
+ fp.write(data)
+ self.assertFalse(zipfile.is_zipfile(fp))
def test_damaged_zipfile(self):
"""Check that zipfiles with missing bytes at the end raise BadZipFile."""
@@ -2669,6 +2708,17 @@ class LzmaBadCrcTests(AbstractBadCrcTests, unittest.TestCase):
b'ePK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00'
b'\x00>\x00\x00\x00\x00\x00')
+@requires_zstd()
+class ZstdBadCrcTests(AbstractBadCrcTests, unittest.TestCase):
+ compression = zipfile.ZIP_ZSTANDARD
+ zip_with_bad_crc = (
+ b'PK\x03\x04?\x00\x00\x00]\x00\x00\x00!\x00V\xb1\x17J\x14\x00'
+ b'\x00\x00\x0b\x00\x00\x00\x05\x00\x00\x00afile(\xb5/\xfd\x00'
+ b'XY\x00\x00Hello WorldPK\x01\x02?\x03?\x00\x00\x00]\x00\x00\x00'
+ b'!\x00V\xb0\x17J\x14\x00\x00\x00\x0b\x00\x00\x00\x05\x00\x00\x00'
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00afilePK'
+ b'\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00\x007\x00\x00\x00'
+ b'\x00\x00')
class DecryptionTests(unittest.TestCase):
"""Check that ZIP decryption works. Since the library does not
@@ -2896,6 +2946,10 @@ class LzmaTestsWithRandomBinaryFiles(AbstractTestsWithRandomBinaryFiles,
unittest.TestCase):
compression = zipfile.ZIP_LZMA
+@requires_zstd()
+class ZstdTestsWithRandomBinaryFiles(AbstractTestsWithRandomBinaryFiles,
+ unittest.TestCase):
+ compression = zipfile.ZIP_ZSTANDARD
# Provide the tell() method but not seek()
class Tellable:
@@ -3144,7 +3198,7 @@ class TestWithDirectory(unittest.TestCase):
with zipfile.ZipFile(TESTFN, "w") as zipf:
zipf.write(dirpath)
zinfo = zipf.filelist[0]
- self.assertTrue(zinfo.filename.endswith("/x/"))
+ self.assertEndsWith(zinfo.filename, "/x/")
self.assertEqual(zinfo.external_attr, (mode << 16) | 0x10)
zipf.write(dirpath, "y")
zinfo = zipf.filelist[1]
@@ -3152,7 +3206,7 @@ class TestWithDirectory(unittest.TestCase):
self.assertEqual(zinfo.external_attr, (mode << 16) | 0x10)
with zipfile.ZipFile(TESTFN, "r") as zipf:
zinfo = zipf.filelist[0]
- self.assertTrue(zinfo.filename.endswith("/x/"))
+ self.assertEndsWith(zinfo.filename, "/x/")
self.assertEqual(zinfo.external_attr, (mode << 16) | 0x10)
zinfo = zipf.filelist[1]
self.assertTrue(zinfo.filename, "y/")
@@ -3172,7 +3226,7 @@ class TestWithDirectory(unittest.TestCase):
self.assertEqual(zinfo.external_attr, (0o40775 << 16) | 0x10)
with zipfile.ZipFile(TESTFN, "r") as zipf:
zinfo = zipf.filelist[0]
- self.assertTrue(zinfo.filename.endswith("x/"))
+ self.assertEndsWith(zinfo.filename, "x/")
self.assertEqual(zinfo.external_attr, (0o40775 << 16) | 0x10)
target = os.path.join(TESTFN2, "target")
os.mkdir(target)
@@ -3607,7 +3661,7 @@ class EncodedMetadataTests(unittest.TestCase):
except OSError:
pass
except UnicodeEncodeError:
- self.skipTest(f'cannot encode file name {fn!r}')
+ self.skipTest(f'cannot encode file name {fn!a}')
zipfile.main(["--metadata-encoding=shift_jis", "-e", TESTFN, TESTFN2])
listing = os.listdir(TESTFN2)
diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py
index 1f288c8b45d..b5b4acf5f85 100644
--- a/Lib/test/test_zipimport.py
+++ b/Lib/test/test_zipimport.py
@@ -835,11 +835,11 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
s = io.StringIO()
print_tb(tb, 1, s)
- self.assertTrue(s.getvalue().endswith(
+ self.assertEndsWith(s.getvalue(),
' def do_raise(): raise TypeError\n'
'' if support.has_no_debug_ranges() else
' ^^^^^^^^^^^^^^^\n'
- ))
+ )
else:
raise AssertionError("This ought to be impossible")
diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py
index 4d97fe56f3a..c57ab51eca1 100644
--- a/Lib/test/test_zlib.py
+++ b/Lib/test/test_zlib.py
@@ -119,6 +119,114 @@ class ChecksumTestCase(unittest.TestCase):
self.assertEqual(binascii.crc32(b'spam'), zlib.crc32(b'spam'))
+class ChecksumCombineMixin:
+ """Mixin class for testing checksum combination."""
+
+ N = 1000
+ default_iv: int
+
+ def parse_iv(self, iv):
+ """Parse an IV value.
+
+ - The default IV is returned if *iv* is None.
+ - A random IV is returned if *iv* is -1.
+ - Otherwise, *iv* is returned as is.
+ """
+ if iv is None:
+ return self.default_iv
+ if iv == -1:
+ return random.randint(1, 0x80000000)
+ return iv
+
+ def checksum(self, data, init=None):
+ """Compute the checksum of data with a given initial value.
+
+ The *init* value is parsed by ``parse_iv``.
+ """
+ iv = self.parse_iv(init)
+ return self._checksum(data, iv)
+
+ def _checksum(self, data, init):
+ raise NotImplementedError
+
+ def combine(self, a, b, blen):
+ """Combine two checksums together."""
+ raise NotImplementedError
+
+ def get_random_data(self, data_len, *, iv=None):
+ """Get a triplet (data, iv, checksum)."""
+ data = random.randbytes(data_len)
+ init = self.parse_iv(iv)
+ checksum = self.checksum(data, init)
+ return data, init, checksum
+
+ def test_combine_empty(self):
+ for _ in range(self.N):
+ a, iv, checksum = self.get_random_data(32, iv=-1)
+ res = self.combine(iv, self.checksum(a), len(a))
+ self.assertEqual(res, checksum)
+
+ def test_combine_no_iv(self):
+ for _ in range(self.N):
+ a, _, chk_a = self.get_random_data(32)
+ b, _, chk_b = self.get_random_data(64)
+ res = self.combine(chk_a, chk_b, len(b))
+ self.assertEqual(res, self.checksum(a + b))
+
+ def test_combine_no_iv_invalid_length(self):
+ a, _, chk_a = self.get_random_data(32)
+ b, _, chk_b = self.get_random_data(64)
+ checksum = self.checksum(a + b)
+ for invalid_len in [1, len(a), 48, len(b) + 1, 191]:
+ invalid_res = self.combine(chk_a, chk_b, invalid_len)
+ self.assertNotEqual(invalid_res, checksum)
+
+ self.assertRaises(TypeError, self.combine, 0, 0, "len")
+
+ def test_combine_with_iv(self):
+ for _ in range(self.N):
+ a, iv_a, chk_a_with_iv = self.get_random_data(32, iv=-1)
+ chk_a_no_iv = self.checksum(a)
+ b, iv_b, chk_b_with_iv = self.get_random_data(64, iv=-1)
+ chk_b_no_iv = self.checksum(b)
+
+ # We can represent c = COMBINE(CHK(a, iv_a), CHK(b, iv_b)) as:
+ #
+ # c = CHK(CHK(b'', iv_a) + CHK(a) + CHK(b'', iv_b) + CHK(b))
+ # = COMBINE(
+ # COMBINE(CHK(b'', iv_a), CHK(a)),
+ # COMBINE(CHK(b'', iv_b), CHK(b)),
+ # )
+ # = COMBINE(COMBINE(iv_a, CHK(a)), COMBINE(iv_b, CHK(b)))
+ tmp0 = self.combine(iv_a, chk_a_no_iv, len(a))
+ tmp1 = self.combine(iv_b, chk_b_no_iv, len(b))
+ expected = self.combine(tmp0, tmp1, len(b))
+ checksum = self.combine(chk_a_with_iv, chk_b_with_iv, len(b))
+ self.assertEqual(checksum, expected)
+
+
+class CRC32CombineTestCase(ChecksumCombineMixin, unittest.TestCase):
+
+ default_iv = 0
+
+ def _checksum(self, data, init):
+ return zlib.crc32(data, init)
+
+ def combine(self, a, b, blen):
+ return zlib.crc32_combine(a, b, blen)
+
+
+class Adler32CombineTestCase(ChecksumCombineMixin, unittest.TestCase):
+
+ default_iv = 1
+
+ def _checksum(self, data, init):
+ return zlib.adler32(data, init)
+
+ def combine(self, a, b, blen):
+ return zlib.adler32_combine(a, b, blen)
+
+
# Issue #10276 - check that inputs >=4 GiB are handled correctly.
class ChecksumBigBufferTestCase(unittest.TestCase):
diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py
index d2845495c7f..44e87e71c8e 100644
--- a/Lib/test/test_zoneinfo/test_zoneinfo.py
+++ b/Lib/test/test_zoneinfo/test_zoneinfo.py
@@ -58,6 +58,10 @@ def tearDownModule():
shutil.rmtree(TEMP_DIR)
+class CustomError(Exception):
+ pass
+
+
class TzPathUserMixin:
"""
Adds a setUp() and tearDown() to make TZPATH manipulations thread-safe.
@@ -404,6 +408,25 @@ class ZoneInfoTest(TzPathUserMixin, ZoneInfoTestBase):
self.assertEqual(t.utcoffset(), offset.utcoffset)
self.assertEqual(t.dst(), offset.dst)
+ def test_cache_exception(self):
+ class Incomparable(str):
+ eq_called = False
+ def __eq__(self, other):
+ self.eq_called = True
+ raise CustomError
+ __hash__ = str.__hash__
+
+ key = "America/Los_Angeles"
+ tz1 = self.klass(key)
+ key = Incomparable(key)
+ try:
+ tz2 = self.klass(key)
+ except CustomError:
+ self.assertTrue(key.eq_called)
+ else:
+ self.assertFalse(key.eq_called)
+ self.assertIs(tz2, tz1)
+
class CZoneInfoTest(ZoneInfoTest):
module = c_zoneinfo
@@ -1507,6 +1530,26 @@ class ZoneInfoCacheTest(TzPathUserMixin, ZoneInfoTestBase):
self.assertIsNot(dub0, dub1)
self.assertIs(tok0, tok1)
+ def test_clear_cache_refleak(self):
+ class Stringy(str):
+ allow_comparisons = True
+ def __eq__(self, other):
+ if not self.allow_comparisons:
+ raise CustomError
+ return super().__eq__(other)
+ __hash__ = str.__hash__
+
+ key = Stringy("America/Los_Angeles")
+ self.klass(key)
+ key.allow_comparisons = False
+ try:
+ # Note: This is try/except rather than assertRaises because
+ # there is no guarantee that the key is even still in the cache,
+ # or that the key for the cache is the original `key` object.
+ self.klass.clear_cache(only_keys="America/Los_Angeles")
+ except CustomError:
+ pass
+
class CZoneInfoCacheTest(ZoneInfoCacheTest):
module = c_zoneinfo
@@ -1915,8 +1958,8 @@ class ExtensionBuiltTest(unittest.TestCase):
def test_cache_location(self):
# The pure Python version stores caches on attributes, but the C
# extension stores them in C globals (at least for now)
- self.assertFalse(hasattr(c_zoneinfo.ZoneInfo, "_weak_cache"))
- self.assertTrue(hasattr(py_zoneinfo.ZoneInfo, "_weak_cache"))
+ self.assertNotHasAttr(c_zoneinfo.ZoneInfo, "_weak_cache")
+ self.assertHasAttr(py_zoneinfo.ZoneInfo, "_weak_cache")
def test_gc_tracked(self):
import gc
diff --git a/Lib/test/test_zstd.py b/Lib/test/test_zstd.py
new file mode 100644
index 00000000000..d4c28aed38e
--- /dev/null
+++ b/Lib/test/test_zstd.py
@@ -0,0 +1,2794 @@
+import array
+import gc
+import io
+import pathlib
+import random
+import re
+import os
+import unittest
+import tempfile
+import threading
+
+from test.support.import_helper import import_module
+from test.support import threading_helper
+from test.support import _1M
+
+_zstd = import_module("_zstd")
+zstd = import_module("compression.zstd")
+
+from compression.zstd import (
+ open,
+ compress,
+ decompress,
+ ZstdCompressor,
+ ZstdDecompressor,
+ ZstdDict,
+ ZstdError,
+ zstd_version,
+ zstd_version_info,
+ COMPRESSION_LEVEL_DEFAULT,
+ get_frame_info,
+ get_frame_size,
+ finalize_dict,
+ train_dict,
+ CompressionParameter,
+ DecompressionParameter,
+ Strategy,
+ ZstdFile,
+)
+
+_1K = 1024
+_130_1K = 130 * _1K
+DICT_SIZE1 = 3*_1K
+
+DAT_130K_D = None
+DAT_130K_C = None
+
+DECOMPRESSED_DAT = None
+COMPRESSED_DAT = None
+
+DECOMPRESSED_100_PLUS_32KB = None
+COMPRESSED_100_PLUS_32KB = None
+
+SKIPPABLE_FRAME = None
+
+THIS_FILE_BYTES = None
+THIS_FILE_STR = None
+COMPRESSED_THIS_FILE = None
+
+COMPRESSED_BOGUS = None
+
+SAMPLES = None
+
+TRAINED_DICT = None
+
+SUPPORT_MULTITHREADING = False
+
+C_INT_MIN = -(2**31)
+C_INT_MAX = (2**31) - 1
+
+
+def setUpModule():
+ global SUPPORT_MULTITHREADING
+ SUPPORT_MULTITHREADING = CompressionParameter.nb_workers.bounds() != (0, 0)
+ # uncompressed size 130KB, more than a zstd block.
+ # with a frame epilogue, 4 bytes checksum.
+ global DAT_130K_D
+ DAT_130K_D = bytes([random.randint(0, 127) for _ in range(130*_1K)])
+
+ global DAT_130K_C
+ DAT_130K_C = compress(DAT_130K_D, options={CompressionParameter.checksum_flag:1})
+
+ global DECOMPRESSED_DAT
+ DECOMPRESSED_DAT = b'abcdefg123456' * 1000
+
+ global COMPRESSED_DAT
+ COMPRESSED_DAT = compress(DECOMPRESSED_DAT)
+
+ global DECOMPRESSED_100_PLUS_32KB
+ DECOMPRESSED_100_PLUS_32KB = b'a' * (100 + 32*_1K)
+
+ global COMPRESSED_100_PLUS_32KB
+ COMPRESSED_100_PLUS_32KB = compress(DECOMPRESSED_100_PLUS_32KB)
+
+ global SKIPPABLE_FRAME
+ SKIPPABLE_FRAME = (0x184D2A50).to_bytes(4, byteorder='little') + \
+ (32*_1K).to_bytes(4, byteorder='little') + \
+ b'a' * (32*_1K)
+
+ global THIS_FILE_BYTES, THIS_FILE_STR
+ with io.open(os.path.abspath(__file__), 'rb') as f:
+ THIS_FILE_BYTES = f.read()
+ THIS_FILE_BYTES = re.sub(rb'\r?\n', rb'\n', THIS_FILE_BYTES)
+ THIS_FILE_STR = THIS_FILE_BYTES.decode('utf-8')
+
+ global COMPRESSED_THIS_FILE
+ COMPRESSED_THIS_FILE = compress(THIS_FILE_BYTES)
+
+ global COMPRESSED_BOGUS
+ COMPRESSED_BOGUS = DECOMPRESSED_DAT
+
+ # dict data
+ words = [b'red', b'green', b'yellow', b'black', b'withe', b'blue',
+ b'lilac', b'purple', b'navy', b'glod', b'silver', b'olive',
+ b'dog', b'cat', b'tiger', b'lion', b'fish', b'bird']
+ lst = []
+ for i in range(300):
+ sample = [b'%s = %d' % (random.choice(words), random.randrange(100))
+ for j in range(20)]
+ sample = b'\n'.join(sample)
+
+ lst.append(sample)
+ global SAMPLES
+ SAMPLES = lst
+ assert len(SAMPLES) > 10
+
+ global TRAINED_DICT
+ TRAINED_DICT = train_dict(SAMPLES, 3*_1K)
+ assert len(TRAINED_DICT.dict_content) <= 3*_1K
+
+
+class FunctionsTestCase(unittest.TestCase):
+
+ def test_version(self):
+ s = ".".join((str(i) for i in zstd_version_info))
+ self.assertEqual(s, zstd_version)
+
+ def test_compressionLevel_values(self):
+ min, max = CompressionParameter.compression_level.bounds()
+ self.assertIs(type(COMPRESSION_LEVEL_DEFAULT), int)
+ self.assertIs(type(min), int)
+ self.assertIs(type(max), int)
+ self.assertLess(min, max)
+
+ def test_roundtrip_default(self):
+ raw_dat = THIS_FILE_BYTES[: len(THIS_FILE_BYTES) // 6]
+ dat1 = compress(raw_dat)
+ dat2 = decompress(dat1)
+ self.assertEqual(dat2, raw_dat)
+
+ def test_roundtrip_level(self):
+ raw_dat = THIS_FILE_BYTES[: len(THIS_FILE_BYTES) // 6]
+ level_min, level_max = CompressionParameter.compression_level.bounds()
+
+ for level in range(max(-20, level_min), level_max + 1):
+ dat1 = compress(raw_dat, level)
+ dat2 = decompress(dat1)
+ self.assertEqual(dat2, raw_dat)
+
+ def test_get_frame_info(self):
+ # no dict
+ info = get_frame_info(COMPRESSED_100_PLUS_32KB[:20])
+ self.assertEqual(info.decompressed_size, 32 * _1K + 100)
+ self.assertEqual(info.dictionary_id, 0)
+
+ # use dict
+ dat = compress(b"a" * 345, zstd_dict=TRAINED_DICT)
+ info = get_frame_info(dat)
+ self.assertEqual(info.decompressed_size, 345)
+ self.assertEqual(info.dictionary_id, TRAINED_DICT.dict_id)
+
+ with self.assertRaisesRegex(ZstdError, "not less than the frame header"):
+ get_frame_info(b"aaaaaaaaaaaaaa")
+
+ def test_get_frame_size(self):
+ size = get_frame_size(COMPRESSED_100_PLUS_32KB)
+ self.assertEqual(size, len(COMPRESSED_100_PLUS_32KB))
+
+ with self.assertRaisesRegex(ZstdError, "not less than this complete frame"):
+ get_frame_size(b"aaaaaaaaaaaaaa")
+
+ def test_decompress_2x130_1K(self):
+ decompressed_size = get_frame_info(DAT_130K_C).decompressed_size
+ self.assertEqual(decompressed_size, _130_1K)
+
+ dat = decompress(DAT_130K_C + DAT_130K_C)
+ self.assertEqual(len(dat), 2 * _130_1K)
+
+
+class CompressorTestCase(unittest.TestCase):
+
+ def test_simple_compress_bad_args(self):
+ # ZstdCompressor
+ self.assertRaises(TypeError, ZstdCompressor, [])
+ self.assertRaises(TypeError, ZstdCompressor, level=3.14)
+ self.assertRaises(TypeError, ZstdCompressor, level="abc")
+ self.assertRaises(TypeError, ZstdCompressor, options=b"abc")
+
+ self.assertRaises(TypeError, ZstdCompressor, zstd_dict=123)
+ self.assertRaises(TypeError, ZstdCompressor, zstd_dict=b"abcd1234")
+ self.assertRaises(TypeError, ZstdCompressor, zstd_dict={1: 2, 3: 4})
+
+ # valid range for compression level is [-(1<<17), 22]
+ msg = r'illegal compression level {}; the valid range is \[-?\d+, -?\d+\]'
+ with self.assertRaisesRegex(ValueError, msg.format(C_INT_MAX)):
+ ZstdCompressor(C_INT_MAX)
+ with self.assertRaisesRegex(ValueError, msg.format(C_INT_MIN)):
+ ZstdCompressor(C_INT_MIN)
+ msg = r'illegal compression level; the valid range is \[-?\d+, -?\d+\]'
+ with self.assertRaisesRegex(ValueError, msg):
+ ZstdCompressor(level=-(2**1000))
+ with self.assertRaisesRegex(ValueError, msg):
+ ZstdCompressor(level=2**1000)
+
+ with self.assertRaises(ValueError):
+ ZstdCompressor(options={CompressionParameter.window_log: 100})
+ with self.assertRaises(ValueError):
+ ZstdCompressor(options={3333: 100})
+
+ # Method bad arguments
+ zc = ZstdCompressor()
+ self.assertRaises(TypeError, zc.compress)
+ self.assertRaises((TypeError, ValueError), zc.compress, b"foo", b"bar")
+ self.assertRaises(TypeError, zc.compress, "str")
+ self.assertRaises((TypeError, ValueError), zc.flush, b"foo")
+ self.assertRaises(TypeError, zc.flush, b"blah", 1)
+
+ self.assertRaises(ValueError, zc.compress, b'', -1)
+ self.assertRaises(ValueError, zc.compress, b'', 3)
+ self.assertRaises(ValueError, zc.flush, zc.CONTINUE) # 0
+ self.assertRaises(ValueError, zc.flush, 3)
+
+ zc.compress(b'')
+ zc.compress(b'', zc.CONTINUE)
+ zc.compress(b'', zc.FLUSH_BLOCK)
+ zc.compress(b'', zc.FLUSH_FRAME)
+ empty = zc.flush()
+ zc.flush(zc.FLUSH_BLOCK)
+ zc.flush(zc.FLUSH_FRAME)
+
+ def test_compress_parameters(self):
+ d = {CompressionParameter.compression_level : 10,
+
+ CompressionParameter.window_log : 12,
+ CompressionParameter.hash_log : 10,
+ CompressionParameter.chain_log : 12,
+ CompressionParameter.search_log : 12,
+ CompressionParameter.min_match : 4,
+ CompressionParameter.target_length : 12,
+ CompressionParameter.strategy : Strategy.lazy,
+
+ CompressionParameter.enable_long_distance_matching : 1,
+ CompressionParameter.ldm_hash_log : 12,
+ CompressionParameter.ldm_min_match : 11,
+ CompressionParameter.ldm_bucket_size_log : 5,
+ CompressionParameter.ldm_hash_rate_log : 12,
+
+ CompressionParameter.content_size_flag : 1,
+ CompressionParameter.checksum_flag : 1,
+ CompressionParameter.dict_id_flag : 0,
+
+ CompressionParameter.nb_workers : 2 if SUPPORT_MULTITHREADING else 0,
+ CompressionParameter.job_size : 5*_1M if SUPPORT_MULTITHREADING else 0,
+ CompressionParameter.overlap_log : 9 if SUPPORT_MULTITHREADING else 0,
+ }
+ ZstdCompressor(options=d)
+
+ d1 = d.copy()
+ # larger than signed int
+ d1[CompressionParameter.ldm_bucket_size_log] = C_INT_MAX
+ with self.assertRaises(ValueError):
+ ZstdCompressor(options=d1)
+ # smaller than signed int
+ d1[CompressionParameter.ldm_bucket_size_log] = C_INT_MIN
+ with self.assertRaises(ValueError):
+ ZstdCompressor(options=d1)
+
+ # out of bounds compression level
+ level_min, level_max = CompressionParameter.compression_level.bounds()
+ with self.assertRaises(ValueError):
+ compress(b'', level_max+1)
+ with self.assertRaises(ValueError):
+ compress(b'', level_min-1)
+ with self.assertRaises(ValueError):
+ compress(b'', 2**1000)
+ with self.assertRaises(ValueError):
+ compress(b'', -(2**1000))
+ with self.assertRaises(ValueError):
+ compress(b'', options={
+ CompressionParameter.compression_level: level_max+1})
+ with self.assertRaises(ValueError):
+ compress(b'', options={
+ CompressionParameter.compression_level: level_min-1})
+
+ # zstd lib doesn't support MT compression
+ if not SUPPORT_MULTITHREADING:
+ with self.assertRaises(ValueError):
+ ZstdCompressor(options={CompressionParameter.nb_workers:4})
+ with self.assertRaises(ValueError):
+ ZstdCompressor(options={CompressionParameter.job_size:4})
+ with self.assertRaises(ValueError):
+ ZstdCompressor(options={CompressionParameter.overlap_log:4})
+
+ # out of bounds error msg
+ option = {CompressionParameter.window_log:100}
+ with self.assertRaisesRegex(
+ ValueError,
+ "compression parameter 'window_log' received an illegal value 100; "
+ r'the valid range is \[-?\d+, -?\d+\]',
+ ):
+ compress(b'', options=option)
+
+ def test_unknown_compression_parameter(self):
+ KEY = 100001234
+ option = {CompressionParameter.compression_level: 10,
+ KEY: 200000000}
+ pattern = rf"invalid compression parameter 'unknown parameter \(key {KEY}\)'"
+ with self.assertRaisesRegex(ValueError, pattern):
+ ZstdCompressor(options=option)
+
+ @unittest.skipIf(not SUPPORT_MULTITHREADING,
+ "zstd build doesn't support multi-threaded compression")
+ def test_zstd_multithread_compress(self):
+ size = 40*_1M
+ b = THIS_FILE_BYTES * (size // len(THIS_FILE_BYTES))
+
+ options = {CompressionParameter.compression_level : 4,
+ CompressionParameter.nb_workers : 2}
+
+ # compress()
+ dat1 = compress(b, options=options)
+ dat2 = decompress(dat1)
+ self.assertEqual(dat2, b)
+
+ # ZstdCompressor
+ c = ZstdCompressor(options=options)
+ dat1 = c.compress(b, c.CONTINUE)
+ dat2 = c.compress(b, c.FLUSH_BLOCK)
+ dat3 = c.compress(b, c.FLUSH_FRAME)
+ dat4 = decompress(dat1+dat2+dat3)
+ self.assertEqual(dat4, b * 3)
+
+ # ZstdFile
+ with ZstdFile(io.BytesIO(), 'w', options=options) as f:
+ f.write(b)
+
+ def test_compress_flushblock(self):
+ point = len(THIS_FILE_BYTES) // 2
+
+ c = ZstdCompressor()
+ self.assertEqual(c.last_mode, c.FLUSH_FRAME)
+ dat1 = c.compress(THIS_FILE_BYTES[:point])
+ self.assertEqual(c.last_mode, c.CONTINUE)
+ dat1 += c.compress(THIS_FILE_BYTES[point:], c.FLUSH_BLOCK)
+ self.assertEqual(c.last_mode, c.FLUSH_BLOCK)
+ dat2 = c.flush()
+ pattern = "Compressed data ended before the end-of-stream marker"
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(dat1)
+
+ dat3 = decompress(dat1 + dat2)
+
+ self.assertEqual(dat3, THIS_FILE_BYTES)
+
+ def test_compress_flushframe(self):
+ # test compress & decompress
+ point = len(THIS_FILE_BYTES) // 2
+
+ c = ZstdCompressor()
+
+ dat1 = c.compress(THIS_FILE_BYTES[:point])
+ self.assertEqual(c.last_mode, c.CONTINUE)
+
+ dat1 += c.compress(THIS_FILE_BYTES[point:], c.FLUSH_FRAME)
+ self.assertEqual(c.last_mode, c.FLUSH_FRAME)
+
+ nt = get_frame_info(dat1)
+ self.assertEqual(nt.decompressed_size, None) # no content size
+
+ dat2 = decompress(dat1)
+
+ self.assertEqual(dat2, THIS_FILE_BYTES)
+
+ # single .FLUSH_FRAME mode has content size
+ c = ZstdCompressor()
+ dat = c.compress(THIS_FILE_BYTES, mode=c.FLUSH_FRAME)
+ self.assertEqual(c.last_mode, c.FLUSH_FRAME)
+
+ nt = get_frame_info(dat)
+ self.assertEqual(nt.decompressed_size, len(THIS_FILE_BYTES))
+
+ def test_compress_empty(self):
+ # output empty content frame
+ self.assertNotEqual(compress(b''), b'')
+
+ c = ZstdCompressor()
+ self.assertNotEqual(c.compress(b'', c.FLUSH_FRAME), b'')
+
+ def test_set_pledged_input_size(self):
+ DAT = DECOMPRESSED_100_PLUS_32KB
+ CHUNK_SIZE = len(DAT) // 3
+
+ # wrong value
+ c = ZstdCompressor()
+ with self.assertRaisesRegex(ValueError,
+ r'should be a positive int less than \d+'):
+ c.set_pledged_input_size(-300)
+ # overflow
+ with self.assertRaisesRegex(ValueError,
+ r'should be a positive int less than \d+'):
+ c.set_pledged_input_size(2**64)
+ # ZSTD_CONTENTSIZE_ERROR is invalid
+ with self.assertRaisesRegex(ValueError,
+ r'should be a positive int less than \d+'):
+ c.set_pledged_input_size(2**64-2)
+ # ZSTD_CONTENTSIZE_UNKNOWN should use None
+ with self.assertRaisesRegex(ValueError,
+ r'should be a positive int less than \d+'):
+ c.set_pledged_input_size(2**64-1)
+
+ # check valid values are settable
+ c.set_pledged_input_size(2**63)
+ c.set_pledged_input_size(2**64-3)
+
+ # check that zero means empty frame
+ c = ZstdCompressor(level=1)
+ c.set_pledged_input_size(0)
+ c.compress(b'')
+ dat = c.flush()
+ ret = get_frame_info(dat)
+ self.assertEqual(ret.decompressed_size, 0)
+
+
+ # wrong mode
+ c = ZstdCompressor(level=1)
+ c.compress(b'123456')
+ self.assertEqual(c.last_mode, c.CONTINUE)
+ with self.assertRaisesRegex(ValueError,
+ r'last_mode == FLUSH_FRAME'):
+ c.set_pledged_input_size(300)
+
+ # None value
+ c = ZstdCompressor(level=1)
+ c.set_pledged_input_size(None)
+ dat = c.compress(DAT) + c.flush()
+
+ ret = get_frame_info(dat)
+ self.assertEqual(ret.decompressed_size, None)
+
+ # correct value
+ c = ZstdCompressor(level=1)
+ c.set_pledged_input_size(len(DAT))
+
+ chunks = []
+ posi = 0
+ while posi < len(DAT):
+ dat = c.compress(DAT[posi:posi+CHUNK_SIZE])
+ posi += CHUNK_SIZE
+ chunks.append(dat)
+
+ dat = c.flush()
+ chunks.append(dat)
+ chunks = b''.join(chunks)
+
+ ret = get_frame_info(chunks)
+ self.assertEqual(ret.decompressed_size, len(DAT))
+ self.assertEqual(decompress(chunks), DAT)
+
+ c.set_pledged_input_size(len(DAT)) # the second frame
+ dat = c.compress(DAT) + c.flush()
+
+ ret = get_frame_info(dat)
+ self.assertEqual(ret.decompressed_size, len(DAT))
+ self.assertEqual(decompress(dat), DAT)
+
+ # not enough data
+ c = ZstdCompressor(level=1)
+ c.set_pledged_input_size(len(DAT)+1)
+
+ for start in range(0, len(DAT), CHUNK_SIZE):
+ end = min(start+CHUNK_SIZE, len(DAT))
+ _dat = c.compress(DAT[start:end])
+
+ with self.assertRaises(ZstdError):
+ c.flush()
+
+ # too much data
+ c = ZstdCompressor(level=1)
+ c.set_pledged_input_size(len(DAT))
+
+ for start in range(0, len(DAT), CHUNK_SIZE):
+ end = min(start+CHUNK_SIZE, len(DAT))
+ _dat = c.compress(DAT[start:end])
+
+ with self.assertRaises(ZstdError):
+ c.compress(b'extra', ZstdCompressor.FLUSH_FRAME)
+
+ # content size not set if content_size_flag == 0
+ c = ZstdCompressor(options={CompressionParameter.content_size_flag: 0})
+ c.set_pledged_input_size(10)
+ dat1 = c.compress(b"hello")
+ dat2 = c.compress(b"world")
+ dat3 = c.flush()
+ frame_data = get_frame_info(dat1 + dat2 + dat3)
+ self.assertIsNone(frame_data.decompressed_size)
+
+
+class DecompressorTestCase(unittest.TestCase):
+
+ def test_simple_decompress_bad_args(self):
+ # ZstdDecompressor
+ self.assertRaises(TypeError, ZstdDecompressor, ())
+ self.assertRaises(TypeError, ZstdDecompressor, zstd_dict=123)
+ self.assertRaises(TypeError, ZstdDecompressor, zstd_dict=b'abc')
+ self.assertRaises(TypeError, ZstdDecompressor, zstd_dict={1:2, 3:4})
+
+ self.assertRaises(TypeError, ZstdDecompressor, options=123)
+ self.assertRaises(TypeError, ZstdDecompressor, options='abc')
+ self.assertRaises(TypeError, ZstdDecompressor, options=b'abc')
+
+ with self.assertRaises(ValueError):
+ ZstdDecompressor(options={C_INT_MAX: 100})
+ with self.assertRaises(ValueError):
+ ZstdDecompressor(options={C_INT_MIN: 100})
+ with self.assertRaises(ValueError):
+ ZstdDecompressor(options={0: C_INT_MAX})
+ with self.assertRaises(OverflowError):
+ ZstdDecompressor(options={2**1000: 100})
+ with self.assertRaises(OverflowError):
+ ZstdDecompressor(options={-(2**1000): 100})
+ with self.assertRaises(OverflowError):
+ ZstdDecompressor(options={0: -(2**1000)})
+
+ with self.assertRaises(ValueError):
+ ZstdDecompressor(options={DecompressionParameter.window_log_max: 100})
+ with self.assertRaises(ValueError):
+ ZstdDecompressor(options={3333: 100})
+
+ empty = compress(b'')
+ lzd = ZstdDecompressor()
+ self.assertRaises(TypeError, lzd.decompress)
+ self.assertRaises(TypeError, lzd.decompress, b"foo", b"bar")
+ self.assertRaises(TypeError, lzd.decompress, "str")
+ lzd.decompress(empty)
+
+ def test_decompress_parameters(self):
+ d = {DecompressionParameter.window_log_max : 15}
+ ZstdDecompressor(options=d)
+
+ d1 = d.copy()
+ # larger than signed int
+ d1[DecompressionParameter.window_log_max] = 2**1000
+ with self.assertRaises(OverflowError):
+ ZstdDecompressor(None, d1)
+ # smaller than signed int
+ d1[DecompressionParameter.window_log_max] = -(2**1000)
+ with self.assertRaises(OverflowError):
+ ZstdDecompressor(None, d1)
+
+ d1[DecompressionParameter.window_log_max] = C_INT_MAX
+ with self.assertRaises(ValueError):
+ ZstdDecompressor(None, d1)
+ d1[DecompressionParameter.window_log_max] = C_INT_MIN
+ with self.assertRaises(ValueError):
+ ZstdDecompressor(None, d1)
+
+ # out of bounds error msg
+ options = {DecompressionParameter.window_log_max:100}
+ with self.assertRaisesRegex(
+ ValueError,
+ "decompression parameter 'window_log_max' received an illegal value 100; "
+ r'the valid range is \[-?\d+, -?\d+\]',
+ ):
+ decompress(b'', options=options)
+
+ # out of bounds deecompression parameter
+ options[DecompressionParameter.window_log_max] = C_INT_MAX
+ with self.assertRaises(ValueError):
+ decompress(b'', options=options)
+ options[DecompressionParameter.window_log_max] = C_INT_MIN
+ with self.assertRaises(ValueError):
+ decompress(b'', options=options)
+ options[DecompressionParameter.window_log_max] = 2**1000
+ with self.assertRaises(OverflowError):
+ decompress(b'', options=options)
+ options[DecompressionParameter.window_log_max] = -(2**1000)
+ with self.assertRaises(OverflowError):
+ decompress(b'', options=options)
+
+ def test_unknown_decompression_parameter(self):
+ KEY = 100001234
+ options = {DecompressionParameter.window_log_max: DecompressionParameter.window_log_max.bounds()[1],
+ KEY: 200000000}
+ pattern = rf"invalid decompression parameter 'unknown parameter \(key {KEY}\)'"
+ with self.assertRaisesRegex(ValueError, pattern):
+ ZstdDecompressor(options=options)
+
+ def test_decompress_epilogue_flags(self):
+ # DAT_130K_C has a 4 bytes checksum at frame epilogue
+
+ # full unlimited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C)
+ self.assertEqual(len(dat), _130_1K)
+ self.assertFalse(d.needs_input)
+
+ with self.assertRaises(EOFError):
+ dat = d.decompress(b'')
+
+ # full limited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C, _130_1K)
+ self.assertEqual(len(dat), _130_1K)
+ self.assertFalse(d.needs_input)
+
+ with self.assertRaises(EOFError):
+ dat = d.decompress(b'', 0)
+
+ # [:-4] unlimited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-4])
+ self.assertEqual(len(dat), _130_1K)
+ self.assertTrue(d.needs_input)
+
+ dat = d.decompress(b'')
+ self.assertEqual(len(dat), 0)
+ self.assertTrue(d.needs_input)
+
+ # [:-4] limited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-4], _130_1K)
+ self.assertEqual(len(dat), _130_1K)
+ self.assertFalse(d.needs_input)
+
+ dat = d.decompress(b'', 0)
+ self.assertEqual(len(dat), 0)
+ self.assertFalse(d.needs_input)
+
+ # [:-3] unlimited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-3])
+ self.assertEqual(len(dat), _130_1K)
+ self.assertTrue(d.needs_input)
+
+ dat = d.decompress(b'')
+ self.assertEqual(len(dat), 0)
+ self.assertTrue(d.needs_input)
+
+ # [:-3] limited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-3], _130_1K)
+ self.assertEqual(len(dat), _130_1K)
+ self.assertFalse(d.needs_input)
+
+ dat = d.decompress(b'', 0)
+ self.assertEqual(len(dat), 0)
+ self.assertFalse(d.needs_input)
+
+ # [:-1] unlimited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-1])
+ self.assertEqual(len(dat), _130_1K)
+ self.assertTrue(d.needs_input)
+
+ dat = d.decompress(b'')
+ self.assertEqual(len(dat), 0)
+ self.assertTrue(d.needs_input)
+
+ # [:-1] limited
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-1], _130_1K)
+ self.assertEqual(len(dat), _130_1K)
+ self.assertFalse(d.needs_input)
+
+ dat = d.decompress(b'', 0)
+ self.assertEqual(len(dat), 0)
+ self.assertFalse(d.needs_input)
+
+ def test_decompressor_arg(self):
+ zd = ZstdDict(b'12345678', is_raw=True)
+
+ with self.assertRaises(TypeError):
+ d = ZstdDecompressor(zstd_dict={})
+
+ with self.assertRaises(TypeError):
+ d = ZstdDecompressor(options=zd)
+
+ ZstdDecompressor()
+ ZstdDecompressor(zd, {})
+ ZstdDecompressor(zstd_dict=zd, options={DecompressionParameter.window_log_max:25})
+
+ def test_decompressor_1(self):
+ # empty
+ d = ZstdDecompressor()
+ dat = d.decompress(b'')
+
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+
+ # 130_1K full
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C)
+
+ self.assertEqual(len(dat), _130_1K)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+
+ # 130_1K full, limit output
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C, _130_1K)
+
+ self.assertEqual(len(dat), _130_1K)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+
+ # 130_1K, without 4 bytes checksum
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-4])
+
+ self.assertEqual(len(dat), _130_1K)
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+
+ # above, limit output
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C[:-4], _130_1K)
+
+ self.assertEqual(len(dat), _130_1K)
+ self.assertFalse(d.eof)
+ self.assertFalse(d.needs_input)
+
+ # full, unused_data
+ TRAIL = b'89234893abcd'
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT_130K_C + TRAIL, _130_1K)
+
+ self.assertEqual(len(dat), _130_1K)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, TRAIL)
+
+ def test_decompressor_chunks_read_300(self):
+ TRAIL = b'89234893abcd'
+ DAT = DAT_130K_C + TRAIL
+ d = ZstdDecompressor()
+
+ bi = io.BytesIO(DAT)
+ lst = []
+ while True:
+ if d.needs_input:
+ dat = bi.read(300)
+ if not dat:
+ break
+ else:
+ raise Exception('should not get here')
+
+ ret = d.decompress(dat)
+ lst.append(ret)
+ if d.eof:
+ break
+
+ ret = b''.join(lst)
+
+ self.assertEqual(len(ret), _130_1K)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data + bi.read(), TRAIL)
+
+ def test_decompressor_chunks_read_3(self):
+ TRAIL = b'89234893'
+ DAT = DAT_130K_C + TRAIL
+ d = ZstdDecompressor()
+
+ bi = io.BytesIO(DAT)
+ lst = []
+ while True:
+ if d.needs_input:
+ dat = bi.read(3)
+ if not dat:
+ break
+ else:
+ dat = b''
+
+ ret = d.decompress(dat, 1)
+ lst.append(ret)
+ if d.eof:
+ break
+
+ ret = b''.join(lst)
+
+ self.assertEqual(len(ret), _130_1K)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data + bi.read(), TRAIL)
+
+
+ def test_decompress_empty(self):
+ with self.assertRaises(ZstdError):
+ decompress(b'')
+
+ d = ZstdDecompressor()
+ self.assertEqual(d.decompress(b''), b'')
+ self.assertFalse(d.eof)
+
+ def test_decompress_empty_content_frame(self):
+ DAT = compress(b'')
+ # decompress
+ self.assertGreaterEqual(len(DAT), 4)
+ self.assertEqual(decompress(DAT), b'')
+
+ with self.assertRaises(ZstdError):
+ decompress(DAT[:-1])
+
+ # ZstdDecompressor
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT)
+ self.assertEqual(dat, b'')
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ d = ZstdDecompressor()
+ dat = d.decompress(DAT[:-1])
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+class DecompressorFlagsTestCase(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ options = {CompressionParameter.checksum_flag:1}
+ c = ZstdCompressor(options=options)
+
+ cls.DECOMPRESSED_42 = b'a'*42
+ cls.FRAME_42 = c.compress(cls.DECOMPRESSED_42, c.FLUSH_FRAME)
+
+ cls.DECOMPRESSED_60 = b'a'*60
+ cls.FRAME_60 = c.compress(cls.DECOMPRESSED_60, c.FLUSH_FRAME)
+
+ cls.FRAME_42_60 = cls.FRAME_42 + cls.FRAME_60
+ cls.DECOMPRESSED_42_60 = cls.DECOMPRESSED_42 + cls.DECOMPRESSED_60
+
+ cls._130_1K = 130*_1K
+
+ c = ZstdCompressor()
+ cls.UNKNOWN_FRAME_42 = c.compress(cls.DECOMPRESSED_42) + c.flush()
+ cls.UNKNOWN_FRAME_60 = c.compress(cls.DECOMPRESSED_60) + c.flush()
+ cls.UNKNOWN_FRAME_42_60 = cls.UNKNOWN_FRAME_42 + cls.UNKNOWN_FRAME_60
+
+ cls.TRAIL = b'12345678abcdefg!@#$%^&*()_+|'
+
+ def test_function_decompress(self):
+
+ self.assertEqual(len(decompress(COMPRESSED_100_PLUS_32KB)), 100+32*_1K)
+
+ # 1 frame
+ self.assertEqual(decompress(self.FRAME_42), self.DECOMPRESSED_42)
+
+ self.assertEqual(decompress(self.UNKNOWN_FRAME_42), self.DECOMPRESSED_42)
+
+ pattern = r"Compressed data ended before the end-of-stream marker"
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(self.FRAME_42[:1])
+
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(self.FRAME_42[:-4])
+
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(self.FRAME_42[:-1])
+
+ # 2 frames
+ self.assertEqual(decompress(self.FRAME_42_60), self.DECOMPRESSED_42_60)
+
+ self.assertEqual(decompress(self.UNKNOWN_FRAME_42_60), self.DECOMPRESSED_42_60)
+
+ self.assertEqual(decompress(self.FRAME_42 + self.UNKNOWN_FRAME_60),
+ self.DECOMPRESSED_42_60)
+
+ self.assertEqual(decompress(self.UNKNOWN_FRAME_42 + self.FRAME_60),
+ self.DECOMPRESSED_42_60)
+
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(self.FRAME_42_60[:-4])
+
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(self.UNKNOWN_FRAME_42_60[:-1])
+
+ # 130_1K
+ self.assertEqual(decompress(DAT_130K_C), DAT_130K_D)
+
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(DAT_130K_C[:-4])
+
+ with self.assertRaisesRegex(ZstdError, pattern):
+ decompress(DAT_130K_C[:-1])
+
+ # Unknown frame descriptor
+ with self.assertRaisesRegex(ZstdError, "Unknown frame descriptor"):
+ decompress(b'aaaaaaaaa')
+
+ with self.assertRaisesRegex(ZstdError, "Unknown frame descriptor"):
+ decompress(self.FRAME_42 + b'aaaaaaaaa')
+
+ with self.assertRaisesRegex(ZstdError, "Unknown frame descriptor"):
+ decompress(self.UNKNOWN_FRAME_42_60 + b'aaaaaaaaa')
+
+ # doesn't match checksum
+ checksum = DAT_130K_C[-4:]
+ if checksum[0] == 255:
+ wrong_checksum = bytes([254]) + checksum[1:]
+ else:
+ wrong_checksum = bytes([checksum[0]+1]) + checksum[1:]
+
+ dat = DAT_130K_C[:-4] + wrong_checksum
+
+ with self.assertRaisesRegex(ZstdError, "doesn't match checksum"):
+ decompress(dat)
+
+ def test_function_skippable(self):
+ self.assertEqual(decompress(SKIPPABLE_FRAME), b'')
+ self.assertEqual(decompress(SKIPPABLE_FRAME + SKIPPABLE_FRAME), b'')
+
+ # 1 frame + 2 skippable
+ self.assertEqual(len(decompress(SKIPPABLE_FRAME + SKIPPABLE_FRAME + DAT_130K_C)),
+ self._130_1K)
+
+ self.assertEqual(len(decompress(DAT_130K_C + SKIPPABLE_FRAME + SKIPPABLE_FRAME)),
+ self._130_1K)
+
+ self.assertEqual(len(decompress(SKIPPABLE_FRAME + DAT_130K_C + SKIPPABLE_FRAME)),
+ self._130_1K)
+
+ # unknown size
+ self.assertEqual(decompress(SKIPPABLE_FRAME + self.UNKNOWN_FRAME_60),
+ self.DECOMPRESSED_60)
+
+ self.assertEqual(decompress(self.UNKNOWN_FRAME_60 + SKIPPABLE_FRAME),
+ self.DECOMPRESSED_60)
+
+ # 2 frames + 1 skippable
+ self.assertEqual(decompress(self.FRAME_42 + SKIPPABLE_FRAME + self.FRAME_60),
+ self.DECOMPRESSED_42_60)
+
+ self.assertEqual(decompress(SKIPPABLE_FRAME + self.FRAME_42_60),
+ self.DECOMPRESSED_42_60)
+
+ self.assertEqual(decompress(self.UNKNOWN_FRAME_42_60 + SKIPPABLE_FRAME),
+ self.DECOMPRESSED_42_60)
+
+ # incomplete
+ with self.assertRaises(ZstdError):
+ decompress(SKIPPABLE_FRAME[:1])
+
+ with self.assertRaises(ZstdError):
+ decompress(SKIPPABLE_FRAME[:-1])
+
+ with self.assertRaises(ZstdError):
+ decompress(self.FRAME_42 + SKIPPABLE_FRAME[:-1])
+
+ # Unknown frame descriptor
+ with self.assertRaisesRegex(ZstdError, "Unknown frame descriptor"):
+ decompress(b'aaaaaaaaa' + SKIPPABLE_FRAME)
+
+ with self.assertRaisesRegex(ZstdError, "Unknown frame descriptor"):
+ decompress(SKIPPABLE_FRAME + b'aaaaaaaaa')
+
+ with self.assertRaisesRegex(ZstdError, "Unknown frame descriptor"):
+ decompress(SKIPPABLE_FRAME + SKIPPABLE_FRAME + b'aaaaaaaaa')
+
+ def test_decompressor_1(self):
+ # empty 1
+ d = ZstdDecompressor()
+
+ dat = d.decompress(b'')
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ dat = d.decompress(b'', 0)
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ dat = d.decompress(COMPRESSED_100_PLUS_32KB + b'a')
+ self.assertEqual(dat, DECOMPRESSED_100_PLUS_32KB)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'a')
+ self.assertEqual(d.unused_data, b'a') # twice
+
+ # empty 2
+ d = ZstdDecompressor()
+
+ dat = d.decompress(b'', 0)
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ dat = d.decompress(b'')
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ dat = d.decompress(COMPRESSED_100_PLUS_32KB + b'a')
+ self.assertEqual(dat, DECOMPRESSED_100_PLUS_32KB)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'a')
+ self.assertEqual(d.unused_data, b'a') # twice
+
+ # 1 frame
+ d = ZstdDecompressor()
+ dat = d.decompress(self.FRAME_42)
+
+ self.assertEqual(dat, self.DECOMPRESSED_42)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ with self.assertRaises(EOFError):
+ d.decompress(b'')
+
+ # 1 frame, trail
+ d = ZstdDecompressor()
+ dat = d.decompress(self.FRAME_42 + self.TRAIL)
+
+ self.assertEqual(dat, self.DECOMPRESSED_42)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, self.TRAIL)
+ self.assertEqual(d.unused_data, self.TRAIL) # twice
+
+ # 1 frame, 32_1K
+ temp = compress(b'a'*(32*_1K))
+ d = ZstdDecompressor()
+ dat = d.decompress(temp, 32*_1K)
+
+ self.assertEqual(dat, b'a'*(32*_1K))
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ with self.assertRaises(EOFError):
+ d.decompress(b'')
+
+ # 1 frame, 32_1K+100, trail
+ d = ZstdDecompressor()
+ dat = d.decompress(COMPRESSED_100_PLUS_32KB+self.TRAIL, 100) # 100 bytes
+
+ self.assertEqual(len(dat), 100)
+ self.assertFalse(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+
+ dat = d.decompress(b'') # 32_1K
+
+ self.assertEqual(len(dat), 32*_1K)
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, self.TRAIL)
+ self.assertEqual(d.unused_data, self.TRAIL) # twice
+
+ with self.assertRaises(EOFError):
+ d.decompress(b'')
+
+ # incomplete 1
+ d = ZstdDecompressor()
+ dat = d.decompress(self.FRAME_60[:1])
+
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ # incomplete 2
+ d = ZstdDecompressor()
+
+ dat = d.decompress(self.FRAME_60[:-4])
+ self.assertEqual(dat, self.DECOMPRESSED_60)
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ # incomplete 3
+ d = ZstdDecompressor()
+
+ dat = d.decompress(self.FRAME_60[:-1])
+ self.assertEqual(dat, self.DECOMPRESSED_60)
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+
+ # incomplete 4
+ d = ZstdDecompressor()
+
+ dat = d.decompress(self.FRAME_60[:-4], 60)
+ self.assertEqual(dat, self.DECOMPRESSED_60)
+ self.assertFalse(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ dat = d.decompress(b'')
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ # Unknown frame descriptor
+ d = ZstdDecompressor()
+ with self.assertRaisesRegex(ZstdError, "Unknown frame descriptor"):
+ d.decompress(b'aaaaaaaaa')
+
+ def test_decompressor_skippable(self):
+ # 1 skippable
+ d = ZstdDecompressor()
+ dat = d.decompress(SKIPPABLE_FRAME)
+
+ self.assertEqual(dat, b'')
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ # 1 skippable, max_length=0
+ d = ZstdDecompressor()
+ dat = d.decompress(SKIPPABLE_FRAME, 0)
+
+ self.assertEqual(dat, b'')
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ # 1 skippable, trail
+ d = ZstdDecompressor()
+ dat = d.decompress(SKIPPABLE_FRAME + self.TRAIL)
+
+ self.assertEqual(dat, b'')
+ self.assertTrue(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, self.TRAIL)
+ self.assertEqual(d.unused_data, self.TRAIL) # twice
+
+ # incomplete
+ d = ZstdDecompressor()
+ dat = d.decompress(SKIPPABLE_FRAME[:-1])
+
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ # incomplete
+ d = ZstdDecompressor()
+ dat = d.decompress(SKIPPABLE_FRAME[:-1], 0)
+
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertFalse(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+ dat = d.decompress(b'')
+
+ self.assertEqual(dat, b'')
+ self.assertFalse(d.eof)
+ self.assertTrue(d.needs_input)
+ self.assertEqual(d.unused_data, b'')
+ self.assertEqual(d.unused_data, b'') # twice
+
+
+
+class ZstdDictTestCase(unittest.TestCase):
+
+ def test_is_raw(self):
+ # must be passed as a keyword argument
+ with self.assertRaises(TypeError):
+ ZstdDict(bytes(8), True)
+
+ # content < 8
+ b = b'1234567'
+ with self.assertRaises(ValueError):
+ ZstdDict(b)
+
+ # content == 8
+ b = b'12345678'
+ zd = ZstdDict(b, is_raw=True)
+ self.assertEqual(zd.dict_id, 0)
+
+ temp = compress(b'aaa12345678', level=3, zstd_dict=zd)
+ self.assertEqual(b'aaa12345678', decompress(temp, zd))
+
+ # is_raw == False
+ b = b'12345678abcd'
+ with self.assertRaises(ValueError):
+ ZstdDict(b)
+
+ # read only attributes
+ with self.assertRaises(AttributeError):
+ zd.dict_content = b
+
+ with self.assertRaises(AttributeError):
+ zd.dict_id = 10000
+
+ # ZstdDict arguments
+ zd = ZstdDict(TRAINED_DICT.dict_content, is_raw=False)
+ self.assertNotEqual(zd.dict_id, 0)
+
+ zd = ZstdDict(TRAINED_DICT.dict_content, is_raw=True)
+ self.assertNotEqual(zd.dict_id, 0) # note this assertion
+
+ with self.assertRaises(TypeError):
+ ZstdDict("12345678abcdef", is_raw=True)
+ with self.assertRaises(TypeError):
+ ZstdDict(TRAINED_DICT)
+
+ # invalid parameter
+ with self.assertRaises(TypeError):
+ ZstdDict(desk333=345)
+
+ def test_invalid_dict(self):
+ DICT_MAGIC = 0xEC30A437.to_bytes(4, byteorder='little')
+ dict_content = DICT_MAGIC + b'abcdefghighlmnopqrstuvwxyz'
+
+ # corrupted
+ zd = ZstdDict(dict_content, is_raw=False)
+ with self.assertRaisesRegex(ZstdError, r'ZSTD_CDict.*?content\.$'):
+ ZstdCompressor(zstd_dict=zd.as_digested_dict)
+ with self.assertRaisesRegex(ZstdError, r'ZSTD_DDict.*?content\.$'):
+ ZstdDecompressor(zd)
+
+ # wrong type
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdCompressor(zstd_dict=[zd, 1])
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdCompressor(zstd_dict=(zd, 1.0))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdCompressor(zstd_dict=(zd,))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdCompressor(zstd_dict=(zd, 1, 2))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdCompressor(zstd_dict=(zd, -1))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdCompressor(zstd_dict=(zd, 3))
+ with self.assertRaises(OverflowError):
+ ZstdCompressor(zstd_dict=(zd, 2**1000))
+ with self.assertRaises(OverflowError):
+ ZstdCompressor(zstd_dict=(zd, -2**1000))
+
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdDecompressor(zstd_dict=[zd, 1])
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdDecompressor(zstd_dict=(zd, 1.0))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdDecompressor((zd,))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdDecompressor((zd, 1, 2))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdDecompressor((zd, -1))
+ with self.assertRaisesRegex(TypeError, r'should be a ZstdDict object'):
+ ZstdDecompressor((zd, 3))
+ with self.assertRaises(OverflowError):
+ ZstdDecompressor((zd, 2**1000))
+ with self.assertRaises(OverflowError):
+ ZstdDecompressor((zd, -2**1000))
+
+ def test_train_dict(self):
+ TRAINED_DICT = train_dict(SAMPLES, DICT_SIZE1)
+ ZstdDict(TRAINED_DICT.dict_content, is_raw=False)
+
+ self.assertNotEqual(TRAINED_DICT.dict_id, 0)
+ self.assertGreater(len(TRAINED_DICT.dict_content), 0)
+ self.assertLessEqual(len(TRAINED_DICT.dict_content), DICT_SIZE1)
+ self.assertTrue(re.match(r'^<ZstdDict dict_id=\d+ dict_size=\d+>$', str(TRAINED_DICT)))
+
+ # compress/decompress
+ c = ZstdCompressor(zstd_dict=TRAINED_DICT)
+ for sample in SAMPLES:
+ dat1 = compress(sample, zstd_dict=TRAINED_DICT)
+ dat2 = decompress(dat1, TRAINED_DICT)
+ self.assertEqual(sample, dat2)
+
+ dat1 = c.compress(sample)
+ dat1 += c.flush()
+ dat2 = decompress(dat1, TRAINED_DICT)
+ self.assertEqual(sample, dat2)
+
+ def test_finalize_dict(self):
+ DICT_SIZE2 = 200*_1K
+ C_LEVEL = 6
+
+ try:
+ dic2 = finalize_dict(TRAINED_DICT, SAMPLES, DICT_SIZE2, C_LEVEL)
+ except NotImplementedError:
+ # < v1.4.5 at compile-time, >= v.1.4.5 at run-time
+ return
+
+ self.assertNotEqual(dic2.dict_id, 0)
+ self.assertGreater(len(dic2.dict_content), 0)
+ self.assertLessEqual(len(dic2.dict_content), DICT_SIZE2)
+
+ # compress/decompress
+ c = ZstdCompressor(C_LEVEL, zstd_dict=dic2)
+ for sample in SAMPLES:
+ dat1 = compress(sample, C_LEVEL, zstd_dict=dic2)
+ dat2 = decompress(dat1, dic2)
+ self.assertEqual(sample, dat2)
+
+ dat1 = c.compress(sample)
+ dat1 += c.flush()
+ dat2 = decompress(dat1, dic2)
+ self.assertEqual(sample, dat2)
+
+ # dict mismatch
+ self.assertNotEqual(TRAINED_DICT.dict_id, dic2.dict_id)
+
+ dat1 = compress(SAMPLES[0], zstd_dict=TRAINED_DICT)
+ with self.assertRaises(ZstdError):
+ decompress(dat1, dic2)
+
+ def test_train_dict_arguments(self):
+ with self.assertRaises(ValueError):
+ train_dict([], 100*_1K)
+
+ with self.assertRaises(ValueError):
+ train_dict(SAMPLES, -100)
+
+ with self.assertRaises(ValueError):
+ train_dict(SAMPLES, 0)
+
+ def test_finalize_dict_arguments(self):
+ with self.assertRaises(TypeError):
+ finalize_dict({1:2}, (b'aaa', b'bbb'), 100*_1K, 2)
+
+ with self.assertRaises(ValueError):
+ finalize_dict(TRAINED_DICT, [], 100*_1K, 2)
+
+ with self.assertRaises(ValueError):
+ finalize_dict(TRAINED_DICT, SAMPLES, -100, 2)
+
+ with self.assertRaises(ValueError):
+ finalize_dict(TRAINED_DICT, SAMPLES, 0, 2)
+
+ def test_train_dict_c(self):
+ # argument wrong type
+ with self.assertRaises(TypeError):
+ _zstd.train_dict({}, (), 100)
+ with self.assertRaises(TypeError):
+ _zstd.train_dict(bytearray(), (), 100)
+ with self.assertRaises(TypeError):
+ _zstd.train_dict(b'', 99, 100)
+ with self.assertRaises(TypeError):
+ _zstd.train_dict(b'', [], 100)
+ with self.assertRaises(TypeError):
+ _zstd.train_dict(b'', (), 100.1)
+ with self.assertRaises(TypeError):
+ _zstd.train_dict(b'', (99.1,), 100)
+ with self.assertRaises(ValueError):
+ _zstd.train_dict(b'abc', (4, -1), 100)
+ with self.assertRaises(ValueError):
+ _zstd.train_dict(b'abc', (2,), 100)
+ with self.assertRaises(ValueError):
+ _zstd.train_dict(b'', (99,), 100)
+
+ # size > size_t
+ with self.assertRaises(ValueError):
+ _zstd.train_dict(b'', (2**1000,), 100)
+ with self.assertRaises(ValueError):
+ _zstd.train_dict(b'', (-2**1000,), 100)
+
+ # dict_size <= 0
+ with self.assertRaises(ValueError):
+ _zstd.train_dict(b'', (), 0)
+ with self.assertRaises(ValueError):
+ _zstd.train_dict(b'', (), -1)
+
+ with self.assertRaises(ZstdError):
+ _zstd.train_dict(b'', (), 1)
+
+ def test_finalize_dict_c(self):
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(1, 2, 3, 4, 5)
+
+ # argument wrong type
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict({}, b'', (), 100, 5)
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(bytearray(TRAINED_DICT.dict_content), b'', (), 100, 5)
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, {}, (), 100, 5)
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, bytearray(), (), 100, 5)
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', 99, 100, 5)
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', [], 100, 5)
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), 100.1, 5)
+ with self.assertRaises(TypeError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), 100, 5.1)
+
+ with self.assertRaises(ValueError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'abc', (4, -1), 100, 5)
+ with self.assertRaises(ValueError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'abc', (2,), 100, 5)
+ with self.assertRaises(ValueError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (99,), 100, 5)
+
+ # size > size_t
+ with self.assertRaises(ValueError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (2**1000,), 100, 5)
+ with self.assertRaises(ValueError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (-2**1000,), 100, 5)
+
+ # dict_size <= 0
+ with self.assertRaises(ValueError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), 0, 5)
+ with self.assertRaises(ValueError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), -1, 5)
+ with self.assertRaises(OverflowError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), 2**1000, 5)
+ with self.assertRaises(OverflowError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), -2**1000, 5)
+
+ with self.assertRaises(OverflowError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), 100, 2**1000)
+ with self.assertRaises(OverflowError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), 100, -2**1000)
+
+ with self.assertRaises(ZstdError):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content, b'', (), 100, 5)
+
+ def test_train_buffer_protocol_samples(self):
+ def _nbytes(dat):
+ if isinstance(dat, (bytes, bytearray)):
+ return len(dat)
+ return memoryview(dat).nbytes
+
+ # prepare samples
+ chunk_lst = []
+ wrong_size_lst = []
+ correct_size_lst = []
+ for _ in range(300):
+ arr = array.array('Q', [random.randint(0, 20) for i in range(20)])
+ chunk_lst.append(arr)
+ correct_size_lst.append(_nbytes(arr))
+ wrong_size_lst.append(len(arr))
+ concatenation = b''.join(chunk_lst)
+
+ # wrong size list
+ with self.assertRaisesRegex(ValueError,
+ "The samples size tuple doesn't match the concatenation's size"):
+ _zstd.train_dict(concatenation, tuple(wrong_size_lst), 100*_1K)
+
+ # correct size list
+ _zstd.train_dict(concatenation, tuple(correct_size_lst), 3*_1K)
+
+ # wrong size list
+ with self.assertRaisesRegex(ValueError,
+ "The samples size tuple doesn't match the concatenation's size"):
+ _zstd.finalize_dict(TRAINED_DICT.dict_content,
+ concatenation, tuple(wrong_size_lst), 300*_1K, 5)
+
+ # correct size list
+ _zstd.finalize_dict(TRAINED_DICT.dict_content,
+ concatenation, tuple(correct_size_lst), 300*_1K, 5)
+
+ def test_as_prefix(self):
+ # V1
+ V1 = THIS_FILE_BYTES
+ zd = ZstdDict(V1, is_raw=True)
+
+ # V2
+ mid = len(V1) // 2
+ V2 = V1[:mid] + \
+ (b'a' if V1[mid] != int.from_bytes(b'a') else b'b') + \
+ V1[mid+1:]
+
+ # compress
+ dat = compress(V2, zstd_dict=zd.as_prefix)
+ self.assertEqual(get_frame_info(dat).dictionary_id, 0)
+
+ # decompress
+ self.assertEqual(decompress(dat, zd.as_prefix), V2)
+
+ # use wrong prefix
+ zd2 = ZstdDict(SAMPLES[0], is_raw=True)
+ try:
+ decompressed = decompress(dat, zd2.as_prefix)
+ except ZstdError: # expected
+ pass
+ else:
+ self.assertNotEqual(decompressed, V2)
+
+ # read only attribute
+ with self.assertRaises(AttributeError):
+ zd.as_prefix = b'1234'
+
+ def test_as_digested_dict(self):
+ zd = TRAINED_DICT
+
+ # test .as_digested_dict
+ dat = compress(SAMPLES[0], zstd_dict=zd.as_digested_dict)
+ self.assertEqual(decompress(dat, zd.as_digested_dict), SAMPLES[0])
+ with self.assertRaises(AttributeError):
+ zd.as_digested_dict = b'1234'
+
+ # test .as_undigested_dict
+ dat = compress(SAMPLES[0], zstd_dict=zd.as_undigested_dict)
+ self.assertEqual(decompress(dat, zd.as_undigested_dict), SAMPLES[0])
+ with self.assertRaises(AttributeError):
+ zd.as_undigested_dict = b'1234'
+
+ def test_advanced_compression_parameters(self):
+ options = {CompressionParameter.compression_level: 6,
+ CompressionParameter.window_log: 20,
+ CompressionParameter.enable_long_distance_matching: 1}
+
+ # automatically select
+ dat = compress(SAMPLES[0], options=options, zstd_dict=TRAINED_DICT)
+ self.assertEqual(decompress(dat, TRAINED_DICT), SAMPLES[0])
+
+ # explicitly select
+ dat = compress(SAMPLES[0], options=options, zstd_dict=TRAINED_DICT.as_digested_dict)
+ self.assertEqual(decompress(dat, TRAINED_DICT), SAMPLES[0])
+
+ def test_len(self):
+ self.assertEqual(len(TRAINED_DICT), len(TRAINED_DICT.dict_content))
+ self.assertIn(str(len(TRAINED_DICT)), str(TRAINED_DICT))
+
+class FileTestCase(unittest.TestCase):
+ def setUp(self):
+ self.DECOMPRESSED_42 = b'a'*42
+ self.FRAME_42 = compress(self.DECOMPRESSED_42)
+
+ def test_init(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ pass
+ with ZstdFile(io.BytesIO(), "w") as f:
+ pass
+ with ZstdFile(io.BytesIO(), "x") as f:
+ pass
+ with ZstdFile(io.BytesIO(), "a") as f:
+ pass
+
+ with ZstdFile(io.BytesIO(), "w", level=12) as f:
+ pass
+ with ZstdFile(io.BytesIO(), "w", options={CompressionParameter.checksum_flag:1}) as f:
+ pass
+ with ZstdFile(io.BytesIO(), "w", options={}) as f:
+ pass
+ with ZstdFile(io.BytesIO(), "w", level=20, zstd_dict=TRAINED_DICT) as f:
+ pass
+
+ with ZstdFile(io.BytesIO(), "r", options={DecompressionParameter.window_log_max:25}) as f:
+ pass
+ with ZstdFile(io.BytesIO(), "r", options={}, zstd_dict=TRAINED_DICT) as f:
+ pass
+
+ def test_init_with_PathLike_filename(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ filename = pathlib.Path(tmp_f.name)
+
+ with ZstdFile(filename, "a") as f:
+ f.write(DECOMPRESSED_100_PLUS_32KB)
+ with ZstdFile(filename) as f:
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB)
+
+ with ZstdFile(filename, "a") as f:
+ f.write(DECOMPRESSED_100_PLUS_32KB)
+ with ZstdFile(filename) as f:
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB * 2)
+
+ os.remove(filename)
+
+ def test_init_with_filename(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ filename = pathlib.Path(tmp_f.name)
+
+ with ZstdFile(filename) as f:
+ pass
+ with ZstdFile(filename, "w") as f:
+ pass
+ with ZstdFile(filename, "a") as f:
+ pass
+
+ os.remove(filename)
+
+ def test_init_mode(self):
+ bi = io.BytesIO()
+
+ with ZstdFile(bi, "r"):
+ pass
+ with ZstdFile(bi, "rb"):
+ pass
+ with ZstdFile(bi, "w"):
+ pass
+ with ZstdFile(bi, "wb"):
+ pass
+ with ZstdFile(bi, "a"):
+ pass
+ with ZstdFile(bi, "ab"):
+ pass
+
+ def test_init_with_x_mode(self):
+ with tempfile.NamedTemporaryFile() as tmp_f:
+ filename = pathlib.Path(tmp_f.name)
+
+ for mode in ("x", "xb"):
+ with ZstdFile(filename, mode):
+ pass
+ with self.assertRaises(FileExistsError):
+ with ZstdFile(filename, mode):
+ pass
+ os.remove(filename)
+
+ def test_init_bad_mode(self):
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), (3, "x"))
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "xt")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "x+")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "rx")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "wx")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "rt")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "r+")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "wt")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "w+")
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "rw")
+
+ with self.assertRaisesRegex(TypeError,
+ r"not be a CompressionParameter"):
+ ZstdFile(io.BytesIO(), 'rb',
+ options={CompressionParameter.compression_level:5})
+ with self.assertRaisesRegex(TypeError,
+ r"not be a DecompressionParameter"):
+ ZstdFile(io.BytesIO(), 'wb',
+ options={DecompressionParameter.window_log_max:21})
+
+ with self.assertRaises(TypeError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "r", level=12)
+
+ def test_init_bad_check(self):
+ with self.assertRaises(TypeError):
+ ZstdFile(io.BytesIO(), "w", level='asd')
+ # CHECK_UNKNOWN and anything above CHECK_ID_MAX should be invalid.
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(), "w", options={999:9999})
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(), "w", options={CompressionParameter.window_log:99})
+
+ with self.assertRaises(TypeError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "r", options=33)
+
+ with self.assertRaises(OverflowError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB),
+ options={DecompressionParameter.window_log_max:2**31})
+
+ with self.assertRaises(ValueError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB),
+ options={444:333})
+
+ with self.assertRaises(TypeError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), zstd_dict={1:2})
+
+ with self.assertRaises(TypeError):
+ ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), zstd_dict=b'dict123456')
+
+ def test_init_close_fp(self):
+ # get a temp file name
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ tmp_f.write(DAT_130K_C)
+ filename = tmp_f.name
+
+ with self.assertRaises(TypeError):
+ ZstdFile(filename, options={'a':'b'})
+
+ # for PyPy
+ gc.collect()
+
+ os.remove(filename)
+
+ def test_close(self):
+ with io.BytesIO(COMPRESSED_100_PLUS_32KB) as src:
+ f = ZstdFile(src)
+ f.close()
+ # ZstdFile.close() should not close the underlying file object.
+ self.assertFalse(src.closed)
+ # Try closing an already-closed ZstdFile.
+ f.close()
+ self.assertFalse(src.closed)
+
+ # Test with a real file on disk, opened directly by ZstdFile.
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ filename = pathlib.Path(tmp_f.name)
+
+ f = ZstdFile(filename)
+ fp = f._fp
+ f.close()
+ # Here, ZstdFile.close() *should* close the underlying file object.
+ self.assertTrue(fp.closed)
+ # Try closing an already-closed ZstdFile.
+ f.close()
+
+ os.remove(filename)
+
+ def test_closed(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ try:
+ self.assertFalse(f.closed)
+ f.read()
+ self.assertFalse(f.closed)
+ finally:
+ f.close()
+ self.assertTrue(f.closed)
+
+ f = ZstdFile(io.BytesIO(), "w")
+ try:
+ self.assertFalse(f.closed)
+ finally:
+ f.close()
+ self.assertTrue(f.closed)
+
+ def test_fileno(self):
+ # 1
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ try:
+ self.assertRaises(io.UnsupportedOperation, f.fileno)
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.fileno)
+
+ # 2
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ filename = pathlib.Path(tmp_f.name)
+
+ f = ZstdFile(filename)
+ try:
+ self.assertEqual(f.fileno(), f._fp.fileno())
+ self.assertIsInstance(f.fileno(), int)
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.fileno)
+
+ os.remove(filename)
+
+ # 3, no .fileno() method
+ class C:
+ def read(self, size=-1):
+ return b'123'
+ with ZstdFile(C(), 'rb') as f:
+ with self.assertRaisesRegex(AttributeError, r'fileno'):
+ f.fileno()
+
+ def test_name(self):
+ # 1
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ try:
+ with self.assertRaises(AttributeError):
+ f.name
+ finally:
+ f.close()
+ with self.assertRaises(ValueError):
+ f.name
+
+ # 2
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ filename = pathlib.Path(tmp_f.name)
+
+ f = ZstdFile(filename)
+ try:
+ self.assertEqual(f.name, f._fp.name)
+ self.assertIsInstance(f.name, str)
+ finally:
+ f.close()
+ with self.assertRaises(ValueError):
+ f.name
+
+ os.remove(filename)
+
+ # 3, no .filename property
+ class C:
+ def read(self, size=-1):
+ return b'123'
+ with ZstdFile(C(), 'rb') as f:
+ with self.assertRaisesRegex(AttributeError, r'name'):
+ f.name
+
+ def test_seekable(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ try:
+ self.assertTrue(f.seekable())
+ f.read()
+ self.assertTrue(f.seekable())
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.seekable)
+
+ f = ZstdFile(io.BytesIO(), "w")
+ try:
+ self.assertFalse(f.seekable())
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.seekable)
+
+ src = io.BytesIO(COMPRESSED_100_PLUS_32KB)
+ src.seekable = lambda: False
+ f = ZstdFile(src)
+ try:
+ self.assertFalse(f.seekable())
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.seekable)
+
+ def test_readable(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ try:
+ self.assertTrue(f.readable())
+ f.read()
+ self.assertTrue(f.readable())
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.readable)
+
+ f = ZstdFile(io.BytesIO(), "w")
+ try:
+ self.assertFalse(f.readable())
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.readable)
+
+ def test_writable(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ try:
+ self.assertFalse(f.writable())
+ f.read()
+ self.assertFalse(f.writable())
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.writable)
+
+ f = ZstdFile(io.BytesIO(), "w")
+ try:
+ self.assertTrue(f.writable())
+ finally:
+ f.close()
+ self.assertRaises(ValueError, f.writable)
+
+ def test_read_0(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ self.assertEqual(f.read(0), b"")
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB)
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB),
+ options={DecompressionParameter.window_log_max:20}) as f:
+ self.assertEqual(f.read(0), b"")
+
+ # empty file
+ with ZstdFile(io.BytesIO(b'')) as f:
+ self.assertEqual(f.read(0), b"")
+ with self.assertRaises(EOFError):
+ f.read(10)
+
+ with ZstdFile(io.BytesIO(b'')) as f:
+ with self.assertRaises(EOFError):
+ f.read(10)
+
+ def test_read_10(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ chunks = []
+ while True:
+ result = f.read(10)
+ if not result:
+ break
+ self.assertLessEqual(len(result), 10)
+ chunks.append(result)
+ self.assertEqual(b"".join(chunks), DECOMPRESSED_100_PLUS_32KB)
+
+ def test_read_multistream(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB * 5)) as f:
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB * 5)
+
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB + SKIPPABLE_FRAME)) as f:
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB)
+
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB + COMPRESSED_DAT)) as f:
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB + DECOMPRESSED_DAT)
+
+ def test_read_incomplete(self):
+ with ZstdFile(io.BytesIO(DAT_130K_C[:-200])) as f:
+ self.assertRaises(EOFError, f.read)
+
+ # Trailing data isn't a valid compressed stream
+ with ZstdFile(io.BytesIO(self.FRAME_42 + b'12345')) as f:
+ self.assertRaises(ZstdError, f.read)
+
+ with ZstdFile(io.BytesIO(SKIPPABLE_FRAME + b'12345')) as f:
+ self.assertRaises(ZstdError, f.read)
+
+ def test_read_truncated(self):
+ # Drop stream epilogue: 4 bytes checksum
+ truncated = DAT_130K_C[:-4]
+ with ZstdFile(io.BytesIO(truncated)) as f:
+ self.assertRaises(EOFError, f.read)
+
+ with ZstdFile(io.BytesIO(truncated)) as f:
+ # this is an important test, make sure it doesn't raise EOFError.
+ self.assertEqual(f.read(130*_1K), DAT_130K_D)
+ with self.assertRaises(EOFError):
+ f.read(1)
+
+ # Incomplete header
+ for i in range(1, 20):
+ with ZstdFile(io.BytesIO(truncated[:i])) as f:
+ self.assertRaises(EOFError, f.read, 1)
+
+ def test_read_bad_args(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_DAT))
+ f.close()
+ self.assertRaises(ValueError, f.read)
+ with ZstdFile(io.BytesIO(), "w") as f:
+ self.assertRaises(ValueError, f.read)
+ with ZstdFile(io.BytesIO(COMPRESSED_DAT)) as f:
+ self.assertRaises(TypeError, f.read, float())
+
+ def test_read_bad_data(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_BOGUS)) as f:
+ self.assertRaises(ZstdError, f.read)
+
+ def test_read_exception(self):
+ class C:
+ def read(self, size=-1):
+ raise OSError
+ with ZstdFile(C()) as f:
+ with self.assertRaises(OSError):
+ f.read(10)
+
+ def test_read1(self):
+ with ZstdFile(io.BytesIO(DAT_130K_C)) as f:
+ blocks = []
+ while True:
+ result = f.read1()
+ if not result:
+ break
+ blocks.append(result)
+ self.assertEqual(b"".join(blocks), DAT_130K_D)
+ self.assertEqual(f.read1(), b"")
+
+ def test_read1_0(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_DAT)) as f:
+ self.assertEqual(f.read1(0), b"")
+
+ def test_read1_10(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_DAT)) as f:
+ blocks = []
+ while True:
+ result = f.read1(10)
+ if not result:
+ break
+ blocks.append(result)
+ self.assertEqual(b"".join(blocks), DECOMPRESSED_DAT)
+ self.assertEqual(f.read1(), b"")
+
+ def test_read1_multistream(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB * 5)) as f:
+ blocks = []
+ while True:
+ result = f.read1()
+ if not result:
+ break
+ blocks.append(result)
+ self.assertEqual(b"".join(blocks), DECOMPRESSED_100_PLUS_32KB * 5)
+ self.assertEqual(f.read1(), b"")
+
+ def test_read1_bad_args(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ f.close()
+ self.assertRaises(ValueError, f.read1)
+ with ZstdFile(io.BytesIO(), "w") as f:
+ self.assertRaises(ValueError, f.read1)
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ self.assertRaises(TypeError, f.read1, None)
+
+ def test_readinto(self):
+ arr = array.array("I", range(100))
+ self.assertEqual(len(arr), 100)
+ self.assertEqual(len(arr) * arr.itemsize, 400)
+ ba = bytearray(300)
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ # 0 length output buffer
+ self.assertEqual(f.readinto(ba[0:0]), 0)
+
+ # use correct length for buffer protocol object
+ self.assertEqual(f.readinto(arr), 400)
+ self.assertEqual(arr.tobytes(), DECOMPRESSED_100_PLUS_32KB[:400])
+
+ # normal readinto
+ self.assertEqual(f.readinto(ba), 300)
+ self.assertEqual(ba, DECOMPRESSED_100_PLUS_32KB[400:700])
+
+ def test_peek(self):
+ with ZstdFile(io.BytesIO(DAT_130K_C)) as f:
+ result = f.peek()
+ self.assertGreater(len(result), 0)
+ self.assertTrue(DAT_130K_D.startswith(result))
+ self.assertEqual(f.read(), DAT_130K_D)
+ with ZstdFile(io.BytesIO(DAT_130K_C)) as f:
+ result = f.peek(10)
+ self.assertGreater(len(result), 0)
+ self.assertTrue(DAT_130K_D.startswith(result))
+ self.assertEqual(f.read(), DAT_130K_D)
+
+ def test_peek_bad_args(self):
+ with ZstdFile(io.BytesIO(), "w") as f:
+ self.assertRaises(ValueError, f.peek)
+
+ def test_iterator(self):
+ with io.BytesIO(THIS_FILE_BYTES) as f:
+ lines = f.readlines()
+ compressed = compress(THIS_FILE_BYTES)
+
+ # iter
+ with ZstdFile(io.BytesIO(compressed)) as f:
+ self.assertListEqual(list(iter(f)), lines)
+
+ # readline
+ with ZstdFile(io.BytesIO(compressed)) as f:
+ for line in lines:
+ self.assertEqual(f.readline(), line)
+ self.assertEqual(f.readline(), b'')
+ self.assertEqual(f.readline(), b'')
+
+ # readlines
+ with ZstdFile(io.BytesIO(compressed)) as f:
+ self.assertListEqual(f.readlines(), lines)
+
+ def test_decompress_limited(self):
+ _ZSTD_DStreamInSize = 128*_1K + 3
+
+ bomb = compress(b'\0' * int(2e6), level=10)
+ self.assertLess(len(bomb), _ZSTD_DStreamInSize)
+
+ decomp = ZstdFile(io.BytesIO(bomb))
+ self.assertEqual(decomp.read(1), b'\0')
+
+ # BufferedReader uses 128 KiB buffer in __init__.py
+ max_decomp = 128*_1K
+ self.assertLessEqual(decomp._buffer.raw.tell(), max_decomp,
+ "Excessive amount of data was decompressed")
+
+ def test_write(self):
+ raw_data = THIS_FILE_BYTES[: len(THIS_FILE_BYTES) // 6]
+ with io.BytesIO() as dst:
+ with ZstdFile(dst, "w") as f:
+ f.write(raw_data)
+
+ comp = ZstdCompressor()
+ expected = comp.compress(raw_data) + comp.flush()
+ self.assertEqual(dst.getvalue(), expected)
+
+ with io.BytesIO() as dst:
+ with ZstdFile(dst, "w", level=12) as f:
+ f.write(raw_data)
+
+ comp = ZstdCompressor(12)
+ expected = comp.compress(raw_data) + comp.flush()
+ self.assertEqual(dst.getvalue(), expected)
+
+ with io.BytesIO() as dst:
+ with ZstdFile(dst, "w", options={CompressionParameter.checksum_flag:1}) as f:
+ f.write(raw_data)
+
+ comp = ZstdCompressor(options={CompressionParameter.checksum_flag:1})
+ expected = comp.compress(raw_data) + comp.flush()
+ self.assertEqual(dst.getvalue(), expected)
+
+ with io.BytesIO() as dst:
+ options = {CompressionParameter.compression_level:-5,
+ CompressionParameter.checksum_flag:1}
+ with ZstdFile(dst, "w",
+ options=options) as f:
+ f.write(raw_data)
+
+ comp = ZstdCompressor(options=options)
+ expected = comp.compress(raw_data) + comp.flush()
+ self.assertEqual(dst.getvalue(), expected)
+
+ def test_write_empty_frame(self):
+ # .FLUSH_FRAME generates an empty content frame
+ c = ZstdCompressor()
+ self.assertNotEqual(c.flush(c.FLUSH_FRAME), b'')
+ self.assertNotEqual(c.flush(c.FLUSH_FRAME), b'')
+
+ # don't generate empty content frame
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ pass
+ self.assertEqual(bo.getvalue(), b'')
+
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ f.flush(f.FLUSH_FRAME)
+ self.assertEqual(bo.getvalue(), b'')
+
+ # if .write(b''), generate empty content frame
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ f.write(b'')
+ self.assertNotEqual(bo.getvalue(), b'')
+
+ # has an empty content frame
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ f.flush(f.FLUSH_BLOCK)
+ self.assertNotEqual(bo.getvalue(), b'')
+
+ def test_write_empty_block(self):
+ # If no internal data, .FLUSH_BLOCK return b''.
+ c = ZstdCompressor()
+ self.assertEqual(c.flush(c.FLUSH_BLOCK), b'')
+ self.assertNotEqual(c.compress(b'123', c.FLUSH_BLOCK),
+ b'')
+ self.assertEqual(c.flush(c.FLUSH_BLOCK), b'')
+ self.assertEqual(c.compress(b''), b'')
+ self.assertEqual(c.compress(b''), b'')
+ self.assertEqual(c.flush(c.FLUSH_BLOCK), b'')
+
+ # mode = .last_mode
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ f.write(b'123')
+ f.flush(f.FLUSH_BLOCK)
+ fp_pos = f._fp.tell()
+ self.assertNotEqual(fp_pos, 0)
+ f.flush(f.FLUSH_BLOCK)
+ self.assertEqual(f._fp.tell(), fp_pos)
+
+ # mode != .last_mode
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ f.flush(f.FLUSH_BLOCK)
+ self.assertEqual(f._fp.tell(), 0)
+ f.write(b'')
+ f.flush(f.FLUSH_BLOCK)
+ self.assertEqual(f._fp.tell(), 0)
+
+ def test_write_101(self):
+ with io.BytesIO() as dst:
+ with ZstdFile(dst, "w") as f:
+ for start in range(0, len(THIS_FILE_BYTES), 101):
+ f.write(THIS_FILE_BYTES[start:start+101])
+
+ comp = ZstdCompressor()
+ expected = comp.compress(THIS_FILE_BYTES) + comp.flush()
+ self.assertEqual(dst.getvalue(), expected)
+
+ def test_write_append(self):
+ def comp(data):
+ comp = ZstdCompressor()
+ return comp.compress(data) + comp.flush()
+
+ part1 = THIS_FILE_BYTES[:_1K]
+ part2 = THIS_FILE_BYTES[_1K:1536]
+ part3 = THIS_FILE_BYTES[1536:]
+ expected = b"".join(comp(x) for x in (part1, part2, part3))
+ with io.BytesIO() as dst:
+ with ZstdFile(dst, "w") as f:
+ f.write(part1)
+ with ZstdFile(dst, "a") as f:
+ f.write(part2)
+ with ZstdFile(dst, "a") as f:
+ f.write(part3)
+ self.assertEqual(dst.getvalue(), expected)
+
+ def test_write_bad_args(self):
+ f = ZstdFile(io.BytesIO(), "w")
+ f.close()
+ self.assertRaises(ValueError, f.write, b"foo")
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB), "r") as f:
+ self.assertRaises(ValueError, f.write, b"bar")
+ with ZstdFile(io.BytesIO(), "w") as f:
+ self.assertRaises(TypeError, f.write, None)
+ self.assertRaises(TypeError, f.write, "text")
+ self.assertRaises(TypeError, f.write, 789)
+
+ def test_writelines(self):
+ def comp(data):
+ comp = ZstdCompressor()
+ return comp.compress(data) + comp.flush()
+
+ with io.BytesIO(THIS_FILE_BYTES) as f:
+ lines = f.readlines()
+ with io.BytesIO() as dst:
+ with ZstdFile(dst, "w") as f:
+ f.writelines(lines)
+ expected = comp(THIS_FILE_BYTES)
+ self.assertEqual(dst.getvalue(), expected)
+
+ def test_seek_forward(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ f.seek(555)
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB[555:])
+
+ def test_seek_forward_across_streams(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB * 2)) as f:
+ f.seek(len(DECOMPRESSED_100_PLUS_32KB) + 123)
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB[123:])
+
+ def test_seek_forward_relative_to_current(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ f.read(100)
+ f.seek(1236, 1)
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB[1336:])
+
+ def test_seek_forward_relative_to_end(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ f.seek(-555, 2)
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB[-555:])
+
+ def test_seek_backward(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ f.read(1001)
+ f.seek(211)
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB[211:])
+
+ def test_seek_backward_across_streams(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB * 2)) as f:
+ f.read(len(DECOMPRESSED_100_PLUS_32KB) + 333)
+ f.seek(737)
+ self.assertEqual(f.read(),
+ DECOMPRESSED_100_PLUS_32KB[737:] + DECOMPRESSED_100_PLUS_32KB)
+
+ def test_seek_backward_relative_to_end(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ f.seek(-150, 2)
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB[-150:])
+
+ def test_seek_past_end(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ f.seek(len(DECOMPRESSED_100_PLUS_32KB) + 9001)
+ self.assertEqual(f.tell(), len(DECOMPRESSED_100_PLUS_32KB))
+ self.assertEqual(f.read(), b"")
+
+ def test_seek_past_start(self):
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ f.seek(-88)
+ self.assertEqual(f.tell(), 0)
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB)
+
+ def test_seek_bad_args(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ f.close()
+ self.assertRaises(ValueError, f.seek, 0)
+ with ZstdFile(io.BytesIO(), "w") as f:
+ self.assertRaises(ValueError, f.seek, 0)
+ with ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB)) as f:
+ self.assertRaises(ValueError, f.seek, 0, 3)
+ # io.BufferedReader raises TypeError instead of ValueError
+ self.assertRaises((TypeError, ValueError), f.seek, 9, ())
+ self.assertRaises(TypeError, f.seek, None)
+ self.assertRaises(TypeError, f.seek, b"derp")
+
+ def test_seek_not_seekable(self):
+ class C(io.BytesIO):
+ def seekable(self):
+ return False
+ obj = C(COMPRESSED_100_PLUS_32KB)
+ with ZstdFile(obj, 'r') as f:
+ d = f.read(1)
+ self.assertFalse(f.seekable())
+ with self.assertRaisesRegex(io.UnsupportedOperation,
+ 'File or stream is not seekable'):
+ f.seek(0)
+ d += f.read()
+ self.assertEqual(d, DECOMPRESSED_100_PLUS_32KB)
+
+ def test_tell(self):
+ with ZstdFile(io.BytesIO(DAT_130K_C)) as f:
+ pos = 0
+ while True:
+ self.assertEqual(f.tell(), pos)
+ result = f.read(random.randint(171, 189))
+ if not result:
+ break
+ pos += len(result)
+ self.assertEqual(f.tell(), len(DAT_130K_D))
+ with ZstdFile(io.BytesIO(), "w") as f:
+ for pos in range(0, len(DAT_130K_D), 143):
+ self.assertEqual(f.tell(), pos)
+ f.write(DAT_130K_D[pos:pos+143])
+ self.assertEqual(f.tell(), len(DAT_130K_D))
+
+ def test_tell_bad_args(self):
+ f = ZstdFile(io.BytesIO(COMPRESSED_100_PLUS_32KB))
+ f.close()
+ self.assertRaises(ValueError, f.tell)
+
+ def test_file_dict(self):
+ # default
+ bi = io.BytesIO()
+ with ZstdFile(bi, 'w', zstd_dict=TRAINED_DICT) as f:
+ f.write(SAMPLES[0])
+ bi.seek(0)
+ with ZstdFile(bi, zstd_dict=TRAINED_DICT) as f:
+ dat = f.read()
+ self.assertEqual(dat, SAMPLES[0])
+
+ # .as_(un)digested_dict
+ bi = io.BytesIO()
+ with ZstdFile(bi, 'w', zstd_dict=TRAINED_DICT.as_digested_dict) as f:
+ f.write(SAMPLES[0])
+ bi.seek(0)
+ with ZstdFile(bi, zstd_dict=TRAINED_DICT.as_undigested_dict) as f:
+ dat = f.read()
+ self.assertEqual(dat, SAMPLES[0])
+
+ def test_file_prefix(self):
+ bi = io.BytesIO()
+ with ZstdFile(bi, 'w', zstd_dict=TRAINED_DICT.as_prefix) as f:
+ f.write(SAMPLES[0])
+ bi.seek(0)
+ with ZstdFile(bi, zstd_dict=TRAINED_DICT.as_prefix) as f:
+ dat = f.read()
+ self.assertEqual(dat, SAMPLES[0])
+
+ def test_UnsupportedOperation(self):
+ # 1
+ with ZstdFile(io.BytesIO(), 'r') as f:
+ with self.assertRaises(io.UnsupportedOperation):
+ f.write(b'1234')
+
+ # 2
+ class T:
+ def read(self, size):
+ return b'a' * size
+
+ with self.assertRaises(TypeError): # on creation
+ with ZstdFile(T(), 'w') as f:
+ pass
+
+ # 3
+ with ZstdFile(io.BytesIO(), 'w') as f:
+ with self.assertRaises(io.UnsupportedOperation):
+ f.read(100)
+ with self.assertRaises(io.UnsupportedOperation):
+ f.seek(100)
+ self.assertEqual(f.closed, True)
+ with self.assertRaises(ValueError):
+ f.readable()
+ with self.assertRaises(ValueError):
+ f.tell()
+ with self.assertRaises(ValueError):
+ f.read(100)
+
+ def test_read_readinto_readinto1(self):
+ lst = []
+ with ZstdFile(io.BytesIO(COMPRESSED_THIS_FILE*5)) as f:
+ while True:
+ method = random.randint(0, 2)
+ size = random.randint(0, 300)
+
+ if method == 0:
+ dat = f.read(size)
+ if not dat and size:
+ break
+ lst.append(dat)
+ elif method == 1:
+ ba = bytearray(size)
+ read_size = f.readinto(ba)
+ if read_size == 0 and size:
+ break
+ lst.append(bytes(ba[:read_size]))
+ elif method == 2:
+ ba = bytearray(size)
+ read_size = f.readinto1(ba)
+ if read_size == 0 and size:
+ break
+ lst.append(bytes(ba[:read_size]))
+ self.assertEqual(b''.join(lst), THIS_FILE_BYTES*5)
+
+ def test_zstdfile_flush(self):
+ # closed
+ f = ZstdFile(io.BytesIO(), 'w')
+ f.close()
+ with self.assertRaises(ValueError):
+ f.flush()
+
+ # read
+ with ZstdFile(io.BytesIO(), 'r') as f:
+ # does nothing for read-only stream
+ f.flush()
+
+ # write
+ DAT = b'abcd'
+ bi = io.BytesIO()
+ with ZstdFile(bi, 'w') as f:
+ self.assertEqual(f.write(DAT), len(DAT))
+ self.assertEqual(f.tell(), len(DAT))
+ self.assertEqual(bi.tell(), 0) # not enough for a block
+
+ self.assertEqual(f.flush(), None)
+ self.assertEqual(f.tell(), len(DAT))
+ self.assertGreater(bi.tell(), 0) # flushed
+
+ # write, no .flush() method
+ class C:
+ def write(self, b):
+ return len(b)
+ with ZstdFile(C(), 'w') as f:
+ self.assertEqual(f.write(DAT), len(DAT))
+ self.assertEqual(f.tell(), len(DAT))
+
+ self.assertEqual(f.flush(), None)
+ self.assertEqual(f.tell(), len(DAT))
+
+ def test_zstdfile_flush_mode(self):
+ self.assertEqual(ZstdFile.FLUSH_BLOCK, ZstdCompressor.FLUSH_BLOCK)
+ self.assertEqual(ZstdFile.FLUSH_FRAME, ZstdCompressor.FLUSH_FRAME)
+ with self.assertRaises(AttributeError):
+ ZstdFile.CONTINUE
+
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ # flush block
+ self.assertEqual(f.write(b'123'), 3)
+ self.assertIsNone(f.flush(f.FLUSH_BLOCK))
+ p1 = bo.tell()
+ # mode == .last_mode, should return
+ self.assertIsNone(f.flush())
+ p2 = bo.tell()
+ self.assertEqual(p1, p2)
+ # flush frame
+ self.assertEqual(f.write(b'456'), 3)
+ self.assertIsNone(f.flush(mode=f.FLUSH_FRAME))
+ # flush frame
+ self.assertEqual(f.write(b'789'), 3)
+ self.assertIsNone(f.flush(f.FLUSH_FRAME))
+ p1 = bo.tell()
+ # mode == .last_mode, should return
+ self.assertIsNone(f.flush(f.FLUSH_FRAME))
+ p2 = bo.tell()
+ self.assertEqual(p1, p2)
+ self.assertEqual(decompress(bo.getvalue()), b'123456789')
+
+ bo = io.BytesIO()
+ with ZstdFile(bo, 'w') as f:
+ f.write(b'123')
+ with self.assertRaisesRegex(ValueError, r'\.FLUSH_.*?\.FLUSH_'):
+ f.flush(ZstdCompressor.CONTINUE)
+ with self.assertRaises(ValueError):
+ f.flush(-1)
+ with self.assertRaises(ValueError):
+ f.flush(123456)
+ with self.assertRaises(TypeError):
+ f.flush(node=ZstdCompressor.CONTINUE)
+ with self.assertRaises((TypeError, ValueError)):
+ f.flush('FLUSH_FRAME')
+ with self.assertRaises(TypeError):
+ f.flush(b'456', f.FLUSH_BLOCK)
+
+ def test_zstdfile_truncate(self):
+ with ZstdFile(io.BytesIO(), 'w') as f:
+ with self.assertRaises(io.UnsupportedOperation):
+ f.truncate(200)
+
+ def test_zstdfile_iter_issue45475(self):
+ lines = [l for l in ZstdFile(io.BytesIO(COMPRESSED_THIS_FILE))]
+ self.assertGreater(len(lines), 0)
+
+ def test_append_new_file(self):
+ with tempfile.NamedTemporaryFile(delete=True) as tmp_f:
+ filename = tmp_f.name
+
+ with ZstdFile(filename, 'a') as f:
+ pass
+ self.assertTrue(os.path.isfile(filename))
+
+ os.remove(filename)
+
+class OpenTestCase(unittest.TestCase):
+
+ def test_binary_modes(self):
+ with open(io.BytesIO(COMPRESSED_100_PLUS_32KB), "rb") as f:
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB)
+ with io.BytesIO() as bio:
+ with open(bio, "wb") as f:
+ f.write(DECOMPRESSED_100_PLUS_32KB)
+ file_data = decompress(bio.getvalue())
+ self.assertEqual(file_data, DECOMPRESSED_100_PLUS_32KB)
+ with open(bio, "ab") as f:
+ f.write(DECOMPRESSED_100_PLUS_32KB)
+ file_data = decompress(bio.getvalue())
+ self.assertEqual(file_data, DECOMPRESSED_100_PLUS_32KB * 2)
+
+ def test_text_modes(self):
+ # empty input
+ with self.assertRaises(EOFError):
+ with open(io.BytesIO(b''), "rt", encoding="utf-8", newline='\n') as reader:
+ for _ in reader:
+ pass
+
+ # read
+ uncompressed = THIS_FILE_STR.replace(os.linesep, "\n")
+ with open(io.BytesIO(COMPRESSED_THIS_FILE), "rt", encoding="utf-8") as f:
+ self.assertEqual(f.read(), uncompressed)
+
+ with io.BytesIO() as bio:
+ # write
+ with open(bio, "wt", encoding="utf-8") as f:
+ f.write(uncompressed)
+ file_data = decompress(bio.getvalue()).decode("utf-8")
+ self.assertEqual(file_data.replace(os.linesep, "\n"), uncompressed)
+ # append
+ with open(bio, "at", encoding="utf-8") as f:
+ f.write(uncompressed)
+ file_data = decompress(bio.getvalue()).decode("utf-8")
+ self.assertEqual(file_data.replace(os.linesep, "\n"), uncompressed * 2)
+
+ def test_bad_params(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ TESTFN = pathlib.Path(tmp_f.name)
+
+ with self.assertRaises(ValueError):
+ open(TESTFN, "")
+ with self.assertRaises(ValueError):
+ open(TESTFN, "rbt")
+ with self.assertRaises(ValueError):
+ open(TESTFN, "rb", encoding="utf-8")
+ with self.assertRaises(ValueError):
+ open(TESTFN, "rb", errors="ignore")
+ with self.assertRaises(ValueError):
+ open(TESTFN, "rb", newline="\n")
+
+ os.remove(TESTFN)
+
+ def test_option(self):
+ options = {DecompressionParameter.window_log_max:25}
+ with open(io.BytesIO(COMPRESSED_100_PLUS_32KB), "rb", options=options) as f:
+ self.assertEqual(f.read(), DECOMPRESSED_100_PLUS_32KB)
+
+ options = {CompressionParameter.compression_level:12}
+ with io.BytesIO() as bio:
+ with open(bio, "wb", options=options) as f:
+ f.write(DECOMPRESSED_100_PLUS_32KB)
+ file_data = decompress(bio.getvalue())
+ self.assertEqual(file_data, DECOMPRESSED_100_PLUS_32KB)
+
+ def test_encoding(self):
+ uncompressed = THIS_FILE_STR.replace(os.linesep, "\n")
+
+ with io.BytesIO() as bio:
+ with open(bio, "wt", encoding="utf-16-le") as f:
+ f.write(uncompressed)
+ file_data = decompress(bio.getvalue()).decode("utf-16-le")
+ self.assertEqual(file_data.replace(os.linesep, "\n"), uncompressed)
+ bio.seek(0)
+ with open(bio, "rt", encoding="utf-16-le") as f:
+ self.assertEqual(f.read().replace(os.linesep, "\n"), uncompressed)
+
+ def test_encoding_error_handler(self):
+ with io.BytesIO(compress(b"foo\xffbar")) as bio:
+ with open(bio, "rt", encoding="ascii", errors="ignore") as f:
+ self.assertEqual(f.read(), "foobar")
+
+ def test_newline(self):
+ # Test with explicit newline (universal newline mode disabled).
+ text = THIS_FILE_STR.replace(os.linesep, "\n")
+ with io.BytesIO() as bio:
+ with open(bio, "wt", encoding="utf-8", newline="\n") as f:
+ f.write(text)
+ bio.seek(0)
+ with open(bio, "rt", encoding="utf-8", newline="\r") as f:
+ self.assertEqual(f.readlines(), [text])
+
+ def test_x_mode(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_f:
+ TESTFN = pathlib.Path(tmp_f.name)
+
+ for mode in ("x", "xb", "xt"):
+ os.remove(TESTFN)
+
+ if mode == "xt":
+ encoding = "utf-8"
+ else:
+ encoding = None
+ with open(TESTFN, mode, encoding=encoding):
+ pass
+ with self.assertRaises(FileExistsError):
+ with open(TESTFN, mode):
+ pass
+
+ os.remove(TESTFN)
+
+ def test_open_dict(self):
+ # default
+ bi = io.BytesIO()
+ with open(bi, 'w', zstd_dict=TRAINED_DICT) as f:
+ f.write(SAMPLES[0])
+ bi.seek(0)
+ with open(bi, zstd_dict=TRAINED_DICT) as f:
+ dat = f.read()
+ self.assertEqual(dat, SAMPLES[0])
+
+ # .as_(un)digested_dict
+ bi = io.BytesIO()
+ with open(bi, 'w', zstd_dict=TRAINED_DICT.as_digested_dict) as f:
+ f.write(SAMPLES[0])
+ bi.seek(0)
+ with open(bi, zstd_dict=TRAINED_DICT.as_undigested_dict) as f:
+ dat = f.read()
+ self.assertEqual(dat, SAMPLES[0])
+
+ # invalid dictionary
+ bi = io.BytesIO()
+ with self.assertRaisesRegex(TypeError, 'zstd_dict'):
+ open(bi, 'w', zstd_dict={1:2, 2:3})
+
+ with self.assertRaisesRegex(TypeError, 'zstd_dict'):
+ open(bi, 'w', zstd_dict=b'1234567890')
+
+ def test_open_prefix(self):
+ bi = io.BytesIO()
+ with open(bi, 'w', zstd_dict=TRAINED_DICT.as_prefix) as f:
+ f.write(SAMPLES[0])
+ bi.seek(0)
+ with open(bi, zstd_dict=TRAINED_DICT.as_prefix) as f:
+ dat = f.read()
+ self.assertEqual(dat, SAMPLES[0])
+
+ def test_buffer_protocol(self):
+ # don't use len() for buffer protocol objects
+ arr = array.array("i", range(1000))
+ LENGTH = len(arr) * arr.itemsize
+
+ with open(io.BytesIO(), "wb") as f:
+ self.assertEqual(f.write(arr), LENGTH)
+ self.assertEqual(f.tell(), LENGTH)
+
+class FreeThreadingMethodTests(unittest.TestCase):
+
+ @threading_helper.reap_threads
+ @threading_helper.requires_working_threading()
+ def test_compress_locking(self):
+ input = b'a'* (16*_1K)
+ num_threads = 8
+
+ comp = ZstdCompressor()
+ parts = []
+ for _ in range(num_threads):
+ res = comp.compress(input, ZstdCompressor.FLUSH_BLOCK)
+ if res:
+ parts.append(res)
+ rest1 = comp.flush()
+ expected = b''.join(parts) + rest1
+
+ comp = ZstdCompressor()
+ output = []
+ def run_method(method, input_data, output_data):
+ res = method(input_data, ZstdCompressor.FLUSH_BLOCK)
+ if res:
+ output_data.append(res)
+ threads = []
+
+ for i in range(num_threads):
+ thread = threading.Thread(target=run_method, args=(comp.compress, input, output))
+
+ threads.append(thread)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+ rest2 = comp.flush()
+ self.assertEqual(rest1, rest2)
+ actual = b''.join(output) + rest2
+ self.assertEqual(expected, actual)
+
+ @threading_helper.reap_threads
+ @threading_helper.requires_working_threading()
+ def test_decompress_locking(self):
+ input = compress(b'a'* (16*_1K))
+ num_threads = 8
+ # to ensure we decompress over multiple calls, set maxsize
+ window_size = _1K * 16//num_threads
+
+ decomp = ZstdDecompressor()
+ parts = []
+ for _ in range(num_threads):
+ res = decomp.decompress(input, window_size)
+ if res:
+ parts.append(res)
+ expected = b''.join(parts)
+
+ comp = ZstdDecompressor()
+ output = []
+ def run_method(method, input_data, output_data):
+ res = method(input_data, window_size)
+ if res:
+ output_data.append(res)
+ threads = []
+
+ for i in range(num_threads):
+ thread = threading.Thread(target=run_method, args=(comp.decompress, input, output))
+
+ threads.append(thread)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+ actual = b''.join(output)
+ self.assertEqual(expected, actual)
+
+ @threading_helper.reap_threads
+ @threading_helper.requires_working_threading()
+ def test_compress_shared_dict(self):
+ num_threads = 8
+
+ def run_method(b):
+ level = threading.get_ident() % 4
+ # sync threads to increase chance of contention on
+ # capsule storing dictionary levels
+ b.wait()
+ ZstdCompressor(level=level,
+ zstd_dict=TRAINED_DICT.as_digested_dict)
+ b.wait()
+ ZstdCompressor(level=level,
+ zstd_dict=TRAINED_DICT.as_undigested_dict)
+ b.wait()
+ ZstdCompressor(level=level,
+ zstd_dict=TRAINED_DICT.as_prefix)
+ threads = []
+
+ b = threading.Barrier(num_threads)
+ for i in range(num_threads):
+ thread = threading.Thread(target=run_method, args=(b,))
+
+ threads.append(thread)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+ @threading_helper.reap_threads
+ @threading_helper.requires_working_threading()
+ def test_decompress_shared_dict(self):
+ num_threads = 8
+
+ def run_method(b):
+ # sync threads to increase chance of contention on
+ # decompression dictionary
+ b.wait()
+ ZstdDecompressor(zstd_dict=TRAINED_DICT.as_digested_dict)
+ b.wait()
+ ZstdDecompressor(zstd_dict=TRAINED_DICT.as_undigested_dict)
+ b.wait()
+ ZstdDecompressor(zstd_dict=TRAINED_DICT.as_prefix)
+ threads = []
+
+ b = threading.Barrier(num_threads)
+ for i in range(num_threads):
+ thread = threading.Thread(target=run_method, args=(b,))
+
+ threads.append(thread)
+
+ with threading_helper.start_threads(threads):
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/threading.py b/Lib/threading.py
index 39a1a7f4cdf..b6c451d1fba 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -123,7 +123,7 @@ def gettrace():
Lock = _LockType
-def RLock(*args, **kwargs):
+def RLock():
"""Factory function that returns a new reentrant lock.
A reentrant lock must be released by the thread that acquired it. Once a
@@ -132,16 +132,9 @@ def RLock(*args, **kwargs):
acquired it.
"""
- if args or kwargs:
- import warnings
- warnings.warn(
- 'Passing arguments to RLock is deprecated and will be removed in 3.15',
- DeprecationWarning,
- stacklevel=2,
- )
if _CRLock is None:
- return _PyRLock(*args, **kwargs)
- return _CRLock(*args, **kwargs)
+ return _PyRLock()
+ return _CRLock()
class _RLock:
"""This class implements reentrant lock objects.
@@ -165,7 +158,7 @@ class _RLock:
except KeyError:
pass
return "<%s %s.%s object owner=%r count=%d at %s>" % (
- "locked" if self._block.locked() else "unlocked",
+ "locked" if self.locked() else "unlocked",
self.__class__.__module__,
self.__class__.__qualname__,
owner,
@@ -244,7 +237,7 @@ class _RLock:
def locked(self):
"""Return whether this object is locked."""
- return self._count > 0
+ return self._block.locked()
# Internal methods used by condition variables
@@ -951,6 +944,8 @@ class Thread:
# This thread is alive.
self._ident = new_ident
assert self._os_thread_handle.ident == new_ident
+ if _HAVE_THREAD_NATIVE_ID:
+ self._set_native_id()
else:
# Otherwise, the thread is dead, Jim. _PyThread_AfterFork()
# already marked our handle done.
diff --git a/Lib/tokenize.py b/Lib/tokenize.py
index 117b485b934..7e71755068e 100644
--- a/Lib/tokenize.py
+++ b/Lib/tokenize.py
@@ -86,7 +86,7 @@ def _all_string_prefixes():
# The valid string prefixes. Only contain the lower case versions,
# and don't contain any permutations (include 'fr', but not
# 'rf'). The various permutations will be generated.
- _valid_string_prefixes = ['b', 'r', 'u', 'f', 'br', 'fr']
+ _valid_string_prefixes = ['b', 'r', 'u', 'f', 't', 'br', 'fr', 'tr']
# if we add binary f-strings, add: ['fb', 'fbr']
result = {''}
for prefix in _valid_string_prefixes:
@@ -274,7 +274,7 @@ class Untokenizer:
toks_append = self.tokens.append
startline = token[0] in (NEWLINE, NL)
prevstring = False
- in_fstring = 0
+ in_fstring_or_tstring = 0
for tok in _itertools.chain([token], iterable):
toknum, tokval = tok[:2]
@@ -293,10 +293,10 @@ class Untokenizer:
else:
prevstring = False
- if toknum == FSTRING_START:
- in_fstring += 1
- elif toknum == FSTRING_END:
- in_fstring -= 1
+ if toknum in {FSTRING_START, TSTRING_START}:
+ in_fstring_or_tstring += 1
+ elif toknum in {FSTRING_END, TSTRING_END}:
+ in_fstring_or_tstring -= 1
if toknum == INDENT:
indents.append(tokval)
continue
@@ -311,8 +311,8 @@ class Untokenizer:
elif toknum in {FSTRING_MIDDLE, TSTRING_MIDDLE}:
tokval = self.escape_brackets(tokval)
- # Insert a space between two consecutive brackets if we are in an f-string
- if tokval in {"{", "}"} and self.tokens and self.tokens[-1] == tokval and in_fstring:
+ # Insert a space between two consecutive brackets if we are in an f-string or t-string
+ if tokval in {"{", "}"} and self.tokens and self.tokens[-1] == tokval and in_fstring_or_tstring:
tokval = ' ' + tokval
# Insert a space between two consecutive f-strings
@@ -518,7 +518,7 @@ def _main(args=None):
sys.exit(1)
# Parse the arguments and options
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument(dest='filename', nargs='?',
metavar='filename.py',
help='the file to tokenize; defaults to stdin')
diff --git a/Lib/trace.py b/Lib/trace.py
index a87bc6d61a8..cf8817f4383 100644
--- a/Lib/trace.py
+++ b/Lib/trace.py
@@ -604,7 +604,7 @@ class Trace:
def main():
import argparse
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument('--version', action='version', version='trace 2.0')
grp = parser.add_argument_group('Main options',
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 16ba7fc2ee8..a1f175dbbaa 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -10,9 +10,9 @@ import codeop
import keyword
import tokenize
import io
-from contextlib import suppress
import _colorize
-from _colorize import ANSIColors
+
+from contextlib import suppress
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
'format_exception_only', 'format_list', 'format_stack',
@@ -187,15 +187,13 @@ def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=
valuestr = _safe_string(value, 'exception')
end_char = "\n" if insert_final_newline else ""
if colorize:
- if value is None or not valuestr:
- line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}{end_char}"
- else:
- line = f"{ANSIColors.BOLD_MAGENTA}{etype}{ANSIColors.RESET}: {ANSIColors.MAGENTA}{valuestr}{ANSIColors.RESET}{end_char}"
+ theme = _colorize.get_theme(force_color=True).traceback
else:
- if value is None or not valuestr:
- line = f"{etype}{end_char}"
- else:
- line = f"{etype}: {valuestr}{end_char}"
+ theme = _colorize.get_theme(force_no_color=True).traceback
+ if value is None or not valuestr:
+ line = f"{theme.type}{etype}{theme.reset}{end_char}"
+ else:
+ line = f"{theme.type}{etype}{theme.reset}: {theme.message}{valuestr}{theme.reset}{end_char}"
return line
@@ -539,21 +537,22 @@ class StackSummary(list):
if frame_summary.filename.startswith("<stdin>-"):
filename = "<stdin>"
if colorize:
- row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
- ANSIColors.MAGENTA,
- filename,
- ANSIColors.RESET,
- ANSIColors.MAGENTA,
- frame_summary.lineno,
- ANSIColors.RESET,
- ANSIColors.MAGENTA,
- frame_summary.name,
- ANSIColors.RESET,
- )
- )
+ theme = _colorize.get_theme(force_color=True).traceback
else:
- row.append(' File "{}", line {}, in {}\n'.format(
- filename, frame_summary.lineno, frame_summary.name))
+ theme = _colorize.get_theme(force_no_color=True).traceback
+ row.append(
+ ' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
+ theme.filename,
+ filename,
+ theme.reset,
+ theme.line_no,
+ frame_summary.lineno,
+ theme.reset,
+ theme.frame,
+ frame_summary.name,
+ theme.reset,
+ )
+ )
if frame_summary._dedented_lines and frame_summary._dedented_lines.strip():
if (
frame_summary.colno is None or
@@ -672,11 +671,11 @@ class StackSummary(list):
for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]):
caret_group = list(group)
if color == "^":
- colorized_line_parts.append(ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET)
- colorized_carets_parts.append(ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET)
+ colorized_line_parts.append(theme.error_highlight + "".join(char for char, _ in caret_group) + theme.reset)
+ colorized_carets_parts.append(theme.error_highlight + "".join(caret for _, caret in caret_group) + theme.reset)
elif color == "~":
- colorized_line_parts.append(ANSIColors.RED + "".join(char for char, _ in caret_group) + ANSIColors.RESET)
- colorized_carets_parts.append(ANSIColors.RED + "".join(caret for _, caret in caret_group) + ANSIColors.RESET)
+ colorized_line_parts.append(theme.error_range + "".join(char for char, _ in caret_group) + theme.reset)
+ colorized_carets_parts.append(theme.error_range + "".join(caret for _, caret in caret_group) + theme.reset)
else:
colorized_line_parts.append("".join(char for char, _ in caret_group))
colorized_carets_parts.append("".join(caret for _, caret in caret_group))
@@ -1378,20 +1377,20 @@ class TracebackException:
"""Format SyntaxError exceptions (internal helper)."""
# Show exactly where the problem was found.
colorize = kwargs.get("colorize", False)
+ if colorize:
+ theme = _colorize.get_theme(force_color=True).traceback
+ else:
+ theme = _colorize.get_theme(force_no_color=True).traceback
filename_suffix = ''
if self.lineno is not None:
- if colorize:
- yield ' File {}"{}"{}, line {}{}{}\n'.format(
- ANSIColors.MAGENTA,
- self.filename or "<string>",
- ANSIColors.RESET,
- ANSIColors.MAGENTA,
- self.lineno,
- ANSIColors.RESET,
- )
- else:
- yield ' File "{}", line {}\n'.format(
- self.filename or "<string>", self.lineno)
+ yield ' File {}"{}"{}, line {}{}{}\n'.format(
+ theme.filename,
+ self.filename or "<string>",
+ theme.reset,
+ theme.line_no,
+ self.lineno,
+ theme.reset,
+ )
elif self.filename is not None:
filename_suffix = ' ({})'.format(self.filename)
@@ -1441,11 +1440,11 @@ class TracebackException:
# colorize from colno to end_colno
ltext = (
ltext[:colno] +
- ANSIColors.BOLD_RED + ltext[colno:end_colno] + ANSIColors.RESET +
+ theme.error_highlight + ltext[colno:end_colno] + theme.reset +
ltext[end_colno:]
)
- start_color = ANSIColors.BOLD_RED
- end_color = ANSIColors.RESET
+ start_color = theme.error_highlight
+ end_color = theme.reset
yield ' {}\n'.format(ltext)
yield ' {}{}{}{}\n'.format(
"".join(caretspace),
@@ -1456,17 +1455,15 @@ class TracebackException:
else:
yield ' {}\n'.format(ltext)
msg = self.msg or "<no detail available>"
- if colorize:
- yield "{}{}{}: {}{}{}{}\n".format(
- ANSIColors.BOLD_MAGENTA,
- stype,
- ANSIColors.RESET,
- ANSIColors.MAGENTA,
- msg,
- ANSIColors.RESET,
- filename_suffix)
- else:
- yield "{}: {}{}\n".format(stype, msg, filename_suffix)
+ yield "{}{}{}: {}{}{}{}\n".format(
+ theme.type,
+ stype,
+ theme.reset,
+ theme.message,
+ msg,
+ theme.reset,
+ filename_suffix,
+ )
def format(self, *, chain=True, _ctx=None, **kwargs):
"""Format the exception.
@@ -1598,7 +1595,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
if isinstance(exc_value, AttributeError):
obj = exc_value.obj
try:
- d = dir(obj)
+ try:
+ d = dir(obj)
+ except TypeError: # Attributes are unsortable, e.g. int and str
+ d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
+ d = sorted([x for x in d if isinstance(x, str)])
hide_underscored = (wrong_name[:1] != '_')
if hide_underscored and tb is not None:
while tb.tb_next is not None:
@@ -1613,7 +1614,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
elif isinstance(exc_value, ImportError):
try:
mod = __import__(exc_value.name)
- d = dir(mod)
+ try:
+ d = dir(mod)
+ except TypeError: # Attributes are unsortable, e.g. int and str
+ d = list(mod.__dict__.keys())
+ d = sorted([x for x in d if isinstance(x, str)])
if wrong_name[:1] != '_':
d = [x for x in d if x[:1] != '_']
except Exception:
@@ -1631,6 +1636,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
+ list(frame.f_globals)
+ list(frame.f_builtins)
)
+ d = [x for x in d if isinstance(x, str)]
# Check first if we are in a method and the instance
# has the wrong name as attribute
diff --git a/Lib/types.py b/Lib/types.py
index 6efac339434..cf0549315a7 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -250,7 +250,6 @@ class DynamicClassAttribute:
class _GeneratorWrapper:
- # TODO: Implement this in C.
def __init__(self, gen):
self.__wrapped = gen
self.__isgen = gen.__class__ is GeneratorType
@@ -305,7 +304,6 @@ def coroutine(func):
# Check if 'func' is a generator function.
# (0x20 == CO_GENERATOR)
if co_flags & 0x20:
- # TODO: Implement this in C.
co = func.__code__
# 0x100 == CO_ITERABLE_COROUTINE
func.__code__ = co.replace(co_flags=co.co_flags | 0x100)
diff --git a/Lib/typing.py b/Lib/typing.py
index f70dcd0b5b7..ed1dd4fc641 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -956,12 +956,8 @@ def evaluate_forward_ref(
"""Evaluate a forward reference as a type hint.
This is similar to calling the ForwardRef.evaluate() method,
- but unlike that method, evaluate_forward_ref() also:
-
- * Recursively evaluates forward references nested within the type hint.
- * Rejects certain objects that are not valid type hints.
- * Replaces type hints that evaluate to None with types.NoneType.
- * Supports the *FORWARDREF* and *STRING* formats.
+ but unlike that method, evaluate_forward_ref() also
+ recursively evaluates forward references nested within the type hint.
*forward_ref* must be an instance of ForwardRef. *owner*, if given,
should be the object that holds the annotations that the forward reference
@@ -981,23 +977,24 @@ def evaluate_forward_ref(
if forward_ref.__forward_arg__ in _recursive_guard:
return forward_ref
- try:
- value = forward_ref.evaluate(globals=globals, locals=locals,
- type_params=type_params, owner=owner)
- except NameError:
- if format == _lazy_annotationlib.Format.FORWARDREF:
- return forward_ref
- else:
- raise
-
- type_ = _type_check(
- value,
- "Forward references must evaluate to types.",
- is_argument=forward_ref.__forward_is_argument__,
- allow_special_forms=forward_ref.__forward_is_class__,
- )
+ if format is None:
+ format = _lazy_annotationlib.Format.VALUE
+ value = forward_ref.evaluate(globals=globals, locals=locals,
+ type_params=type_params, owner=owner, format=format)
+
+ if (isinstance(value, _lazy_annotationlib.ForwardRef)
+ and format == _lazy_annotationlib.Format.FORWARDREF):
+ return value
+
+ if isinstance(value, str):
+ value = _make_forward_ref(value, module=forward_ref.__forward_module__,
+ owner=owner or forward_ref.__owner__,
+ is_argument=forward_ref.__forward_is_argument__,
+ is_class=forward_ref.__forward_is_class__)
+ if owner is None:
+ owner = forward_ref.__owner__
return _eval_type(
- type_,
+ value,
globals,
locals,
type_params,
@@ -1649,6 +1646,9 @@ class _UnionGenericAliasMeta(type):
return True
return NotImplemented
+ def __hash__(self):
+ return hash(Union)
+
class _UnionGenericAlias(metaclass=_UnionGenericAliasMeta):
"""Compatibility hack.
@@ -2335,12 +2335,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
# This only affects ForwardRefs.
base_globals, base_locals = base_locals, base_globals
for name, value in ann.items():
- if value is None:
- value = type(None)
if isinstance(value, str):
value = _make_forward_ref(value, is_argument=False, is_class=True)
value = _eval_type(value, base_globals, base_locals, base.__type_params__,
format=format, owner=obj)
+ if value is None:
+ value = type(None)
hints[name] = value
if include_extras or format == Format.STRING:
return hints
@@ -2374,8 +2374,6 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
localns = globalns
type_params = getattr(obj, "__type_params__", ())
for name, value in hints.items():
- if value is None:
- value = type(None)
if isinstance(value, str):
# class-level forward refs were handled above, this must be either
# a module-level annotation or a function argument annotation
@@ -2384,7 +2382,10 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
is_argument=not isinstance(obj, types.ModuleType),
is_class=False,
)
- hints[name] = _eval_type(value, globalns, localns, type_params, format=format, owner=obj)
+ value = _eval_type(value, globalns, localns, type_params, format=format, owner=obj)
+ if value is None:
+ value = type(None)
+ hints[name] = value
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
@@ -2906,7 +2907,7 @@ class NamedTupleMeta(type):
types = ns["__annotations__"]
field_names = list(types)
annotate = _make_eager_annotate(types)
- elif (original_annotate := _lazy_annotationlib.get_annotate_function(ns)) is not None:
+ elif (original_annotate := _lazy_annotationlib.get_annotate_from_class_namespace(ns)) is not None:
types = _lazy_annotationlib.call_annotate_function(
original_annotate, _lazy_annotationlib.Format.FORWARDREF)
field_names = list(types)
@@ -2968,7 +2969,7 @@ class NamedTupleMeta(type):
return nm_tpl
-def NamedTuple(typename, fields=_sentinel, /, **kwargs):
+def NamedTuple(typename, fields, /):
"""Typed version of namedtuple.
Usage::
@@ -2988,48 +2989,9 @@ def NamedTuple(typename, fields=_sentinel, /, **kwargs):
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
- if fields is _sentinel:
- if kwargs:
- deprecated_thing = "Creating NamedTuple classes using keyword arguments"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "Use the class-based or functional syntax instead."
- )
- else:
- deprecated_thing = "Failing to pass a value for the 'fields' parameter"
- example = f"`{typename} = NamedTuple({typename!r}, [])`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. "
- ) + example + "."
- elif fields is None:
- if kwargs:
- raise TypeError(
- "Cannot pass `None` as the 'fields' parameter "
- "and also specify fields using keyword arguments"
- )
- else:
- deprecated_thing = "Passing `None` as the 'fields' parameter"
- example = f"`{typename} = NamedTuple({typename!r}, [])`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. "
- ) + example + "."
- elif kwargs:
- raise TypeError("Either list of fields or keywords"
- " can be provided to NamedTuple, not both")
- if fields is _sentinel or fields is None:
- import warnings
- warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
- fields = kwargs.items()
types = {n: _type_check(t, f"field {n} annotation must be a type")
for n, t in fields}
field_names = [n for n, _ in fields]
-
nt = _make_nmtuple(typename, field_names, _make_eager_annotate(types), module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt
@@ -3084,15 +3046,17 @@ class _TypedDictMeta(type):
else:
generic_base = ()
+ ns_annotations = ns.pop('__annotations__', None)
+
tp_dict = type.__new__(_TypedDictMeta, name, (*generic_base, dict), ns)
if not hasattr(tp_dict, '__orig_bases__'):
tp_dict.__orig_bases__ = bases
- if "__annotations__" in ns:
+ if ns_annotations is not None:
own_annotate = None
- own_annotations = ns["__annotations__"]
- elif (own_annotate := _lazy_annotationlib.get_annotate_function(ns)) is not None:
+ own_annotations = ns_annotations
+ elif (own_annotate := _lazy_annotationlib.get_annotate_from_class_namespace(ns)) is not None:
own_annotations = _lazy_annotationlib.call_annotate_function(
own_annotate, _lazy_annotationlib.Format.FORWARDREF, owner=tp_dict
)
@@ -3162,7 +3126,7 @@ class _TypedDictMeta(type):
if base_annotate is None:
continue
base_annos = _lazy_annotationlib.call_annotate_function(
- base.__annotate__, format, owner=base)
+ base_annotate, format, owner=base)
annos.update(base_annos)
if own_annotate is not None:
own = _lazy_annotationlib.call_annotate_function(
@@ -3198,7 +3162,7 @@ class _TypedDictMeta(type):
__instancecheck__ = __subclasscheck__
-def TypedDict(typename, fields=_sentinel, /, *, total=True):
+def TypedDict(typename, fields, /, *, total=True):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
TypedDict creates a dictionary type such that a type checker will expect all
@@ -3253,24 +3217,6 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True):
username: str # the "username" key can be changed
"""
- if fields is _sentinel or fields is None:
- import warnings
-
- if fields is _sentinel:
- deprecated_thing = "Failing to pass a value for the 'fields' parameter"
- else:
- deprecated_thing = "Passing `None` as the 'fields' parameter"
-
- example = f"`{typename} = TypedDict({typename!r}, {{{{}}}})`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. "
- ) + example + "."
- warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
- fields = {}
-
ns = {'__annotations__': dict(fields)}
module = _caller()
if module is not None:
@@ -3477,7 +3423,7 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
- def readlines(self, hint: int = -1) -> List[AnyStr]:
+ def readlines(self, hint: int = -1) -> list[AnyStr]:
pass
@abstractmethod
@@ -3493,7 +3439,7 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
- def truncate(self, size: int = None) -> int:
+ def truncate(self, size: int | None = None) -> int:
pass
@abstractmethod
@@ -3505,11 +3451,11 @@ class IO(Generic[AnyStr]):
pass
@abstractmethod
- def writelines(self, lines: List[AnyStr]) -> None:
+ def writelines(self, lines: list[AnyStr]) -> None:
pass
@abstractmethod
- def __enter__(self) -> 'IO[AnyStr]':
+ def __enter__(self) -> IO[AnyStr]:
pass
@abstractmethod
@@ -3523,11 +3469,11 @@ class BinaryIO(IO[bytes]):
__slots__ = ()
@abstractmethod
- def write(self, s: Union[bytes, bytearray]) -> int:
+ def write(self, s: bytes | bytearray) -> int:
pass
@abstractmethod
- def __enter__(self) -> 'BinaryIO':
+ def __enter__(self) -> BinaryIO:
pass
@@ -3548,7 +3494,7 @@ class TextIO(IO[str]):
@property
@abstractmethod
- def errors(self) -> Optional[str]:
+ def errors(self) -> str | None:
pass
@property
@@ -3562,7 +3508,7 @@ class TextIO(IO[str]):
pass
@abstractmethod
- def __enter__(self) -> 'TextIO':
+ def __enter__(self) -> TextIO:
pass
diff --git a/Lib/unittest/_log.py b/Lib/unittest/_log.py
index 94868e5bb95..3d69385ea24 100644
--- a/Lib/unittest/_log.py
+++ b/Lib/unittest/_log.py
@@ -30,7 +30,7 @@ class _AssertLogsContext(_BaseTestCaseContext):
LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
- def __init__(self, test_case, logger_name, level, no_logs):
+ def __init__(self, test_case, logger_name, level, no_logs, formatter=None):
_BaseTestCaseContext.__init__(self, test_case)
self.logger_name = logger_name
if level:
@@ -39,13 +39,14 @@ class _AssertLogsContext(_BaseTestCaseContext):
self.level = logging.INFO
self.msg = None
self.no_logs = no_logs
+ self.formatter = formatter
def __enter__(self):
if isinstance(self.logger_name, logging.Logger):
logger = self.logger = self.logger_name
else:
logger = self.logger = logging.getLogger(self.logger_name)
- formatter = logging.Formatter(self.LOGGING_FORMAT)
+ formatter = self.formatter or logging.Formatter(self.LOGGING_FORMAT)
handler = _CapturingHandler()
handler.setLevel(self.level)
handler.setFormatter(formatter)
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index 884fc1b21f6..eba50839cd3 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -149,9 +149,7 @@ def doModuleCleanups():
except Exception as exc:
exceptions.append(exc)
if exceptions:
- # Swallows all but first exception. If a multi-exception handler
- # gets written we should use that here instead.
- raise exceptions[0]
+ raise ExceptionGroup('module cleanup failed', exceptions)
def skip(reason):
@@ -851,7 +849,7 @@ class TestCase(object):
context = _AssertNotWarnsContext(expected_warning, self)
return context.handle('_assertNotWarns', args, kwargs)
- def assertLogs(self, logger=None, level=None):
+ def assertLogs(self, logger=None, level=None, formatter=None):
"""Fail unless a log message of level *level* or higher is emitted
on *logger_name* or its children. If omitted, *level* defaults to
INFO and *logger* defaults to the root logger.
@@ -863,6 +861,8 @@ class TestCase(object):
`records` attribute will be a list of the corresponding LogRecord
objects.
+ Optionally supply `formatter` to control how messages are formatted.
+
Example::
with self.assertLogs('foo', level='INFO') as cm:
@@ -873,7 +873,7 @@ class TestCase(object):
"""
# Lazy import to avoid importing logging if it is not needed.
from ._log import _AssertLogsContext
- return _AssertLogsContext(self, logger, level, no_logs=False)
+ return _AssertLogsContext(self, logger, level, no_logs=False, formatter=formatter)
def assertNoLogs(self, logger=None, level=None):
""" Fail unless no log messages of level *level* or higher are emitted
diff --git a/Lib/unittest/main.py b/Lib/unittest/main.py
index c3869de3f6f..6fd949581f3 100644
--- a/Lib/unittest/main.py
+++ b/Lib/unittest/main.py
@@ -197,7 +197,7 @@ class TestProgram(object):
return parser
def _getMainArgParser(self, parent):
- parser = argparse.ArgumentParser(parents=[parent])
+ parser = argparse.ArgumentParser(parents=[parent], color=True)
parser.prog = self.progName
parser.print_help = self._print_help
@@ -208,7 +208,7 @@ class TestProgram(object):
return parser
def _getDiscoveryArgParser(self, parent):
- parser = argparse.ArgumentParser(parents=[parent])
+ parser = argparse.ArgumentParser(parents=[parent], color=True)
parser.prog = '%s discover' % self.progName
parser.epilog = ('For test discovery all test modules must be '
'importable from the top level directory of the '
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 55cb4b1f6af..e1dbfdacf56 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -569,6 +569,11 @@ class NonCallableMock(Base):
__dict__['_mock_methods'] = spec
__dict__['_spec_asyncs'] = _spec_asyncs
+ def _mock_extend_spec_methods(self, spec_methods):
+ methods = self.__dict__.get('_mock_methods') or []
+ methods.extend(spec_methods)
+ self.__dict__['_mock_methods'] = methods
+
def __get_return_value(self):
ret = self._mock_return_value
if self._mock_delegate is not None:
@@ -981,7 +986,7 @@ class NonCallableMock(Base):
def assert_called_once_with(self, /, *args, **kwargs):
- """assert that the mock was called exactly once and that that call was
+ """assert that the mock was called exactly once and that call was
with the specified arguments."""
if not self.call_count == 1:
msg = ("Expected '%s' to be called once. Called %s times.%s"
@@ -2766,14 +2771,16 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
raise InvalidSpecError(f'Cannot autospec a Mock object. '
f'[object={spec!r}]')
is_async_func = _is_async_func(spec)
+ _kwargs = {'spec': spec}
entries = [(entry, _missing) for entry in dir(spec)]
if is_type and instance and is_dataclass(spec):
+ is_dataclass_spec = True
dataclass_fields = fields(spec)
entries.extend((f.name, f.type) for f in dataclass_fields)
- _kwargs = {'spec': [f.name for f in dataclass_fields]}
+ dataclass_spec_list = [f.name for f in dataclass_fields]
else:
- _kwargs = {'spec': spec}
+ is_dataclass_spec = False
if spec_set:
_kwargs = {'spec_set': spec}
@@ -2810,6 +2817,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name,
name=_name, **_kwargs)
+ if is_dataclass_spec:
+ mock._mock_extend_spec_methods(dataclass_spec_list)
if isinstance(spec, FunctionTypes):
# should only happen at the top level because we don't
diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py
index eb0234a2617..5f22d91aebd 100644
--- a/Lib/unittest/runner.py
+++ b/Lib/unittest/runner.py
@@ -4,7 +4,7 @@ import sys
import time
import warnings
-from _colorize import get_colors
+from _colorize import get_theme
from . import result
from .case import _SubTest
@@ -45,7 +45,7 @@ class TextTestResult(result.TestResult):
self.showAll = verbosity > 1
self.dots = verbosity == 1
self.descriptions = descriptions
- self._ansi = get_colors(file=stream)
+ self._theme = get_theme(tty_file=stream).unittest
self._newline = True
self.durations = durations
@@ -79,101 +79,99 @@ class TextTestResult(result.TestResult):
def addSubTest(self, test, subtest, err):
if err is not None:
- red, reset = self._ansi.RED, self._ansi.RESET
+ t = self._theme
if self.showAll:
if issubclass(err[0], subtest.failureException):
- self._write_status(subtest, f"{red}FAIL{reset}")
+ self._write_status(subtest, f"{t.fail}FAIL{t.reset}")
else:
- self._write_status(subtest, f"{red}ERROR{reset}")
+ self._write_status(subtest, f"{t.fail}ERROR{t.reset}")
elif self.dots:
if issubclass(err[0], subtest.failureException):
- self.stream.write(f"{red}F{reset}")
+ self.stream.write(f"{t.fail}F{t.reset}")
else:
- self.stream.write(f"{red}E{reset}")
+ self.stream.write(f"{t.fail}E{t.reset}")
self.stream.flush()
super(TextTestResult, self).addSubTest(test, subtest, err)
def addSuccess(self, test):
super(TextTestResult, self).addSuccess(test)
- green, reset = self._ansi.GREEN, self._ansi.RESET
+ t = self._theme
if self.showAll:
- self._write_status(test, f"{green}ok{reset}")
+ self._write_status(test, f"{t.passed}ok{t.reset}")
elif self.dots:
- self.stream.write(f"{green}.{reset}")
+ self.stream.write(f"{t.passed}.{t.reset}")
self.stream.flush()
def addError(self, test, err):
super(TextTestResult, self).addError(test, err)
- red, reset = self._ansi.RED, self._ansi.RESET
+ t = self._theme
if self.showAll:
- self._write_status(test, f"{red}ERROR{reset}")
+ self._write_status(test, f"{t.fail}ERROR{t.reset}")
elif self.dots:
- self.stream.write(f"{red}E{reset}")
+ self.stream.write(f"{t.fail}E{t.reset}")
self.stream.flush()
def addFailure(self, test, err):
super(TextTestResult, self).addFailure(test, err)
- red, reset = self._ansi.RED, self._ansi.RESET
+ t = self._theme
if self.showAll:
- self._write_status(test, f"{red}FAIL{reset}")
+ self._write_status(test, f"{t.fail}FAIL{t.reset}")
elif self.dots:
- self.stream.write(f"{red}F{reset}")
+ self.stream.write(f"{t.fail}F{t.reset}")
self.stream.flush()
def addSkip(self, test, reason):
super(TextTestResult, self).addSkip(test, reason)
- yellow, reset = self._ansi.YELLOW, self._ansi.RESET
+ t = self._theme
if self.showAll:
- self._write_status(test, f"{yellow}skipped{reset} {reason!r}")
+ self._write_status(test, f"{t.warn}skipped{t.reset} {reason!r}")
elif self.dots:
- self.stream.write(f"{yellow}s{reset}")
+ self.stream.write(f"{t.warn}s{t.reset}")
self.stream.flush()
def addExpectedFailure(self, test, err):
super(TextTestResult, self).addExpectedFailure(test, err)
- yellow, reset = self._ansi.YELLOW, self._ansi.RESET
+ t = self._theme
if self.showAll:
- self.stream.writeln(f"{yellow}expected failure{reset}")
+ self.stream.writeln(f"{t.warn}expected failure{t.reset}")
self.stream.flush()
elif self.dots:
- self.stream.write(f"{yellow}x{reset}")
+ self.stream.write(f"{t.warn}x{t.reset}")
self.stream.flush()
def addUnexpectedSuccess(self, test):
super(TextTestResult, self).addUnexpectedSuccess(test)
- red, reset = self._ansi.RED, self._ansi.RESET
+ t = self._theme
if self.showAll:
- self.stream.writeln(f"{red}unexpected success{reset}")
+ self.stream.writeln(f"{t.fail}unexpected success{t.reset}")
self.stream.flush()
elif self.dots:
- self.stream.write(f"{red}u{reset}")
+ self.stream.write(f"{t.fail}u{t.reset}")
self.stream.flush()
def printErrors(self):
- bold_red = self._ansi.BOLD_RED
- red = self._ansi.RED
- reset = self._ansi.RESET
+ t = self._theme
if self.dots or self.showAll:
self.stream.writeln()
self.stream.flush()
- self.printErrorList(f"{red}ERROR{reset}", self.errors)
- self.printErrorList(f"{red}FAIL{reset}", self.failures)
+ self.printErrorList(f"{t.fail}ERROR{t.reset}", self.errors)
+ self.printErrorList(f"{t.fail}FAIL{t.reset}", self.failures)
unexpectedSuccesses = getattr(self, "unexpectedSuccesses", ())
if unexpectedSuccesses:
self.stream.writeln(self.separator1)
for test in unexpectedSuccesses:
self.stream.writeln(
- f"{red}UNEXPECTED SUCCESS{bold_red}: "
- f"{self.getDescription(test)}{reset}"
+ f"{t.fail}UNEXPECTED SUCCESS{t.fail_info}: "
+ f"{self.getDescription(test)}{t.reset}"
)
self.stream.flush()
def printErrorList(self, flavour, errors):
- bold_red, reset = self._ansi.BOLD_RED, self._ansi.RESET
+ t = self._theme
for test, err in errors:
self.stream.writeln(self.separator1)
self.stream.writeln(
- f"{flavour}{bold_red}: {self.getDescription(test)}{reset}"
+ f"{flavour}{t.fail_info}: {self.getDescription(test)}{t.reset}"
)
self.stream.writeln(self.separator2)
self.stream.writeln("%s" % err)
@@ -286,31 +284,26 @@ class TextTestRunner(object):
expected_fails, unexpected_successes, skipped = results
infos = []
- ansi = get_colors(file=self.stream)
- bold_red = ansi.BOLD_RED
- green = ansi.GREEN
- red = ansi.RED
- reset = ansi.RESET
- yellow = ansi.YELLOW
+ t = get_theme(tty_file=self.stream).unittest
if not result.wasSuccessful():
- self.stream.write(f"{bold_red}FAILED{reset}")
+ self.stream.write(f"{t.fail_info}FAILED{t.reset}")
failed, errored = len(result.failures), len(result.errors)
if failed:
- infos.append(f"{bold_red}failures={failed}{reset}")
+ infos.append(f"{t.fail_info}failures={failed}{t.reset}")
if errored:
- infos.append(f"{bold_red}errors={errored}{reset}")
+ infos.append(f"{t.fail_info}errors={errored}{t.reset}")
elif run == 0 and not skipped:
- self.stream.write(f"{yellow}NO TESTS RAN{reset}")
+ self.stream.write(f"{t.warn}NO TESTS RAN{t.reset}")
else:
- self.stream.write(f"{green}OK{reset}")
+ self.stream.write(f"{t.passed}OK{t.reset}")
if skipped:
- infos.append(f"{yellow}skipped={skipped}{reset}")
+ infos.append(f"{t.warn}skipped={skipped}{t.reset}")
if expected_fails:
- infos.append(f"{yellow}expected failures={expected_fails}{reset}")
+ infos.append(f"{t.warn}expected failures={expected_fails}{t.reset}")
if unexpected_successes:
infos.append(
- f"{red}unexpected successes={unexpected_successes}{reset}"
+ f"{t.fail}unexpected successes={unexpected_successes}{t.reset}"
)
if infos:
self.stream.writeln(" (%s)" % (", ".join(infos),))
diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py
index 6f45b6fe5f6..ae9ca2d615d 100644
--- a/Lib/unittest/suite.py
+++ b/Lib/unittest/suite.py
@@ -223,6 +223,11 @@ class TestSuite(BaseTestSuite):
if result._moduleSetUpFailed:
try:
case.doModuleCleanups()
+ except ExceptionGroup as eg:
+ for e in eg.exceptions:
+ self._createClassOrModuleLevelException(result, e,
+ 'setUpModule',
+ currentModule)
except Exception as e:
self._createClassOrModuleLevelException(result, e,
'setUpModule',
@@ -235,15 +240,15 @@ class TestSuite(BaseTestSuite):
errorName = f'{method_name} ({parent})'
self._addClassOrModuleLevelException(result, exc, errorName, info)
- def _addClassOrModuleLevelException(self, result, exception, errorName,
+ def _addClassOrModuleLevelException(self, result, exc, errorName,
info=None):
error = _ErrorHolder(errorName)
addSkip = getattr(result, 'addSkip', None)
- if addSkip is not None and isinstance(exception, case.SkipTest):
- addSkip(error, str(exception))
+ if addSkip is not None and isinstance(exc, case.SkipTest):
+ addSkip(error, str(exc))
else:
if not info:
- result.addError(error, sys.exc_info())
+ result.addError(error, (type(exc), exc, exc.__traceback__))
else:
result.addError(error, info)
@@ -273,6 +278,13 @@ class TestSuite(BaseTestSuite):
previousModule)
try:
case.doModuleCleanups()
+ except ExceptionGroup as eg:
+ if isinstance(result, _DebugResult):
+ raise
+ for e in eg.exceptions:
+ self._createClassOrModuleLevelException(result, e,
+ 'tearDownModule',
+ previousModule)
except Exception as e:
if isinstance(result, _DebugResult):
raise
diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py
index 9a6b29a90a2..41dc5d7b35d 100644
--- a/Lib/urllib/request.py
+++ b/Lib/urllib/request.py
@@ -1466,7 +1466,7 @@ class FileHandler(BaseHandler):
def open_local_file(self, req):
import email.utils
import mimetypes
- localfile = url2pathname(req.full_url, require_scheme=True)
+ localfile = url2pathname(req.full_url, require_scheme=True, resolve_host=True)
try:
stats = os.stat(localfile)
size = stats.st_size
@@ -1482,7 +1482,7 @@ class FileHandler(BaseHandler):
file_open = open_local_file
-def _is_local_authority(authority):
+def _is_local_authority(authority, resolve):
# Compare hostnames
if not authority or authority == 'localhost':
return True
@@ -1494,9 +1494,11 @@ def _is_local_authority(authority):
if authority == hostname:
return True
# Compare IP addresses
+ if not resolve:
+ return False
try:
address = socket.gethostbyname(authority)
- except (socket.gaierror, AttributeError):
+ except (socket.gaierror, AttributeError, UnicodeEncodeError):
return False
return address in FileHandler().get_names()
@@ -1641,13 +1643,16 @@ class DataHandler(BaseHandler):
return addinfourl(io.BytesIO(data), headers, url)
-# Code move from the old urllib module
+# Code moved from the old urllib module
-def url2pathname(url, *, require_scheme=False):
+def url2pathname(url, *, require_scheme=False, resolve_host=False):
"""Convert the given file URL to a local file system path.
The 'file:' scheme prefix must be omitted unless *require_scheme*
is set to true.
+
+ The URL authority may be resolved with gethostbyname() if
+ *resolve_host* is set to true.
"""
if require_scheme:
scheme, url = _splittype(url)
@@ -1655,7 +1660,7 @@ def url2pathname(url, *, require_scheme=False):
raise URLError("URL is missing a 'file:' scheme")
authority, url = _splithost(url)
if os.name == 'nt':
- if not _is_local_authority(authority):
+ if not _is_local_authority(authority, resolve_host):
# e.g. file://server/share/file.txt
url = '//' + authority + url
elif url[:3] == '///':
@@ -1669,7 +1674,7 @@ def url2pathname(url, *, require_scheme=False):
# Older URLs use a pipe after a drive letter
url = url[:1] + ':' + url[2:]
url = url.replace('/', '\\')
- elif not _is_local_authority(authority):
+ elif not _is_local_authority(authority, resolve_host):
raise URLError("file:// scheme is supported only on localhost")
encoding = sys.getfilesystemencoding()
errors = sys.getfilesystemencodeerrors()
diff --git a/Lib/uuid.py b/Lib/uuid.py
index 2c16c3f0f5a..313f2fc46cb 100644
--- a/Lib/uuid.py
+++ b/Lib/uuid.py
@@ -633,39 +633,43 @@ def _netstat_getnode():
try:
import _uuid
_generate_time_safe = getattr(_uuid, "generate_time_safe", None)
+ _has_stable_extractable_node = _uuid.has_stable_extractable_node
_UuidCreate = getattr(_uuid, "UuidCreate", None)
except ImportError:
_uuid = None
_generate_time_safe = None
+ _has_stable_extractable_node = False
_UuidCreate = None
def _unix_getnode():
"""Get the hardware address on Unix using the _uuid extension module."""
- if _generate_time_safe:
+ if _generate_time_safe and _has_stable_extractable_node:
uuid_time, _ = _generate_time_safe()
return UUID(bytes=uuid_time).node
def _windll_getnode():
"""Get the hardware address on Windows using the _uuid extension module."""
- if _UuidCreate:
+ if _UuidCreate and _has_stable_extractable_node:
uuid_bytes = _UuidCreate()
return UUID(bytes_le=uuid_bytes).node
def _random_getnode():
"""Get a random node ID."""
- # RFC 4122, $4.1.6 says "For systems with no IEEE address, a randomly or
- # pseudo-randomly generated value may be used; see Section 4.5. The
- # multicast bit must be set in such addresses, in order that they will
- # never conflict with addresses obtained from network cards."
+ # RFC 9562, §6.10-3 says that
+ #
+ # Implementations MAY elect to obtain a 48-bit cryptographic-quality
+ # random number as per Section 6.9 to use as the Node ID. [...] [and]
+ # implementations MUST set the least significant bit of the first octet
+ # of the Node ID to 1. This bit is the unicast or multicast bit, which
+ # will never be set in IEEE 802 addresses obtained from network cards.
#
# The "multicast bit" of a MAC address is defined to be "the least
# significant bit of the first octet". This works out to be the 41st bit
# counting from 1 being the least significant bit, or 1<<40.
#
# See https://en.wikipedia.org/w/index.php?title=MAC_address&oldid=1128764812#Universal_vs._local_(U/L_bit)
- import random
- return random.getrandbits(48) | (1 << 40)
+ return int.from_bytes(os.urandom(6)) | (1 << 40)
# _OS_GETTERS, when known, are targeted for a specific OS or platform.
@@ -949,7 +953,9 @@ def main():
import argparse
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- description="Generate a UUID using the selected UUID function.")
+ description="Generate a UUID using the selected UUID function.",
+ color=True,
+ )
parser.add_argument("-u", "--uuid",
choices=uuid_funcs.keys(),
default="uuid4",
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index dc4c9ef3531..dc9c5991df7 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -313,11 +313,8 @@ class EnvBuilder:
copier(context.executable, path)
if not os.path.islink(path):
os.chmod(path, 0o755)
-
- suffixes = ['python', 'python3', f'python3.{sys.version_info[1]}']
- if sys.version_info[:2] == (3, 14):
- suffixes.append('𝜋thon')
- for suffix in suffixes:
+ for suffix in ('python', 'python3',
+ f'python3.{sys.version_info[1]}'):
path = os.path.join(binpath, suffix)
if not os.path.exists(path):
# Issue 18807: make copies if
@@ -624,7 +621,9 @@ def main(args=None):
'created, you may wish to '
'activate it, e.g. by '
'sourcing an activate script '
- 'in its bin directory.')
+ 'in its bin directory.',
+ color=True,
+ )
parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
help='A directory to create the environment in.')
parser.add_argument('--system-site-packages', default=False,
diff --git a/Lib/wave.py b/Lib/wave.py
index a34af244c3e..929609fa524 100644
--- a/Lib/wave.py
+++ b/Lib/wave.py
@@ -20,10 +20,6 @@ This returns an instance of a class with the following public methods:
compression type ('not compressed' linear samples)
getparams() -- returns a namedtuple consisting of all of the
above in the above order
- getmarkers() -- returns None (for compatibility with the
- old aifc module)
- getmark(id) -- raises an error since the mark does not
- exist (for compatibility with the old aifc module)
readframes(n) -- returns at most n frames of audio
rewind() -- rewind to the beginning of the audio stream
setpos(pos) -- seek to the specified position
@@ -341,16 +337,6 @@ class Wave_read:
self.getframerate(), self.getnframes(),
self.getcomptype(), self.getcompname())
- def getmarkers(self):
- import warnings
- warnings._deprecated("Wave_read.getmarkers", remove=(3, 15))
- return None
-
- def getmark(self, id):
- import warnings
- warnings._deprecated("Wave_read.getmark", remove=(3, 15))
- raise Error('no marks')
-
def setpos(self, pos):
if pos < 0 or pos > self._nframes:
raise Error('position not in range')
@@ -551,21 +537,6 @@ class Wave_write:
return _wave_params(self._nchannels, self._sampwidth, self._framerate,
self._nframes, self._comptype, self._compname)
- def setmark(self, id, pos, name):
- import warnings
- warnings._deprecated("Wave_write.setmark", remove=(3, 15))
- raise Error('setmark() not supported')
-
- def getmark(self, id):
- import warnings
- warnings._deprecated("Wave_write.getmark", remove=(3, 15))
- raise Error('no marks')
-
- def getmarkers(self):
- import warnings
- warnings._deprecated("Wave_write.getmarkers", remove=(3, 15))
- return None
-
def tell(self):
return self._nframeswritten
diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
index ab50ec1ee95..f2e2394089d 100644
--- a/Lib/webbrowser.py
+++ b/Lib/webbrowser.py
@@ -719,7 +719,9 @@ if sys.platform == "ios":
def parse_args(arg_list: list[str] | None):
import argparse
- parser = argparse.ArgumentParser(description="Open URL in a web browser.")
+ parser = argparse.ArgumentParser(
+ description="Open URL in a web browser.", color=True,
+ )
parser.add_argument("url", help="URL to open")
group = parser.add_mutually_exclusive_group()
diff --git a/Lib/wsgiref/handlers.py b/Lib/wsgiref/handlers.py
index cafe872c7aa..9353fb67862 100644
--- a/Lib/wsgiref/handlers.py
+++ b/Lib/wsgiref/handlers.py
@@ -69,7 +69,8 @@ def read_environ():
# Python 3's http.server.CGIHTTPRequestHandler decodes
# using the urllib.unquote default of UTF-8, amongst other
- # issues.
+ # issues. While the CGI handler is removed in 3.15, this
+ # is kept for legacy reasons.
elif (
software.startswith('simplehttp/')
and 'python/3' in software
diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py
index 44ab5d18624..dafe5b1b8a0 100644
--- a/Lib/xml/etree/ElementTree.py
+++ b/Lib/xml/etree/ElementTree.py
@@ -527,7 +527,9 @@ class ElementTree:
"""
def __init__(self, element=None, file=None):
- # assert element is None or iselement(element)
+ if element is not None and not iselement(element):
+ raise TypeError('expected an Element, not %s' %
+ type(element).__name__)
self._root = element # first node
if file:
self.parse(file)
@@ -543,7 +545,9 @@ class ElementTree:
with the given element. Use with care!
"""
- # assert iselement(element)
+ if not iselement(element):
+ raise TypeError('expected an Element, not %s'
+ % type(element).__name__)
self._root = element
def parse(self, source, parser=None):
@@ -709,6 +713,8 @@ class ElementTree:
of start/end tags
"""
+ if self._root is None:
+ raise TypeError('ElementTree not initialized')
if not method:
method = "xml"
elif method not in _serialize:
diff --git a/Lib/xmlrpc/server.py b/Lib/xmlrpc/server.py
index 90a356fbb8e..8130c739af2 100644
--- a/Lib/xmlrpc/server.py
+++ b/Lib/xmlrpc/server.py
@@ -578,7 +578,7 @@ class SimpleXMLRPCServer(socketserver.TCPServer,
"""
allow_reuse_address = True
- allow_reuse_port = True
+ allow_reuse_port = False
# Warning: this is for debugging purposes only! Never set this to True in
# production code, as will be sending out sensitive information (exception
diff --git a/Lib/zipapp.py b/Lib/zipapp.py
index 59b444075a6..7a4ef96ea0f 100644
--- a/Lib/zipapp.py
+++ b/Lib/zipapp.py
@@ -187,7 +187,7 @@ def main(args=None):
"""
import argparse
- parser = argparse.ArgumentParser()
+ parser = argparse.ArgumentParser(color=True)
parser.add_argument('--output', '-o', default=None,
help="The name of the output archive. "
"Required if SOURCE is an archive.")
diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py
index b7840d0f945..18caeb3e04a 100644
--- a/Lib/zipfile/__init__.py
+++ b/Lib/zipfile/__init__.py
@@ -31,10 +31,15 @@ try:
except ImportError:
lzma = None
+try:
+ from compression import zstd # We may need its compression method
+except ImportError:
+ zstd = None
+
__all__ = ["BadZipFile", "BadZipfile", "error",
"ZIP_STORED", "ZIP_DEFLATED", "ZIP_BZIP2", "ZIP_LZMA",
- "is_zipfile", "ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile",
- "Path"]
+ "ZIP_ZSTANDARD", "is_zipfile", "ZipInfo", "ZipFile", "PyZipFile",
+ "LargeZipFile", "Path"]
class BadZipFile(Exception):
pass
@@ -58,12 +63,14 @@ ZIP_STORED = 0
ZIP_DEFLATED = 8
ZIP_BZIP2 = 12
ZIP_LZMA = 14
+ZIP_ZSTANDARD = 93
# Other ZIP compression methods not supported
DEFAULT_VERSION = 20
ZIP64_VERSION = 45
BZIP2_VERSION = 46
LZMA_VERSION = 63
+ZSTANDARD_VERSION = 63
# we recognize (but not necessarily support) all features up to that version
MAX_EXTRACT_VERSION = 63
@@ -227,8 +234,19 @@ class _Extra(bytes):
def _check_zipfile(fp):
try:
- if _EndRecData(fp):
- return True # file has correct magic number
+ endrec = _EndRecData(fp)
+ if endrec:
+ if endrec[_ECD_ENTRIES_TOTAL] == 0 and endrec[_ECD_SIZE] == 0 and endrec[_ECD_OFFSET] == 0:
+ return True # Empty zipfiles are still zipfiles
+ elif endrec[_ECD_DISK_NUMBER] == endrec[_ECD_DISK_START]:
+ # Central directory is on the same disk
+ fp.seek(sum(_handle_prepended_data(endrec)))
+ if endrec[_ECD_SIZE] >= sizeCentralDir:
+ data = fp.read(sizeCentralDir) # CD is where we expect it to be
+ if len(data) == sizeCentralDir:
+ centdir = struct.unpack(structCentralDir, data) # CD is the right size
+ if centdir[_CD_SIGNATURE] == stringCentralDir:
+ return True # First central directory entry has correct magic number
except OSError:
pass
return False
@@ -251,6 +269,22 @@ def is_zipfile(filename):
pass
return result
+def _handle_prepended_data(endrec, debug=0):
+ size_cd = endrec[_ECD_SIZE] # bytes in central directory
+ offset_cd = endrec[_ECD_OFFSET] # offset of central directory
+
+ # "concat" is zero, unless zip was concatenated to another file
+ concat = endrec[_ECD_LOCATION] - size_cd - offset_cd
+ if endrec[_ECD_SIGNATURE] == stringEndArchive64:
+ # If Zip64 extension structures are present, account for them
+ concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator)
+
+ if debug > 2:
+ inferred = concat + offset_cd
+ print("given, inferred, offset", offset_cd, inferred, concat)
+
+ return offset_cd, concat
+
def _EndRecData64(fpin, offset, endrec):
"""
Read the ZIP64 end-of-archive records and use that to update endrec
@@ -505,6 +539,8 @@ class ZipInfo:
min_version = max(BZIP2_VERSION, min_version)
elif self.compress_type == ZIP_LZMA:
min_version = max(LZMA_VERSION, min_version)
+ elif self.compress_type == ZIP_ZSTANDARD:
+ min_version = max(ZSTANDARD_VERSION, min_version)
self.extract_version = max(min_version, self.extract_version)
self.create_version = max(min_version, self.create_version)
@@ -766,6 +802,7 @@ compressor_names = {
14: 'lzma',
18: 'terse',
19: 'lz77',
+ 93: 'zstd',
97: 'wavpack',
98: 'ppmd',
}
@@ -785,6 +822,10 @@ def _check_compression(compression):
if not lzma:
raise RuntimeError(
"Compression requires the (missing) lzma module")
+ elif compression == ZIP_ZSTANDARD:
+ if not zstd:
+ raise RuntimeError(
+ "Compression requires the (missing) compression.zstd module")
else:
raise NotImplementedError("That compression method is not supported")
@@ -801,6 +842,8 @@ def _get_compressor(compress_type, compresslevel=None):
# compresslevel is ignored for ZIP_LZMA
elif compress_type == ZIP_LZMA:
return LZMACompressor()
+ elif compress_type == ZIP_ZSTANDARD:
+ return zstd.ZstdCompressor(level=compresslevel)
else:
return None
@@ -815,6 +858,8 @@ def _get_decompressor(compress_type):
return bz2.BZ2Decompressor()
elif compress_type == ZIP_LZMA:
return LZMADecompressor()
+ elif compress_type == ZIP_ZSTANDARD:
+ return zstd.ZstdDecompressor()
else:
descr = compressor_names.get(compress_type)
if descr:
@@ -1334,7 +1379,8 @@ class ZipFile:
mode: The mode can be either read 'r', write 'w', exclusive create 'x',
or append 'a'.
compression: ZIP_STORED (no compression), ZIP_DEFLATED (requires zlib),
- ZIP_BZIP2 (requires bz2) or ZIP_LZMA (requires lzma).
+ ZIP_BZIP2 (requires bz2), ZIP_LZMA (requires lzma), or
+ ZIP_ZSTANDARD (requires compression.zstd).
allowZip64: if True ZipFile will create files with ZIP64 extensions when
needed, otherwise it will raise an exception when this would
be necessary.
@@ -1343,6 +1389,9 @@ class ZipFile:
When using ZIP_STORED or ZIP_LZMA this keyword has no effect.
When using ZIP_DEFLATED integers 0 through 9 are accepted.
When using ZIP_BZIP2 integers 1 through 9 are accepted.
+ When using ZIP_ZSTANDARD integers -7 though 22 are common,
+ see the CompressionParameter enum in compression.zstd for
+ details.
"""
@@ -1479,28 +1528,21 @@ class ZipFile:
raise BadZipFile("File is not a zip file")
if self.debug > 1:
print(endrec)
- size_cd = endrec[_ECD_SIZE] # bytes in central directory
- offset_cd = endrec[_ECD_OFFSET] # offset of central directory
self._comment = endrec[_ECD_COMMENT] # archive comment
- # "concat" is zero, unless zip was concatenated to another file
- concat = endrec[_ECD_LOCATION] - size_cd - offset_cd
- if endrec[_ECD_SIGNATURE] == stringEndArchive64:
- # If Zip64 extension structures are present, account for them
- concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator)
+ offset_cd, concat = _handle_prepended_data(endrec, self.debug)
+
+ # self.start_dir: Position of start of central directory
+ self.start_dir = offset_cd + concat
# store the offset to the beginning of data for the
# .data_offset property
self._data_offset = concat
- if self.debug > 2:
- inferred = concat + offset_cd
- print("given, inferred, offset", offset_cd, inferred, concat)
- # self.start_dir: Position of start of central directory
- self.start_dir = offset_cd + concat
if self.start_dir < 0:
raise BadZipFile("Bad offset for central directory")
fp.seek(self.start_dir, 0)
+ size_cd = endrec[_ECD_SIZE]
data = fp.read(size_cd)
fp = io.BytesIO(data)
total = 0
@@ -2075,6 +2117,8 @@ class ZipFile:
min_version = max(BZIP2_VERSION, min_version)
elif zinfo.compress_type == ZIP_LZMA:
min_version = max(LZMA_VERSION, min_version)
+ elif zinfo.compress_type == ZIP_ZSTANDARD:
+ min_version = max(ZSTANDARD_VERSION, min_version)
extract_version = max(min_version, zinfo.extract_version)
create_version = max(min_version, zinfo.create_version)
@@ -2317,7 +2361,7 @@ def main(args=None):
import argparse
description = 'A simple command-line interface for zipfile module.'
- parser = argparse.ArgumentParser(description=description)
+ parser = argparse.ArgumentParser(description=description, color=True)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-l', '--list', metavar='<zipfile>',
help='Show listing of a zipfile')
diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py
index 5ae16ec970d..faae4c84cae 100644
--- a/Lib/zipfile/_path/__init__.py
+++ b/Lib/zipfile/_path/__init__.py
@@ -7,19 +7,19 @@ https://github.com/python/importlib_metadata/wiki/Development-Methodology
for more detail.
"""
+import functools
import io
-import posixpath
-import zipfile
import itertools
-import contextlib
import pathlib
+import posixpath
import re
import stat
import sys
+import zipfile
+from ._functools import save_method_args
from .glob import Translator
-
__all__ = ['Path']
@@ -86,13 +86,12 @@ class InitializedState:
Mix-in to save the initialization state for pickling.
"""
+ @save_method_args
def __init__(self, *args, **kwargs):
- self.__args = args
- self.__kwargs = kwargs
super().__init__(*args, **kwargs)
def __getstate__(self):
- return self.__args, self.__kwargs
+ return self._saved___init__.args, self._saved___init__.kwargs
def __setstate__(self, state):
args, kwargs = state
@@ -181,22 +180,27 @@ class FastLookup(CompleteDirs):
"""
def namelist(self):
- with contextlib.suppress(AttributeError):
- return self.__names
- self.__names = super().namelist()
- return self.__names
+ return self._namelist
+
+ @functools.cached_property
+ def _namelist(self):
+ return super().namelist()
def _name_set(self):
- with contextlib.suppress(AttributeError):
- return self.__lookup
- self.__lookup = super()._name_set()
- return self.__lookup
+ return self._name_set_prop
+
+ @functools.cached_property
+ def _name_set_prop(self):
+ return super()._name_set()
def _extract_text_encoding(encoding=None, *args, **kwargs):
# compute stack level so that the caller of the caller sees any warning.
is_pypy = sys.implementation.name == 'pypy'
- stack_level = 3 + is_pypy
+ # PyPy no longer special cased after 7.3.19 (or maybe 7.3.18)
+ # See jaraco/zipp#143
+ is_old_pypi = is_pypy and sys.pypy_version_info < (7, 3, 19)
+ stack_level = 3 + is_old_pypi
return io.text_encoding(encoding, stack_level), args, kwargs
@@ -351,7 +355,7 @@ class Path:
return io.TextIOWrapper(stream, encoding, *args, **kwargs)
def _base(self):
- return pathlib.PurePosixPath(self.at or self.root.filename)
+ return pathlib.PurePosixPath(self.at) if self.at else self.filename
@property
def name(self):
diff --git a/Lib/zipfile/_path/_functools.py b/Lib/zipfile/_path/_functools.py
new file mode 100644
index 00000000000..7390be21873
--- /dev/null
+++ b/Lib/zipfile/_path/_functools.py
@@ -0,0 +1,20 @@
+import collections
+import functools
+
+
+# from jaraco.functools 4.0.2
+def save_method_args(method):
+ """
+ Wrap a method such that when it is called, the args and kwargs are
+ saved on the method.
+ """
+ args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs') # noqa: PYI024
+
+ @functools.wraps(method)
+ def wrapper(self, /, *args, **kwargs):
+ attr_name = '_saved_' + method.__name__
+ attr = args_and_kwargs(args, kwargs)
+ setattr(self, attr_name, attr)
+ return method(self, *args, **kwargs)
+
+ return wrapper
diff --git a/Lib/zipfile/_path/glob.py b/Lib/zipfile/_path/glob.py
index d7fe45a4947..bd2839304b7 100644
--- a/Lib/zipfile/_path/glob.py
+++ b/Lib/zipfile/_path/glob.py
@@ -1,7 +1,6 @@
import os
import re
-
_default_seps = os.sep + str(os.altsep) * bool(os.altsep)
diff --git a/Lib/zoneinfo/_common.py b/Lib/zoneinfo/_common.py
index 6e05abc3239..03cc42149f9 100644
--- a/Lib/zoneinfo/_common.py
+++ b/Lib/zoneinfo/_common.py
@@ -9,9 +9,13 @@ def load_tzdata(key):
resource_name = components[-1]
try:
- return resources.files(package_name).joinpath(resource_name).open("rb")
+ path = resources.files(package_name).joinpath(resource_name)
+ # gh-85702: Prevent PermissionError on Windows
+ if path.is_dir():
+ raise IsADirectoryError
+ return path.open("rb")
except (ImportError, FileNotFoundError, UnicodeEncodeError, IsADirectoryError):
- # There are three types of exception that can be raised that all amount
+ # There are four types of exception that can be raised that all amount
# to "we cannot find this key":
#
# ImportError: If package_name doesn't exist (e.g. if tzdata is not
diff --git a/Lib/zoneinfo/_zoneinfo.py b/Lib/zoneinfo/_zoneinfo.py
index b77dc0ed391..3ffdb4c8371 100644
--- a/Lib/zoneinfo/_zoneinfo.py
+++ b/Lib/zoneinfo/_zoneinfo.py
@@ -75,12 +75,12 @@ class ZoneInfo(tzinfo):
return obj
@classmethod
- def from_file(cls, fobj, /, key=None):
+ def from_file(cls, file_obj, /, key=None):
obj = super().__new__(cls)
obj._key = key
obj._file_path = None
- obj._load_file(fobj)
- obj._file_repr = repr(fobj)
+ obj._load_file(file_obj)
+ obj._file_repr = repr(file_obj)
# Disable pickling for objects created from files
obj.__reduce__ = obj._file_reduce
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 37ce0b55203..66b34b779f2 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -426,7 +426,7 @@ PYTHON_OBJS= \
Python/asdl.o \
Python/assemble.o \
Python/ast.o \
- Python/ast_opt.o \
+ Python/ast_preprocess.o \
Python/ast_unparse.o \
Python/bltinmodule.o \
Python/brc.o \
@@ -1012,7 +1012,12 @@ $(LIBRARY): $(LIBRARY_OBJS)
$(AR) $(ARFLAGS) $@ $(LIBRARY_OBJS)
libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS)
- $(BLDSHARED) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM)
+ # AIX Linker don't support "-h" option
+ if test "$(MACHDEP)" != "aix"; then \
+ $(BLDSHARED) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \
+ else \
+ $(BLDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \
+ fi
if test $(INSTSONAME) != $@; then \
$(LN) -f $(INSTSONAME) $@; \
fi
@@ -1201,6 +1206,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/unicodeobject.h \
$(srcdir)/Include/warnings.h \
$(srcdir)/Include/weakrefobject.h \
+ $(srcdir)/Python/remote_debug.h \
\
pyconfig.h \
$(PARSER_HEADERS) \
@@ -1948,7 +1954,7 @@ regen-pegen:
$(srcdir)/Grammar/python.gram \
$(srcdir)/Grammar/Tokens \
-o $(srcdir)/Parser/parser.c.new
- $(UPDATE_FILE) $(srcdir)/Parser/parser.c $(srcdir)/Parser/parser.c.new
+ $(UPDATE_FILE) --create $(srcdir)/Parser/parser.c $(srcdir)/Parser/parser.c.new
.PHONY: regen-ast
regen-ast:
@@ -2507,9 +2513,8 @@ maninstall: altmaninstall
XMLLIBSUBDIRS= xml xml/dom xml/etree xml/parsers xml/sax
LIBSUBDIRS= asyncio \
collections \
- compression compression/bz2 compression/gzip \
- compression/lzma compression/zlib compression/_common \
- concurrent concurrent/futures \
+ compression compression/_common compression/zstd \
+ concurrent concurrent/futures concurrent/interpreters \
csv \
ctypes ctypes/macholib \
curses \
@@ -2568,7 +2573,6 @@ TESTSUBDIRS= idlelib/idle_test \
test/subprocessdata \
test/support \
test/support/_hypothesis_stubs \
- test/support/interpreters \
test/test_asyncio \
test/test_capi \
test/test_cext \
@@ -3313,7 +3317,7 @@ MODULE_CMATH_DEPS=$(srcdir)/Modules/_math.h
MODULE_MATH_DEPS=$(srcdir)/Modules/_math.h
MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@
MODULE_UNICODEDATA_DEPS=$(srcdir)/Modules/unicodedata_db.h $(srcdir)/Modules/unicodename_db.h
-MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h $(srcdir)/Modules/_complex.h
+MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h
MODULE__CTYPES_TEST_DEPS=$(srcdir)/Modules/_ctypes/_ctypes_test_generated.c.h
MODULE__CTYPES_MALLOC_CLOSURE=@MODULE__CTYPES_MALLOC_CLOSURE@
MODULE__DECIMAL_DEPS=$(srcdir)/Modules/_decimal/docstrings.h @LIBMPDEC_INTERNAL@
@@ -3341,6 +3345,7 @@ MODULE__TESTCAPI_DEPS=$(srcdir)/Modules/_testcapi/parts.h $(srcdir)/Modules/_tes
MODULE__TESTLIMITEDCAPI_DEPS=$(srcdir)/Modules/_testlimitedcapi/testcapi_long.h $(srcdir)/Modules/_testlimitedcapi/parts.h $(srcdir)/Modules/_testlimitedcapi/util.h
MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h
MODULE__SQLITE3_DEPS=$(srcdir)/Modules/_sqlite/connection.h $(srcdir)/Modules/_sqlite/cursor.h $(srcdir)/Modules/_sqlite/microprotocols.h $(srcdir)/Modules/_sqlite/module.h $(srcdir)/Modules/_sqlite/prepare_protocol.h $(srcdir)/Modules/_sqlite/row.h $(srcdir)/Modules/_sqlite/util.h
+MODULE__ZSTD_DEPS=$(srcdir)/Modules/_zstd/_zstdmodule.h $(srcdir)/Modules/_zstd/buffer.h $(srcdir)/Modules/_zstd/zstddict.h
CODECS_COMMON_HEADERS=$(srcdir)/Modules/cjkcodecs/multibytecodec.h $(srcdir)/Modules/cjkcodecs/cjkcodecs.h
MODULE__CODECS_CN_DEPS=$(srcdir)/Modules/cjkcodecs/mappings_cn.h $(CODECS_COMMON_HEADERS)
diff --git a/Misc/ACKS b/Misc/ACKS
index 42068ec6aef..d1490e1e46c 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -478,6 +478,7 @@ Dean Draayer
Fred L. Drake, Jr.
Mehdi Drissi
Derk Drukker
+Weilin Du
John DuBois
Paul Dubois
Jacques Ducasse
@@ -658,6 +659,7 @@ Michael Goderbauer
Karan Goel
Jeroen Van Goey
Christoph Gohlke
+Daniel Golding
Tim Golden
Yonatan Goldschmidt
Mark Gollahon
@@ -763,6 +765,7 @@ Chris Herborth
Ivan Herman
Jürgen Hermann
Joshua Jay Herman
+Kevin Hernandez
Gary Herron
Ernie Hershey
Thomas Herve
@@ -795,6 +798,7 @@ Albert Hofkamp
Chris Hogan
Tomas Hoger
Jonathan Hogg
+John Keith Hohm
Vladyslav Hoi
Kamilla Holanda
Steve Holden
@@ -940,6 +944,7 @@ Anton Kasyanov
Lou Kates
Makoto Kato
Irit Katriel
+Kattni
Hiroaki Kawai
Dmitry Kazakov
Brian Kearns
@@ -966,6 +971,7 @@ Beomsoo Bombs Kim
Derek D. Kim
Gihwan Kim
Jan Kim
+Noah Kim
Taek Joo Kim
Yeojin Kim
Sam Kimbrel
@@ -1287,6 +1293,7 @@ Paul Moore
Ross Moore
Ben Morgan
Emily Morehouse
+Semyon Moroz
Derek Morr
James A Morrison
Martin Morrison
@@ -1361,6 +1368,7 @@ Milan Oberkirch
Pascal Oberndoerfer
Géry Ogam
Seonkyo Ok
+Andrea Oliveri
Jeffrey Ollie
Adam Olsen
Bryan Olson
@@ -1474,6 +1482,7 @@ Jean-François Piéronne
Oleg Plakhotnyuk
Anatoliy Platonov
Marcel Plch
+Stefan Pochmann
Kirill Podoprigora
Remi Pointel
Jon Poler
@@ -1863,6 +1872,7 @@ Neil Tallim
Geoff Talvola
Anish Tambe
Musashi Tamura
+Long Tan
William Tanksley
Christian Tanzer
Steven Taschuk
diff --git a/Misc/NEWS.d/3.10.0a3.rst b/Misc/NEWS.d/3.10.0a3.rst
index 33c3e14b7a4..3f3fb7ec599 100644
--- a/Misc/NEWS.d/3.10.0a3.rst
+++ b/Misc/NEWS.d/3.10.0a3.rst
@@ -1395,9 +1395,9 @@ but now can get the condition by calling the new private
.. nonce: -Br3Co
.. section: C API
-:c:func:`Py_GetPath`, :c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`,
-:c:func:`Py_GetProgramFullPath`, :c:func:`Py_GetPythonHome` and
-:c:func:`Py_GetProgramName` functions now return ``NULL`` if called before
+:c:func:`!Py_GetPath`, :c:func:`!Py_GetPrefix`, :c:func:`!Py_GetExecPrefix`,
+:c:func:`!Py_GetProgramFullPath`, :c:func:`!Py_GetPythonHome` and
+:c:func:`!Py_GetProgramName` functions now return ``NULL`` if called before
:c:func:`Py_Initialize` (before Python is initialized). Use the new
:ref:`Python Initialization Configuration API <init-config>` to get the
:ref:`Python Path Configuration. <init-path-config>`. Patch by Victor
diff --git a/Misc/NEWS.d/3.11.0a4.rst b/Misc/NEWS.d/3.11.0a4.rst
index a2d36202045..47cbf33c3bb 100644
--- a/Misc/NEWS.d/3.11.0a4.rst
+++ b/Misc/NEWS.d/3.11.0a4.rst
@@ -1161,7 +1161,7 @@ no-op now.
.. nonce: Lq2_gR
.. section: C API
-Replaced deprecated usage of :c:func:`PyImport_ImportModuleNoBlock` with
+Replaced deprecated usage of :c:func:`!PyImport_ImportModuleNoBlock` with
:c:func:`PyImport_ImportModule` in stdlib modules. Patch by Kumar Aditya.
..
diff --git a/Misc/NEWS.d/3.13.0a1.rst b/Misc/NEWS.d/3.13.0a1.rst
index 91e9fee7e37..0a93cbcea0f 100644
--- a/Misc/NEWS.d/3.13.0a1.rst
+++ b/Misc/NEWS.d/3.13.0a1.rst
@@ -2294,7 +2294,7 @@ superclass. Patch by James Hilton-Balfe
.. nonce: VksX1D
.. section: Library
-:class:`http.server.CGIHTTPRequestHandler` has been deprecated for removal
+:class:`!http.server.CGIHTTPRequestHandler` has been deprecated for removal
in 3.15. Its design is old and the web world has long since moved beyond
CGI.
@@ -6538,7 +6538,7 @@ to hide implementation details. Patch by Victor Stinner.
.. nonce: FQJG5B
.. section: C API
-Deprecate the :c:func:`PyImport_ImportModuleNoBlock` function which is just
+Deprecate the :c:func:`!PyImport_ImportModuleNoBlock` function which is just
an alias to :c:func:`PyImport_ImportModule` since Python 3.3. Patch by
Victor Stinner.
@@ -6593,12 +6593,12 @@ functions, deprecated in Python 3.9. Patch by Victor Stinner.
Deprecate old Python initialization functions:
* :c:func:`PySys_ResetWarnOptions`
-* :c:func:`Py_GetExecPrefix`
-* :c:func:`Py_GetPath`
-* :c:func:`Py_GetPrefix`
-* :c:func:`Py_GetProgramFullPath`
-* :c:func:`Py_GetProgramName`
-* :c:func:`Py_GetPythonHome`
+* :c:func:`!Py_GetExecPrefix`
+* :c:func:`!Py_GetPath`
+* :c:func:`!Py_GetPrefix`
+* :c:func:`!Py_GetProgramFullPath`
+* :c:func:`!Py_GetProgramName`
+* :c:func:`!Py_GetPythonHome`
Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/3.14.0a1.rst b/Misc/NEWS.d/3.14.0a1.rst
index 98639f0d350..67451a7e008 100644
--- a/Misc/NEWS.d/3.14.0a1.rst
+++ b/Misc/NEWS.d/3.14.0a1.rst
@@ -1033,7 +1033,7 @@ retrieve the spec information.
.. nonce: s3vKql
.. section: Library
-:mod:`argparse` vim supports abbreviated single-dash long options separated
+:mod:`argparse` supports abbreviated single-dash long options separated
by ``=`` from its value.
..
diff --git a/Misc/NEWS.d/3.14.0a6.rst b/Misc/NEWS.d/3.14.0a6.rst
index bafd8845de6..d8840b6f283 100644
--- a/Misc/NEWS.d/3.14.0a6.rst
+++ b/Misc/NEWS.d/3.14.0a6.rst
@@ -1325,7 +1325,7 @@ variable.
.. nonce: d75n8U
.. section: Core and Builtins
-Adapt :func:`reversed` for use in the free-theading build. The
+Adapt :func:`reversed` for use in the free-threading build. The
:func:`reversed` is still not thread-safe in the sense that concurrent
iterations may see the same object, but they will not corrupt the
interpreter state.
diff --git a/Misc/NEWS.d/3.14.0b1.rst b/Misc/NEWS.d/3.14.0b1.rst
new file mode 100644
index 00000000000..5847dea7d5e
--- /dev/null
+++ b/Misc/NEWS.d/3.14.0b1.rst
@@ -0,0 +1,2112 @@
+.. date: 2025-04-25-13-34-27
+.. gh-issue: 132930
+.. nonce: 6MJumW
+.. release date: 2025-05-06
+.. section: Windows
+
+Marks the installer for Windows as deprecated and updates documentation to
+cover the new Python install manager.
+
+..
+
+.. date: 2025-03-27-16-22-58
+.. gh-issue: 127405
+.. nonce: aASs2Z
+.. section: Windows
+
+Add ``ABIFLAGS`` to :func:`sysconfig.get_config_vars` on Windows. Patch by
+Xuehai Pan.
+
+..
+
+.. date: 2025-03-10-08-19-22
+.. gh-issue: 130453
+.. nonce: 9B0x8k
+.. section: Tools/Demos
+
+Allow passing multiple keyword arguments with the same function name in
+:program:`pygettext`.
+
+..
+
+.. date: 2025-02-16-19-00-00
+.. gh-issue: 130195
+.. nonce: 19274
+.. section: Tools/Demos
+
+Add warning messages when :program:`pygettext` unimplemented
+``-a/--extract-all`` option is called.
+
+..
+
+.. date: 2025-04-29-14-56-37
+.. gh-issue: 133131
+.. nonce: 1pchjl
+.. section: Tests
+
+The iOS testbed will now select the most recently released "SE-class" device
+for testing if a device isn't explicitly specified.
+
+..
+
+.. date: 2025-04-23-12-40-27
+.. gh-issue: 91048
+.. nonce: WJQCdV
+.. section: Tests
+
+Add ability to externally inspect all pending asyncio tasks, even if no task
+is currently entered on the event loop.
+
+..
+
+.. date: 2025-04-23-02-23-37
+.. gh-issue: 109981
+.. nonce: IX3k8p
+.. section: Tests
+
+The test helper that counts the list of open file descriptors now uses the
+optimised ``/dev/fd`` approach on all Apple platforms, not just macOS. This
+avoids crashes caused by guarded file descriptors.
+
+..
+
+.. date: 2025-04-18-14-00-38
+.. gh-issue: 132678
+.. nonce: j_ZKf2
+.. section: Tests
+
+Add ``--prioritize`` to ``-m test``. This option allows the user to specify
+which selected tests should execute first, even if the order is otherwise
+randomized. This is particularly useful for tests that run the longest.
+
+..
+
+.. date: 2025-03-17-19-47-27
+.. gh-issue: 131290
+.. nonce: NyCIXR
+.. section: Tests
+
+Tests in :file:`Lib/test` can now be correctly executed as standalone
+scripts.
+
+..
+
+.. date: 2024-02-18-02-53-25
+.. gh-issue: 115322
+.. nonce: Um2Sjx
+.. section: Security
+
+The underlying extension modules behind :mod:`readline`:, :mod:`subprocess`,
+and :mod:`ctypes` now raise audit events on previously uncovered code paths
+that could lead to file system access related to C function calling and
+external binary execution. The ``ctypes.call_function`` audit hook has also
+been fixed to use an unsigned value for its ``function pointer``.
+
+..
+
+.. date: 2025-05-06-00-10-10
+.. gh-issue: 133490
+.. nonce: Ubrppz
+.. section: Library
+
+Add color support to PDB in remote mode.
+
+..
+
+.. date: 2025-05-04-16-37-28
+.. gh-issue: 132493
+.. nonce: 5yjZ75
+.. section: Library
+
+Avoid eagerly evaluating annotations in functions decorated with
+:func:`reprlib.recursive_repr`.
+
+..
+
+.. date: 2025-05-04-16-00-01
+.. gh-issue: 130645
+.. nonce: yNwKue
+.. section: Library
+
+Add color to stdlib argparse CLIs. Patch by Hugo van Kemenade.
+
+..
+
+.. date: 2025-05-04-15-39-25
+.. gh-issue: 119180
+.. nonce: avZ3Hm
+.. section: Library
+
+Make :func:`annotationlib.get_annotations` succeed with the ``FORWARDREF``
+format if evaluating the annotations throws an exception other than
+:exc:`NameError` or :exc:`AttributeError`.
+
+..
+
+.. date: 2025-05-04-13-46-20
+.. gh-issue: 133351
+.. nonce: YsZls1
+.. section: Library
+
+Fix remote PDB to correctly request tab completions for Python expressions
+from the server when completing a continuation line of a multi-line Python
+block.
+
+..
+
+.. date: 2025-05-04-13-40-05
+.. gh-issue: 133367
+.. nonce: E5nl2u
+.. section: Library
+
+Add the ``--feature-version``, ``--optimize``, and ``--show-empty`` options
+to the :mod:`ast` command-line interface. Patch by Semyon Moroz.
+
+..
+
+.. date: 2025-05-03-21-55-33
+.. gh-issue: 133363
+.. nonce: PTLnRP
+.. section: Library
+
+The :class:`cmd.Cmd` class has been fixed to reliably call the
+``completedefault`` method whenever the ``do_shell`` method is not defined
+and tab completion is requested for a line beginning with ``!``.
+
+..
+
+.. date: 2025-05-03-18-48-54
+.. gh-issue: 113081
+.. nonce: JsLJ1X
+.. section: Library
+
+Highlight syntax on source code in :mod:`pdb`.
+
+..
+
+.. date: 2025-05-03-16-04-04
+.. gh-issue: 133349
+.. nonce: kAhJDY
+.. section: Library
+
+Introduced auto-indent in :mod:`pdb` multi-line input.
+
+..
+
+.. date: 2025-05-03-13-19-22
+.. gh-issue: 133306
+.. nonce: ustKV3
+.. section: Library
+
+Use ``\z`` instead of ``\Z`` in :func:`fnmatch.translate` and
+:func:`glob.translate`.
+
+..
+
+.. date: 2025-05-02-21-35-03
+.. gh-issue: 133306
+.. nonce: -vBye5
+.. section: Library
+
+Support ``\z`` as a synonym for ``\Z`` in :mod:`regular expressions <re>`.
+
+..
+
+.. date: 2025-05-02-17-23-41
+.. gh-issue: 133300
+.. nonce: oAh1P2
+.. section: Library
+
+Make :class:`argparse.ArgumentParser`'s ``suggest_on_error`` a keyword-only
+parameter. Patch by Hugo van Kemenade.
+
+..
+
+.. date: 2025-05-02-13-16-44
+.. gh-issue: 133290
+.. nonce: R5WrLM
+.. section: Library
+
+Fix attribute caching issue when setting :attr:`ctypes._Pointer._type_` in
+the undocumented and deprecated :func:`!ctypes.SetPointerType` function and
+the undocumented :meth:`!set_type` method.
+
+..
+
+.. date: 2025-05-01-18-32-44
+.. gh-issue: 133223
+.. nonce: KE_T5f
+.. section: Library
+
+When PDB is attached to a remote process, do a better job of intercepting
+Ctrl+C and forwarding it to the remote process.
+
+..
+
+.. date: 2025-04-29-23-20-52
+.. gh-issue: 133153
+.. nonce: M-w9yC
+.. section: Library
+
+Do not complete :mod:`pdb` commands in ``interact`` mode of :mod:`pdb`.
+
+..
+
+.. date: 2025-04-29-13-40-05
+.. gh-issue: 133139
+.. nonce: 9yCcC2
+.. section: Library
+
+Add the :func:`curses.assume_default_colors` function, a refinement of the
+:func:`curses.use_default_colors` function which allows to change the color
+pair ``0``.
+
+..
+
+.. date: 2025-04-29-02-23-04
+.. gh-issue: 133089
+.. nonce: 8Jy1ZS
+.. section: Library
+
+Use original timeout value for :exc:`subprocess.TimeoutExpired` when the
+func :meth:`subprocess.run` is called with a timeout instead of sometimes a
+confusing partial remaining time out value used internally on the final
+``wait()``.
+
+..
+
+.. date: 2025-04-27-15-21-05
+.. gh-issue: 133036
+.. nonce: HCNYA7
+.. section: Library
+
+:func:`codecs.open` is now deprecated. Use :func:`open` instead. Contributed
+by Inada Naoki.
+
+..
+
+.. date: 2025-04-26-17-41-20
+.. gh-issue: 132987
+.. nonce: xxBCqg
+.. section: Library
+
+Many builtin and extension functions which accept an unsigned integer
+argument, now use :meth:`~object.__index__` if available.
+
+..
+
+.. date: 2025-04-26-15-43-23
+.. gh-issue: 124703
+.. nonce: jc5auS
+.. section: Library
+
+Set return code to ``1`` when aborting process from :mod:`pdb`.
+
+..
+
+.. date: 2025-04-26-14-44-21
+.. gh-issue: 133005
+.. nonce: y4SRfk
+.. section: Library
+
+Support passing ``preset`` option to :func:`tarfile.open` when using
+``'w|xz'`` mode.
+
+..
+
+.. date: 2025-04-26-12-25-42
+.. gh-issue: 115032
+.. nonce: jnM2Co
+.. section: Library
+
+Support for custom logging handlers with the *strm* argument is deprecated
+and scheduled for removal in Python 3.16. Define handlers with the *stream*
+argument instead. Patch by Mariusz Felisiak.
+
+..
+
+.. date: 2025-04-26-10-57-15
+.. gh-issue: 132991
+.. nonce: ekkqdt
+.. section: Library
+
+Add :data:`!socket.IP_FREEBIND` constant on Linux 2.4 and later.
+
+..
+
+.. date: 2025-04-26-10-54-38
+.. gh-issue: 132995
+.. nonce: JuDF9p
+.. section: Library
+
+Bump the version of pip bundled in ensurepip to version 25.1.1
+
+..
+
+.. date: 2025-04-25-21-41-45
+.. gh-issue: 132933
+.. nonce: yO3ySJ
+.. section: Library
+
+The zipapp module now applies the filter when creating the list of files to
+add, rather than waiting until the file is being added to the archive.
+
+..
+
+.. date: 2025-04-25-16-20-49
+.. gh-issue: 121249
+.. nonce: uue2nK
+.. section: Library
+
+Always support the :c:expr:`float complex` and :c:expr:`double complex` C
+types in the :mod:`struct` module. Patch by Sergey B Kirpichev.
+
+..
+
+.. date: 2025-04-25-12-55-06
+.. gh-issue: 132915
+.. nonce: XuKCXn
+.. section: Library
+
+:func:`fcntl.fcntl` and :func:`fcntl.ioctl` can now detect a buffer overflow
+and raise :exc:`SystemError`. The stack and memory can be corrupted in such
+case, so treat this error as fatal.
+
+..
+
+.. date: 2025-04-25-10-51-00
+.. gh-issue: 132017
+.. nonce: SIGCONT1
+.. section: Library
+
+Fix error when ``pyrepl`` is suspended, then resumed and terminated.
+
+..
+
+.. date: 2025-04-24-21-22-46
+.. gh-issue: 132893
+.. nonce: KFuxZ2
+.. section: Library
+
+Improved :meth:`statistics.NormalDist.cdf` accuracy for inputs smaller than
+the mean.
+
+..
+
+.. date: 2025-04-24-18-07-49
+.. gh-issue: 130328
+.. nonce: z7CN8z
+.. section: Library
+
+Speedup pasting in ``PyREPL`` on Windows. Fix by Chris Eibl.
+
+..
+
+.. date: 2025-04-24-09-10-04
+.. gh-issue: 132882
+.. nonce: 6zoyp5
+.. section: Library
+
+Fix copying of :class:`typing.Union` objects containing objects that do not
+support the ``|`` operator.
+
+..
+
+.. date: 2025-04-24-01-03-40
+.. gh-issue: 93696
+.. nonce: kM-MBp
+.. section: Library
+
+Fixed the breakpoint display error for frozen modules in :mod:`pdb`.
+
+..
+
+.. date: 2025-04-23-18-35-09
+.. gh-issue: 129965
+.. nonce: nj7Fx2
+.. section: Library
+
+Add MIME types for ``.7z``, ``.apk``, ``.deb``, ``.glb``, ``.gltf``,
+``.gz``, ``.m4v``, ``.php``, ``.rar``, ``.rpm``, ``.stl`` and ``.wmv``.
+Patch by Hugo van Kemenade.
+
+..
+
+.. date: 2025-04-23-14-50-45
+.. gh-issue: 132742
+.. nonce: PB6B7F
+.. section: Library
+
+:func:`fcntl.fcntl` now supports arbitrary :term:`bytes-like objects
+<bytes-like object>`, not only :class:`bytes`. :func:`fcntl.ioctl` now
+automatically retries system calls failing with EINTR and releases the GIL
+during a system call even for large bytes-like object.
+
+..
+
+.. date: 2025-04-22-19-45-46
+.. gh-issue: 132451
+.. nonce: eIzMvE
+.. section: Library
+
+The CLI for the PDB debugger now accepts a ``-p PID`` argument to allow
+attaching to a running process. The process must be running the same version
+of Python as the one running PDB.
+
+..
+
+.. date: 2025-04-22-16-35-37
+.. gh-issue: 125618
+.. nonce: PEocn3
+.. section: Library
+
+Add a *format* parameter to :meth:`annotationlib.ForwardRef.evaluate`.
+Evaluating annotations in the ``FORWARDREF`` format now succeeds in more
+cases that would previously have raised an exception.
+
+..
+
+.. date: 2025-04-22-13-42-12
+.. gh-issue: 132805
+.. nonce: r-dhmJ
+.. section: Library
+
+Fix incorrect handling of nested non-constant values in the FORWARDREF
+format in :mod:`annotationlib`.
+
+..
+
+.. date: 2025-04-19-19-58-27
+.. gh-issue: 132734
+.. nonce: S6F9Cs
+.. section: Library
+
+Add new constants for Bluetooth :mod:`sockets <socket>`.
+
+..
+
+.. date: 2025-04-18-14-34-43
+.. gh-issue: 132673
+.. nonce: 0sliCv
+.. section: Library
+
+Fix :exc:`AssertionError` raised on :class:`ctypes.Structure` with ``_align_
+= 0`` and ``_fields_ = []``.
+
+..
+
+.. date: 2025-04-18-10-00-09
+.. gh-issue: 132578
+.. nonce: ruNvF-
+.. section: Library
+
+Rename the ``threading.Thread._handle`` field to avoid shadowing methods
+defined on subclasses of ``threading.Thread``.
+
+..
+
+.. date: 2025-04-16-11-44-56
+.. gh-issue: 132561
+.. nonce: ekkDPE
+.. section: Library
+
+Fix the public ``locked`` method of ``multiprocessing.SemLock`` class. Also
+adding 2 tests for the derivated :class:`multiprocessing.Lock` and
+:class:`multiprocessing.RLock` classes.
+
+..
+
+.. date: 2025-04-16-01-41-34
+.. gh-issue: 121468
+.. nonce: rxgE1z
+.. section: Library
+
+Add :func:`pdb.set_trace_async` function to support :keyword:`await`
+statements in :mod:`pdb`.
+
+..
+
+.. date: 2025-04-15-08-39-14
+.. gh-issue: 132493
+.. nonce: V0gLkU
+.. section: Library
+
+:class:`typing.Protocol` now uses :func:`annotationlib.get_annotations` when
+checking whether or not an instance implements the protocol with
+:func:`isinstance`. This enables support for ``isinstance`` checks against
+classes with deferred annotations.
+
+..
+
+.. date: 2025-04-15-03-20-00
+.. gh-issue: 132536
+.. nonce: i5Pvof
+.. section: Library
+
+Do not disable :monitoring-event:`PY_THROW` event in :mod:`bdb` because it
+can't be disabled.
+
+..
+
+.. date: 2025-04-14-23-00-00
+.. gh-issue: 132527
+.. nonce: kTi8T7
+.. section: Library
+
+Include the valid typecode 'w' in the error message when an invalid typecode
+is passed to :class:`array.array`.
+
+..
+
+.. date: 2025-04-14-20-38-43
+.. gh-issue: 132099
+.. nonce: 0l0LlK
+.. section: Library
+
+The Bluetooth socket with the :data:`~socket.BTPROTO_HCI` protocol on Linux
+now accepts an address in the format of an integer ``device_id``, not only a
+tuple ``(device_id,)``.
+
+..
+
+.. date: 2025-04-14-17-24-50
+.. gh-issue: 81793
+.. nonce: OhRTTT
+.. section: Library
+
+Fix :func:`os.link` on platforms (like Linux) where the system
+:c:func:`!link` function does not follow symlinks. On Linux, it now follows
+symlinks by default or if ``follow_symlinks=True`` is specified. On Windows,
+it now raises an error if ``follow_symlinks=True`` is passed. On macOS, it
+now raises an error if ``follow_symlinks=False`` is passed and the system
+:c:func:`!linkat` function is not available at runtime.
+
+..
+
+.. date: 2025-04-13-21-35-50
+.. gh-issue: 132493
+.. nonce: 5SAQJn
+.. section: Library
+
+Support creation of :class:`typing.Protocol` classes with annotations that
+cannot be resolved at class creation time.
+
+..
+
+.. date: 2025-04-13-21-22-37
+.. gh-issue: 132491
+.. nonce: jJfT4e
+.. section: Library
+
+Rename ``annotationlib.value_to_string`` to :func:`annotationlib.type_repr`
+and provide better handling for function objects.
+
+..
+
+.. date: 2025-04-13-21-11-11
+.. gh-issue: 132426
+.. nonce: SZno1d
+.. section: Library
+
+Add :func:`annotationlib.get_annotate_from_class_namespace` as a helper for
+accessing annotations in metaclasses, and remove
+``annotationlib.get_annotate_function``.
+
+..
+
+.. date: 2025-04-13-19-17-14
+.. gh-issue: 70145
+.. nonce: nJ2MKg
+.. section: Library
+
+Add support for channels in Bluetooth HCI protocol
+(:const:`~socket.BTPROTO_HCI`).
+
+..
+
+.. date: 2025-04-12-19-42-51
+.. gh-issue: 131913
+.. nonce: twOx7K
+.. section: Library
+
+Add a shortcut function :func:`multiprocessing.Process.interrupt` alongside
+the existing :func:`multiprocessing.Process.terminate` and
+:func:`multiprocessing.Process.kill` for an improved control over child
+process termination.
+
+..
+
+.. date: 2025-04-12-16-29-42
+.. gh-issue: 132439
+.. nonce: 3twrU6
+.. section: Library
+
+Fix ``PyREPL`` on Windows: characters entered via AltGr are swallowed. Patch
+by Chris Eibl.
+
+..
+
+.. date: 2025-04-12-12-59-51
+.. gh-issue: 132429
+.. nonce: OEIdlW
+.. section: Library
+
+Fix support of Bluetooth sockets on NetBSD and DragonFly BSD. Add support
+for *cid* and *bdaddr_type* in the BTPROTO_L2CAP address on FreeBSD. Return
+*cid* in ``getsockname()`` for BTPROTO_L2CAP if it is not zero.
+
+..
+
+.. date: 2025-04-12-09-30-24
+.. gh-issue: 132106
+.. nonce: OxUds3
+.. section: Library
+
+:meth:`QueueListener.start <logging.handlers.QueueListener.start>` now
+raises a :exc:`RuntimeError` if the listener is already started.
+
+..
+
+.. date: 2025-04-11-21-48-49
+.. gh-issue: 132417
+.. nonce: uILGdS
+.. section: Library
+
+Fix a ``NULL`` pointer dereference when a C function called using
+:mod:`ctypes` with ``restype`` :class:`~ctypes.py_object` returns ``NULL``.
+
+..
+
+.. date: 2025-04-11-12-41-47
+.. gh-issue: 132385
+.. nonce: 86HoA7
+.. section: Library
+
+Fix instance error suggestions trigger potential exceptions in
+:meth:`object.__getattr__` in :mod:`traceback`.
+
+..
+
+.. date: 2025-04-10-21-43-04
+.. gh-issue: 125866
+.. nonce: EZ9X8D
+.. section: Library
+
+Add optional *add_scheme* argument to :func:`urllib.request.pathname2url`;
+when set to true, a complete URL is returned. Likewise add optional
+*require_scheme* argument to :func:`~urllib.request.url2pathname`; when set
+to true, a complete URL is accepted.
+
+..
+
+.. date: 2025-04-10-13-06-42
+.. gh-issue: 132308
+.. nonce: 1js5SI
+.. section: Library
+
+A :class:`traceback.TracebackException` now correctly renders the
+``__context__`` and ``__cause__`` attributes from :ref:`falsey <truth>`
+:class:`Exception`, and the ``exceptions`` attribute from falsey
+:class:`ExceptionGroup`.
+
+..
+
+.. date: 2025-04-09-19-07-22
+.. gh-issue: 130645
+.. nonce: cVfE1X
+.. section: Library
+
+Add colour to :mod:`argparse` help output. Patch by Hugo van Kemenade.
+
+..
+
+.. date: 2025-04-08-14-50-39
+.. gh-issue: 127495
+.. nonce: Q0V0bS
+.. section: Library
+
+In PyREPL, append a new entry to the ``PYTHON_HISTORY`` file *after* every
+statement. This should preserve command-line history after interpreter is
+terminated. Patch by Sergey B Kirpichev.
+
+..
+
+.. date: 2025-04-08-10-45-22
+.. gh-issue: 129463
+.. nonce: b1qEP3
+.. section: Library
+
+Comparison of :class:`annotationlib.ForwardRef` objects no longer uses the
+internal ``__code__`` and ``__ast_node__`` attributes, which are used as
+caches.
+
+..
+
+.. date: 2025-04-08-01-55-11
+.. gh-issue: 132250
+.. nonce: APBFCw
+.. section: Library
+
+Fixed the :exc:`SystemError` in :mod:`cProfile` when locating the actual C
+function of a method raises an exception.
+
+..
+
+.. date: 2025-04-06-21-17-14
+.. gh-issue: 132064
+.. nonce: ktPwDM
+.. section: Library
+
+:func:`annotationlib.get_annotations` now uses the ``__annotate__``
+attribute if it is present, even if ``__annotations__`` is not present.
+Additionally, the function now raises a :py:exc:`TypeError` if it is passed
+an object that does not have any annotatins.
+
+..
+
+.. date: 2025-04-06-14-34-29
+.. gh-issue: 130664
+.. nonce: JF2r-U
+.. section: Library
+
+Support the ``'_'`` digit separator in formatting of the integral part of
+:class:`~decimal.Decimal`'s. Patch by Sergey B Kirpichev.
+
+..
+
+.. date: 2025-04-05-16-05-34
+.. gh-issue: 131952
+.. nonce: HX6gCX
+.. section: Library
+
+Add color output to the :program:`json` CLI. Patch by Tomas Roun.
+
+..
+
+.. date: 2025-04-05-15-05-09
+.. gh-issue: 132063
+.. nonce: KHnslU
+.. section: Library
+
+Prevent exceptions that evaluate as falsey (namely, when their ``__bool__``
+method returns ``False`` or their ``__len__`` method returns 0) from being
+ignored by :class:`concurrent.futures.ProcessPoolExecutor` and
+:class:`concurrent.futures.ThreadPoolExecutor`.
+
+..
+
+.. date: 2025-04-05-02-22-49
+.. gh-issue: 132106
+.. nonce: XMjhQJ
+.. section: Library
+
+:class:`logging.handlers.QueueListener` now implements the context manager
+protocol, allowing it to be used in a :keyword:`with` statement.
+
+..
+
+.. date: 2025-04-03-20-28-54
+.. gh-issue: 132054
+.. nonce: c1nlOx
+.. section: Library
+
+The ``application/yaml`` mime type (:rfc:`9512`) is now supported by
+:mod:`mimetypes`. Patch by Sasha "Nelie" Chernykh and Hugo van Kemenade.
+
+..
+
+.. date: 2025-04-03-17-19-42
+.. gh-issue: 119605
+.. nonce: c7QXAA
+.. section: Library
+
+Respect ``follow_wrapped`` for :meth:`!__init__` and :meth:`!__new__`
+methods when getting the class signature for a class with
+:func:`inspect.signature`. Preserve class signature after wrapping with
+:func:`warnings.deprecated`. Patch by Xuehai Pan.
+
+..
+
+.. date: 2025-04-03-00-56-48
+.. gh-issue: 118761
+.. nonce: Vb0S1B
+.. section: Library
+
+Improve import times by up to 33x for the :mod:`shlex` module, and improve
+the performance of :func:`shlex.quote` by up to 12x. Patch by Adam Turner.
+
+..
+
+.. date: 2025-04-01-18-24-58
+.. gh-issue: 85302
+.. nonce: 7knfUf
+.. section: Library
+
+Add support for :data:`~socket.BTPROTO_SCO` in sockets on FreeBSD.
+
+..
+
+.. date: 2025-03-26-10-56-22
+.. gh-issue: 131757
+.. nonce: pFRdmN
+.. section: Library
+
+Make :func:`functools.lru_cache` call the cached function unlocked to allow
+concurrency.
+
+..
+
+.. date: 2025-03-23-11-33-09
+.. gh-issue: 131423
+.. nonce: bQlcEb
+.. section: Library
+
+:mod:`ssl` can show descriptions for errors added in OpenSSL 3.4.1. Patch by
+Bénédikt Tran.
+
+..
+
+.. date: 2025-03-21-21-24-36
+.. gh-issue: 131434
+.. nonce: BPkyyh
+.. section: Library
+
+Improve error reporting for incorrect format in :func:`time.strptime`.
+
+..
+
+.. date: 2025-03-21-17-34-27
+.. gh-issue: 131524
+.. nonce: Vj1pO_
+.. section: Library
+
+Add help message to :mod:`platform` command-line interface. Contributed by
+Harry Lees.
+
+..
+
+.. date: 2025-03-17-23-07-57
+.. gh-issue: 100926
+.. nonce: B8gcbz
+.. section: Library
+
+Move :func:`ctypes.POINTER` types cache from a global internal cache
+(``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
+attribute of the corresponding :mod:`ctypes` types. This will stop the cache
+from growing without limits in some situations.
+
+..
+
+.. date: 2025-03-16-17-40-00
+.. gh-issue: 85702
+.. nonce: qudq12
+.. section: Library
+
+If ``zoneinfo._common.load_tzdata`` is given a package without a resource a
+``ZoneInfoNotFoundError`` is raised rather than a :exc:`IsADirectoryError`.
+
+..
+
+.. date: 2025-03-14-14-18-49
+.. gh-issue: 123471
+.. nonce: sduBKk
+.. section: Library
+
+Make concurrent iterations over :class:`itertools.repeat` safe under
+free-threading.
+
+..
+
+.. date: 2025-03-11-21-08-46
+.. gh-issue: 131127
+.. nonce: whcVdY
+.. section: Library
+
+Systems using LibreSSL now successfully build.
+
+..
+
+.. date: 2025-03-09-10-37-00
+.. gh-issue: 89157
+.. nonce: qg3r138
+.. section: Library
+
+Make the pure Python implementation of :func:`datetime.date.fromisoformat`,
+only accept ASCII strings for consistency with the C implementation.
+
+..
+
+.. date: 2025-03-07-17-47-32
+.. gh-issue: 130941
+.. nonce: 7_GvhW
+.. section: Library
+
+Fix :class:`configparser.ConfigParser` parsing empty interpolation with
+``allow_no_value`` set to ``True``.
+
+..
+
+.. date: 2025-03-01-15-00-00
+.. gh-issue: 110067
+.. nonce: 1ad3as
+.. section: Library
+
+Make :mod:`heapq` max-heap functions :func:`heapq.heapify_max`,
+:func:`heapq.heappush_max`, :func:`heapq.heappop_max`, and
+:func:`heapq.heapreplace_max` public. Previous underscored naming is kept
+for backwards compatibility. Additionally, the missing function
+:func:`heapq.heappushpop_max` has been added to both the C and Python
+implementations.
+
+..
+
+.. date: 2025-03-01-12-37-08
+.. gh-issue: 129098
+.. nonce: eJ2-6L
+.. section: Library
+
+Fix REPL traceback reporting when using :func:`compile` with an inexisting
+file. Patch by Bénédikt Tran.
+
+..
+
+.. date: 2025-02-27-14-25-01
+.. gh-issue: 130631
+.. nonce: dmZcZM
+.. section: Library
+
+:func:`!http.cookiejar.join_header_words` is now more similar to the
+original Perl version. It now quotes the same set of characters and always
+quote values that end with ``"\n"``.
+
+..
+
+.. date: 2025-02-24-12-22-51
+.. gh-issue: 130482
+.. nonce: p2DrrL
+.. section: Library
+
+Add ability to specify name for :class:`!tkinter.OptionMenu` and
+:class:`!tkinter.ttk.OptionMenu`.
+
+..
+
+.. date: 2025-02-24-07-08-11
+.. gh-issue: 77065
+.. nonce: 8uW0Wf
+.. section: Library
+
+Add keyword-only optional argument *echo_char* for :meth:`getpass.getpass`
+for optional visual keyboard feedback support. Patch by Semyon Moroz.
+
+..
+
+.. date: 2025-02-22-13-07-06
+.. gh-issue: 130317
+.. nonce: tnxd0I
+.. section: Library
+
+Fix :c:func:`PyFloat_Pack2` and :c:func:`PyFloat_Unpack2` for NaN's with
+payload. This corrects round-trip for :func:`struct.unpack` and
+:func:`struct.pack` in case of the IEEE 754 binary16 "half precision" type.
+Patch by Sergey B Kirpichev.
+
+..
+
+.. date: 2025-02-21-15-46-43
+.. gh-issue: 130402
+.. nonce: Rwu_KK
+.. section: Library
+
+Joining running daemon threads during interpreter shutdown now raises
+:exc:`PythonFinalizationError`.
+
+..
+
+.. date: 2025-02-16-06-25-01
+.. gh-issue: 130167
+.. nonce: kUg7Rc
+.. section: Library
+
+Improve speed of :func:`difflib.IS_LINE_JUNK`. Patch by Semyon Moroz.
+
+..
+
+.. date: 2025-02-12-16-37-34
+.. gh-issue: 101410
+.. nonce: 0GInct
+.. section: Library
+
+Added more detailed messages for domain errors in the :mod:`math` module.
+
+..
+
+.. date: 2025-02-11-10-22-11
+.. gh-issue: 128384
+.. nonce: jyWEkA
+.. section: Library
+
+Make :class:`warnings.catch_warnings` use a context variable for holding the
+warning filtering state if the :data:`sys.flags.context_aware_warnings` flag
+is set to true. This makes using the context manager thread-safe in
+multi-threaded programs. The flag is true by default in free-threaded
+builds and is otherwise false. The value of the flag can be overridden by
+the the :option:`-X context_aware_warnings <-X>` command-line option or by
+the :envvar:`PYTHON_CONTEXT_AWARE_WARNINGS` environment variable.
+
+..
+
+.. date: 2025-02-06-11-23-51
+.. gh-issue: 129719
+.. nonce: Of6rvb
+.. section: Library
+
+Fix missing :data:`!socket.CAN_RAW_ERR_FILTER` constant in the socket module
+on Linux systems. It was missing since Python 3.11.
+
+..
+
+.. date: 2025-01-21-11-48-19
+.. gh-issue: 129027
+.. nonce: w0vxzZ
+.. section: Library
+
+Raise :exc:`DeprecationWarning` for :func:`sys._clear_type_cache`. This
+function was deprecated in Python 3.13 but it didn't raise a runtime
+warning.
+
+..
+
+.. date: 2024-12-28-11-01-36
+.. gh-issue: 128307
+.. nonce: BRCYTA
+.. section: Library
+
+Add ``eager_start`` keyword argument to :meth:`asyncio.loop.create_task`
+
+..
+
+.. date: 2024-12-21-13-31-55
+.. gh-issue: 127604
+.. nonce: etL5mf
+.. section: Library
+
+Add support for printing the C stack trace on systems that support it via
+:func:`faulthandler.dump_c_stack` or via the *c_stack* argument in
+:func:`faulthandler.enable`.
+
+..
+
+.. date: 2024-11-29-13-06-52
+.. gh-issue: 127385
+.. nonce: PErcyB
+.. section: Library
+
+Add the ``F_DUPFD_QUERY`` constant to the :mod:`fcntl` module.
+
+..
+
+.. date: 2024-11-14-21-17-48
+.. gh-issue: 126838
+.. nonce: Yr5vKF
+.. section: Library
+
+Add *resolve_host* keyword-only parameter to
+:func:`urllib.request.url2pathname`, and fix handling of file URLs with
+authorities.
+
+..
+
+.. date: 2024-09-18-09-15-40
+.. gh-issue: 82129
+.. nonce: GQwt3u
+.. section: Library
+
+Fix :exc:`NameError` when calling :func:`typing.get_type_hints` on a
+:func:`dataclasses.dataclass` created by :func:`dataclasses.make_dataclass`
+with un-annotated fields.
+
+..
+
+.. date: 2024-08-02-20-01-36
+.. gh-issue: 122559
+.. nonce: 2JlJr3
+.. section: Library
+
+Remove :meth:`!__reduce__` and :meth:`!__reduce_ex__` methods that always
+raise :exc:`TypeError` in the C implementation of :class:`io.FileIO`,
+:class:`io.BufferedReader`, :class:`io.BufferedWriter` and
+:class:`io.BufferedRandom` and replace them with default
+:meth:`!__getstate__` methods that raise :exc:`!TypeError`. This restores
+fine details of behavior of Python 3.11 and older versions.
+
+..
+
+.. date: 2024-07-23-17-08-41
+.. gh-issue: 122179
+.. nonce: 0jZm9h
+.. section: Library
+
+:func:`hashlib.file_digest` now raises :exc:`BlockingIOError` when no data
+is available during non-blocking I/O. Before, it added spurious null bytes
+to the digest.
+
+..
+
+.. date: 2024-07-19-07-16-50
+.. gh-issue: 53032
+.. nonce: paXN3p
+.. section: Library
+
+Expose :func:`decimal.IEEEContext` to support creation of contexts
+corresponding to the IEEE 754 (2008) decimal interchange formats. Patch by
+Sergey B Kirpichev.
+
+..
+
+.. date: 2024-06-07-15-03-54
+.. gh-issue: 120220
+.. nonce: NNxrr_
+.. section: Library
+
+Deprecate the :class:`!tkinter.Variable` methods :meth:`!trace_variable`,
+:meth:`!trace_vdelete` and :meth:`!trace_vinfo`. Methods :meth:`!trace_add`,
+:meth:`!trace_remove` and :meth:`!trace_info` can be used instead.
+
+..
+
+.. date: 2023-12-29-09-44-41
+.. gh-issue: 113539
+.. nonce: YDkv9O
+.. section: Library
+
+:mod:`webbrowser`: Names in the :envvar:`BROWSER` environment variable can
+now refer to already registered web browsers, instead of always generating a
+new browser command.
+
+This makes it possible to set :envvar:`BROWSER` to the value of one of the
+supported browsers on macOS.
+
+..
+
+.. bpo: 44172
+.. date: 2021-05-18-19-12-58
+.. nonce: rJ_-CI
+.. section: Library
+
+Keep a reference to original :mod:`curses` windows in subwindows so that the
+original window does not get deleted before subwindows.
+
+..
+
+.. date: 2019-09-10-09-28-52
+.. gh-issue: 75223
+.. nonce: VyAJS9
+.. section: Library
+
+Deprecate undotted extensions in :meth:`mimetypes.MimeTypes.add_type`. Patch
+by Hugo van Kemenade.
+
+..
+
+.. date: 2024-11-08-18-07-13
+.. gh-issue: 112936
+.. nonce: 1Q2RcP
+.. section: IDLE
+
+fix IDLE: no Shell menu item in single-process mode.
+
+..
+
+.. date: 2025-03-28-18-25-43
+.. gh-issue: 107006
+.. nonce: BxFijD
+.. section: Documentation
+
+Move documentation and example code for :class:`threading.local` from its
+docstring to the official docs.
+
+..
+
+.. date: 2024-10-08-10-44-14
+.. gh-issue: 125142
+.. nonce: HVlHrs
+.. section: Documentation
+
+As part of the builtin help intro text, show the keyboard shortcuts for the
+new, non-basic REPL (F1, F2, and F3).
+
+..
+
+.. date: 2025-05-05-15-33-35
+.. gh-issue: 133336
+.. nonce: miffFi
+.. section: Core and Builtins
+
+:option:`!-J` is no longer reserved for use by Jython. Patch by Adam Turner.
+
+..
+
+.. date: 2025-05-05-12-03-46
+.. gh-issue: 133261
+.. nonce: bL1gqz
+.. section: Core and Builtins
+
+Fix bug where the cycle GC could untrack objects in the trashcan because
+they looked like they were immortal. When objects are added to the trashcan,
+we take care to ensure they keep a mortal reference count.
+
+..
+
+.. date: 2025-05-04-19-46-14
+.. gh-issue: 133346
+.. nonce: nRXi4f
+.. section: Core and Builtins
+
+Added experimental color theming support to the ``_colorize`` module.
+
+..
+
+.. date: 2025-05-04-14-47-26
+.. gh-issue: 132917
+.. nonce: DrEU1y
+.. section: Core and Builtins
+
+For the free-threaded build, check the process memory usage increase before
+triggering a full automatic garbage collection. If the memory used has not
+increased 10% since the last collection then defer it.
+
+..
+
+.. date: 2025-05-03-19-04-03
+.. gh-issue: 91048
+.. nonce: S8QWSw
+.. section: Core and Builtins
+
+Add a new ``python -m asyncio ps PID`` command-line interface to inspect
+asyncio tasks in a running Python process. Displays a flat table of await
+relationships. A variant showing a tree view is also available as ``python
+-m asyncio pstree PID``. Both are useful for debugging async code. Patch by
+Pablo Galindo, Łukasz Langa, Yury Selivanov, and Marta Gomez Macias.
+
+..
+
+.. date: 2025-05-03-07-41-21
+.. gh-issue: 133304
+.. nonce: YMuSne
+.. section: Core and Builtins
+
+Workaround NaN's "canonicalization" in :c:func:`PyFloat_Pack4` and
+:c:func:`PyFloat_Unpack4` on RISC-V.
+
+..
+
+.. date: 2025-05-01-11-06-29
+.. gh-issue: 133197
+.. nonce: BHjfh4
+.. section: Core and Builtins
+
+Improve :exc:`SyntaxError` error messages for incompatible string / bytes
+prefixes.
+
+..
+
+.. date: 2025-04-30-19-07-11
+.. gh-issue: 133231
+.. nonce: H9T8g_
+.. section: Core and Builtins
+
+Add new utilities of observing JIT compilation:
+:func:`sys._jit.is_available`, :func:`sys._jit.is_enabled`, and
+:func:`sys._jit.is_active`.
+
+..
+
+.. date: 2025-04-30-13-09-20
+.. gh-issue: 133194
+.. nonce: 25_G5c
+.. section: Core and Builtins
+
+:func:`ast.parse` will no longer parse new :pep:`758` syntax with older
+*feature_version* passed.
+
+..
+
+.. date: 2025-04-30-00-21-54
+.. gh-issue: 131798
+.. nonce: D6T5_u
+.. section: Core and Builtins
+
+Split ``CALL_LEN`` into several uops allowing the JIT to remove them when
+optimizing. Patch by Diego Russo.
+
+..
+
+.. date: 2025-04-26-18-43-31
+.. gh-issue: 131798
+.. nonce: FsIypo
+.. section: Core and Builtins
+
+Use ``sym_new_type`` instead of ``sym_new_not_null`` for _BUILD_STRING,
+_BUILD_SET
+
+..
+
+.. date: 2025-04-26-17-50-47
+.. gh-issue: 132942
+.. nonce: aEEZvZ
+.. section: Core and Builtins
+
+Fix two races in the type lookup cache. This affected the free-threaded
+build and could cause crashes (apparently quite difficult to trigger).
+
+..
+
+.. date: 2025-04-26-13-57-13
+.. gh-issue: 131798
+.. nonce: Gt8CGE
+.. section: Core and Builtins
+
+Propagate the return type of ``_BINARY_OP_SUBSCR_TUPLE_INT`` in JIT. Patch
+by Tomas Roun
+
+..
+
+.. date: 2025-04-26-08-49-05
+.. gh-issue: 132952
+.. nonce: ifvP10
+.. section: Core and Builtins
+
+Speed up startup with the ``-S`` argument by importing the private ``_io``
+module instead of :mod:`io`. This fixes a performance regression introduced
+earlier in Python 3.14 development and restores performance to the level of
+Python 3.13.
+
+..
+
+.. date: 2025-04-25-14-56-45
+.. gh-issue: 131798
+.. nonce: NpcKub
+.. section: Core and Builtins
+
+Allow the JIT to remove int guards after ``_CALL_LEN`` by setting the return
+type to int. Patch by Diego Russo
+
+..
+
+.. date: 2025-04-23-20-54-17
+.. gh-issue: 131798
+.. nonce: XYlp09
+.. section: Core and Builtins
+
+Split ``CALL_TUPLE_1`` into several uops allowing the JIT to remove some of
+them. Patch by Tomas Roun
+
+..
+
+.. date: 2025-04-23-20-42-55
+.. gh-issue: 131798
+.. nonce: wVQ1Gt
+.. section: Core and Builtins
+
+Split ``CALL_STR_1`` into several uops allowing the JIT to remove some of
+them. Patch by Tomas Roun
+
+..
+
+.. date: 2025-04-23-11-34-39
+.. gh-issue: 132825
+.. nonce: _yv0uL
+.. section: Core and Builtins
+
+Enhance unhashable key/element error messages for :class:`dict` and
+:class:`set`. Patch by Victor Stinner.
+
+..
+
+.. date: 2025-04-22-19-00-03
+.. gh-issue: 131591
+.. nonce: CdEqBr
+.. section: Core and Builtins
+
+Reset any :pep:`768` remote debugging pending call in children after
+:func:`os.fork` calls.
+
+..
+
+.. date: 2025-04-22-16-38-43
+.. gh-issue: 132713
+.. nonce: mBWTSZ
+.. section: Core and Builtins
+
+Fix ``repr(list)`` race condition: hold a strong reference to the item while
+calling ``repr(item)``. Patch by Victor Stinner.
+
+..
+
+.. date: 2025-04-22-15-37-05
+.. gh-issue: 132661
+.. nonce: XE_A42
+.. section: Core and Builtins
+
+Implement :pep:`750` (Template Strings). Add new syntax for t-strings and
+implement new internal :class:`!string.templatelib.Template` and
+:class:`!string.templatelib.Interpolation` types.
+
+..
+
+.. date: 2025-04-21-09-22-15
+.. gh-issue: 132479
+.. nonce: CCe2sE
+.. section: Core and Builtins
+
+Fix compiler crash in certain circumstances where multiple module-level
+annotations include comprehensions and other nested scopes.
+
+..
+
+.. date: 2025-04-21-07-39-59
+.. gh-issue: 132747
+.. nonce: L-cnej
+.. section: Core and Builtins
+
+Fix a crash when calling :meth:`~object.__get__` of a :term:`method` with a
+:const:`None` second argument.
+
+..
+
+.. date: 2025-04-20-10-37-39
+.. gh-issue: 132744
+.. nonce: ArrCp8
+.. section: Core and Builtins
+
+Certain calls now check for runaway recursion and respect the system
+recursion limit.
+
+..
+
+.. date: 2025-04-19-22-59-24
+.. gh-issue: 132449
+.. nonce: xjdw4p
+.. section: Core and Builtins
+
+Syntax errors that look like misspellings of Python keywords now provide a
+helpful fix suggestion for the typo. Contributed by Pablo Galindo Salgado.
+
+..
+
+.. date: 2025-04-19-18-07-34
+.. gh-issue: 132737
+.. nonce: 9mW1il
+.. section: Core and Builtins
+
+Support profiling code that requires ``__main__``, such as :mod:`pickle`.
+
+..
+
+.. date: 2025-04-17-16-20-03
+.. gh-issue: 132639
+.. nonce: zRVYU3
+.. section: Core and Builtins
+
+Added :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and
+:c:func:`PyLong_FromUnsignedNativeBytes` to the limited C API.
+
+..
+
+.. date: 2025-04-17-11-40-13
+.. gh-issue: 100239
+.. nonce: 9RxIxY
+.. section: Core and Builtins
+
+Add specialisation for ``BINARY_OP/SUBSCR`` on list and slice.
+
+..
+
+.. date: 2025-04-15-10-09-49
+.. gh-issue: 132508
+.. nonce: zVe3iI
+.. section: Core and Builtins
+
+Uses tagged integers on the evaluation stack to represent the instruction
+offsets when reraising an exception. This avoids the need to box the integer
+which could fail in low memory conditions.
+
+..
+
+.. date: 2025-04-13-17-18-01
+.. gh-issue: 124476
+.. nonce: fvGfQ7
+.. section: Core and Builtins
+
+Fix decoding from the locale encoding in the C.UTF-8 locale.
+
+..
+
+.. date: 2025-04-13-10-34-27
+.. gh-issue: 131927
+.. nonce: otp80n
+.. section: Core and Builtins
+
+Compiler warnings originating from the same module and line number are now
+only emitted once, matching the behaviour of warnings emitted from user
+code. This can also be configured with :mod:`warnings` filters.
+
+..
+
+.. date: 2025-04-13-01-50-40
+.. gh-issue: 132457
+.. nonce: 1q-1xz
+.. section: Core and Builtins
+
+Make :func:`staticmethod` and :func:`classmethod` generic.
+
+..
+
+.. date: 2025-04-12-19-41-16
+.. gh-issue: 131798
+.. nonce: JkSocg
+.. section: Core and Builtins
+
+Use ``sym_new_type`` instead of ``sym_new_not_null`` for _BUILD_LIST,
+_BUILD_SET, _BUILD_MAP
+
+..
+
+.. date: 2025-04-11-22-01-07
+.. gh-issue: 131798
+.. nonce: TTu_xH
+.. section: Core and Builtins
+
+Split ``CALL_TYPE_1`` into several uops allowing the JIT to remove some of
+them.
+
+..
+
+.. date: 2025-04-11-18-46-37
+.. gh-issue: 132386
+.. nonce: pMBFTe
+.. section: Core and Builtins
+
+Fix crash when passing a dict subclass as the ``globals`` parameter to
+:func:`exec`.
+
+..
+
+.. date: 2025-04-10-10-29-45
+.. gh-issue: 127682
+.. nonce: X0HoGz
+.. section: Core and Builtins
+
+No longer call ``__iter__`` twice when creating and executing a generator
+expression. Creating a generator expression from a non-interable will raise
+only when the generator expression is executed. This brings the behavior of
+generator expressions in line with other generators.
+
+..
+
+.. date: 2025-04-09-21-51-37
+.. gh-issue: 132261
+.. nonce: gL8thm
+.. section: Core and Builtins
+
+The internal storage for annotations and annotate functions on classes now
+uses different keys in the class dictionary. This eliminates various edge
+cases where access to the ``__annotate__`` and ``__annotations__``
+attributes would behave unpredictably.
+
+..
+
+.. date: 2025-04-09-20-49-04
+.. gh-issue: 132284
+.. nonce: TxTNka
+.. section: Core and Builtins
+
+Don't wrap base ``PyCFunction`` slots on class creation if not overridden.
+
+..
+
+.. date: 2025-04-09-14-05-54
+.. gh-issue: 130415
+.. nonce: llQtUq
+.. section: Core and Builtins
+
+Improve the JIT's ability to remove unused constant and local variable
+loads, and fix an issue where deallocating unused values could cause JIT
+code to crash or behave incorrectly.
+
+..
+
+.. date: 2025-04-09-13-47-33
+.. gh-issue: 126703
+.. nonce: kXiQHj
+.. section: Core and Builtins
+
+Fix possible use after free in cases where a method's definition has the
+same lifetime as its ``self``.
+
+..
+
+.. date: 2025-04-09-12-37-31
+.. gh-issue: 132286
+.. nonce: 1ZdsOa
+.. section: Core and Builtins
+
+Fix that :attr:`type.__annotate__` was not deleted, when
+:attr:`type.__annotations__` was deleted.
+
+..
+
+.. date: 2025-04-08-21-20-12
+.. gh-issue: 131798
+.. nonce: Ft9tIF
+.. section: Core and Builtins
+
+Allow the JIT to remove an extra ``_TO_BOOL_BOOL`` instruction after
+``_CONTAINS_OP_DICT`` by setting the return type to bool.
+
+..
+
+.. date: 2025-04-08-17-48-11
+.. gh-issue: 124715
+.. nonce: xxzQoD
+.. section: Core and Builtins
+
+Prevents against stack overflows when calling :c:func:`Py_DECREF`.
+Third-party extension objects no longer need to use the "trashcan"
+mechanism, as protection is now built into the :c:func:`Py_DECREF` macro.
+
+..
+
+.. date: 2025-04-08-09-20-18
+.. gh-issue: 131798
+.. nonce: Xp1mvN
+.. section: Core and Builtins
+
+Allow the JIT compiler to remove some type checks for operations on lists,
+tuples, dictionaries, and sets.
+
+..
+
+.. date: 2025-04-07-13-46-57
+.. gh-issue: 128398
+.. nonce: gJ2zIF
+.. section: Core and Builtins
+
+Improve error message when an object supporting the synchronous (resp.
+asynchronous) context manager protocol is entered using :keyword:`async
+with` (resp. :keyword:`with`) instead of :keyword:`with` (resp.
+:keyword:`async with`). Patch by Bénédikt Tran.
+
+..
+
+.. date: 2025-04-06-13-17-10
+.. gh-issue: 131798
+.. nonce: uMrfha
+.. section: Core and Builtins
+
+Allow the JIT to remove unicode guards after ``_BINARY_OP_SUBSCR_STR_INT``
+by setting the return type to string.
+
+..
+
+.. date: 2025-03-30-19-58-14
+.. gh-issue: 131878
+.. nonce: uxM26H
+.. section: Core and Builtins
+
+Handle uncaught exceptions in the main input loop for the new REPL.
+
+..
+
+.. date: 2025-03-30-19-49-00
+.. gh-issue: 131878
+.. nonce: J8_cHB
+.. section: Core and Builtins
+
+Fix support of unicode characters with two or more codepoints on Windows in
+the new REPL.
+
+..
+
+.. date: 2025-03-28-15-06-55
+.. gh-issue: 126835
+.. nonce: IpcMTn
+.. section: Core and Builtins
+
+Move constant folding to the peephole optimizer. Rename AST optimization
+related files (Python/ast_opt.c -> Python/ast_preprocess.c), structs
+(_PyASTOptimizeState -> _PyASTPreprocessState) and functions
+(_PyAST_Optimize -> _PyAST_Preprocess, _PyCompile_AstOptimize ->
+_PyCompile_AstPreprocess).
+
+..
+
+.. date: 2025-03-26-04-55-25
+.. gh-issue: 114809
+.. nonce: 8rNyT7
+.. section: Core and Builtins
+
+Add support for macOS multi-arch builds with the JIT enabled
+
+..
+
+.. date: 2025-03-21-19-03-42
+.. gh-issue: 131507
+.. nonce: q9fvyM
+.. section: Core and Builtins
+
+PyREPL now supports syntax highlighing. Contributed by Łukasz Langa.
+
+..
+
+.. date: 2025-03-21-08-47-36
+.. gh-issue: 130907
+.. nonce: rGg-ge
+.. section: Core and Builtins
+
+If the ``__annotations__`` of a module object are accessed while the module
+is executing, return the annotations that have been defined so far, without
+caching them.
+
+..
+
+.. date: 2025-02-18-11-42-58
+.. gh-issue: 130104
+.. nonce: BOicVZ
+.. section: Core and Builtins
+
+Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if
+necessary. Previously it was only called in two-argument :func:`!pow` and
+the binary power operator.
+
+..
+
+.. date: 2025-02-13-05-09-31
+.. gh-issue: 130070
+.. nonce: C8c9gK
+.. section: Core and Builtins
+
+Fixed an assertion error for :func:`exec` passed a string ``source`` and a
+non-``None`` ``closure``. Patch by Bartosz Sławecki.
+
+..
+
+.. date: 2025-02-13-00-14-24
+.. gh-issue: 129958
+.. nonce: Uj7lyY
+.. section: Core and Builtins
+
+Fix a bug that was allowing newlines inconsitently in format specifiers for
+single-quoted f-strings. Patch by Pablo Galindo.
+
+..
+
+.. date: 2025-02-12-01-36-13
+.. gh-issue: 129858
+.. nonce: M-f7Gb
+.. section: Core and Builtins
+
+``elif`` statements that follow an ``else`` block now have a specific error
+message.
+
+..
+
+.. date: 2025-01-26-23-46-43
+.. gh-issue: 69605
+.. nonce: _2Qc1w
+.. section: Core and Builtins
+
+Add module autocomplete to PyREPL.
+
+..
+
+.. date: 2025-01-06-10-55-41
+.. gh-issue: 128555
+.. nonce: tAK_AY
+.. section: Core and Builtins
+
+Add the :data:`sys.flags.thread_inherit_context` flag.
+
+* This flag is set to true by default on the free-threaded build
+ and false otherwise. If the flag is true, starting a new thread using
+ :class:`threading.Thread` will, by default, use a copy of the
+ :class:`contextvars.Context` from the caller of
+ :meth:`threading.Thread.start` rather than using an empty context.
+
+* Add the :option:`-X thread_inherit_context <-X>` command-line option and
+ :envvar:`PYTHON_THREAD_INHERIT_CONTEXT` environment variable, which set the
+ :data:`~sys.flags.thread_inherit_context` flag.
+
+* Add the ``context`` keyword parameter to :class:`~threading.Thread`. It can
+ be used to explicitly pass a context value to be used by a new thread.
+
+* Make the ``_contextvars`` module built-in.
+
+..
+
+.. date: 2024-09-03-15-15-51
+.. gh-issue: 123539
+.. nonce: RKQS0S
+.. section: Core and Builtins
+
+Improve :exc:`SyntaxError` message for using ``import ... as`` and ``from
+... import ... as`` with not a name.
+
+..
+
+.. date: 2024-07-11-12-31-29
+.. gh-issue: 102567
+.. nonce: weRqDn
+.. section: Core and Builtins
+
+:option:`-X importtime <-X>` now accepts value ``2``, which indicates that
+an ``importtime`` entry should also be printed if an imported module has
+already been loaded. Patch by Noah Kim and Adam Turner.
+
+..
+
+.. date: 2024-03-06-22-33-33
+.. gh-issue: 116436
+.. nonce: y8Thkt
+.. section: Core and Builtins
+
+Improve error message when :exc:`TypeError` occurs during
+:meth:`dict.update`
+
+..
+
+.. date: 2023-04-29-23-15-38
+.. gh-issue: 103997
+.. nonce: BS3uVt
+.. section: Core and Builtins
+
+String arguments passed to "-c" are now automatically dedented as if by
+:func:`textwrap.dedent`. This allows "python -c" invocations to be indented
+in shell scripts without causing indentation errors. (Patch by Jon Crall and
+Steven Sun)
+
+..
+
+.. date: 2022-12-29-19-10-36
+.. gh-issue: 89562
+.. nonce: g8m8RC
+.. section: Core and Builtins
+
+Remove ``hostflags`` member from ``PySSLContext`` struct.
+
+..
+
+.. date: 2025-05-01-01-02-11
+.. gh-issue: 133166
+.. nonce: Ly9Ae2
+.. section: C API
+
+Fix regression where :c:func:`PyType_GetModuleByDef` returns NULL without
+setting :exc:`TypeError` when a static type is passed.
+
+..
+
+.. date: 2025-04-29-19-39-16
+.. gh-issue: 133164
+.. nonce: W-XTU7
+.. section: C API
+
+Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` function for
+determining if an object exists as a unique temporary variable on the
+interpreter's stack. This is a replacement for some cases where checking
+that :c:func:`Py_REFCNT` is one is no longer sufficient to determine if it's
+safe to modify a Python object in-place with no visible side effects.
+
+..
+
+.. date: 2025-04-29-06-27-46
+.. gh-issue: 133140
+.. nonce: IPGGc3
+.. section: C API
+
+Add :c:func:`PyUnstable_Object_IsUniquelyReferenced` as a replacement for
+``Py_REFNCT(op) == 1`` on :term:`free threaded <free threading>` builds of
+Python.
+
+..
+
+.. date: 2025-04-28-18-26-37
+.. gh-issue: 131747
+.. nonce: 2AiQ9n
+.. section: C API
+
+On non-Windows platforms, deprecate using :attr:`ctypes.Structure._pack_` to
+use a Windows-compatible layout on non-Windows platforms. The layout should
+be specified explicitly by setting :attr:`ctypes.Structure._layout_` to
+``'ms'``.
+
+..
+
+.. date: 2025-04-28-15-36-01
+.. gh-issue: 128972
+.. nonce: 8bZMIm
+.. section: C API
+
+For non-free-threaded builds, the memory layout of :c:struct:`PyASCIIObject`
+is reverted to match Python 3.13. (Note that the structure is not part of
+stable ABI and so its memory layout is *guaranteed* to remain stable.)
+
+..
+
+.. date: 2025-04-28-13-27-48
+.. gh-issue: 133079
+.. nonce: DJL2sK
+.. section: C API
+
+The undocumented APIs :c:macro:`!Py_C_RECURSION_LIMIT` and
+:c:member:`!PyThreadState.c_recursion_remaining`, added in 3.13, are removed
+without a deprecation period.
+
+..
+
+.. date: 2025-04-26-12-00-52
+.. gh-issue: 132987
+.. nonce: vykZGN
+.. section: C API
+
+The ``k`` and ``K`` formats in :c:func:`PyArg_Parse` now support the
+:meth:`~object.__index__` special method, like all other integer formats.
+
+..
+
+.. date: 2025-04-25-11-39-24
+.. gh-issue: 132909
+.. nonce: JC3n_l
+.. section: C API
+
+Fix an overflow when handling the :ref:`K <capi-py-buildvalue-format-K>`
+format in :c:func:`Py_BuildValue`. Patch by Bénédikt Tran.
+
+..
+
+.. date: 2025-04-22-13-59-30
+.. gh-issue: 132798
+.. nonce: asfafhs
+.. section: C API
+
+Deprecated and undocumented functions :c:func:`!PyUnicode_AsEncodedObject`,
+:c:func:`!PyUnicode_AsDecodedObject`, :c:func:`!PyUnicode_AsEncodedUnicode`
+and :c:func:`!PyUnicode_AsDecodedUnicode` are scheduled for removal in 3.15.
+
+..
+
+.. date: 2025-04-13-20-52-39
+.. gh-issue: 132470
+.. nonce: UqBQjN
+.. section: C API
+
+Creating a :class:`ctypes.CField` with a *byte_size* that does not match the
+actual type size now raises a :exc:`ValueError` instead of crashing the
+interpreter.
+
+..
+
+.. date: 2025-01-08-18-55-57
+.. gh-issue: 112068
+.. nonce: ofI5Fl
+.. section: C API
+
+Add support of nullable arguments in :c:func:`PyArg_Parse` and similar
+functions. Adding ``?`` after any format unit makes ``None`` be accepted as
+a value.
+
+..
+
+.. date: 2024-12-31-15-28-14
+.. gh-issue: 50333
+.. nonce: KxQUXa
+.. section: C API
+
+Non-tuple sequences are deprecated as argument for the ``(items)`` format
+unit in :c:func:`PyArg_ParseTuple` and other :ref:`argument parsing
+<arg-parsing>` functions if *items* contains format units which store a
+:ref:`borrowed buffer <c-arg-borrowed-buffer>` or a :term:`borrowed
+reference`.
+
+..
+
+.. date: 2025-05-01-17-27-06
+.. gh-issue: 113464
+.. nonce: vjE5X4
+.. section: Build
+
+Use the cpython-bin-deps "externals" repository for Windows LLVM dependency
+management. Installing LLVM manually is no longer necessary for Windows JIT
+builds.
+
+..
+
+.. date: 2025-04-30-11-07-53
+.. gh-issue: 133183
+.. nonce: zCKUeQ
+.. section: Build
+
+iOS compiler shims now include ``IPHONEOS_DEPLOYMENT_TARGET`` in target
+triples, ensuring that SDK version minimums are honored.
+
+..
+
+.. date: 2025-04-30-10-23-18
+.. gh-issue: 133167
+.. nonce: E0jrYJ
+.. section: Build
+
+Fix compilation process with ``--enable-optimizations`` and
+``--without-docstrings``.
+
+..
+
+.. date: 2025-04-29-15-29-11
+.. gh-issue: 133171
+.. nonce: YbwbwP
+.. section: Build
+
+Since free-threaded builds do not support the experimental JIT compiler,
+prevent these configurations from being combined.
+
+..
+
+.. date: 2025-04-20-20-07-44
+.. gh-issue: 132758
+.. nonce: N2a3wp
+.. section: Build
+
+Fix building with tail call interpreter and pystats.
+
+..
+
+.. date: 2025-04-17-19-10-15
+.. gh-issue: 132649
+.. nonce: DZqGoq
+.. section: Build
+
+The :file:`PC\layout` script now allows passing ``--include-tcltk`` on
+Windows ARM64.
+
+..
+
+.. date: 2025-04-08-09-11-32
+.. gh-issue: 132257
+.. nonce: oZWBV-
+.. section: Build
+
+Change the default LTO flags on GCC to not pass ``-flto-partition=none``,
+and allow parallelization of LTO. For newer GNU makes and GCC, this has a
+multiple factor speedup for LTO build times, with no noticeable loss in
+performance.
+
+..
+
+.. date: 2025-04-02-21-08-36
+.. gh-issue: 132026
+.. nonce: ptnR7T
+.. section: Build
+
+Fix use of undefined identifiers in platform triplet detection on MIPS Linux
+platforms.
diff --git a/Misc/NEWS.d/3.9.0a1.rst b/Misc/NEWS.d/3.9.0a1.rst
index d75132b0aac..cc24bae5881 100644
--- a/Misc/NEWS.d/3.9.0a1.rst
+++ b/Misc/NEWS.d/3.9.0a1.rst
@@ -5536,8 +5536,8 @@ Tyler Kieft.
.. section: C API
:c:func:`!Py_SetPath` now sets :data:`sys.executable` to the program full
-path (:c:func:`Py_GetProgramFullPath`) rather than to the program name
-(:c:func:`Py_GetProgramName`).
+path (:c:func:`!Py_GetProgramFullPath`) rather than to the program name
+(:c:func:`!Py_GetProgramName`).
..
diff --git a/Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst b/Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst
new file mode 100644
index 00000000000..3667e2778b7
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2024-12-04-10-00-35.gh-issue-127545.t0THjE.rst
@@ -0,0 +1 @@
+Fix crash when building on Linux/m68k.
diff --git a/Misc/NEWS.d/next/Build/2025-04-02-21-08-36.gh-issue-132026.ptnR7T.rst b/Misc/NEWS.d/next/Build/2025-04-02-21-08-36.gh-issue-132026.ptnR7T.rst
deleted file mode 100644
index 5490b98f25b..00000000000
--- a/Misc/NEWS.d/next/Build/2025-04-02-21-08-36.gh-issue-132026.ptnR7T.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix use of undefined identifiers in platform triplet detection on MIPS Linux platforms.
diff --git a/Misc/NEWS.d/next/Build/2025-04-08-09-11-32.gh-issue-132257.oZWBV-.rst b/Misc/NEWS.d/next/Build/2025-04-08-09-11-32.gh-issue-132257.oZWBV-.rst
deleted file mode 100644
index 5bf20d2f1d0..00000000000
--- a/Misc/NEWS.d/next/Build/2025-04-08-09-11-32.gh-issue-132257.oZWBV-.rst
+++ /dev/null
@@ -1 +0,0 @@
-Change the default LTO flags on GCC to not pass ``-flto-partition=none``, and allow parallelization of LTO. For newer GNU makes and GCC, this has a multiple factor speedup for LTO build times, with no noticeable loss in performance.
diff --git a/Misc/NEWS.d/next/Build/2025-04-16-09-38-48.gh-issue-117088.EFt_5c.rst b/Misc/NEWS.d/next/Build/2025-04-16-09-38-48.gh-issue-117088.EFt_5c.rst
new file mode 100644
index 00000000000..0845b055139
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-04-16-09-38-48.gh-issue-117088.EFt_5c.rst
@@ -0,0 +1 @@
+AIX linker don't support -h option, so avoid it through platform check
diff --git a/Misc/NEWS.d/next/Build/2025-04-17-19-10-15.gh-issue-132649.DZqGoq.rst b/Misc/NEWS.d/next/Build/2025-04-17-19-10-15.gh-issue-132649.DZqGoq.rst
deleted file mode 100644
index 358d4b0f476..00000000000
--- a/Misc/NEWS.d/next/Build/2025-04-17-19-10-15.gh-issue-132649.DZqGoq.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-The :file:`PC\layout` script now allows passing ``--include-tcltk`` on
-Windows ARM64.
diff --git a/Misc/NEWS.d/next/Build/2025-04-20-20-07-44.gh-issue-132758.N2a3wp.rst b/Misc/NEWS.d/next/Build/2025-04-20-20-07-44.gh-issue-132758.N2a3wp.rst
deleted file mode 100644
index 0645c35c18c..00000000000
--- a/Misc/NEWS.d/next/Build/2025-04-20-20-07-44.gh-issue-132758.N2a3wp.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix building with tail call interpreter and pystats.
diff --git a/Misc/NEWS.d/next/Build/2025-04-29-15-29-11.gh-issue-133171.YbwbwP.rst b/Misc/NEWS.d/next/Build/2025-04-29-15-29-11.gh-issue-133171.YbwbwP.rst
deleted file mode 100644
index 6207ffa851d..00000000000
--- a/Misc/NEWS.d/next/Build/2025-04-29-15-29-11.gh-issue-133171.YbwbwP.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Since free-threaded builds do not support the experimental JIT compiler,
-prevent these configurations from being combined.
diff --git a/Misc/NEWS.d/next/Build/2025-04-30-10-23-18.gh-issue-133167.E0jrYJ.rst b/Misc/NEWS.d/next/Build/2025-04-30-10-23-18.gh-issue-133167.E0jrYJ.rst
deleted file mode 100644
index ee6d5a3b879..00000000000
--- a/Misc/NEWS.d/next/Build/2025-04-30-10-23-18.gh-issue-133167.E0jrYJ.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix compilation process with ``--enable-optimizations`` and
-``--without-docstrings``.
diff --git a/Misc/NEWS.d/next/Build/2025-04-30-11-07-53.gh-issue-133183.zCKUeQ.rst b/Misc/NEWS.d/next/Build/2025-04-30-11-07-53.gh-issue-133183.zCKUeQ.rst
deleted file mode 100644
index e359f9ef473..00000000000
--- a/Misc/NEWS.d/next/Build/2025-04-30-11-07-53.gh-issue-133183.zCKUeQ.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-iOS compiler shims now include ``IPHONEOS_DEPLOYMENT_TARGET`` in target
-triples, ensuring that SDK version minimums are honored.
diff --git a/Misc/NEWS.d/next/Build/2025-05-01-17-27-06.gh-issue-113464.vjE5X4.rst b/Misc/NEWS.d/next/Build/2025-05-01-17-27-06.gh-issue-113464.vjE5X4.rst
deleted file mode 100644
index d150b19d5f1..00000000000
--- a/Misc/NEWS.d/next/Build/2025-05-01-17-27-06.gh-issue-113464.vjE5X4.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Use the cpython-bin-deps "externals" repository for Windows LLVM dependency
-management. Installing LLVM manually is no longer necessary for Windows JIT
-builds.
diff --git a/Misc/NEWS.d/next/Build/2025-05-14-09-43-48.gh-issue-131769.H0oy5x.rst b/Misc/NEWS.d/next/Build/2025-05-14-09-43-48.gh-issue-131769.H0oy5x.rst
new file mode 100644
index 00000000000..834b0d9f7e1
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-05-14-09-43-48.gh-issue-131769.H0oy5x.rst
@@ -0,0 +1 @@
+Fix detecting when the build Python in a cross-build is a pydebug build.
diff --git a/Misc/NEWS.d/next/Build/2025-05-16-07-46-06.gh-issue-115119.ALBgS_.rst b/Misc/NEWS.d/next/Build/2025-05-16-07-46-06.gh-issue-115119.ALBgS_.rst
new file mode 100644
index 00000000000..8c2d15a3228
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-05-16-07-46-06.gh-issue-115119.ALBgS_.rst
@@ -0,0 +1,4 @@
+Removed implicit fallback to the bundled copy of the ``libmpdec`` library.
+Now this should be explicitly enabled via :option:`--with-system-libmpdec`
+set to ``no`` or :option:`!--without-system-libmpdec`. Patch by Sergey
+B Kirpichev.
diff --git a/Misc/NEWS.d/next/Build/2025-05-19-18-09-20.gh-issue-134273.ZAliyy.rst b/Misc/NEWS.d/next/Build/2025-05-19-18-09-20.gh-issue-134273.ZAliyy.rst
new file mode 100644
index 00000000000..3eb13cefbe6
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-05-19-18-09-20.gh-issue-134273.ZAliyy.rst
@@ -0,0 +1 @@
+Add support for configuring compiler flags for the JIT with ``CFLAGS_JIT``
diff --git a/Misc/NEWS.d/next/Build/2025-05-21-19-46-28.gh-issue-134455.vdwlrq.rst b/Misc/NEWS.d/next/Build/2025-05-21-19-46-28.gh-issue-134455.vdwlrq.rst
new file mode 100644
index 00000000000..08833b3344f
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-05-21-19-46-28.gh-issue-134455.vdwlrq.rst
@@ -0,0 +1,2 @@
+Fixed ``build-details.json`` generation to use the correct ``c_api.headers``
+as defined in :pep:`739`, instead of ``c_api.include``.
diff --git a/Misc/NEWS.d/next/Build/2025-05-21-22-13-30.gh-issue-134486.yvdL6f.rst b/Misc/NEWS.d/next/Build/2025-05-21-22-13-30.gh-issue-134486.yvdL6f.rst
new file mode 100644
index 00000000000..2754e61f018
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-05-21-22-13-30.gh-issue-134486.yvdL6f.rst
@@ -0,0 +1,3 @@
+The :mod:`ctypes` module now performs a more portable test for the
+definition of :manpage:`alloca(3)`, fixing a compilation failure on
+NetBSD.
diff --git a/Misc/NEWS.d/next/Build/2025-05-24-16-59-20.gh-issue-134632.i0W2hc.rst b/Misc/NEWS.d/next/Build/2025-05-24-16-59-20.gh-issue-134632.i0W2hc.rst
new file mode 100644
index 00000000000..f41c8744b8a
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-05-24-16-59-20.gh-issue-134632.i0W2hc.rst
@@ -0,0 +1,3 @@
+Fixed ``build-details.json`` generation to use ``INCLUDEPY``, in order to
+reference the ``pythonX.Y`` subdirectory of the include directory, as
+required in :pep:`739`, instead of the top-level include directory.
diff --git a/Misc/NEWS.d/next/Build/2025-05-30-11-02-30.gh-issue-134923.gBkRg4.rst b/Misc/NEWS.d/next/Build/2025-05-30-11-02-30.gh-issue-134923.gBkRg4.rst
new file mode 100644
index 00000000000..a742a6add8a
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-05-30-11-02-30.gh-issue-134923.gBkRg4.rst
@@ -0,0 +1,3 @@
+Windows builds with profile-guided optimization enabled now use
+``/GENPROFILE`` and ``/USEPROFILE`` instead of deprecated ``/LTCG:``
+options.
diff --git a/Misc/NEWS.d/next/Build/2025-06-14-10-32-11.gh-issue-135497.ajlV4F.rst b/Misc/NEWS.d/next/Build/2025-06-14-10-32-11.gh-issue-135497.ajlV4F.rst
new file mode 100644
index 00000000000..c84663b1466
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-06-14-10-32-11.gh-issue-135497.ajlV4F.rst
@@ -0,0 +1 @@
+Fix the detection of ``MAXLOGNAME`` in the ``configure.ac`` script.
diff --git a/Misc/NEWS.d/next/Build/2025-06-16-07-20-28.gh-issue-119132.fcI8s7.rst b/Misc/NEWS.d/next/Build/2025-06-16-07-20-28.gh-issue-119132.fcI8s7.rst
new file mode 100644
index 00000000000..3eb0805b9ce
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-06-16-07-20-28.gh-issue-119132.fcI8s7.rst
@@ -0,0 +1 @@
+Remove "experimental" tag from the CPython free-threading build.
diff --git a/Misc/NEWS.d/next/Build/2025-06-25-13-27-14.gh-issue-135927.iCNPQc.rst b/Misc/NEWS.d/next/Build/2025-06-25-13-27-14.gh-issue-135927.iCNPQc.rst
new file mode 100644
index 00000000000..21a2c87d344
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-06-25-13-27-14.gh-issue-135927.iCNPQc.rst
@@ -0,0 +1 @@
+Fix building with MSVC when passing option ``/std:clatest``.
diff --git a/Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst b/Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst
new file mode 100644
index 00000000000..279e588f3ad
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2023-10-18-14-36-35.gh-issue-108512.fMZLfr.rst
@@ -0,0 +1,2 @@
+Add functions :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`,
+:c:func:`PySys_GetOptionalAttr` and :c:func:`PySys_GetOptionalAttrString`.
diff --git a/Misc/NEWS.d/next/C_API/2024-12-31-15-28-14.gh-issue-50333.KxQUXa.rst b/Misc/NEWS.d/next/C_API/2024-12-31-15-28-14.gh-issue-50333.KxQUXa.rst
deleted file mode 100644
index 5b761d1d1cf..00000000000
--- a/Misc/NEWS.d/next/C_API/2024-12-31-15-28-14.gh-issue-50333.KxQUXa.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Non-tuple sequences are deprecated as argument for the ``(items)`` format
-unit in :c:func:`PyArg_ParseTuple` and other :ref:`argument parsing
-<arg-parsing>` functions if *items* contains format units which store
-a :ref:`borrowed buffer <c-arg-borrowed-buffer>` or
-a :term:`borrowed reference`.
diff --git a/Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst b/Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst
deleted file mode 100644
index d49b1735825..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Add support of nullable arguments in :c:func:`PyArg_Parse` and similar
-functions. Adding ``?`` after any format unit makes ``None`` be accepted as
-a value.
diff --git a/Misc/NEWS.d/next/C_API/2025-04-13-20-52-39.gh-issue-132470.UqBQjN.rst b/Misc/NEWS.d/next/C_API/2025-04-13-20-52-39.gh-issue-132470.UqBQjN.rst
deleted file mode 100644
index 5a03908fb91..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-04-13-20-52-39.gh-issue-132470.UqBQjN.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Creating a :class:`ctypes.CField` with a *byte_size* that does not match the actual
-type size now raises a :exc:`ValueError` instead of crashing the interpreter.
diff --git a/Misc/NEWS.d/next/C_API/2025-04-14-07-41-28.gh-issue-131185.ZCjMHD.rst b/Misc/NEWS.d/next/C_API/2025-04-14-07-41-28.gh-issue-131185.ZCjMHD.rst
new file mode 100644
index 00000000000..aa0e8bca93b
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-04-14-07-41-28.gh-issue-131185.ZCjMHD.rst
@@ -0,0 +1,2 @@
+:c:func:`PyGILState_Ensure` no longer crashes when called after interpreter
+finalization.
diff --git a/Misc/NEWS.d/next/C_API/2025-04-22-13-59-30.gh-issue-132798.asfafhs.rst b/Misc/NEWS.d/next/C_API/2025-04-22-13-59-30.gh-issue-132798.asfafhs.rst
deleted file mode 100644
index d779d5a37e7..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-04-22-13-59-30.gh-issue-132798.asfafhs.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Deprecated and undocumented functions :c:func:`!PyUnicode_AsEncodedObject`,
-:c:func:`!PyUnicode_AsDecodedObject`, :c:func:`!PyUnicode_AsEncodedUnicode`
-and :c:func:`!PyUnicode_AsDecodedUnicode` are scheduled for removal in 3.15.
diff --git a/Misc/NEWS.d/next/C_API/2025-04-25-11-39-24.gh-issue-132909.JC3n_l.rst b/Misc/NEWS.d/next/C_API/2025-04-25-11-39-24.gh-issue-132909.JC3n_l.rst
deleted file mode 100644
index 81a37d0595e..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-04-25-11-39-24.gh-issue-132909.JC3n_l.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix an overflow when handling the :ref:`K <capi-py-buildvalue-format-K>` format
-in :c:func:`Py_BuildValue`. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst b/Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst
deleted file mode 100644
index e4a33e61319..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-04-26-12-00-52.gh-issue-132987.vykZGN.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-The ``k`` and ``K`` formats in :c:func:`PyArg_Parse` now support the
-:meth:`~object.__index__` special method, like all other integer formats.
diff --git a/Misc/NEWS.d/next/C_API/2025-04-28-13-27-48.gh-issue-133079.DJL2sK.rst b/Misc/NEWS.d/next/C_API/2025-04-28-13-27-48.gh-issue-133079.DJL2sK.rst
deleted file mode 100644
index 43632452861..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-04-28-13-27-48.gh-issue-133079.DJL2sK.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-The undocumented APIs :c:macro:`!Py_C_RECURSION_LIMIT` and
-:c:member:`!PyThreadState.c_recursion_remaining`, added in 3.13, are removed
-without a deprecation period.
diff --git a/Misc/NEWS.d/next/C_API/2025-04-28-15-36-01.gh-issue-128972.8bZMIm.rst b/Misc/NEWS.d/next/C_API/2025-04-28-15-36-01.gh-issue-128972.8bZMIm.rst
deleted file mode 100644
index 4b6a6e3606f..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-04-28-15-36-01.gh-issue-128972.8bZMIm.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-For non-free-threaded builds, the memory layout of :c:struct:`PyASCIIObject`
-is reverted to match Python 3.13. (Note that the structure is not part of
-stable ABI and so its memory layout is *guaranteed* to remain stable.)
diff --git a/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst b/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst
deleted file mode 100644
index dec7c76dd95..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` function for
-determining if an object exists as a unique temporary variable on the
-interpreter's stack. This is a replacement for some cases where checking
-that :c:func:`Py_REFCNT` is one is no longer sufficient to determine if it's
-safe to modify a Python object in-place with no visible side effects.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-01-01-02-11.gh-issue-133166.Ly9Ae2.rst b/Misc/NEWS.d/next/C_API/2025-05-01-01-02-11.gh-issue-133166.Ly9Ae2.rst
deleted file mode 100644
index 976b823b441..00000000000
--- a/Misc/NEWS.d/next/C_API/2025-05-01-01-02-11.gh-issue-133166.Ly9Ae2.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix regression where :c:func:`PyType_GetModuleByDef` returns NULL without
-setting :exc:`TypeError` when a static type is passed.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-07-21-18-00.gh-issue-133610.asdfjs.rst b/Misc/NEWS.d/next/C_API/2025-05-07-21-18-00.gh-issue-133610.asdfjs.rst
new file mode 100644
index 00000000000..bdc53331f6a
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-07-21-18-00.gh-issue-133610.asdfjs.rst
@@ -0,0 +1,3 @@
+Remove deprecated functions :c:func:`!PyUnicode_AsDecodedObject`,
+:c:func:`!PyUnicode_AsDecodedUnicode`, :c:func:`!PyUnicode_AsEncodedObject`,
+and :c:func:`!PyUnicode_AsEncodedUnicode`.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-08-12-25-47.gh-issue-133644.Yb86Rm.rst b/Misc/NEWS.d/next/C_API/2025-05-08-12-25-47.gh-issue-133644.Yb86Rm.rst
new file mode 100644
index 00000000000..9569456eb76
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-08-12-25-47.gh-issue-133644.Yb86Rm.rst
@@ -0,0 +1,2 @@
+Remove deprecated alias :c:func:`!PyImport_ImportModuleNoBlock` of
+:c:func:`PyImport_ImportModule`. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-08-13-14-45.gh-issue-133644.J8_KZ2.rst b/Misc/NEWS.d/next/C_API/2025-05-08-13-14-45.gh-issue-133644.J8_KZ2.rst
new file mode 100644
index 00000000000..a9275e81112
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-08-13-14-45.gh-issue-133644.J8_KZ2.rst
@@ -0,0 +1,2 @@
+Remove deprecated Python initialization getter functions ``Py_Get*``.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-13-16-06-46.gh-issue-133968.6alWst.rst b/Misc/NEWS.d/next/C_API/2025-05-13-16-06-46.gh-issue-133968.6alWst.rst
new file mode 100644
index 00000000000..47d5a3bda39
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-13-16-06-46.gh-issue-133968.6alWst.rst
@@ -0,0 +1,4 @@
+Add :c:func:`PyUnicodeWriter_WriteASCII` function to write an ASCII string
+into a :c:type:`PyUnicodeWriter`. The function is faster than
+:c:func:`PyUnicodeWriter_WriteUTF8`, but has an undefined behavior if the
+input string contains non-ASCII characters. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst b/Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst
new file mode 100644
index 00000000000..11c7bd59a4d
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-17-14-41-21.gh-issue-134144.xVpZik.rst
@@ -0,0 +1 @@
+Fix crash when calling :c:func:`Py_EndInterpreter` with a :term:`thread state` that isn't the initial thread for the interpreter.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst b/Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst
new file mode 100644
index 00000000000..f060f09de19
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-20-17-13-51.gh-issue-134009.CpCmry.rst
@@ -0,0 +1 @@
+Expose :c:func:`PyMutex_IsLocked` as part of the public C API.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-29-16-56-23.gh-issue-134891.7eKO8U.rst b/Misc/NEWS.d/next/C_API/2025-05-29-16-56-23.gh-issue-134891.7eKO8U.rst
new file mode 100644
index 00000000000..db30d5e9a94
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-29-16-56-23.gh-issue-134891.7eKO8U.rst
@@ -0,0 +1,2 @@
+Add :c:type:`PyUnstable_Unicode_GET_CACHED_HASH` to get the cached hash of a
+string.
diff --git a/Misc/NEWS.d/next/C_API/2025-05-30-11-33-17.gh-issue-134745.GN-zk2.rst b/Misc/NEWS.d/next/C_API/2025-05-30-11-33-17.gh-issue-134745.GN-zk2.rst
new file mode 100644
index 00000000000..a85d2e90576
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-05-30-11-33-17.gh-issue-134745.GN-zk2.rst
@@ -0,0 +1,3 @@
+Change :c:func:`!PyThread_allocate_lock` implementation to ``PyMutex``.
+On Windows, :c:func:`!PyThread_acquire_lock_timed` now supports the *intr_flag*
+parameter: it can be interrupted. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/C_API/2025-06-02-13-19-22.gh-issue-134989.sDDyBN.rst b/Misc/NEWS.d/next/C_API/2025-06-02-13-19-22.gh-issue-134989.sDDyBN.rst
new file mode 100644
index 00000000000..e49f7651065
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-06-02-13-19-22.gh-issue-134989.sDDyBN.rst
@@ -0,0 +1,2 @@
+Implement :c:func:`PyObject_DelAttr` and :c:func:`PyObject_DelAttrString` as
+macros in the limited C API 3.12 and older. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/C_API/2025-06-05-11-06-07.gh-issue-134989.74p4ud.rst b/Misc/NEWS.d/next/C_API/2025-06-05-11-06-07.gh-issue-134989.74p4ud.rst
new file mode 100644
index 00000000000..844e9a66664
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-06-05-11-06-07.gh-issue-134989.74p4ud.rst
@@ -0,0 +1,3 @@
+Fix ``Py_RETURN_NONE``, ``Py_RETURN_TRUE`` and ``Py_RETURN_FALSE`` macros in
+the limited C API 3.11 and older: don't treat ``Py_None``, ``Py_True`` and
+``Py_False`` as immortal. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/C_API/2025-06-19-12-47-18.gh-issue-133157.1WA85f.rst b/Misc/NEWS.d/next/C_API/2025-06-19-12-47-18.gh-issue-133157.1WA85f.rst
new file mode 100644
index 00000000000..1b37d884e57
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-06-19-12-47-18.gh-issue-133157.1WA85f.rst
@@ -0,0 +1 @@
+Remove the private, undocumented macro :c:macro:`!_Py_NO_SANITIZE_UNDEFINED`.
diff --git a/Misc/NEWS.d/next/C_API/2025-07-01-16-22-39.gh-issue-135075.angu3J.rst b/Misc/NEWS.d/next/C_API/2025-07-01-16-22-39.gh-issue-135075.angu3J.rst
new file mode 100644
index 00000000000..88e0fa65f45
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-07-01-16-22-39.gh-issue-135075.angu3J.rst
@@ -0,0 +1,2 @@
+Make :c:func:`PyObject_SetAttr` and :c:func:`PyObject_SetAttrString` fail if
+called with ``NULL`` value and an exception set. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2022-12-29-19-10-36.gh-issue-89562.g8m8RC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2022-12-29-19-10-36.gh-issue-89562.g8m8RC.rst
deleted file mode 100644
index 41e87c0e348..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2022-12-29-19-10-36.gh-issue-89562.g8m8RC.rst
+++ /dev/null
@@ -1 +0,0 @@
-Remove ``hostflags`` member from ``PySSLContext`` struct.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2023-04-29-23-15-38.gh-issue-103997.BS3uVt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2023-04-29-23-15-38.gh-issue-103997.BS3uVt.rst
deleted file mode 100644
index 511ca8fa732..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2023-04-29-23-15-38.gh-issue-103997.BS3uVt.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-String arguments passed to "-c" are now automatically dedented as if by
-:func:`textwrap.dedent`. This allows "python -c" invocations to be indented
-in shell scripts without causing indentation errors. (Patch by Jon Crall and
-Steven Sun)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-03-06-22-33-33.gh-issue-116436.y8Thkt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-03-06-22-33-33.gh-issue-116436.y8Thkt.rst
deleted file mode 100644
index f9c3ab1f0a5..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2024-03-06-22-33-33.gh-issue-116436.y8Thkt.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improve error message when :exc:`TypeError` occurs during :meth:`dict.update`
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-03-15-15-51.gh-issue-123539.RKQS0S.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-03-15-15-51.gh-issue-123539.RKQS0S.rst
deleted file mode 100644
index 602cb9a0841..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-03-15-15-51.gh-issue-123539.RKQS0S.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Improve :exc:`SyntaxError` message for using ``import ... as``
-and ``from ... import ... as`` with not a name.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst
deleted file mode 100644
index e0b468e76a0..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-Add the :data:`sys.flags.thread_inherit_context` flag.
-
-* This flag is set to true by default on the free-threaded build
- and false otherwise. If the flag is true, starting a new thread using
- :class:`threading.Thread` will, by default, use a copy of the
- :class:`contextvars.Context` from the caller of
- :meth:`threading.Thread.start` rather than using an empty context.
-
-* Add the :option:`-X thread_inherit_context <-X>` command-line option and
- :envvar:`PYTHON_THREAD_INHERIT_CONTEXT` environment variable, which set the
- :data:`~sys.flags.thread_inherit_context` flag.
-
-* Add the ``context`` keyword parameter to :class:`~threading.Thread`. It can
- be used to explicitly pass a context value to be used by a new thread.
-
-* Make the ``_contextvars`` module built-in.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-26-23-46-43.gh-issue-69605._2Qc1w.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-26-23-46-43.gh-issue-69605._2Qc1w.rst
deleted file mode 100644
index 1f85be8bc61..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-26-23-46-43.gh-issue-69605._2Qc1w.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add module autocomplete to PyREPL.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-12-01-36-13.gh-issue-129858.M-f7Gb.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-12-01-36-13.gh-issue-129858.M-f7Gb.rst
deleted file mode 100644
index 2d2c88ba48d..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-12-01-36-13.gh-issue-129858.M-f7Gb.rst
+++ /dev/null
@@ -1 +0,0 @@
-``elif`` statements that follow an ``else`` block now have a specific error message.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-00-14-24.gh-issue-129958.Uj7lyY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-00-14-24.gh-issue-129958.Uj7lyY.rst
deleted file mode 100644
index c0fa76c89e4..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-00-14-24.gh-issue-129958.Uj7lyY.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix a bug that was allowing newlines inconsitently in format specifiers for
-single-quoted f-strings. Patch by Pablo Galindo.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-05-09-31.gh-issue-130070.C8c9gK.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-05-09-31.gh-issue-130070.C8c9gK.rst
deleted file mode 100644
index f9e135f1902..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-13-05-09-31.gh-issue-130070.C8c9gK.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed an assertion error for :func:`exec` passed a string ``source`` and a non-``None`` ``closure``. Patch by Bartosz Sławecki.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst
deleted file mode 100644
index 9539ad3d79d..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-18-11-42-58.gh-issue-130104.BOicVZ.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Three-argument :func:`pow` now try calling :meth:`~object.__rpow__` if
-necessary.
-Previously it was only called in two-argument :func:`!pow` and the binary
-power operator.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-14-13-08-20.gh-issue-127266._tyfBp.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-14-13-08-20.gh-issue-127266._tyfBp.rst
new file mode 100644
index 00000000000..b26977628de
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-14-13-08-20.gh-issue-127266._tyfBp.rst
@@ -0,0 +1,6 @@
+In the free-threaded build, avoid data races caused by updating type slots
+or type flags after the type was initially created. For those (typically
+rare) cases, use the stop-the-world mechanism. Remove the use of atomics
+when reading or writing type flags. The use of atomics is not sufficient to
+avoid races (since flags are sometimes read without a lock and without
+atomics) and are no longer required.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst
deleted file mode 100644
index 587627e979a..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-08-47-36.gh-issue-130907.rGg-ge.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-If the ``__annotations__`` of a module object are accessed while the
-module is executing, return the annotations that have been defined so far,
-without caching them.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-19-03-42.gh-issue-131507.q9fvyM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-19-03-42.gh-issue-131507.q9fvyM.rst
deleted file mode 100644
index 354a116c533..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-21-19-03-42.gh-issue-131507.q9fvyM.rst
+++ /dev/null
@@ -1 +0,0 @@
-PyREPL now supports syntax highlighing. Contributed by Łukasz Langa.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-26-04-55-25.gh-issue-114809.8rNyT7.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-26-04-55-25.gh-issue-114809.8rNyT7.rst
deleted file mode 100644
index 19d92c33bc6..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-26-04-55-25.gh-issue-114809.8rNyT7.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add support for macOS multi-arch builds with the JIT enabled
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-16-41-00.gh-issue-133379.asdjhjdf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-16-41-00.gh-issue-133379.asdjhjdf.rst
new file mode 100644
index 00000000000..cf2e1e4eaff
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-04-16-41-00.gh-issue-133379.asdjhjdf.rst
@@ -0,0 +1 @@
+Correct usage of *arguments* in error messages.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-06-13-17-10.gh-issue-131798.uMrfha.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-06-13-17-10.gh-issue-131798.uMrfha.rst
deleted file mode 100644
index 5ea5fcecc33..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-06-13-17-10.gh-issue-131798.uMrfha.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Allow the JIT to remove unicode guards after ``_BINARY_OP_SUBSCR_STR_INT``
-by setting the return type to string.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-07-13-46-57.gh-issue-128398.gJ2zIF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-07-13-46-57.gh-issue-128398.gJ2zIF.rst
deleted file mode 100644
index 792332db6ef..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-07-13-46-57.gh-issue-128398.gJ2zIF.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Improve error message when an object supporting the synchronous (resp.
-asynchronous) context manager protocol is entered using :keyword:`async
-with` (resp. :keyword:`with`) instead of :keyword:`with` (resp.
-:keyword:`async with`). Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-09-20-18.gh-issue-131798.Xp1mvN.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-09-20-18.gh-issue-131798.Xp1mvN.rst
deleted file mode 100644
index 91b7a661972..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-09-20-18.gh-issue-131798.Xp1mvN.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Allow the JIT compiler to remove some type checks for operations on lists,
-tuples, dictionaries, and sets.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-17-48-11.gh-issue-124715.xxzQoD.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-17-48-11.gh-issue-124715.xxzQoD.rst
deleted file mode 100644
index f0e33185609..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-17-48-11.gh-issue-124715.xxzQoD.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Prevents against stack overflows when calling :c:func:`Py_DECREF`. Third-party
-extension objects no longer need to use the "trashcan" mechanism, as
-protection is now built into the :c:func:`Py_DECREF` macro.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-21-20-12.gh-issue-131798.Ft9tIF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-21-20-12.gh-issue-131798.Ft9tIF.rst
deleted file mode 100644
index 90c0fb35f94..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-08-21-20-12.gh-issue-131798.Ft9tIF.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Allow the JIT to remove an extra ``_TO_BOOL_BOOL`` instruction after
-``_CONTAINS_OP_DICT`` by setting the return type to bool.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-12-37-31.gh-issue-132286.1ZdsOa.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-12-37-31.gh-issue-132286.1ZdsOa.rst
deleted file mode 100644
index 82dcbd3bc10..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-12-37-31.gh-issue-132286.1ZdsOa.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix that :attr:`type.__annotate__` was not deleted, when
-:attr:`type.__annotations__` was deleted.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-13-47-33.gh-issue-126703.kXiQHj.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-13-47-33.gh-issue-126703.kXiQHj.rst
deleted file mode 100644
index d0461e17d0f..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-13-47-33.gh-issue-126703.kXiQHj.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix possible use after free in cases where a method's definition has the same lifetime as its ``self``.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-14-05-54.gh-issue-130415.llQtUq.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-14-05-54.gh-issue-130415.llQtUq.rst
deleted file mode 100644
index 03523de79b6..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-14-05-54.gh-issue-130415.llQtUq.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Improve the JIT's ability to remove unused constant and local variable
-loads, and fix an issue where deallocating unused values could cause JIT
-code to crash or behave incorrectly.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-20-49-04.gh-issue-132284.TxTNka.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-20-49-04.gh-issue-132284.TxTNka.rst
deleted file mode 100644
index b63a75f1e7e..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-20-49-04.gh-issue-132284.TxTNka.rst
+++ /dev/null
@@ -1 +0,0 @@
-Don't wrap base ``PyCFunction`` slots on class creation if not overridden.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-21-51-37.gh-issue-132261.gL8thm.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-21-51-37.gh-issue-132261.gL8thm.rst
deleted file mode 100644
index 0e58cf7f957..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-09-21-51-37.gh-issue-132261.gL8thm.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-The internal storage for annotations and annotate functions on classes now
-uses different keys in the class dictionary. This eliminates various edge
-cases where access to the ``__annotate__`` and ``__annotations__``
-attributes would behave unpredictably.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-10-29-45.gh-issue-127682.X0HoGz.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-10-29-45.gh-issue-127682.X0HoGz.rst
deleted file mode 100644
index b87750eb516..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-10-10-29-45.gh-issue-127682.X0HoGz.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-No longer call ``__iter__`` twice when creating and executing a generator expression.
-Creating a generator expression from a non-interable will raise only when the
-generator expression is executed.
-This brings the behavior of generator expressions in line with other generators.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-18-46-37.gh-issue-132386.pMBFTe.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-18-46-37.gh-issue-132386.pMBFTe.rst
deleted file mode 100644
index 65ba7fc182b..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-18-46-37.gh-issue-132386.pMBFTe.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix crash when passing a dict subclass as the ``globals`` parameter to
-:func:`exec`.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-22-01-07.gh-issue-131798.TTu_xH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-22-01-07.gh-issue-131798.TTu_xH.rst
deleted file mode 100644
index 4b23034fc3e..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-11-22-01-07.gh-issue-131798.TTu_xH.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Split ``CALL_TYPE_1`` into several uops allowing the JIT to remove some of
-them.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-12-19-41-16.gh-issue-131798.JkSocg.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-12-19-41-16.gh-issue-131798.JkSocg.rst
deleted file mode 100644
index 5a9c0cde35f..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-12-19-41-16.gh-issue-131798.JkSocg.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Use ``sym_new_type`` instead of ``sym_new_not_null`` for _BUILD_LIST,
-_BUILD_SET, _BUILD_MAP
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-10-34-27.gh-issue-131927.otp80n.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-10-34-27.gh-issue-131927.otp80n.rst
deleted file mode 100644
index 9aa940a10da..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-10-34-27.gh-issue-131927.otp80n.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Compiler warnings originating from the same module and line number are now
-only emitted once, matching the behaviour of warnings emitted from user
-code. This can also be configured with :mod:`warnings` filters.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-17-18-01.gh-issue-124476.fvGfQ7.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-17-18-01.gh-issue-124476.fvGfQ7.rst
deleted file mode 100644
index be0ecee95de..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-13-17-18-01.gh-issue-124476.fvGfQ7.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix decoding from the locale encoding in the C.UTF-8 locale.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-15-10-09-49.gh-issue-132508.zVe3iI.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-15-10-09-49.gh-issue-132508.zVe3iI.rst
deleted file mode 100644
index 1f4fe1d5f4e..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-15-10-09-49.gh-issue-132508.zVe3iI.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Uses tagged integers on the evaluation stack to represent the instruction
-offsets when reraising an exception. This avoids the need to box the integer
-which could fail in low memory conditions.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-11-40-13.gh-issue-100239.9RxIxY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-11-40-13.gh-issue-100239.9RxIxY.rst
deleted file mode 100644
index 87ff6836b82..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-11-40-13.gh-issue-100239.9RxIxY.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add specialisation for ``BINARY_OP/SUBSCR`` on list and slice.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-16-20-03.gh-issue-132639.zRVYU3.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-16-20-03.gh-issue-132639.zRVYU3.rst
deleted file mode 100644
index 8d5446c8f9a..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-17-16-20-03.gh-issue-132639.zRVYU3.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Added :c:func:`PyLong_AsNativeBytes`, :c:func:`PyLong_FromNativeBytes` and
-:c:func:`PyLong_FromUnsignedNativeBytes` to the limited C API.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst
new file mode 100644
index 00000000000..aadaf2169fd
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst
@@ -0,0 +1 @@
+Automatically constant evaluate bytecode operations marked as pure in the JIT optimizer.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-17-16-46.gh-issue-132542.7T_TY_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-17-16-46.gh-issue-132542.7T_TY_.rst
new file mode 100644
index 00000000000..c69ce5efded
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-17-16-46.gh-issue-132542.7T_TY_.rst
@@ -0,0 +1,2 @@
+Update :attr:`Thread.native_id <threading.Thread.native_id>` after
+:manpage:`fork(2)` to ensure accuracy. Patch by Noam Cohen.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-18-07-34.gh-issue-132737.9mW1il.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-18-07-34.gh-issue-132737.9mW1il.rst
deleted file mode 100644
index 0f010917825..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-18-07-34.gh-issue-132737.9mW1il.rst
+++ /dev/null
@@ -1 +0,0 @@
-Support profiling code that requires ``__main__``, such as :mod:`pickle`.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-22-59-24.gh-issue-132449.xjdw4p.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-22-59-24.gh-issue-132449.xjdw4p.rst
deleted file mode 100644
index 05603abe64d..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-22-59-24.gh-issue-132449.xjdw4p.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Syntax errors that look like misspellings of Python keywords now provide a
-helpful fix suggestion for the typo. Contributed by Pablo Galindo Salgado.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-20-10-37-39.gh-issue-132744.ArrCp8.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-20-10-37-39.gh-issue-132744.ArrCp8.rst
deleted file mode 100644
index bcd72568c52..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-20-10-37-39.gh-issue-132744.ArrCp8.rst
+++ /dev/null
@@ -1 +0,0 @@
-Certain calls now check for runaway recursion and respect the system recursion limit.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-07-39-59.gh-issue-132747.L-cnej.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-07-39-59.gh-issue-132747.L-cnej.rst
deleted file mode 100644
index c6d45b09f64..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-07-39-59.gh-issue-132747.L-cnej.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix a crash when calling :meth:`~object.__get__` of a :term:`method` with a
-:const:`None` second argument.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-09-22-15.gh-issue-132479.CCe2sE.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-09-22-15.gh-issue-132479.CCe2sE.rst
deleted file mode 100644
index 851bb4fb70e..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-21-09-22-15.gh-issue-132479.CCe2sE.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix compiler crash in certain circumstances where multiple module-level
-annotations include comprehensions and other nested scopes.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-15-37-05.gh-issue-132661.XE_A42.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-15-37-05.gh-issue-132661.XE_A42.rst
deleted file mode 100644
index 666e8de943b..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-15-37-05.gh-issue-132661.XE_A42.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Implement :pep:`750` (Template Strings). Add new syntax for t-strings and
-implement new internal :class:`!string.templatelib.Template` and
-:class:`!string.templatelib.Interpolation` types.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-16-38-43.gh-issue-132713.mBWTSZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-16-38-43.gh-issue-132713.mBWTSZ.rst
deleted file mode 100644
index 877b42374a3..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-16-38-43.gh-issue-132713.mBWTSZ.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix ``repr(list)`` race condition: hold a strong reference to the item while
-calling ``repr(item)``. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-19-00-03.gh-issue-131591.CdEqBr.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-19-00-03.gh-issue-131591.CdEqBr.rst
deleted file mode 100644
index e237649e592..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-22-19-00-03.gh-issue-131591.CdEqBr.rst
+++ /dev/null
@@ -1 +0,0 @@
-Reset any :pep:`768` remote debugging pending call in children after :func:`os.fork` calls.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-11-34-39.gh-issue-132825._yv0uL.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-11-34-39.gh-issue-132825._yv0uL.rst
deleted file mode 100644
index d751837c44a..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-11-34-39.gh-issue-132825._yv0uL.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Enhance unhashable key/element error messages for :class:`dict` and
-:class:`set`. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-42-55.gh-issue-131798.wVQ1Gt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-42-55.gh-issue-131798.wVQ1Gt.rst
deleted file mode 100644
index 71d081f7062..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-42-55.gh-issue-131798.wVQ1Gt.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Split ``CALL_STR_1`` into several uops allowing the JIT to remove some of
-them. Patch by Tomas Roun
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-54-17.gh-issue-131798.XYlp09.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-54-17.gh-issue-131798.XYlp09.rst
deleted file mode 100644
index d7db951433e..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-23-20-54-17.gh-issue-131798.XYlp09.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Split ``CALL_TUPLE_1`` into several uops allowing the JIT to remove some of
-them. Patch by Tomas Roun
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-25-14-56-45.gh-issue-131798.NpcKub.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-25-14-56-45.gh-issue-131798.NpcKub.rst
deleted file mode 100644
index 8214870284e..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-25-14-56-45.gh-issue-131798.NpcKub.rst
+++ /dev/null
@@ -1 +0,0 @@
-Allow the JIT to remove int guards after ``_CALL_LEN`` by setting the return type to int. Patch by Diego Russo
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst
deleted file mode 100644
index 2792ce35c15..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Speed up startup with the ``-S`` argument by importing the
-private ``_io`` module instead of :mod:`io`. This fixes a performance
-regression introduced earlier in Python 3.14 development and restores performance
-to the level of Python 3.13.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-13-57-13.gh-issue-131798.Gt8CGE.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-13-57-13.gh-issue-131798.Gt8CGE.rst
deleted file mode 100644
index f4049240f7d..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-13-57-13.gh-issue-131798.Gt8CGE.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Propagate the return type of ``_BINARY_OP_SUBSCR_TUPLE_INT`` in JIT. Patch
-by Tomas Roun
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-01.gh-issue-131798.XiOgw5.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-01.gh-issue-131798.XiOgw5.rst
new file mode 100644
index 00000000000..45ab1bea6b1
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-01.gh-issue-131798.XiOgw5.rst
@@ -0,0 +1,2 @@
+Narrow the return type and constant-evaluate ``CALL_ISINSTANCE`` for a
+subset of known values in the JIT. Patch by Tomas Roun
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-47.gh-issue-132942.aEEZvZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-47.gh-issue-132942.aEEZvZ.rst
deleted file mode 100644
index 9b7cf551618..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-17-50-47.gh-issue-132942.aEEZvZ.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix two races in the type lookup cache. This affected the free-threaded
-build and could cause crashes (apparently quite difficult to trigger).
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-18-43-31.gh-issue-131798.FsIypo.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-18-43-31.gh-issue-131798.FsIypo.rst
deleted file mode 100644
index a252d2b69fc..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-18-43-31.gh-issue-131798.FsIypo.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Use ``sym_new_type`` instead of ``sym_new_not_null`` for _BUILD_STRING,
-_BUILD_SET
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-13-09-20.gh-issue-133194.25_G5c.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-13-09-20.gh-issue-133194.25_G5c.rst
deleted file mode 100644
index fa597db114c..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-13-09-20.gh-issue-133194.25_G5c.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:func:`ast.parse` will no longer parse new :pep:`758` syntax with older
-*feature_version* passed.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-14-13-01.gh-issue-132554.GqQaUp.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-14-13-01.gh-issue-132554.GqQaUp.rst
new file mode 100644
index 00000000000..bfe2d633309
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-30-14-13-01.gh-issue-132554.GqQaUp.rst
@@ -0,0 +1,4 @@
+Change iteration to use "virtual iterators" for sequences. Instead of
+creating an iterator, a tagged integer representing the next index is pushed
+to the stack above the iterable. For non-sequence iterators, ``NULL`` is
+pushed.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-01-11-06-29.gh-issue-133197.BHjfh4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-01-11-06-29.gh-issue-133197.BHjfh4.rst
deleted file mode 100644
index 009bc376053..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-01-11-06-29.gh-issue-133197.BHjfh4.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Improve :exc:`SyntaxError` error messages for incompatible string / bytes
-prefixes.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst
deleted file mode 100644
index 64ae40a79a4..00000000000
--- a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Workaround NaN's "canonicalization" in :c:func:`PyFloat_Pack4`
-and :c:func:`PyFloat_Unpack4` on RISC-V.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-13-36-01.gh-issue-131798.U4_QEJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-13-36-01.gh-issue-131798.U4_QEJ.rst
new file mode 100644
index 00000000000..ca8eb999ae5
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-13-36-01.gh-issue-131798.U4_QEJ.rst
@@ -0,0 +1,2 @@
+Split ``CALL_ISINSTANCE`` into several uops, allowing the JIT to remove some
+of them.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-22-31-53.gh-issue-131798.fQ0ato.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-22-31-53.gh-issue-131798.fQ0ato.rst
new file mode 100644
index 00000000000..f322d43b30a
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-22-31-53.gh-issue-131798.fQ0ato.rst
@@ -0,0 +1,2 @@
+Allow the JIT to remove int guards after ``_GET_LEN`` by setting the return
+type to int.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-06-15-01-41.gh-issue-133516.RqWVf2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-06-15-01-41.gh-issue-133516.RqWVf2.rst
new file mode 100644
index 00000000000..b93ba11f932
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-06-15-01-41.gh-issue-133516.RqWVf2.rst
@@ -0,0 +1,2 @@
+Raise :exc:`ValueError` when constants ``True``, ``False`` or ``None`` are
+used as an identifier after NFKC normalization.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-23-26-53.gh-issue-133541.bHIC55.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-23-26-53.gh-issue-133541.bHIC55.rst
new file mode 100644
index 00000000000..4f4cd847fa5
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-23-26-53.gh-issue-133541.bHIC55.rst
@@ -0,0 +1,2 @@
+Inconsistent indentation in user input crashed the new REPL when syntax
+highlighting was active. This is now fixed.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-08-13-48-02.gh-issue-132762.tKbygC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-08-13-48-02.gh-issue-132762.tKbygC.rst
new file mode 100644
index 00000000000..80b830ebd78
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-08-13-48-02.gh-issue-132762.tKbygC.rst
@@ -0,0 +1 @@
+:meth:`~dict.fromkeys` no longer loops forever when adding a small set of keys to a large base dict. Patch by Angela Liss.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-09-18-11-21.gh-issue-133778.pWEV3t.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-09-18-11-21.gh-issue-133778.pWEV3t.rst
new file mode 100644
index 00000000000..6eb6881213c
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-09-18-11-21.gh-issue-133778.pWEV3t.rst
@@ -0,0 +1,2 @@
+Fix bug where assigning to the :attr:`~type.__annotations__` attributes of
+classes defined under ``from __future__ import annotations`` had no effect.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst
new file mode 100644
index 00000000000..05bf6103314
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst
@@ -0,0 +1 @@
+Fix hashtable in dict can be bigger than intended in some situations.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-13-40-42.gh-issue-133886.ryBAyo.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-13-40-42.gh-issue-133886.ryBAyo.rst
new file mode 100644
index 00000000000..fd1020f05d6
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-13-40-42.gh-issue-133886.ryBAyo.rst
@@ -0,0 +1,2 @@
+Fix :func:`sys.remote_exec` for non-ASCII paths in non-UTF-8 locales and
+non-UTF-8 paths in UTF-8 locales.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-15-11-38-16.gh-issue-133999.uBZ8uS.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-15-11-38-16.gh-issue-133999.uBZ8uS.rst
new file mode 100644
index 00000000000..7d9c49688c3
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-15-11-38-16.gh-issue-133999.uBZ8uS.rst
@@ -0,0 +1,2 @@
+Fix :exc:`SyntaxError` regression in :keyword:`except` parsing after
+:gh:`123440`.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-09-06-38.gh-issue-134036.st2e-B.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-09-06-38.gh-issue-134036.st2e-B.rst
new file mode 100644
index 00000000000..176aab1c93a
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-09-06-38.gh-issue-134036.st2e-B.rst
@@ -0,0 +1,2 @@
+Improve :exc:`SyntaxError` message when using invalid :keyword:`raise`
+statements.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-17-25-52.gh-issue-134100.5-FbLK.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-17-25-52.gh-issue-134100.5-FbLK.rst
new file mode 100644
index 00000000000..d672347f9ad
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-17-25-52.gh-issue-134100.5-FbLK.rst
@@ -0,0 +1,2 @@
+Fix a use-after-free bug that occurs when an imported module isn't
+in :data:`sys.modules` after its initial import. Patch by Nico-Posada.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-20-59-12.gh-issue-134119.w8expI.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-20-59-12.gh-issue-134119.w8expI.rst
new file mode 100644
index 00000000000..754e8166285
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-16-20-59-12.gh-issue-134119.w8expI.rst
@@ -0,0 +1,2 @@
+Fix crash when calling :func:`next` on an exhausted template string iterator.
+Patch by Jelle Zijlstra.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-17-20-44-51.gh-issue-134158.ewLNLp.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-17-20-44-51.gh-issue-134158.ewLNLp.rst
new file mode 100644
index 00000000000..7b8bab739c3
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-17-20-44-51.gh-issue-134158.ewLNLp.rst
@@ -0,0 +1 @@
+Fix coloring of double braces in f-strings and t-strings in the :term:`REPL`.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-14-33-23.gh-issue-69605.ZMO49F.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-14-33-23.gh-issue-69605.ZMO49F.rst
new file mode 100644
index 00000000000..7b7275fee69
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-18-14-33-23.gh-issue-69605.ZMO49F.rst
@@ -0,0 +1,2 @@
+When auto-completing an import in the :term:`REPL`, finding no candidates
+now issues no suggestion, rather than suggestions from the current namespace.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-15-15-58.gh-issue-131798.PCP71j.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-15-15-58.gh-issue-131798.PCP71j.rst
new file mode 100644
index 00000000000..c816a0afad4
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-15-15-58.gh-issue-131798.PCP71j.rst
@@ -0,0 +1 @@
+Split ``CALL_LIST_APPEND`` into several uops. Patch by Diego Russo.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst
new file mode 100644
index 00000000000..98d770cf054
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-19-20-52-53.gh-issue-134268.HPKX1e.rst
@@ -0,0 +1,2 @@
+Add ``_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW`` and use it to further
+optimize ``CALL_ISINSTANCE``.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-13-58-18.gh-issue-131798.hG8xBw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-13-58-18.gh-issue-131798.hG8xBw.rst
new file mode 100644
index 00000000000..c490ecf1560
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-13-58-18.gh-issue-131798.hG8xBw.rst
@@ -0,0 +1 @@
+Improve the JIT's ability to narrow unknown classes to constant values.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-14-41-50.gh-issue-128066.qzzGfv.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-14-41-50.gh-issue-128066.qzzGfv.rst
new file mode 100644
index 00000000000..f7819027685
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-14-41-50.gh-issue-128066.qzzGfv.rst
@@ -0,0 +1,3 @@
+Fixes an edge case where PyREPL improperly threw an error when Python is
+invoked on a read only filesystem while trying to write history file
+entries.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-23-32-11.gh-issue-131798.G9ZQZw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-23-32-11.gh-issue-131798.G9ZQZw.rst
new file mode 100644
index 00000000000..8eb8782037a
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-20-23-32-11.gh-issue-131798.G9ZQZw.rst
@@ -0,0 +1,2 @@
+Improve the JIT's ability to optimize away cached class attribute and method
+loads.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-13-57-26.gh-issue-131798.QwS5Bb.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-13-57-26.gh-issue-131798.QwS5Bb.rst
new file mode 100644
index 00000000000..f873bbfb4dc
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-13-57-26.gh-issue-131798.QwS5Bb.rst
@@ -0,0 +1 @@
+JIT: replace ``_LOAD_SMALL_INT`` with ``_LOAD_CONST_INLINE_BORROW``
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-15-14-32.gh-issue-130397.aG6EON.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-15-14-32.gh-issue-130397.aG6EON.rst
new file mode 100644
index 00000000000..34a2f4d1278
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-15-14-32.gh-issue-130397.aG6EON.rst
@@ -0,0 +1,3 @@
+Remove special-casing for C stack depth limits for WASI. Due to
+WebAssembly's built-in stack protection this does not pose a security
+concern.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-18-02-56.gh-issue-127960.W3J_2X.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-18-02-56.gh-issue-127960.W3J_2X.rst
new file mode 100644
index 00000000000..730d8a5af51
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-21-18-02-56.gh-issue-127960.W3J_2X.rst
@@ -0,0 +1,3 @@
+PyREPL interactive shell no longer starts with ``__package__`` and
+``__file__`` global names set to ``_pyrepl`` package internals. Contributed
+by Yuichiro Tachibana.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-14-48-19.gh-issue-134381.2BXhth.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-14-48-19.gh-issue-134381.2BXhth.rst
new file mode 100644
index 00000000000..aa8900296ae
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-14-48-19.gh-issue-134381.2BXhth.rst
@@ -0,0 +1 @@
+Fix :exc:`RuntimeError` when using a not-started :class:`threading.Thread` after calling :func:`os.fork`
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-17-49-39.gh-issue-131798.U6ZmFm.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-17-49-39.gh-issue-131798.U6ZmFm.rst
new file mode 100644
index 00000000000..fdb6a2fe0b3
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-22-17-49-39.gh-issue-131798.U6ZmFm.rst
@@ -0,0 +1 @@
+Optimize ``_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW``.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-23-14-54-07.gh-issue-134584.y-WDjf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-23-14-54-07.gh-issue-134584.y-WDjf.rst
new file mode 100644
index 00000000000..5f9e1553ae7
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-23-14-54-07.gh-issue-134584.y-WDjf.rst
@@ -0,0 +1 @@
+Add a reference count elimination pass to the JIT compiler. Patch by Ken Jin.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-25-19-32-15.gh-issue-131798.f5h8aI.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-25-19-32-15.gh-issue-131798.f5h8aI.rst
new file mode 100644
index 00000000000..6ecbfb8d9cf
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-25-19-32-15.gh-issue-131798.f5h8aI.rst
@@ -0,0 +1 @@
+Make the JIT optimizer understand that slicing a string/list/tuple returns the same type.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-15-55-50.gh-issue-133912.-xAguL.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-15-55-50.gh-issue-133912.-xAguL.rst
new file mode 100644
index 00000000000..2118f3d0c35
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-15-55-50.gh-issue-133912.-xAguL.rst
@@ -0,0 +1,2 @@
+Fix the C API function ``PyObject_GenericSetDict`` to handle extension
+classes with inline values.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-18-59-54.gh-issue-134679.FWPBu6.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-18-59-54.gh-issue-134679.FWPBu6.rst
new file mode 100644
index 00000000000..22f1282fea1
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-18-59-54.gh-issue-134679.FWPBu6.rst
@@ -0,0 +1,2 @@
+Fix crash in the :term:`free threading` build's QSBR code that could occur
+when changing an object's ``__dict__`` attribute.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-21-34.gh-issue-131798.b32zkl.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-21-34.gh-issue-131798.b32zkl.rst
new file mode 100644
index 00000000000..ed4b31bd7be
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-21-34.gh-issue-131798.b32zkl.rst
@@ -0,0 +1 @@
+Allow the JIT to remove unnecessary ``_ITER_CHECK_TUPLE`` ops.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-29-00.gh-issue-132617.EmUfQQ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-29-00.gh-issue-132617.EmUfQQ.rst
new file mode 100644
index 00000000000..53aef541e64
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-27-20-29-00.gh-issue-132617.EmUfQQ.rst
@@ -0,0 +1,3 @@
+Fix :meth:`dict.update` modification check that could incorrectly raise a
+"dict mutated during update" error when a different dictionary was modified
+that happens to share the same underlying keys object.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-23-58-50.gh-issue-117852.BO9g7z.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-23-58-50.gh-issue-117852.BO9g7z.rst
new file mode 100644
index 00000000000..fc71cd21a36
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-28-23-58-50.gh-issue-117852.BO9g7z.rst
@@ -0,0 +1 @@
+Fix argument checking of :meth:`~agen.athrow`.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-15-56-19.gh-issue-134908.3a7PxM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-15-56-19.gh-issue-134908.3a7PxM.rst
new file mode 100644
index 00000000000..3178f0aaf88
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-15-56-19.gh-issue-134908.3a7PxM.rst
@@ -0,0 +1 @@
+Fix crash when iterating over lines in a text file on the :term:`free threaded <free threading>` build.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-18-09-54.gh-issue-134889.Ic9UM-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-18-09-54.gh-issue-134889.Ic9UM-.rst
new file mode 100644
index 00000000000..3b86134bf16
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-30-18-09-54.gh-issue-134889.Ic9UM-.rst
@@ -0,0 +1,2 @@
+Fix handling of a few opcodes that leave operands on the stack when
+optimizing ``LOAD_FAST``.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst
new file mode 100644
index 00000000000..1da76561469
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-10-26-46.gh-issue-134876.8mBGJI.rst
@@ -0,0 +1,2 @@
+Add support to :pep:`768` remote debugging for Linux kernels which don't
+have CONFIG_CROSS_MEMORY_ATTACH configured.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-19-24-54.gh-issue-134280.NDVbzY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-19-24-54.gh-issue-134280.NDVbzY.rst
new file mode 100644
index 00000000000..f8227216909
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-31-19-24-54.gh-issue-134280.NDVbzY.rst
@@ -0,0 +1,2 @@
+Disable constant folding for ``~`` with a boolean argument.
+This moves the deprecation warning from compile time to runtime.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-13-57-40.gh-issue-116738.ycJsL8.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-13-57-40.gh-issue-116738.ycJsL8.rst
new file mode 100644
index 00000000000..506eefdb21a
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-13-57-40.gh-issue-116738.ycJsL8.rst
@@ -0,0 +1 @@
+Make methods in :mod:`heapq` thread-safe on the :term:`free threaded <free threading>` build.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-20-13-37.gh-issue-131798.JQRFvR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-20-13-37.gh-issue-131798.JQRFvR.rst
new file mode 100644
index 00000000000..0e68c793e5e
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-02-20-13-37.gh-issue-131798.JQRFvR.rst
@@ -0,0 +1 @@
+Optimize ``_CHECK_METHOD_VERSION`` into ``_CHECK_FUNCTION_VERSION_INLINE`` in JIT-compiled code.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-03-21-06-22.gh-issue-133136.Usnvri.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-03-21-06-22.gh-issue-133136.Usnvri.rst
new file mode 100644
index 00000000000..a9501c13c95
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-03-21-06-22.gh-issue-133136.Usnvri.rst
@@ -0,0 +1,2 @@
+Limit excess memory usage in the :term:`free threading` build when a
+large dictionary or list is resized and accessed by multiple threads.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-05-21-58-30.gh-issue-131798.nt5Ab7.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-05-21-58-30.gh-issue-131798.nt5Ab7.rst
new file mode 100644
index 00000000000..e4b5f610353
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-05-21-58-30.gh-issue-131798.nt5Ab7.rst
@@ -0,0 +1,2 @@
+Optimize away ``_CALL_TYPE_1`` in the JIT when the return type is known.
+Patch by Tomas Roun
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-01-09-44.gh-issue-131798.1SuxO9.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-01-09-44.gh-issue-131798.1SuxO9.rst
new file mode 100644
index 00000000000..a3775262306
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-01-09-44.gh-issue-131798.1SuxO9.rst
@@ -0,0 +1 @@
+Optimize ``_UNARY_INVERT`` in JIT-compiled code.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-19-17-22.gh-issue-131798.XoV8Eb.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-19-17-22.gh-issue-131798.XoV8Eb.rst
new file mode 100644
index 00000000000..6a9d9c683f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-06-19-17-22.gh-issue-131798.XoV8Eb.rst
@@ -0,0 +1 @@
+Optimize ``_UNARY_NEGATIVE`` in JIT-compiled code.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-08-14-24-29.gh-issue-131798.qfw91T.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-08-14-24-29.gh-issue-131798.qfw91T.rst
new file mode 100644
index 00000000000..7965169d46e
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-08-14-24-29.gh-issue-131798.qfw91T.rst
@@ -0,0 +1 @@
+Optimize _CALL_LEN in the JIT when the length is known. Patch by Tomas Roun
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-09-23-57-37.gh-issue-130077.MHknDB.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-09-23-57-37.gh-issue-130077.MHknDB.rst
new file mode 100644
index 00000000000..a7d02426b6f
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-09-23-57-37.gh-issue-130077.MHknDB.rst
@@ -0,0 +1,2 @@
+Properly raise custom syntax errors when incorrect syntax containing names
+that are prefixes of soft keywords is encountered. Patch by Pablo Galindo.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-11-15-08-10.gh-issue-127319.OVGFSZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-11-15-08-10.gh-issue-127319.OVGFSZ.rst
new file mode 100644
index 00000000000..d90153c9684
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-11-15-08-10.gh-issue-127319.OVGFSZ.rst
@@ -0,0 +1,3 @@
+Set the ``allow_reuse_port`` class variable to ``False`` on the XMLRPC,
+logging, and HTTP servers. This matches the behavior in prior Python
+releases, which is to not allow port reuse.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-11-19-52.gh-issue-135422.F6yQi6.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-11-19-52.gh-issue-135422.F6yQi6.rst
new file mode 100644
index 00000000000..bb0f178f91a
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-11-19-52.gh-issue-135422.F6yQi6.rst
@@ -0,0 +1 @@
+Fix regression in :exc:`SyntaxError` messages after :gh:`134036`.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-18-12-42.gh-issue-135371.R_YUtR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-18-12-42.gh-issue-135371.R_YUtR.rst
new file mode 100644
index 00000000000..9f2e825e57b
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-12-18-12-42.gh-issue-135371.R_YUtR.rst
@@ -0,0 +1,4 @@
+Fixed :mod:`asyncio` debugging tools to properly display internal coroutine
+call stacks alongside external task dependencies. The ``python -m asyncio
+ps`` and ``python -m asyncio pstree`` commands now show complete execution
+context. Patch by Pablo Galindo.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-13-16-05-24.gh-issue-135474.67nOl3.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-13-16-05-24.gh-issue-135474.67nOl3.rst
new file mode 100644
index 00000000000..716d9b78748
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-13-16-05-24.gh-issue-135474.67nOl3.rst
@@ -0,0 +1 @@
+Specialize integer operations only on compact integers. This is a CPython internal change.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-14-01-01-14.gh-issue-135496.ER0Me3.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-14-01-01-14.gh-issue-135496.ER0Me3.rst
new file mode 100644
index 00000000000..03b1f4590c5
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-14-01-01-14.gh-issue-135496.ER0Me3.rst
@@ -0,0 +1 @@
+Fix typo in the f-string conversion type error ("exclamanation" -> "exclamation").
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-02-31-42.gh-issue-135543.6b0HOF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-02-31-42.gh-issue-135543.6b0HOF.rst
new file mode 100644
index 00000000000..6efe2a47bac
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-02-31-42.gh-issue-135543.6b0HOF.rst
@@ -0,0 +1,2 @@
+Emit ``sys.remote_exec`` audit event when :func:`sys.remote_exec` is called
+and migrate ``remote_debugger_script`` to ``cpython.remote_debugger_script``.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-03-56-15.gh-issue-135551.hRTQO-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-03-56-15.gh-issue-135551.hRTQO-.rst
new file mode 100644
index 00000000000..22dda2a3e97
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-16-03-56-15.gh-issue-135551.hRTQO-.rst
@@ -0,0 +1 @@
+Sorting randomly ordered lists will often run a bit faster, thanks to a new scheme for picking minimum run lengths from Stefan Pochmann, which arranges for the merge tree to be as evenly balanced as is possible.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-12-50-48.gh-issue-135608.PnHckD.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-12-50-48.gh-issue-135608.PnHckD.rst
new file mode 100644
index 00000000000..a65a0c85fa6
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-12-50-48.gh-issue-135608.PnHckD.rst
@@ -0,0 +1 @@
+Fix a crash in the JIT involving attributes of modules.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-22-34-58.gh-issue-135607.ucsLVu.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-22-34-58.gh-issue-135607.ucsLVu.rst
new file mode 100644
index 00000000000..859259a9ace
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-22-34-58.gh-issue-135607.ucsLVu.rst
@@ -0,0 +1,2 @@
+Fix potential :mod:`weakref` races in an object's destructor on the :term:`free threaded <free
+threading>` build.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-12-19-13.gh-issue-135379.TCvGpj.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-12-19-13.gh-issue-135379.TCvGpj.rst
new file mode 100644
index 00000000000..089d00c77da
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-12-19-13.gh-issue-135379.TCvGpj.rst
@@ -0,0 +1,3 @@
+Changes specialization of ``BINARY_OP`` for ints to only specialize for
+"compact" ints. This streamlines the fast path at the cost of fewer
+specializations when very large integers are used.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-16-45-36.gh-issue-135106.cpl6Aq.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-16-45-36.gh-issue-135106.cpl6Aq.rst
new file mode 100644
index 00000000000..b6e953a7719
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-18-16-45-36.gh-issue-135106.cpl6Aq.rst
@@ -0,0 +1,2 @@
+Restrict the trashcan mechanism to GC'ed objects and untrack them while in
+the trashcan to prevent the GC and trashcan mechanisms conflicting.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-20-14-50-44.gh-issue-134584.3CJdAI.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-20-14-50-44.gh-issue-134584.3CJdAI.rst
new file mode 100644
index 00000000000..715ac7dc925
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-20-14-50-44.gh-issue-134584.3CJdAI.rst
@@ -0,0 +1 @@
+Specialize :opcode:`POP_TOP` in the JIT compiler by specializing for reference lifetime and type. This will also enable easier top of stack caching in the JIT compiler.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-18-08-32.gh-issue-135871.50C528.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-18-08-32.gh-issue-135871.50C528.rst
new file mode 100644
index 00000000000..ce29ddecefe
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-23-18-08-32.gh-issue-135871.50C528.rst
@@ -0,0 +1,2 @@
+Non-blocking mutex lock attempts now return immediately when the lock is busy
+instead of briefly spinning in the :term:`free threading` build.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-06-41-47.gh-issue-129958.EaJuS0.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-06-41-47.gh-issue-129958.EaJuS0.rst
new file mode 100644
index 00000000000..70b3e99425d
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-06-41-47.gh-issue-129958.EaJuS0.rst
@@ -0,0 +1,2 @@
+Differentiate between t-strings and f-strings in syntax error for newlines
+in format specifiers of single-quoted interpolated strings.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-16-46-34.gh-issue-135904.78xfon.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-16-46-34.gh-issue-135904.78xfon.rst
new file mode 100644
index 00000000000..ecbd8fda9a5
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-24-16-46-34.gh-issue-135904.78xfon.rst
@@ -0,0 +1,2 @@
+Perform more aggressive control-flow optimizations on the machine code
+templates emitted by the experimental JIT compiler.
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-26-15-25-51.gh-issue-78465.MbDN8X.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-26-15-25-51.gh-issue-78465.MbDN8X.rst
new file mode 100644
index 00000000000..99734d63c5d
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-26-15-25-51.gh-issue-78465.MbDN8X.rst
@@ -0,0 +1,2 @@
+Fix error message for ``cls.__new__(cls, ...)`` where ``cls`` is not
+instantiable builtin or extension type (with ``tp_new`` set to ``NULL``).
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst
new file mode 100644
index 00000000000..5a622bab8b8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-02-15-18-41.gh-issue-136203.Y934sC.rst
@@ -0,0 +1,2 @@
+Improve :exc:`TypeError` error message, when richcomparing two
+:class:`types.MappingProxyType` objects.
diff --git a/Misc/NEWS.d/next/Documentation/2021-09-15-13-07-25.bpo-45210.RtGk7i.rst b/Misc/NEWS.d/next/Documentation/2021-09-15-13-07-25.bpo-45210.RtGk7i.rst
new file mode 100644
index 00000000000..ce3eba154ba
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2021-09-15-13-07-25.bpo-45210.RtGk7i.rst
@@ -0,0 +1,2 @@
+Document that error indicator may be set in tp_dealloc, and how to avoid
+clobbering it.
diff --git a/Misc/NEWS.d/next/Documentation/2024-10-08-10-44-14.gh-issue-125142.HVlHrs.rst b/Misc/NEWS.d/next/Documentation/2024-10-08-10-44-14.gh-issue-125142.HVlHrs.rst
deleted file mode 100644
index 2340013f5de..00000000000
--- a/Misc/NEWS.d/next/Documentation/2024-10-08-10-44-14.gh-issue-125142.HVlHrs.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-As part of the builtin help intro text, show the keyboard shortcuts for the
-new, non-basic REPL (F1, F2, and F3).
diff --git a/Misc/NEWS.d/next/Documentation/2025-06-10-17-02-06.gh-issue-135171.quHvts.rst b/Misc/NEWS.d/next/Documentation/2025-06-10-17-02-06.gh-issue-135171.quHvts.rst
new file mode 100644
index 00000000000..129ff74189b
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2025-06-10-17-02-06.gh-issue-135171.quHvts.rst
@@ -0,0 +1,2 @@
+Document that the :term:`iterator` for the leftmost :keyword:`!for` clause
+in the generator expression is created immediately.
diff --git a/Misc/NEWS.d/next/IDLE/2024-11-08-18-07-13.gh-issue-112936.1Q2RcP.rst b/Misc/NEWS.d/next/IDLE/2024-11-08-18-07-13.gh-issue-112936.1Q2RcP.rst
deleted file mode 100644
index 8536e38b54a..00000000000
--- a/Misc/NEWS.d/next/IDLE/2024-11-08-18-07-13.gh-issue-112936.1Q2RcP.rst
+++ /dev/null
@@ -1 +0,0 @@
-fix IDLE: no Shell menu item in single-process mode.
diff --git a/Misc/NEWS.d/next/Library/2017-12-30-18-21-00.bpo-28494.Dt_Wks.rst b/Misc/NEWS.d/next/Library/2017-12-30-18-21-00.bpo-28494.Dt_Wks.rst
new file mode 100644
index 00000000000..0c518983770
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-12-30-18-21-00.bpo-28494.Dt_Wks.rst
@@ -0,0 +1 @@
+Improve Zip file validation false positive rate in :func:`zipfile.is_zipfile`.
diff --git a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst b/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst
deleted file mode 100644
index d3c8d1b747e..00000000000
--- a/Misc/NEWS.d/next/Library/2019-09-10-09-28-52.gh-issue-75223.VyAJS9.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Deprecate undotted extensions in :meth:`mimetypes.MimeTypes.add_type`.
-Patch by Hugo van Kemenade.
diff --git a/Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst b/Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst
new file mode 100644
index 00000000000..bcafa64d263
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-12-18-12-46-20.bpo-45959.vPlr3P.rst
@@ -0,0 +1 @@
+:mod:`pprint` can now pretty-print dict views.
diff --git a/Misc/NEWS.d/next/Library/2022-07-24-20-56-32.gh-issue-69426.unccw7.rst b/Misc/NEWS.d/next/Library/2022-07-24-20-56-32.gh-issue-69426.unccw7.rst
new file mode 100644
index 00000000000..d8c081390d0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-07-24-20-56-32.gh-issue-69426.unccw7.rst
@@ -0,0 +1,3 @@
+Fix :class:`html.parser.HTMLParser` to not unescape character entities in
+attribute values if they are followed by an ASCII alphanumeric or an equals
+sign.
diff --git a/Misc/NEWS.d/next/Library/2023-02-13-21-41-34.gh-issue-86155.ppIGSC.rst b/Misc/NEWS.d/next/Library/2023-02-13-21-41-34.gh-issue-86155.ppIGSC.rst
new file mode 100644
index 00000000000..bb85481b229
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-02-13-21-41-34.gh-issue-86155.ppIGSC.rst
@@ -0,0 +1,2 @@
+:meth:`html.parser.HTMLParser.close` no longer loses data when the
+``<script>`` tag is not closed. Patch by Waylan Limberg.
diff --git a/Misc/NEWS.d/next/Library/2023-02-13-21-56-38.gh-issue-62824.CBZzX3.rst b/Misc/NEWS.d/next/Library/2023-02-13-21-56-38.gh-issue-62824.CBZzX3.rst
new file mode 100644
index 00000000000..1fe4e47c9ec
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-02-13-21-56-38.gh-issue-62824.CBZzX3.rst
@@ -0,0 +1 @@
+Fix aliases for ``iso8859_8`` encoding. Patch by Dave Goncalves.
diff --git a/Misc/NEWS.d/next/Library/2023-12-29-09-44-41.gh-issue-113539.YDkv9O.rst b/Misc/NEWS.d/next/Library/2023-12-29-09-44-41.gh-issue-113539.YDkv9O.rst
deleted file mode 100644
index c2c3a2d17c1..00000000000
--- a/Misc/NEWS.d/next/Library/2023-12-29-09-44-41.gh-issue-113539.YDkv9O.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-:mod:`webbrowser`: Names in the :envvar:`BROWSER` environment variable can now
-refer to already registered web browsers, instead of always generating a new
-browser command.
-
-This makes it possible to set :envvar:`BROWSER` to the value of one of the
-supported browsers on macOS.
diff --git a/Misc/NEWS.d/next/Library/2024-06-06-17-49-07.gh-issue-120170.DUxhmT.rst b/Misc/NEWS.d/next/Library/2024-06-06-17-49-07.gh-issue-120170.DUxhmT.rst
new file mode 100644
index 00000000000..ce7d874aa20
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-06-17-49-07.gh-issue-120170.DUxhmT.rst
@@ -0,0 +1,3 @@
+Fix an issue in the :mod:`!_pickle` extension module in which importing
+:mod:`multiprocessing` could change how pickle identifies which module an
+object belongs to, potentially breaking the unpickling of those objects.
diff --git a/Misc/NEWS.d/next/Library/2024-06-07-15-03-54.gh-issue-120220.NNxrr_.rst b/Misc/NEWS.d/next/Library/2024-06-07-15-03-54.gh-issue-120220.NNxrr_.rst
deleted file mode 100644
index b60b9430728..00000000000
--- a/Misc/NEWS.d/next/Library/2024-06-07-15-03-54.gh-issue-120220.NNxrr_.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Deprecate the :class:`!tkinter.Variable` methods :meth:`!trace_variable`,
-:meth:`!trace_vdelete` and :meth:`!trace_vinfo`. Methods :meth:`!trace_add`,
-:meth:`!trace_remove` and :meth:`!trace_info` can be used instead.
diff --git a/Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst b/Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst
deleted file mode 100644
index 86c19bf660d..00000000000
--- a/Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Expose :func:`decimal.IEEEContext` to support creation of contexts
-corresponding to the IEEE 754 (2008) decimal interchange formats.
-Patch by Sergey B Kirpichev.
diff --git a/Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst b/Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst
deleted file mode 100644
index 2b0678f31e8..00000000000
--- a/Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-:func:`hashlib.file_digest` now raises :exc:`BlockingIOError` when no data
-is available during non-blocking I/O. Before, it added spurious null bytes
-to the digest.
diff --git a/Misc/NEWS.d/next/Library/2024-10-22-16-21-55.gh-issue-125843.2ttzYo.rst b/Misc/NEWS.d/next/Library/2024-10-22-16-21-55.gh-issue-125843.2ttzYo.rst
new file mode 100644
index 00000000000..ec8f3a75006
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-22-16-21-55.gh-issue-125843.2ttzYo.rst
@@ -0,0 +1,2 @@
+If possible, indicate which :mod:`curses` C function or macro is responsible
+for raising a :exc:`curses.error` exception. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst b/Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst
new file mode 100644
index 00000000000..09ebee4d41b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-10-28-06-54-22.gh-issue-125028.GEY8Ws.rst
@@ -0,0 +1 @@
+:data:`functools.Placeholder` cannot be passed to :func:`functools.partial` as a keyword argument.
diff --git a/Misc/NEWS.d/next/Library/2024-11-14-21-17-48.gh-issue-126838.Yr5vKF.rst b/Misc/NEWS.d/next/Library/2024-11-14-21-17-48.gh-issue-126838.Yr5vKF.rst
deleted file mode 100644
index 857cc359229..00000000000
--- a/Misc/NEWS.d/next/Library/2024-11-14-21-17-48.gh-issue-126838.Yr5vKF.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Fix issue where :func:`urllib.request.url2pathname` mishandled file URLs with
-authorities. If an authority is present and resolves to ``localhost``, it is
-now discarded. If an authority is present but *doesn't* resolve to
-``localhost``, then on Windows a UNC path is returned (as before), and on
-other platforms a :exc:`urllib.error.URLError` is now raised.
diff --git a/Misc/NEWS.d/next/Library/2024-11-25-10-22-08.gh-issue-126883.MAEF7g.rst b/Misc/NEWS.d/next/Library/2024-11-25-10-22-08.gh-issue-126883.MAEF7g.rst
new file mode 100644
index 00000000000..5e3fa39acf1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-11-25-10-22-08.gh-issue-126883.MAEF7g.rst
@@ -0,0 +1,3 @@
+Add check that timezone fields are in range for
+:meth:`datetime.datetime.fromisoformat` and
+:meth:`datetime.time.fromisoformat`. Patch by Semyon Moroz.
diff --git a/Misc/NEWS.d/next/Library/2024-11-29-13-06-52.gh-issue-127385.PErcyB.rst b/Misc/NEWS.d/next/Library/2024-11-29-13-06-52.gh-issue-127385.PErcyB.rst
deleted file mode 100644
index 3770d586ba4..00000000000
--- a/Misc/NEWS.d/next/Library/2024-11-29-13-06-52.gh-issue-127385.PErcyB.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add the ``F_DUPFD_QUERY`` constant to the :mod:`fcntl` module.
diff --git a/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst b/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst
deleted file mode 100644
index c4d2938a500..00000000000
--- a/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Add support for printing the C stack trace on systems that support it via
-:func:`faulthandler.dump_c_stack` or via the *c_stack* argument in
-:func:`faulthandler.enable`.
diff --git a/Misc/NEWS.d/next/Library/2025-01-21-11-48-19.gh-issue-129027.w0vxzZ.rst b/Misc/NEWS.d/next/Library/2025-01-21-11-48-19.gh-issue-129027.w0vxzZ.rst
deleted file mode 100644
index d2abf53bc0f..00000000000
--- a/Misc/NEWS.d/next/Library/2025-01-21-11-48-19.gh-issue-129027.w0vxzZ.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Raise :exc:`DeprecationWarning` for :func:`sys._clear_type_cache`. This function was deprecated in Python 3.13
-but it didn't raise a runtime warning.
diff --git a/Misc/NEWS.d/next/Library/2025-02-06-11-23-51.gh-issue-129719.Of6rvb.rst b/Misc/NEWS.d/next/Library/2025-02-06-11-23-51.gh-issue-129719.Of6rvb.rst
deleted file mode 100644
index 5e7a3e2f589..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-06-11-23-51.gh-issue-129719.Of6rvb.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix missing :data:`!socket.CAN_RAW_ERR_FILTER` constant in the socket module on Linux systems. It was missing since Python 3.11.
diff --git a/Misc/NEWS.d/next/Library/2025-02-11-10-22-11.gh-issue-128384.jyWEkA.rst b/Misc/NEWS.d/next/Library/2025-02-11-10-22-11.gh-issue-128384.jyWEkA.rst
deleted file mode 100644
index 011e25d8926..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-11-10-22-11.gh-issue-128384.jyWEkA.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Make :class:`warnings.catch_warnings` use a context variable for holding
-the warning filtering state if the :data:`sys.flags.context_aware_warnings`
-flag is set to true. This makes using the context manager thread-safe in
-multi-threaded programs. The flag is true by default in free-threaded builds
-and is otherwise false. The value of the flag can be overridden by the
-the :option:`-X context_aware_warnings <-X>` command-line option or by the
-:envvar:`PYTHON_CONTEXT_AWARE_WARNINGS` environment variable.
diff --git a/Misc/NEWS.d/next/Library/2025-02-12-16-37-34.gh-issue-101410.0GInct.rst b/Misc/NEWS.d/next/Library/2025-02-12-16-37-34.gh-issue-101410.0GInct.rst
deleted file mode 100644
index d91731c3dd9..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-12-16-37-34.gh-issue-101410.0GInct.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added more detailed messages for domain errors in the :mod:`math` module.
diff --git a/Misc/NEWS.d/next/Library/2025-02-16-06-25-01.gh-issue-130167.kUg7Rc.rst b/Misc/NEWS.d/next/Library/2025-02-16-06-25-01.gh-issue-130167.kUg7Rc.rst
deleted file mode 100644
index 3d397084fc1..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-16-06-25-01.gh-issue-130167.kUg7Rc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improve speed of :func:`difflib.IS_LINE_JUNK`. Patch by Semyon Moroz.
diff --git a/Misc/NEWS.d/next/Library/2025-02-21-15-46-43.gh-issue-130402.Rwu_KK.rst b/Misc/NEWS.d/next/Library/2025-02-21-15-46-43.gh-issue-130402.Rwu_KK.rst
deleted file mode 100644
index b91d429f3f7..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-21-15-46-43.gh-issue-130402.Rwu_KK.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Joining running daemon threads during interpreter shutdown
-now raises :exc:`PythonFinalizationError`.
diff --git a/Misc/NEWS.d/next/Library/2025-02-22-13-07-06.gh-issue-130317.tnxd0I.rst b/Misc/NEWS.d/next/Library/2025-02-22-13-07-06.gh-issue-130317.tnxd0I.rst
deleted file mode 100644
index ab69f8806bf..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-22-13-07-06.gh-issue-130317.tnxd0I.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Fix :c:func:`PyFloat_Pack2` and :c:func:`PyFloat_Unpack2` for NaN's with
-payload. This corrects round-trip for :func:`struct.unpack` and
-:func:`struct.pack` in case of the IEEE 754 binary16 "half precision" type.
-Patch by Sergey B Kirpichev.
diff --git a/Misc/NEWS.d/next/Library/2025-02-24-12-22-51.gh-issue-130482.p2DrrL.rst b/Misc/NEWS.d/next/Library/2025-02-24-12-22-51.gh-issue-130482.p2DrrL.rst
deleted file mode 100644
index 38a1e81770f..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-24-12-22-51.gh-issue-130482.p2DrrL.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Add ability to specify name for :class:`!tkinter.OptionMenu` and
-:class:`!tkinter.ttk.OptionMenu`.
diff --git a/Misc/NEWS.d/next/Library/2025-02-27-14-25-01.gh-issue-130631.dmZcZM.rst b/Misc/NEWS.d/next/Library/2025-02-27-14-25-01.gh-issue-130631.dmZcZM.rst
deleted file mode 100644
index c9dc9ba8787..00000000000
--- a/Misc/NEWS.d/next/Library/2025-02-27-14-25-01.gh-issue-130631.dmZcZM.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-:func:`!http.cookiejar.join_header_words` is now more similar to the original
-Perl version. It now quotes the same set of characters and always quote
-values that end with ``"\n"``.
diff --git a/Misc/NEWS.d/next/Library/2025-03-01-12-37-08.gh-issue-129098.eJ2-6L.rst b/Misc/NEWS.d/next/Library/2025-03-01-12-37-08.gh-issue-129098.eJ2-6L.rst
deleted file mode 100644
index 8ac9082a7b4..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-01-12-37-08.gh-issue-129098.eJ2-6L.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix REPL traceback reporting when using :func:`compile` with an inexisting
-file. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-03-07-17-47-32.gh-issue-130941.7_GvhW.rst b/Misc/NEWS.d/next/Library/2025-03-07-17-47-32.gh-issue-130941.7_GvhW.rst
deleted file mode 100644
index 4f0cda8d03e..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-07-17-47-32.gh-issue-130941.7_GvhW.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix :class:`configparser.ConfigParser` parsing empty interpolation with
-``allow_no_value`` set to ``True``.
diff --git a/Misc/NEWS.d/next/Library/2025-03-09-03-13-41.gh-issue-130999.tBRBVB.rst b/Misc/NEWS.d/next/Library/2025-03-09-03-13-41.gh-issue-130999.tBRBVB.rst
new file mode 100644
index 00000000000..157522f9aab
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-03-09-03-13-41.gh-issue-130999.tBRBVB.rst
@@ -0,0 +1,2 @@
+Avoid exiting the new REPL and offer suggestions even if there are non-string
+candidates when errors occur.
diff --git a/Misc/NEWS.d/next/Library/2025-03-09-10-37-00.gh-issue-89157.qg3r138.rst b/Misc/NEWS.d/next/Library/2025-03-09-10-37-00.gh-issue-89157.qg3r138.rst
deleted file mode 100644
index 0031721f89b..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-09-10-37-00.gh-issue-89157.qg3r138.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Make the pure Python implementation of :func:`datetime.date.fromisoformat`,
-only accept ASCII strings for consistency with the C implementation.
diff --git a/Misc/NEWS.d/next/Library/2025-03-11-05-24-14.gh-issue-130664.g0yNMm.rst b/Misc/NEWS.d/next/Library/2025-03-11-05-24-14.gh-issue-130664.g0yNMm.rst
new file mode 100644
index 00000000000..dbe783a2a99
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-03-11-05-24-14.gh-issue-130664.g0yNMm.rst
@@ -0,0 +1,4 @@
+Handle corner-case for :class:`~fractions.Fraction`'s formatting: treat
+zero-padding (preceding the width field by a zero (``'0'``) character) as an
+equivalent to a fill character of ``'0'`` with an alignment type of ``'='``,
+just as in case of :class:`float`'s.
diff --git a/Misc/NEWS.d/next/Library/2025-03-11-21-08-46.gh-issue-131127.whcVdY.rst b/Misc/NEWS.d/next/Library/2025-03-11-21-08-46.gh-issue-131127.whcVdY.rst
deleted file mode 100644
index e8dfbf5f423..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-11-21-08-46.gh-issue-131127.whcVdY.rst
+++ /dev/null
@@ -1 +0,0 @@
-Systems using LibreSSL now successfully build.
diff --git a/Misc/NEWS.d/next/Library/2025-03-13-20-48-58.gh-issue-123471.cM4w4f.rst b/Misc/NEWS.d/next/Library/2025-03-13-20-48-58.gh-issue-123471.cM4w4f.rst
new file mode 100644
index 00000000000..cfc783900de
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-03-13-20-48-58.gh-issue-123471.cM4w4f.rst
@@ -0,0 +1 @@
+Make concurrent iterations over :class:`itertools.cycle` safe under free-threading.
diff --git a/Misc/NEWS.d/next/Library/2025-03-14-14-18-49.gh-issue-123471.sduBKk.rst b/Misc/NEWS.d/next/Library/2025-03-14-14-18-49.gh-issue-123471.sduBKk.rst
deleted file mode 100644
index b3829c72e5c..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-14-14-18-49.gh-issue-123471.sduBKk.rst
+++ /dev/null
@@ -1 +0,0 @@
-Make concurrent iterations over :class:`itertools.repeat` safe under free-threading.
diff --git a/Misc/NEWS.d/next/Library/2025-03-16-17-40-00.gh-issue-85702.qudq12.rst b/Misc/NEWS.d/next/Library/2025-03-16-17-40-00.gh-issue-85702.qudq12.rst
deleted file mode 100644
index 25b549d5f08..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-16-17-40-00.gh-issue-85702.qudq12.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-If ``zoneinfo._common.load_tzdata`` is given a package without a resource a
-``ZoneInfoNotFoundError`` is raised rather than a :exc:`IsADirectoryError`.
diff --git a/Misc/NEWS.d/next/Library/2025-03-17-23-07-57.gh-issue-100926.B8gcbz.rst b/Misc/NEWS.d/next/Library/2025-03-17-23-07-57.gh-issue-100926.B8gcbz.rst
deleted file mode 100644
index 6a71415fbd8..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-17-23-07-57.gh-issue-100926.B8gcbz.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Move :func:`ctypes.POINTER` types cache from a global internal cache
-(``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
-attribute of the corresponding :mod:`ctypes` types.
-This will stop the cache from growing without limits in some situations.
diff --git a/Misc/NEWS.d/next/Library/2025-03-21-17-34-27.gh-issue-131524.Vj1pO_.rst b/Misc/NEWS.d/next/Library/2025-03-21-17-34-27.gh-issue-131524.Vj1pO_.rst
deleted file mode 100644
index 28926d06ca4..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-21-17-34-27.gh-issue-131524.Vj1pO_.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Add help message to :mod:`platform` command-line interface. Contributed by
-Harry Lees.
diff --git a/Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst b/Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst
deleted file mode 100644
index a7b086131cf..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-21-21-24-36.gh-issue-131434.BPkyyh.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improve error reporting for incorrect format in :func:`time.strptime`.
diff --git a/Misc/NEWS.d/next/Library/2025-03-23-11-33-09.gh-issue-131423.bQlcEb.rst b/Misc/NEWS.d/next/Library/2025-03-23-11-33-09.gh-issue-131423.bQlcEb.rst
deleted file mode 100644
index 20ff9ee2834..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-23-11-33-09.gh-issue-131423.bQlcEb.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:mod:`ssl` can show descriptions for errors added in OpenSSL 3.4.1.
-Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst b/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst
deleted file mode 100644
index 89f71ca113a..00000000000
--- a/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst
+++ /dev/null
@@ -1 +0,0 @@
-Make :func:`functools.lru_cache` call the cached function unlocked to allow concurrency.
diff --git a/Misc/NEWS.d/next/Library/2025-04-01-18-24-58.gh-issue-85302.7knfUf.rst b/Misc/NEWS.d/next/Library/2025-04-01-18-24-58.gh-issue-85302.7knfUf.rst
deleted file mode 100644
index aa5c4dfdc72..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-01-18-24-58.gh-issue-85302.7knfUf.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add support for :data:`~socket.BTPROTO_SCO` in sockets on FreeBSD.
diff --git a/Misc/NEWS.d/next/Library/2025-04-03-00-56-48.gh-issue-118761.Vb0S1B.rst b/Misc/NEWS.d/next/Library/2025-04-03-00-56-48.gh-issue-118761.Vb0S1B.rst
deleted file mode 100644
index 6b4b3ed7526..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-03-00-56-48.gh-issue-118761.Vb0S1B.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Improve import times by up to 33x for the :mod:`shlex` module,
-and improve the performance of :func:`shlex.quote` by up to 12x.
-Patch by Adam Turner.
diff --git a/Misc/NEWS.d/next/Library/2025-04-03-17-19-42.gh-issue-119605.c7QXAA.rst b/Misc/NEWS.d/next/Library/2025-04-03-17-19-42.gh-issue-119605.c7QXAA.rst
deleted file mode 100644
index cf8065afc2e..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-03-17-19-42.gh-issue-119605.c7QXAA.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Respect ``follow_wrapped`` for :meth:`!__init__` and :meth:`!__new__` methods
-when getting the class signature for a class with :func:`inspect.signature`.
-Preserve class signature after wrapping with :func:`warnings.deprecated`.
-Patch by Xuehai Pan.
diff --git a/Misc/NEWS.d/next/Library/2025-04-03-20-28-54.gh-issue-132054.c1nlOx.rst b/Misc/NEWS.d/next/Library/2025-04-03-20-28-54.gh-issue-132054.c1nlOx.rst
deleted file mode 100644
index b9602c0b2ca..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-03-20-28-54.gh-issue-132054.c1nlOx.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-The ``application/yaml`` mime type (:rfc:`9512`) is now supported
-by :mod:`mimetypes`. Patch by Sasha "Nelie" Chernykh and Hugo van Kemenade.
diff --git a/Misc/NEWS.d/next/Library/2025-04-05-02-22-49.gh-issue-132106.XMjhQJ.rst b/Misc/NEWS.d/next/Library/2025-04-05-02-22-49.gh-issue-132106.XMjhQJ.rst
deleted file mode 100644
index 376f986adca..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-05-02-22-49.gh-issue-132106.XMjhQJ.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:class:`logging.handlers.QueueListener` now implements the context
-manager protocol, allowing it to be used in a :keyword:`with` statement.
diff --git a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst
deleted file mode 100644
index d3761759772..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Prevent exceptions that evaluate as falsey (namely, when their ``__bool__`` method returns ``False`` or their ``__len__`` method returns 0)
-from being ignored by :class:`concurrent.futures.ProcessPoolExecutor` and :class:`concurrent.futures.ThreadPoolExecutor`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-05-16-05-34.gh-issue-131952.HX6gCX.rst b/Misc/NEWS.d/next/Library/2025-04-05-16-05-34.gh-issue-131952.HX6gCX.rst
deleted file mode 100644
index 4679abf105d..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-05-16-05-34.gh-issue-131952.HX6gCX.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add color output to the :program:`json` CLI. Patch by Tomas Roun.
diff --git a/Misc/NEWS.d/next/Library/2025-04-06-14-34-29.gh-issue-130664.JF2r-U.rst b/Misc/NEWS.d/next/Library/2025-04-06-14-34-29.gh-issue-130664.JF2r-U.rst
deleted file mode 100644
index 294a7e031b2..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-06-14-34-29.gh-issue-130664.JF2r-U.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Support the ``'_'`` digit separator in formatting of the integral part of
-:class:`~decimal.Decimal`'s. Patch by Sergey B Kirpichev.
diff --git a/Misc/NEWS.d/next/Library/2025-04-06-21-17-14.gh-issue-132064.ktPwDM.rst b/Misc/NEWS.d/next/Library/2025-04-06-21-17-14.gh-issue-132064.ktPwDM.rst
deleted file mode 100644
index 2559b711a41..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-06-21-17-14.gh-issue-132064.ktPwDM.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-:func:`annotationlib.get_annotations` now uses the ``__annotate__``
-attribute if it is present, even if ``__annotations__`` is not present.
-Additionally, the function now raises a :py:exc:`TypeError` if it is passed
-an object that does not have any annotatins.
diff --git a/Misc/NEWS.d/next/Library/2025-04-07-06-41-54.gh-issue-131884.ym9BJN.rst b/Misc/NEWS.d/next/Library/2025-04-07-06-41-54.gh-issue-131884.ym9BJN.rst
new file mode 100644
index 00000000000..d9e2eae02dc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-07-06-41-54.gh-issue-131884.ym9BJN.rst
@@ -0,0 +1 @@
+Fix formatting issues in :func:`json.dump` when both *indent* and *skipkeys* are used.
diff --git a/Misc/NEWS.d/next/Library/2025-04-08-01-55-11.gh-issue-132250.APBFCw.rst b/Misc/NEWS.d/next/Library/2025-04-08-01-55-11.gh-issue-132250.APBFCw.rst
deleted file mode 100644
index b49528867c2..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-08-01-55-11.gh-issue-132250.APBFCw.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed the :exc:`SystemError` in :mod:`cProfile` when locating the actual C function of a method raises an exception.
diff --git a/Misc/NEWS.d/next/Library/2025-04-08-10-45-22.gh-issue-129463.b1qEP3.rst b/Misc/NEWS.d/next/Library/2025-04-08-10-45-22.gh-issue-129463.b1qEP3.rst
deleted file mode 100644
index 9c5796f11e2..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-08-10-45-22.gh-issue-129463.b1qEP3.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Comparison of :class:`annotationlib.ForwardRef` objects no longer uses the
-internal ``__code__`` and ``__ast_node__`` attributes, which are used as
-caches.
diff --git a/Misc/NEWS.d/next/Library/2025-04-08-14-50-39.gh-issue-127495.Q0V0bS.rst b/Misc/NEWS.d/next/Library/2025-04-08-14-50-39.gh-issue-127495.Q0V0bS.rst
deleted file mode 100644
index 135d0f65117..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-08-14-50-39.gh-issue-127495.Q0V0bS.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-In PyREPL, append a new entry to the ``PYTHON_HISTORY`` file *after* every
-statement. This should preserve command-line history after interpreter is
-terminated. Patch by Sergey B Kirpichev.
diff --git a/Misc/NEWS.d/next/Library/2025-04-09-19-07-22.gh-issue-130645.cVfE1X.rst b/Misc/NEWS.d/next/Library/2025-04-09-19-07-22.gh-issue-130645.cVfE1X.rst
deleted file mode 100644
index 8c1b366da37..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-09-19-07-22.gh-issue-130645.cVfE1X.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add colour to :mod:`argparse` help output. Patch by Hugo van Kemenade.
diff --git a/Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst b/Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst
deleted file mode 100644
index 8e8b99c2be3..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-10-13-06-42.gh-issue-132308.1js5SI.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-A :class:`traceback.TracebackException` now correctly renders the ``__context__``
-and ``__cause__`` attributes from :ref:`falsey <truth>` :class:`Exception`,
-and the ``exceptions`` attribute from falsey :class:`ExceptionGroup`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-10-21-43-04.gh-issue-125866.EZ9X8D.rst b/Misc/NEWS.d/next/Library/2025-04-10-21-43-04.gh-issue-125866.EZ9X8D.rst
deleted file mode 100644
index 0d60a16a177..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-10-21-43-04.gh-issue-125866.EZ9X8D.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Add optional *add_scheme* argument to :func:`urllib.request.pathname2url`; when
-set to true, a complete URL is returned. Likewise add optional *require_scheme*
-argument to :func:`~urllib.request.url2pathname`; when set to true, a complete
-URL is accepted.
diff --git a/Misc/NEWS.d/next/Library/2025-04-11-12-41-47.gh-issue-132385.86HoA7.rst b/Misc/NEWS.d/next/Library/2025-04-11-12-41-47.gh-issue-132385.86HoA7.rst
deleted file mode 100644
index 9aa2da452d2..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-11-12-41-47.gh-issue-132385.86HoA7.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix instance error suggestions trigger potential exceptions
-in :meth:`object.__getattr__` in :mod:`traceback`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-11-21-48-49.gh-issue-132417.uILGdS.rst b/Misc/NEWS.d/next/Library/2025-04-11-21-48-49.gh-issue-132417.uILGdS.rst
deleted file mode 100644
index 878651c8a0a..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-11-21-48-49.gh-issue-132417.uILGdS.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Fix a ``NULL`` pointer dereference when a C function called using
-:mod:`ctypes` with ``restype`` :class:`~ctypes.py_object` returns
-``NULL``.
diff --git a/Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst b/Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst
deleted file mode 100644
index b6d58a29f9b..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:meth:`QueueListener.start <logging.handlers.QueueListener.start>` now
-raises a :exc:`RuntimeError` if the listener is already started.
diff --git a/Misc/NEWS.d/next/Library/2025-04-12-12-59-51.gh-issue-132429.OEIdlW.rst b/Misc/NEWS.d/next/Library/2025-04-12-12-59-51.gh-issue-132429.OEIdlW.rst
deleted file mode 100644
index 95be9ec922b..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-12-12-59-51.gh-issue-132429.OEIdlW.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Fix support of Bluetooth sockets on NetBSD and DragonFly BSD. Add support
-for *cid* and *bdaddr_type* in the BTPROTO_L2CAP address on FreeBSD. Return
-*cid* in ``getsockname()`` for BTPROTO_L2CAP if it is not zero.
diff --git a/Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst b/Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst
deleted file mode 100644
index be036524bc3..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-12-19-42-51.gh-issue-131913.twOx7K.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add a shortcut function :func:`multiprocessing.Process.interrupt` alongside the existing :func:`multiprocessing.Process.terminate` and :func:`multiprocessing.Process.kill` for an improved control over child process termination.
diff --git a/Misc/NEWS.d/next/Library/2025-04-13-21-22-37.gh-issue-132491.jJfT4e.rst b/Misc/NEWS.d/next/Library/2025-04-13-21-22-37.gh-issue-132491.jJfT4e.rst
deleted file mode 100644
index d29fc9b79b7..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-13-21-22-37.gh-issue-132491.jJfT4e.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Rename ``annotationlib.value_to_string`` to
-:func:`annotationlib.type_repr` and provide better handling for function
-objects.
diff --git a/Misc/NEWS.d/next/Library/2025-04-13-21-35-50.gh-issue-132493.5SAQJn.rst b/Misc/NEWS.d/next/Library/2025-04-13-21-35-50.gh-issue-132493.5SAQJn.rst
deleted file mode 100644
index bda09e2356d..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-13-21-35-50.gh-issue-132493.5SAQJn.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Support creation of :class:`typing.Protocol` classes with annotations that
-cannot be resolved at class creation time.
diff --git a/Misc/NEWS.d/next/Library/2025-04-14-20-38-43.gh-issue-132099.0l0LlK.rst b/Misc/NEWS.d/next/Library/2025-04-14-20-38-43.gh-issue-132099.0l0LlK.rst
deleted file mode 100644
index de69873605f..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-14-20-38-43.gh-issue-132099.0l0LlK.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-The Bluetooth socket with the :data:`~socket.BTPROTO_HCI` protocol on Linux
-now accepts an address in the format of an integer ``device_id``, not only a
-tuple ``(device_id,)``.
diff --git a/Misc/NEWS.d/next/Library/2025-04-14-23-00-00.gh-issue-132527.kTi8T7.rst b/Misc/NEWS.d/next/Library/2025-04-14-23-00-00.gh-issue-132527.kTi8T7.rst
deleted file mode 100644
index 997cc2b784f..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-14-23-00-00.gh-issue-132527.kTi8T7.rst
+++ /dev/null
@@ -1 +0,0 @@
-Include the valid typecode 'w' in the error message when an invalid typecode is passed to :class:`array.array`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-15-03-20-00.gh-issue-132536.i5Pvof.rst b/Misc/NEWS.d/next/Library/2025-04-15-03-20-00.gh-issue-132536.i5Pvof.rst
deleted file mode 100644
index 72f63547306..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-15-03-20-00.gh-issue-132536.i5Pvof.rst
+++ /dev/null
@@ -1 +0,0 @@
-Do not disable :monitoring-event:`PY_THROW` event in :mod:`bdb` because it can't be disabled.
diff --git a/Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst b/Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst
deleted file mode 100644
index a7f76270364..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-15-08-39-14.gh-issue-132493.V0gLkU.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-:class:`typing.Protocol` now uses :func:`annotationlib.get_annotations` when
-checking whether or not an instance implements the protocol with
-:func:`isinstance`. This enables support for ``isinstance`` checks against
-classes with deferred annotations.
diff --git a/Misc/NEWS.d/next/Library/2025-04-16-01-41-34.gh-issue-121468.rxgE1z.rst b/Misc/NEWS.d/next/Library/2025-04-16-01-41-34.gh-issue-121468.rxgE1z.rst
deleted file mode 100644
index a46db6b73b7..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-16-01-41-34.gh-issue-121468.rxgE1z.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add :func:`pdb.set_trace_async` function to support :keyword:`await` statements in :mod:`pdb`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-16-11-44-56.gh-issue-132561.ekkDPE.rst b/Misc/NEWS.d/next/Library/2025-04-16-11-44-56.gh-issue-132561.ekkDPE.rst
deleted file mode 100644
index 862db02b819..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-16-11-44-56.gh-issue-132561.ekkDPE.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix the public ``locked`` method of ``multiprocessing.SemLock`` class.
-Also adding 2 tests for the derivated :class:`multiprocessing.Lock` and :class:`multiprocessing.RLock` classes.
diff --git a/Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst b/Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst
new file mode 100644
index 00000000000..c8743d49ca0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst
@@ -0,0 +1 @@
+Make :class:`io.BytesIO` safe in :term:`free-threaded <free threading>` build.
diff --git a/Misc/NEWS.d/next/Library/2025-04-18-10-00-09.gh-issue-132578.ruNvF-.rst b/Misc/NEWS.d/next/Library/2025-04-18-10-00-09.gh-issue-132578.ruNvF-.rst
deleted file mode 100644
index 56faa5d1235..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-18-10-00-09.gh-issue-132578.ruNvF-.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Rename the ``threading.Thread._handle`` field to avoid shadowing methods
-defined on subclasses of ``threading.Thread``.
diff --git a/Misc/NEWS.d/next/Library/2025-04-18-14-34-43.gh-issue-132673.0sliCv.rst b/Misc/NEWS.d/next/Library/2025-04-18-14-34-43.gh-issue-132673.0sliCv.rst
deleted file mode 100644
index 4d5a26caf0e..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-18-14-34-43.gh-issue-132673.0sliCv.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix :exc:`AssertionError` raised on :class:`ctypes.Structure` with
-``_align_ = 0`` and ``_fields_ = []``.
diff --git a/Misc/NEWS.d/next/Library/2025-04-19-19-58-27.gh-issue-132734.S6F9Cs.rst b/Misc/NEWS.d/next/Library/2025-04-19-19-58-27.gh-issue-132734.S6F9Cs.rst
deleted file mode 100644
index e0825227d6e..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-19-19-58-27.gh-issue-132734.S6F9Cs.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add new constants for Bluetooth :mod:`sockets <socket>`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst b/Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst
new file mode 100644
index 00000000000..a99669a1bc0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-21-00-58-04.gh-issue-127081.3DCl92.rst
@@ -0,0 +1,2 @@
+Fix libc thread safety issues with :mod:`pwd` by locking access to
+``getpwall``.
diff --git a/Misc/NEWS.d/next/Library/2025-04-21-01-03-15.gh-issue-127081.WXRliX.rst b/Misc/NEWS.d/next/Library/2025-04-21-01-03-15.gh-issue-127081.WXRliX.rst
new file mode 100644
index 00000000000..63fed60ced0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-21-01-03-15.gh-issue-127081.WXRliX.rst
@@ -0,0 +1,2 @@
+Fix libc thread safety issues with :mod:`os` by replacing ``getlogin`` with
+``getlogin_r`` re-entrant version.
diff --git a/Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst b/Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst
new file mode 100644
index 00000000000..30643673bf9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst
@@ -0,0 +1,2 @@
+Fix libc thread safety issues with :mod:`dbm` by performing stateful
+operations in critical sections.
diff --git a/Misc/NEWS.d/next/Library/2025-04-22-19-45-46.gh-issue-132451.eIzMvE.rst b/Misc/NEWS.d/next/Library/2025-04-22-19-45-46.gh-issue-132451.eIzMvE.rst
deleted file mode 100644
index 01ca6486853..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-22-19-45-46.gh-issue-132451.eIzMvE.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-The CLI for the PDB debugger now accepts a ``-p PID`` argument to allow
-attaching to a running process. The process must be running the same version
-of Python as the one running PDB.
diff --git a/Misc/NEWS.d/next/Library/2025-04-22-21-00-23.gh-issue-123471.asOLA2.rst b/Misc/NEWS.d/next/Library/2025-04-22-21-00-23.gh-issue-123471.asOLA2.rst
new file mode 100644
index 00000000000..a4b4b6d2c23
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-22-21-00-23.gh-issue-123471.asOLA2.rst
@@ -0,0 +1 @@
+Make concurrent iterations over :class:`itertools.combinations` and :class:`itertools.product` safe under free-threading.
diff --git a/Misc/NEWS.d/next/Library/2025-04-23-14-50-45.gh-issue-132742.PB6B7F.rst b/Misc/NEWS.d/next/Library/2025-04-23-14-50-45.gh-issue-132742.PB6B7F.rst
deleted file mode 100644
index db2905bb3bc..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-23-14-50-45.gh-issue-132742.PB6B7F.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-:func:`fcntl.fcntl` now supports arbitrary :term:`bytes-like objects
-<bytes-like object>`, not only :class:`bytes`. :func:`fcntl.ioctl` now
-automatically retries system calls failing with EINTR and releases the GIL
-during a system call even for large bytes-like object.
diff --git a/Misc/NEWS.d/next/Library/2025-04-23-18-35-09.gh-issue-129965.nj7Fx2.rst b/Misc/NEWS.d/next/Library/2025-04-23-18-35-09.gh-issue-129965.nj7Fx2.rst
deleted file mode 100644
index e693a2c0ffd..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-23-18-35-09.gh-issue-129965.nj7Fx2.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Add MIME types for ``.7z``, ``.apk``, ``.deb``, ``.glb``, ``.gltf``,
-``.gz``, ``.m4v``, ``.php``, ``.rar``, ``.rpm``, ``.stl`` and ``.wmv``.
-Patch by Hugo van Kemenade.
diff --git a/Misc/NEWS.d/next/Library/2025-04-24-01-03-40.gh-issue-93696.kM-MBp.rst b/Misc/NEWS.d/next/Library/2025-04-24-01-03-40.gh-issue-93696.kM-MBp.rst
deleted file mode 100644
index 61448a85a3d..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-24-01-03-40.gh-issue-93696.kM-MBp.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed the breakpoint display error for frozen modules in :mod:`pdb`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-24-09-10-04.gh-issue-132882.6zoyp5.rst b/Misc/NEWS.d/next/Library/2025-04-24-09-10-04.gh-issue-132882.6zoyp5.rst
deleted file mode 100644
index a93469fdcf9..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-24-09-10-04.gh-issue-132882.6zoyp5.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fix copying of :class:`typing.Union` objects containing objects that do not
-support the ``|`` operator.
diff --git a/Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst b/Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst
deleted file mode 100644
index f53b2bd3512..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-24-18-07-49.gh-issue-130328.z7CN8z.rst
+++ /dev/null
@@ -1 +0,0 @@
-Speedup pasting in ``PyREPL`` on Windows. Fix by Chris Eibl.
diff --git a/Misc/NEWS.d/next/Library/2025-04-24-21-22-46.gh-issue-132893.KFuxZ2.rst b/Misc/NEWS.d/next/Library/2025-04-24-21-22-46.gh-issue-132893.KFuxZ2.rst
deleted file mode 100644
index 1f5a4898263..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-24-21-22-46.gh-issue-132893.KFuxZ2.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improved :meth:`statistics.NormalDist.cdf` accuracy for inputs smaller than the mean.
diff --git a/Misc/NEWS.d/next/Library/2025-04-25-11-48-00.gh-issue-122781.ajsdns.rst b/Misc/NEWS.d/next/Library/2025-04-25-11-48-00.gh-issue-122781.ajsdns.rst
new file mode 100644
index 00000000000..5a9a0cdf798
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-25-11-48-00.gh-issue-122781.ajsdns.rst
@@ -0,0 +1,2 @@
+Fix ``%z`` directive in :func:`datetime.datetime.strptime` to allow for no provided
+offset as was documented.
diff --git a/Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst b/Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst
new file mode 100644
index 00000000000..8dcc6190cfd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst
@@ -0,0 +1,2 @@
+:func:`fcntl.fcntl` and :func:`fcntl.ioctl`: Remove the 1024 bytes limit
+on the size of not mutated bytes-like argument.
diff --git a/Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst b/Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst
deleted file mode 100644
index 95a7d9e9159..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-:func:`fcntl.fcntl` and :func:`fcntl.ioctl` can now detect a buffer overflow
-and raise :exc:`SystemError`. The stack and memory can be corrupted in such
-case, so treat this error as fatal.
diff --git a/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst b/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst
new file mode 100644
index 00000000000..e33b061bb9b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst
@@ -0,0 +1,2 @@
+Add :func:`math.isnormal` and :func:`math.issubnormal` functions. Patch by
+Sergey B Kirpichev.
diff --git a/Misc/NEWS.d/next/Library/2025-04-25-16-20-49.gh-issue-121249.uue2nK.rst b/Misc/NEWS.d/next/Library/2025-04-25-16-20-49.gh-issue-121249.uue2nK.rst
deleted file mode 100644
index 28140ab3054..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-25-16-20-49.gh-issue-121249.uue2nK.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Always support the :c:expr:`float complex` and :c:expr:`double complex` C types in
-the :mod:`struct` module. Patch by Sergey B Kirpichev.
diff --git a/Misc/NEWS.d/next/Library/2025-04-25-21-41-45.gh-issue-132933.yO3ySJ.rst b/Misc/NEWS.d/next/Library/2025-04-25-21-41-45.gh-issue-132933.yO3ySJ.rst
deleted file mode 100644
index 7d5eb3b0897..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-25-21-41-45.gh-issue-132933.yO3ySJ.rst
+++ /dev/null
@@ -1 +0,0 @@
-The zipapp module now applies the filter when creating the list of files to add, rather than waiting until the file is being added to the archive.
diff --git a/Misc/NEWS.d/next/Library/2025-04-26-10-54-38.gh-issue-132995.JuDF9p.rst b/Misc/NEWS.d/next/Library/2025-04-26-10-54-38.gh-issue-132995.JuDF9p.rst
deleted file mode 100644
index 266661babef..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-26-10-54-38.gh-issue-132995.JuDF9p.rst
+++ /dev/null
@@ -1 +0,0 @@
-Bump the version of pip bundled in ensurepip to version 25.1.1
diff --git a/Misc/NEWS.d/next/Library/2025-04-26-10-57-15.gh-issue-132991.ekkqdt.rst b/Misc/NEWS.d/next/Library/2025-04-26-10-57-15.gh-issue-132991.ekkqdt.rst
deleted file mode 100644
index f462dac34ce..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-26-10-57-15.gh-issue-132991.ekkqdt.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add :data:`!socket.IP_FREEBIND` constant on Linux 2.4 and later.
diff --git a/Misc/NEWS.d/next/Library/2025-04-26-12-25-42.gh-issue-115032.jnM2Co.rst b/Misc/NEWS.d/next/Library/2025-04-26-12-25-42.gh-issue-115032.jnM2Co.rst
deleted file mode 100644
index 80c10aef069..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-26-12-25-42.gh-issue-115032.jnM2Co.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Support for custom logging handlers with the *strm* argument is deprecated
-and scheduled for removal in Python 3.16. Define handlers with the *stream*
-argument instead. Patch by Mariusz Felisiak.
diff --git a/Misc/NEWS.d/next/Library/2025-04-26-14-44-21.gh-issue-133005.y4SRfk.rst b/Misc/NEWS.d/next/Library/2025-04-26-14-44-21.gh-issue-133005.y4SRfk.rst
deleted file mode 100644
index cb3ad489706..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-26-14-44-21.gh-issue-133005.y4SRfk.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Support passing ``preset`` option to :func:`tarfile.open` when using ``'w|xz'``
-mode.
diff --git a/Misc/NEWS.d/next/Library/2025-04-26-15-43-23.gh-issue-124703.jc5auS.rst b/Misc/NEWS.d/next/Library/2025-04-26-15-43-23.gh-issue-124703.jc5auS.rst
deleted file mode 100644
index 54603dda39e..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-26-15-43-23.gh-issue-124703.jc5auS.rst
+++ /dev/null
@@ -1 +0,0 @@
-Set return code to ``1`` when aborting process from :mod:`pdb`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-26-15-50-12.gh-issue-133009.etBuz5.rst b/Misc/NEWS.d/next/Library/2025-04-26-15-50-12.gh-issue-133009.etBuz5.rst
new file mode 100644
index 00000000000..1f7155c6a40
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-26-15-50-12.gh-issue-133009.etBuz5.rst
@@ -0,0 +1,3 @@
+:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.__deepcopy__
+<object.__deepcopy__>` when the element is concurrently mutated.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-04-26-17-41-20.gh-issue-132987.xxBCqg.rst b/Misc/NEWS.d/next/Library/2025-04-26-17-41-20.gh-issue-132987.xxBCqg.rst
deleted file mode 100644
index 7b75da382a0..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-26-17-41-20.gh-issue-132987.xxBCqg.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Many builtin and extension functions which accept an unsigned integer
-argument, now use :meth:`~object.__index__` if available.
diff --git a/Misc/NEWS.d/next/Library/2025-04-27-15-21-05.gh-issue-133036.HCNYA7.rst b/Misc/NEWS.d/next/Library/2025-04-27-15-21-05.gh-issue-133036.HCNYA7.rst
deleted file mode 100644
index 46b1f5575d0..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-27-15-21-05.gh-issue-133036.HCNYA7.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:func:`codecs.open` is now deprecated. Use :func:`open` instead. Contributed
-by Inada Naoki.
diff --git a/Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst b/Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst
new file mode 100644
index 00000000000..cb3ca3321e3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst
@@ -0,0 +1,4 @@
+``ldexp()`` on Windows doesn't round subnormal results before Windows 11,
+but should. Python's :func:`math.ldexp` wrapper now does round them, so
+results may change slightly, in rare cases of very small results, on
+Windows versions before 11.
diff --git a/Misc/NEWS.d/next/Library/2025-04-29-23-20-52.gh-issue-133153.M-w9yC.rst b/Misc/NEWS.d/next/Library/2025-04-29-23-20-52.gh-issue-133153.M-w9yC.rst
deleted file mode 100644
index c609fa698dc..00000000000
--- a/Misc/NEWS.d/next/Library/2025-04-29-23-20-52.gh-issue-133153.M-w9yC.rst
+++ /dev/null
@@ -1 +0,0 @@
-Do not complete :mod:`pdb` commands in ``interact`` mode of :mod:`pdb`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-30-19-32-18.gh-issue-132969.EagQ3G.rst b/Misc/NEWS.d/next/Library/2025-04-30-19-32-18.gh-issue-132969.EagQ3G.rst
new file mode 100644
index 00000000000..7364c425941
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-30-19-32-18.gh-issue-132969.EagQ3G.rst
@@ -0,0 +1,7 @@
+Prevent the :class:`~concurrent.futures.ProcessPoolExecutor` executor thread,
+which remains running when :meth:`shutdown(wait=False)
+<concurrent.futures.Executor.shutdown>`, from
+attempting to adjust the pool's worker processes after the object state has already been reset during shutdown.
+A combination of conditions, including a worker process having terminated abormally,
+resulted in an exception and a potential hang when the still-running executor thread
+attempted to replace dead workers within the pool.
diff --git a/Misc/NEWS.d/next/Library/2025-05-01-10-56-44.gh-issue-132813.rKurvp.rst b/Misc/NEWS.d/next/Library/2025-05-01-10-56-44.gh-issue-132813.rKurvp.rst
new file mode 100644
index 00000000000..55608528a45
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-01-10-56-44.gh-issue-132813.rKurvp.rst
@@ -0,0 +1,2 @@
+Improve error messages for incorrect types and values of :class:`csv.Dialect`
+attributes.
diff --git a/Misc/NEWS.d/next/Library/2025-05-01-16-03-11.gh-issue-133017.k7RLQp.rst b/Misc/NEWS.d/next/Library/2025-05-01-16-03-11.gh-issue-133017.k7RLQp.rst
new file mode 100644
index 00000000000..1b5bf74fb47
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-01-16-03-11.gh-issue-133017.k7RLQp.rst
@@ -0,0 +1,4 @@
+Improve the error message of :func:`multiprocessing.sharedctypes.Array`,
+:func:`multiprocessing.sharedctypes.RawArray`, :func:`multiprocessing.sharedctypes.Value` and
+:func:`multiprocessing.sharedctypes.RawValue` when an invalid typecode is passed. Patch
+by Tomas Roun
diff --git a/Misc/NEWS.d/next/Library/2025-05-02-13-16-44.gh-issue-133290.R5WrLM.rst b/Misc/NEWS.d/next/Library/2025-05-02-13-16-44.gh-issue-133290.R5WrLM.rst
deleted file mode 100644
index 538cce9357d..00000000000
--- a/Misc/NEWS.d/next/Library/2025-05-02-13-16-44.gh-issue-133290.R5WrLM.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Fix attribute caching issue when setting :attr:`ctypes._Pointer._type_` in
-the undocumented and deprecated :func:`!ctypes.SetPointerType` function and the
-undocumented :meth:`!set_type` method.
diff --git a/Misc/NEWS.d/next/Library/2025-05-02-17-23-41.gh-issue-133300.oAh1P2.rst b/Misc/NEWS.d/next/Library/2025-05-02-17-23-41.gh-issue-133300.oAh1P2.rst
deleted file mode 100644
index a99955243e5..00000000000
--- a/Misc/NEWS.d/next/Library/2025-05-02-17-23-41.gh-issue-133300.oAh1P2.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Make :class:`argparse.ArgumentParser`'s ``suggest_on_error`` a keyword-only
-parameter. Patch by Hugo van Kemenade.
diff --git a/Misc/NEWS.d/next/Library/2025-05-02-21-35-03.gh-issue-133306.-vBye5.rst b/Misc/NEWS.d/next/Library/2025-05-02-21-35-03.gh-issue-133306.-vBye5.rst
deleted file mode 100644
index d0973af5ffc..00000000000
--- a/Misc/NEWS.d/next/Library/2025-05-02-21-35-03.gh-issue-133306.-vBye5.rst
+++ /dev/null
@@ -1 +0,0 @@
-Support ``\z`` as a synonym for ``\Z`` in :mod:`regular expressions <re>`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst b/Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst
deleted file mode 100644
index 5ec4860f553..00000000000
--- a/Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Use ``\z`` instead of ``\Z`` in :func:`fnmatch.translate` and
-:func:`glob.translate`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst b/Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst
new file mode 100644
index 00000000000..ad06ee6b7a2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-04-17-04-55.gh-issue-132493.huirKi.rst
@@ -0,0 +1,2 @@
+Avoid accessing ``__annotations__`` unnecessarily in
+:func:`inspect.signature`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-05-03-14-08.gh-issue-133390.AuTggn.rst b/Misc/NEWS.d/next/Library/2025-05-05-03-14-08.gh-issue-133390.AuTggn.rst
new file mode 100644
index 00000000000..943e4addebc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-05-03-14-08.gh-issue-133390.AuTggn.rst
@@ -0,0 +1,2 @@
+Support keyword completion in the :mod:`sqlite3` command-line interface and add
+:data:`sqlite3.SQLITE_KEYWORDS` constant.
diff --git a/Misc/NEWS.d/next/Library/2025-05-05-10-41-41.gh-issue-133253.J5-xDD.rst b/Misc/NEWS.d/next/Library/2025-05-05-10-41-41.gh-issue-133253.J5-xDD.rst
new file mode 100644
index 00000000000..7009ca258bc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-05-10-41-41.gh-issue-133253.J5-xDD.rst
@@ -0,0 +1 @@
+Fix thread-safety issues in :mod:`linecache`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst b/Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst
new file mode 100644
index 00000000000..f453690cab2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-05-18-50-00.gh-issue-133447.ajshdb.rst
@@ -0,0 +1 @@
+Add basic color to :mod:`sqlite3` CLI interface.
diff --git a/Misc/NEWS.d/next/Library/2025-05-05-22-11-24.gh-issue-133439.LpmyFz.rst b/Misc/NEWS.d/next/Library/2025-05-05-22-11-24.gh-issue-133439.LpmyFz.rst
new file mode 100644
index 00000000000..e0a3ce98bf7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-05-22-11-24.gh-issue-133439.LpmyFz.rst
@@ -0,0 +1,2 @@
+Fix dot commands with trailing spaces are mistaken for multi-line SQL
+statements in the sqlite3 command-line interface.
diff --git a/Misc/NEWS.d/next/Library/2025-05-06-14-44-55.gh-issue-133517.Ca6NgW.rst b/Misc/NEWS.d/next/Library/2025-05-06-14-44-55.gh-issue-133517.Ca6NgW.rst
new file mode 100644
index 00000000000..7c53fc484a8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-06-14-44-55.gh-issue-133517.Ca6NgW.rst
@@ -0,0 +1,2 @@
+Remove :func:`os.listdrives`, :func:`os.listvolumes` and :func:`os.listmounts`
+in non Windows desktop builds since the underlying functionality is missing.
diff --git a/Misc/NEWS.d/next/Library/2025-05-06-22-54-37.gh-issue-133551.rfy1tJ.rst b/Misc/NEWS.d/next/Library/2025-05-06-22-54-37.gh-issue-133551.rfy1tJ.rst
new file mode 100644
index 00000000000..7fedc0818dc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-06-22-54-37.gh-issue-133551.rfy1tJ.rst
@@ -0,0 +1,2 @@
+Support t-strings (:pep:`750`) in :mod:`annotationlib`. Patch by Jelle
+Zijlstra.
diff --git a/Misc/NEWS.d/next/Library/2025-05-07-13-31-06.gh-issue-92897.ubeqGE.rst b/Misc/NEWS.d/next/Library/2025-05-07-13-31-06.gh-issue-92897.ubeqGE.rst
new file mode 100644
index 00000000000..647166bfcf8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-07-13-31-06.gh-issue-92897.ubeqGE.rst
@@ -0,0 +1,2 @@
+Removed the ``check_home`` parameter from :func:`sysconfig.is_python_build`,
+deprecated since Python 3.12.
diff --git a/Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst b/Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst
new file mode 100644
index 00000000000..9d056983439
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-07-14-36-30.gh-issue-133577.BggPk9.rst
@@ -0,0 +1 @@
+Add parameter ``formatter`` to :func:`logging.basicConfig`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-07-19-16-41.gh-issue-133581.kERUCJ.rst b/Misc/NEWS.d/next/Library/2025-05-07-19-16-41.gh-issue-133581.kERUCJ.rst
new file mode 100644
index 00000000000..3749904cd9b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-07-19-16-41.gh-issue-133581.kERUCJ.rst
@@ -0,0 +1,4 @@
+Improve unparsing of t-strings in :func:`ast.unparse` and ``from __future__
+import annotations``. Empty t-strings now round-trip correctly and
+formatting in interpolations is preserved.
+Patch by Jelle Zijlstra.
diff --git a/Misc/NEWS.d/next/Library/2025-05-07-22-15-15.gh-issue-133595.c3U88r.rst b/Misc/NEWS.d/next/Library/2025-05-07-22-15-15.gh-issue-133595.c3U88r.rst
new file mode 100644
index 00000000000..a61c4bf1913
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-07-22-15-15.gh-issue-133595.c3U88r.rst
@@ -0,0 +1,7 @@
+Clean up :class:`sqlite3.Connection` APIs. All parameters of
+:func:`sqlite3.connect` except *database* are now keyword-only. The first
+three parameters of methods :meth:`~sqlite3.Connection.create_function` and
+:meth:`~sqlite3.Connection.create_aggregate` are now positional-only. The
+first parameter of methods :meth:`~sqlite3.Connection.set_authorizer`,
+:meth:`~sqlite3.Connection.set_progress_handler` and
+:meth:`~sqlite3.Connection.set_trace_callback` is now positional-only.
diff --git a/Misc/NEWS.d/next/Library/2025-05-08-13-43-19.gh-issue-133489.9eGS1Z.rst b/Misc/NEWS.d/next/Library/2025-05-08-13-43-19.gh-issue-133489.9eGS1Z.rst
new file mode 100644
index 00000000000..0c07beb7693
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-08-13-43-19.gh-issue-133489.9eGS1Z.rst
@@ -0,0 +1,2 @@
+:func:`random.getrandbits` can now generate more that 2\ :sup:`31` bits.
+:func:`random.randbytes` can now generate more that 256 MiB.
diff --git a/Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst b/Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst
new file mode 100644
index 00000000000..163d9b331d1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-09-08-49-03.gh-issue-133701.KI8tGz.rst
@@ -0,0 +1,3 @@
+Fix bug where :class:`typing.TypedDict` classes defined under ``from
+__future__ import annotations`` and inheriting from another ``TypedDict``
+had an incorrect ``__annotations__`` attribute.
diff --git a/Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst b/Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst
new file mode 100644
index 00000000000..00b556c6a33
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-09-09-10-34.gh-issue-130328.s9h4By.rst
@@ -0,0 +1,2 @@
+Speedup pasting in ``PyREPL`` on Windows in a legacy console. Patch by Chris
+Eibl.
diff --git a/Misc/NEWS.d/next/Library/2025-05-09-15-50-00.gh-issue-77057.fV8SU-.rst b/Misc/NEWS.d/next/Library/2025-05-09-15-50-00.gh-issue-77057.fV8SU-.rst
new file mode 100644
index 00000000000..42107de75c7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-09-15-50-00.gh-issue-77057.fV8SU-.rst
@@ -0,0 +1,2 @@
+Fix handling of invalid markup declarations in
+:class:`html.parser.HTMLParser`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-09-18-29-25.gh-issue-133684.Y1DFSt.rst b/Misc/NEWS.d/next/Library/2025-05-09-18-29-25.gh-issue-133684.Y1DFSt.rst
new file mode 100644
index 00000000000..0cb1bc237a1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-09-18-29-25.gh-issue-133684.Y1DFSt.rst
@@ -0,0 +1,3 @@
+Fix bug where :func:`annotationlib.get_annotations` would return the wrong
+result for certain classes that are part of a class hierarchy where ``from
+__future__ import annotations`` is used.
diff --git a/Misc/NEWS.d/next/Library/2025-05-09-19-05-24.gh-issue-133783.1voCnR.rst b/Misc/NEWS.d/next/Library/2025-05-09-19-05-24.gh-issue-133783.1voCnR.rst
new file mode 100644
index 00000000000..62e742df179
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-09-19-05-24.gh-issue-133783.1voCnR.rst
@@ -0,0 +1,3 @@
+Fix bug with applying :func:`copy.replace` to :mod:`ast` objects. Attributes
+that default to ``None`` were incorrectly treated as required for manually
+created AST nodes.
diff --git a/Misc/NEWS.d/next/Library/2025-05-09-20-59-24.gh-issue-132641.3qTw44.rst b/Misc/NEWS.d/next/Library/2025-05-09-20-59-24.gh-issue-132641.3qTw44.rst
new file mode 100644
index 00000000000..419ff19d9c0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-09-20-59-24.gh-issue-132641.3qTw44.rst
@@ -0,0 +1 @@
+Fixed a race in :func:`functools.lru_cache` under free-threading.
diff --git a/Misc/NEWS.d/next/Library/2025-05-10-11-04-47.gh-issue-133810.03WhnK.rst b/Misc/NEWS.d/next/Library/2025-05-10-11-04-47.gh-issue-133810.03WhnK.rst
new file mode 100644
index 00000000000..4073974e364
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-10-11-04-47.gh-issue-133810.03WhnK.rst
@@ -0,0 +1,3 @@
+Remove :class:`!http.server.CGIHTTPRequestHandler` and ``--cgi`` flag from the
+:program:`python -m http.server` command-line interface. They were
+deprecated in Python 3.13. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-10-12-06-55.gh-issue-133653.Gb2aG4.rst b/Misc/NEWS.d/next/Library/2025-05-10-12-06-55.gh-issue-133653.Gb2aG4.rst
new file mode 100644
index 00000000000..56fb1dc31dc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-10-12-06-55.gh-issue-133653.Gb2aG4.rst
@@ -0,0 +1,7 @@
+Fix :class:`argparse.ArgumentParser` with the *formatter_class* argument.
+Fix TypeError when *formatter_class* is a custom subclass of
+:class:`!HelpFormatter`.
+Fix TypeError when *formatter_class* is not a subclass of
+:class:`!HelpFormatter` and non-standard *prefix_char* is used.
+Fix support of colorizing when *formatter_class* is not a subclass of
+:class:`!HelpFormatter`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst b/Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst
new file mode 100644
index 00000000000..326e767de5f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst
@@ -0,0 +1,2 @@
+Remove support for creating :class:`~typing.NamedTuple` classes via the
+undocumented keyword argument syntax. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst b/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
new file mode 100644
index 00000000000..67b44ac3ef3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
@@ -0,0 +1,3 @@
+Remove support for ``TD = TypedDict("TD")`` and ``TD = TypedDict("TD", None)``
+calls for constructing :class:`typing.TypedDict` objects with zero field.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-11-10-01-48.gh-issue-133866.g3dHP_.rst b/Misc/NEWS.d/next/Library/2025-05-11-10-01-48.gh-issue-133866.g3dHP_.rst
new file mode 100644
index 00000000000..00f13c9a305
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-11-10-01-48.gh-issue-133866.g3dHP_.rst
@@ -0,0 +1,3 @@
+Remove the undocumented function :func:`!ctypes.SetPointerType`,
+which has been deprecated since Python 3.13.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-11-10-28-11.gh-issue-133873.H03nov.rst b/Misc/NEWS.d/next/Library/2025-05-11-10-28-11.gh-issue-133873.H03nov.rst
new file mode 100644
index 00000000000..79f630b2418
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-11-10-28-11.gh-issue-133873.H03nov.rst
@@ -0,0 +1,3 @@
+Remove the deprecated ``getmark()``, ``setmark()`` and ``getmarkers()``
+methods of the :class:`~wave.Wave_read` and :class:`~wave.Wave_write`
+classes, which were deprecated since Python 3.13. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-11-12-56-52.gh-issue-133604.kFxhc8.rst b/Misc/NEWS.d/next/Library/2025-05-11-12-56-52.gh-issue-133604.kFxhc8.rst
new file mode 100644
index 00000000000..526ac38f09b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-11-12-56-52.gh-issue-133604.kFxhc8.rst
@@ -0,0 +1 @@
+Remove :func:`!platform.java_ver` which was deprecated since Python 3.13.
diff --git a/Misc/NEWS.d/next/Library/2025-05-12-06-52-10.gh-issue-133925.elInBY.rst b/Misc/NEWS.d/next/Library/2025-05-12-06-52-10.gh-issue-133925.elInBY.rst
new file mode 100644
index 00000000000..328e28abc3b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-12-06-52-10.gh-issue-133925.elInBY.rst
@@ -0,0 +1 @@
+Make the private class ``typing._UnionGenericAlias`` hashable.
diff --git a/Misc/NEWS.d/next/Library/2025-05-12-20-38-57.gh-issue-133960.Aee79f.rst b/Misc/NEWS.d/next/Library/2025-05-12-20-38-57.gh-issue-133960.Aee79f.rst
new file mode 100644
index 00000000000..66e8483b25b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-12-20-38-57.gh-issue-133960.Aee79f.rst
@@ -0,0 +1,3 @@
+Simplify and improve :func:`typing.evaluate_forward_ref`. It now no longer
+raises errors on certain invalid types. In several situations, it is now
+able to evaluate forward references that were previously unsupported.
diff --git a/Misc/NEWS.d/next/Library/2025-05-13-18-21-59.gh-issue-71253.-3Sf_K.rst b/Misc/NEWS.d/next/Library/2025-05-13-18-21-59.gh-issue-71253.-3Sf_K.rst
new file mode 100644
index 00000000000..714d707f488
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-13-18-21-59.gh-issue-71253.-3Sf_K.rst
@@ -0,0 +1,3 @@
+Raise :exc:`ValueError` in :func:`open` if *opener* returns a negative
+file-descriptor in the Python implementation of :mod:`io` to match the
+C implementation.
diff --git a/Misc/NEWS.d/next/Library/2025-05-13-18-54-56.gh-issue-133970.6G-Oi6.rst b/Misc/NEWS.d/next/Library/2025-05-13-18-54-56.gh-issue-133970.6G-Oi6.rst
new file mode 100644
index 00000000000..ddf456d3939
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-13-18-54-56.gh-issue-133970.6G-Oi6.rst
@@ -0,0 +1,2 @@
+Make :class:`!string.templatelib.Template` and
+:class:`!string.templatelib.Interpolation` generic.
diff --git a/Misc/NEWS.d/next/Library/2025-05-15-00-27-09.gh-issue-134004.e8k4-R.rst b/Misc/NEWS.d/next/Library/2025-05-15-00-27-09.gh-issue-134004.e8k4-R.rst
new file mode 100644
index 00000000000..a9a56d9239b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-15-00-27-09.gh-issue-134004.e8k4-R.rst
@@ -0,0 +1,2 @@
+:mod:`shelve` as well as underlying :mod:`!dbm.dumb` and :mod:`!dbm.sqlite` now have :meth:`!reorganize` methods to
+recover unused free space previously occupied by deleted entries.
diff --git a/Misc/NEWS.d/next/Library/2025-05-15-14-27-01.gh-issue-134062.fRbJet.rst b/Misc/NEWS.d/next/Library/2025-05-15-14-27-01.gh-issue-134062.fRbJet.rst
new file mode 100644
index 00000000000..f62a3ec4801
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-15-14-27-01.gh-issue-134062.fRbJet.rst
@@ -0,0 +1,3 @@
+:mod:`ipaddress`: fix collisions in :meth:`~object.__hash__` for
+:class:`~ipaddress.IPv4Network` and :class:`~ipaddress.IPv6Network`
+objects.
diff --git a/Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst b/Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst
new file mode 100644
index 00000000000..acf3577ece4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst
@@ -0,0 +1,6 @@
+On POSIX-compliant systems, :func:`!multiprocessing.util.get_temp_dir` now
+ignores :envvar:`TMPDIR` (and similar environment variables) if the path
+length of ``AF_UNIX`` socket files exceeds the platform-specific maximum
+length when using the :ref:`forkserver
+<multiprocessing-start-method-forkserver>` start method. Patch by Bénédikt
+Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-16-20-10-25.gh-issue-134098.YyTkKr.rst b/Misc/NEWS.d/next/Library/2025-05-16-20-10-25.gh-issue-134098.YyTkKr.rst
new file mode 100644
index 00000000000..32eff5371c4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-16-20-10-25.gh-issue-134098.YyTkKr.rst
@@ -0,0 +1,2 @@
+Fix handling paths that end with a percent-encoded slash (``%2f`` or
+``%2F``) in :class:`http.server.SimpleHTTPRequestHandler`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-17-12-40-12.gh-issue-133889.Eh-zO4.rst b/Misc/NEWS.d/next/Library/2025-05-17-12-40-12.gh-issue-133889.Eh-zO4.rst
new file mode 100644
index 00000000000..58b213e29f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-17-12-40-12.gh-issue-133889.Eh-zO4.rst
@@ -0,0 +1,3 @@
+The generated directory listing page in
+:class:`http.server.SimpleHTTPRequestHandler` now only shows the decoded
+path component of the requested URL, and not the query and fragment.
diff --git a/Misc/NEWS.d/next/Library/2025-05-17-13-46-20.gh-issue-134097.fgkjE1.rst b/Misc/NEWS.d/next/Library/2025-05-17-13-46-20.gh-issue-134097.fgkjE1.rst
new file mode 100644
index 00000000000..0b388d9db38
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-17-13-46-20.gh-issue-134097.fgkjE1.rst
@@ -0,0 +1 @@
+Fix interaction of the new :term:`REPL` and :option:`-X showrefcount <-X>` command line option.
diff --git a/Misc/NEWS.d/next/Library/2025-05-17-18-08-35.gh-issue-133890.onn9_X.rst b/Misc/NEWS.d/next/Library/2025-05-17-18-08-35.gh-issue-133890.onn9_X.rst
new file mode 100644
index 00000000000..44565a5424e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-17-18-08-35.gh-issue-133890.onn9_X.rst
@@ -0,0 +1,2 @@
+The :mod:`tarfile` module now handles :exc:`UnicodeEncodeError` in the same
+way as :exc:`OSError` when cannot extract a member.
diff --git a/Misc/NEWS.d/next/Library/2025-05-17-20-23-57.gh-issue-133982.smS7au.rst b/Misc/NEWS.d/next/Library/2025-05-17-20-23-57.gh-issue-133982.smS7au.rst
new file mode 100644
index 00000000000..a6753145981
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-17-20-23-57.gh-issue-133982.smS7au.rst
@@ -0,0 +1,3 @@
+Emit :exc:`RuntimeWarning` in the Python implementation of :mod:`io` when
+the :term:`file-like object <file object>` is not closed explicitly in the
+presence of multiple I/O layers.
diff --git a/Misc/NEWS.d/next/Library/2025-05-18-07-25-15.gh-issue-134173.53oOoF.rst b/Misc/NEWS.d/next/Library/2025-05-18-07-25-15.gh-issue-134173.53oOoF.rst
new file mode 100644
index 00000000000..57fba5e21a3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-18-07-25-15.gh-issue-134173.53oOoF.rst
@@ -0,0 +1,3 @@
+Speed up :mod:`asyncio` performance of transferring state from thread
+pool :class:`concurrent.futures.Future` by up to 4.4x. Patch by J. Nick
+Koston.
diff --git a/Misc/NEWS.d/next/Library/2025-05-18-12-23-07.gh-issue-134087.HilZWl.rst b/Misc/NEWS.d/next/Library/2025-05-18-12-23-07.gh-issue-134087.HilZWl.rst
new file mode 100644
index 00000000000..c4a05965f73
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-18-12-23-07.gh-issue-134087.HilZWl.rst
@@ -0,0 +1,3 @@
+Remove support for arbitrary positional or keyword arguments in the C
+implementation of :class:`threading.RLock` objects. This was deprecated
+since Python 3.14. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-18-12-48-39.gh-issue-62184.y11l10.rst b/Misc/NEWS.d/next/Library/2025-05-18-12-48-39.gh-issue-62184.y11l10.rst
new file mode 100644
index 00000000000..7bc994e57fb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-18-12-48-39.gh-issue-62184.y11l10.rst
@@ -0,0 +1,2 @@
+Remove import of C implementation of :class:`io.FileIO` from Python
+implementation which has its own implementation
diff --git a/Misc/NEWS.d/next/Library/2025-05-18-13-23-29.gh-issue-134168.hgx3Xg.rst b/Misc/NEWS.d/next/Library/2025-05-18-13-23-29.gh-issue-134168.hgx3Xg.rst
new file mode 100644
index 00000000000..5a0e20005db
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-18-13-23-29.gh-issue-134168.hgx3Xg.rst
@@ -0,0 +1,2 @@
+:mod:`http.server`: Fix IPv6 address binding and
+:option:`--directory <http.server --directory>` handling when using HTTPS.
diff --git a/Misc/NEWS.d/next/Library/2025-05-18-23-46-21.gh-issue-134152.30HwbX.rst b/Misc/NEWS.d/next/Library/2025-05-18-23-46-21.gh-issue-134152.30HwbX.rst
new file mode 100644
index 00000000000..911a4a59ea6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-18-23-46-21.gh-issue-134152.30HwbX.rst
@@ -0,0 +1 @@
+:mod:`email`: Fix parsing of email message ID with invalid domain.
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst b/Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst
new file mode 100644
index 00000000000..6da3d4147dd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-10-32-11.gh-issue-134152.INJC2j.rst
@@ -0,0 +1,2 @@
+Fixed :exc:`UnboundLocalError` that could occur during :mod:`email` header
+parsing if an expected trailing delimiter is missing in some contexts.
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst b/Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst
new file mode 100644
index 00000000000..a65df886919
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-15-05-24.gh-issue-134235.pz9PwV.rst
@@ -0,0 +1,2 @@
+Updated tab completion on REPL to include builtin modules. Contributed by
+Tom Wang, Hunter Young
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-15-30-00.gh-issue-132983.asdsfs.rst b/Misc/NEWS.d/next/Library/2025-05-19-15-30-00.gh-issue-132983.asdsfs.rst
new file mode 100644
index 00000000000..3893eeafa9c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-15-30-00.gh-issue-132983.asdsfs.rst
@@ -0,0 +1 @@
+Add :mod:`!compression.zstd` version information to ``test.pythoninfo``.
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-17-27-21.gh-issue-80184.LOkbaw.rst b/Misc/NEWS.d/next/Library/2025-05-19-17-27-21.gh-issue-80184.LOkbaw.rst
new file mode 100644
index 00000000000..089268dc4c3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-17-27-21.gh-issue-80184.LOkbaw.rst
@@ -0,0 +1 @@
+The default queue size is now ``socket.SOMAXCONN`` for :class:`socketserver.TCPServer`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-18-12-42.gh-issue-88994.7avvVu.rst b/Misc/NEWS.d/next/Library/2025-05-19-18-12-42.gh-issue-88994.7avvVu.rst
new file mode 100644
index 00000000000..554a0c3bcb2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-18-12-42.gh-issue-88994.7avvVu.rst
@@ -0,0 +1,3 @@
+Change :func:`datetime.datetime.now` to half-even rounding for
+consistency with :func:`datetime.datetime.fromtimestamp`. Patch by
+John Keith Hohm.
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-20-59-06.gh-issue-134209.anhTcF.rst b/Misc/NEWS.d/next/Library/2025-05-19-20-59-06.gh-issue-134209.anhTcF.rst
new file mode 100644
index 00000000000..f985872f3c9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-20-59-06.gh-issue-134209.anhTcF.rst
@@ -0,0 +1,3 @@
+:mod:`curses`: The :meth:`curses.window.instr` and :meth:`curses.window.getstr`
+methods now allocate their internal buffer on the heap instead of the stack;
+in addition, the max buffer size is increased from 1023 to 2047.
diff --git a/Misc/NEWS.d/next/Library/2025-05-20-11-35-08.gh-issue-72902.jzEI-E.rst b/Misc/NEWS.d/next/Library/2025-05-20-11-35-08.gh-issue-72902.jzEI-E.rst
new file mode 100644
index 00000000000..932e751a32a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-20-11-35-08.gh-issue-72902.jzEI-E.rst
@@ -0,0 +1,2 @@
+Improve speed (x1.1-1.8) of the :class:`~fractions.Fraction` constructor for
+typical inputs (:class:`float`'s, :class:`~decimal.Decimal`'s or strings).
diff --git a/Misc/NEWS.d/next/Library/2025-05-20-15-13-43.gh-issue-86802.trF7TM.rst b/Misc/NEWS.d/next/Library/2025-05-20-15-13-43.gh-issue-86802.trF7TM.rst
new file mode 100644
index 00000000000..d3117b16f04
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-20-15-13-43.gh-issue-86802.trF7TM.rst
@@ -0,0 +1,3 @@
+Fixed asyncio memory leak in cancelled shield tasks. For shielded tasks
+where the shield was cancelled, log potential exceptions through the
+exception handler. Contributed by Christian Harries.
diff --git a/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst
new file mode 100644
index 00000000000..7982b52f77a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-20-19-16-30.gh-issue-134323.ZQZGvw.rst
@@ -0,0 +1 @@
+Fix the :meth:`threading.RLock.locked` method.
diff --git a/Misc/NEWS.d/next/Library/2025-05-20-21-45-58.gh-issue-90871.Gkvtp6.rst b/Misc/NEWS.d/next/Library/2025-05-20-21-45-58.gh-issue-90871.Gkvtp6.rst
new file mode 100644
index 00000000000..49397c9705e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-20-21-45-58.gh-issue-90871.Gkvtp6.rst
@@ -0,0 +1,2 @@
+Fixed an off by one error concerning the backlog parameter in
+:meth:`~asyncio.loop.create_unix_server`. Contributed by Christian Harries.
diff --git a/Misc/NEWS.d/next/Library/2025-05-22-13-10-32.gh-issue-114177.3TYUJ3.rst b/Misc/NEWS.d/next/Library/2025-05-22-13-10-32.gh-issue-114177.3TYUJ3.rst
new file mode 100644
index 00000000000..c98fde5fb04
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-22-13-10-32.gh-issue-114177.3TYUJ3.rst
@@ -0,0 +1 @@
+Fix :mod:`asyncio` to not close subprocess pipes which would otherwise error out when the event loop is already closed.
diff --git a/Misc/NEWS.d/next/Library/2025-05-22-14-12-53.gh-issue-134451.M1rD-j.rst b/Misc/NEWS.d/next/Library/2025-05-22-14-12-53.gh-issue-134451.M1rD-j.rst
new file mode 100644
index 00000000000..3c8339f8842
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-22-14-12-53.gh-issue-134451.M1rD-j.rst
@@ -0,0 +1 @@
+Converted ``asyncio.tools.CycleFoundException`` from dataclass to a regular exception type.
diff --git a/Misc/NEWS.d/next/Library/2025-05-22-18-14-13.gh-issue-134546.fjLVzK.rst b/Misc/NEWS.d/next/Library/2025-05-22-18-14-13.gh-issue-134546.fjLVzK.rst
new file mode 100644
index 00000000000..eea897f5918
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-22-18-14-13.gh-issue-134546.fjLVzK.rst
@@ -0,0 +1 @@
+Ensure :mod:`pdb` remote debugging script is readable by remote Python process.
diff --git a/Misc/NEWS.d/next/Library/2025-05-23-10-15-36.gh-issue-134565.zmb66C.rst b/Misc/NEWS.d/next/Library/2025-05-23-10-15-36.gh-issue-134565.zmb66C.rst
new file mode 100644
index 00000000000..17d2b23b62d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-23-10-15-36.gh-issue-134565.zmb66C.rst
@@ -0,0 +1,3 @@
+:func:`unittest.doModuleCleanups` no longer swallows all but first exception
+raised in the cleanup code, but raises a :exc:`ExceptionGroup` if multiple
+errors occurred.
diff --git a/Misc/NEWS.d/next/Library/2025-05-23-20-01-52.gh-issue-134580.xnaJ70.rst b/Misc/NEWS.d/next/Library/2025-05-23-20-01-52.gh-issue-134580.xnaJ70.rst
new file mode 100644
index 00000000000..979d310d3ce
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-23-20-01-52.gh-issue-134580.xnaJ70.rst
@@ -0,0 +1,3 @@
+Improved the styling of HTML diff pages generated by the
+:class:`difflib.HtmlDiff` class, and migrated the output to the HTML5
+standard.
diff --git a/Misc/NEWS.d/next/Library/2025-05-23-23-43-39.gh-issue-134582.9POq3l.rst b/Misc/NEWS.d/next/Library/2025-05-23-23-43-39.gh-issue-134582.9POq3l.rst
new file mode 100644
index 00000000000..23e1d5891b6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-23-23-43-39.gh-issue-134582.9POq3l.rst
@@ -0,0 +1 @@
+Fix tokenize.untokenize() round-trip errors related to t-strings braces escaping
diff --git a/Misc/NEWS.d/next/Library/2025-05-24-03-10-36.gh-issue-80334.z21cMa.rst b/Misc/NEWS.d/next/Library/2025-05-24-03-10-36.gh-issue-80334.z21cMa.rst
new file mode 100644
index 00000000000..228429516db
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-24-03-10-36.gh-issue-80334.z21cMa.rst
@@ -0,0 +1,2 @@
+:func:`multiprocessing.freeze_support` now checks for work on any "spawn"
+start method platform rather than only on Windows.
diff --git a/Misc/NEWS.d/next/Library/2025-05-24-13-10-35.gh-issue-134210.0IuMY2.rst b/Misc/NEWS.d/next/Library/2025-05-24-13-10-35.gh-issue-134210.0IuMY2.rst
new file mode 100644
index 00000000000..b440e8308db
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-24-13-10-35.gh-issue-134210.0IuMY2.rst
@@ -0,0 +1,2 @@
+:func:`curses.window.getch` now correctly handles signals. Patch by Bénédikt
+Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-25-13-46-37.gh-issue-134635.ZlPrlX.rst b/Misc/NEWS.d/next/Library/2025-05-25-13-46-37.gh-issue-134635.ZlPrlX.rst
new file mode 100644
index 00000000000..4cabbf2f896
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-25-13-46-37.gh-issue-134635.ZlPrlX.rst
@@ -0,0 +1,3 @@
+:mod:`zlib`: Allow to combine Adler-32 and CRC-32 checksums via
+:func:`~zlib.adler32_combine` and :func:`~zlib.crc32_combine`. Patch by
+Callum Attryde and Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst
new file mode 100644
index 00000000000..ecdde240b4a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-25-23-23-05.gh-issue-134151.13Wwsb.rst
@@ -0,0 +1,2 @@
+:mod:`email`: Fix :exc:`TypeError` in :func:`email.utils.decode_params`
+when sorting :rfc:`2231` continuations that contain an unnumbered section.
diff --git a/Misc/NEWS.d/next/Library/2025-05-26-11-01-54.gh-issue-134531.my1Fzt.rst b/Misc/NEWS.d/next/Library/2025-05-26-11-01-54.gh-issue-134531.my1Fzt.rst
new file mode 100644
index 00000000000..ee5690df5c4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-26-11-01-54.gh-issue-134531.my1Fzt.rst
@@ -0,0 +1,2 @@
+:mod:`!_hashlib`: Rename internal C functions for :class:`!_hashlib.HASH`
+and :class:`!_hashlib.HASHXOF` objects. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-26-12-31-08.gh-issue-132710.ApU3TZ.rst b/Misc/NEWS.d/next/Library/2025-05-26-12-31-08.gh-issue-132710.ApU3TZ.rst
new file mode 100644
index 00000000000..b7011517aa9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-26-12-31-08.gh-issue-132710.ApU3TZ.rst
@@ -0,0 +1,3 @@
+If possible, ensure that :func:`uuid.getnode` returns the same result even
+across different processes. Previously, the result was constant only within
+the same process. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-26-14-04-39.gh-issue-134696.P04xUa.rst b/Misc/NEWS.d/next/Library/2025-05-26-14-04-39.gh-issue-134696.P04xUa.rst
new file mode 100644
index 00000000000..282eb088b89
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-26-14-04-39.gh-issue-134696.P04xUa.rst
@@ -0,0 +1,5 @@
+Built-in HACL* and OpenSSL implementations of hash function constructors
+now correctly accept the same *documented* named arguments. For instance,
+:func:`~hashlib.md5` could be previously invoked as ``md5(data=data)``
+or ``md5(string=string)`` depending on the underlying implementation
+but these calls were not compatible. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-26-17-06-40.gh-issue-134637.9-3zRL.rst b/Misc/NEWS.d/next/Library/2025-05-26-17-06-40.gh-issue-134637.9-3zRL.rst
new file mode 100644
index 00000000000..2a4d8725210
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-26-17-06-40.gh-issue-134637.9-3zRL.rst
@@ -0,0 +1 @@
+Fix performance regression in calling a :mod:`ctypes` function pointer in :term:`free threading`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-26-22-18-32.gh-issue-134771.RKXpLT.rst b/Misc/NEWS.d/next/Library/2025-05-26-22-18-32.gh-issue-134771.RKXpLT.rst
new file mode 100644
index 00000000000..4b70c6ef398
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-26-22-18-32.gh-issue-134771.RKXpLT.rst
@@ -0,0 +1,2 @@
+The ``time_clockid_converter()`` function now selects correct type for
+``clockid_t`` on Cygwin which fixes a build error.
diff --git a/Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst b/Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst
new file mode 100644
index 00000000000..129d5d98425
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst
@@ -0,0 +1,8 @@
+:ref:`curses.window <curses-window-objects>`: Consistently report failures
+of curses C API calls in Window methods by raising a :exc:`curses.error`.
+This affects :meth:`~curses.window.addch`, :meth:`~curses.window.addnstr`,
+:meth:`~curses.window.addstr`, :meth:`~curses.window.border`,
+:meth:`~curses.window.box`, :meth:`~curses.window.chgat`,
+:meth:`~curses.window.getbkgd`, :meth:`~curses.window.inch`,
+:meth:`~curses.window.insstr` and :meth:`~curses.window.insnstr`.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst b/Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst
new file mode 100644
index 00000000000..e0ef959f125
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst
@@ -0,0 +1,3 @@
+:meth:`curses.window.refresh` and :meth:`curses.window.noutrefresh` now raise
+a :exc:`TypeError` instead of :exc:`curses.error` when called with an incorrect
+number of arguments for :ref:`pads <windows-and-pads>`. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst b/Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst
new file mode 100644
index 00000000000..552b7ca1a71
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst
@@ -0,0 +1,7 @@
+:mod:`curses`: Consistently report failures of curses C API calls in
+module-level methods by raising a :exc:`curses.error`. This affects
+:func:`~curses.assume_default_colors`, :func:`~curses.baudrate`,
+:func:`~curses.cbreak`, :func:`~curses.echo`, :func:`~curses.longname`,
+:func:`~curses.initscr`, :func:`~curses.nl`, :func:`~curses.raw`,
+:func:`~curses.termattrs`, :func:`~curses.termname` and :func:`~curses.unctrl`.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-28-15-53-27.gh-issue-128840.Nur2pB.rst b/Misc/NEWS.d/next/Library/2025-05-28-15-53-27.gh-issue-128840.Nur2pB.rst
new file mode 100644
index 00000000000..faff433aa4b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-28-15-53-27.gh-issue-128840.Nur2pB.rst
@@ -0,0 +1 @@
+Fix parsing long IPv6 addresses with embedded IPv4 address.
diff --git a/Misc/NEWS.d/next/Library/2025-05-28-20-49-29.gh-issue-134857.dVYXVO.rst b/Misc/NEWS.d/next/Library/2025-05-28-20-49-29.gh-issue-134857.dVYXVO.rst
new file mode 100644
index 00000000000..92e38c0bb5a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-28-20-49-29.gh-issue-134857.dVYXVO.rst
@@ -0,0 +1,3 @@
+Improve error report for :mod:`doctest`\ s run with :mod:`unittest`. Remove
+:mod:`!doctest` module frames from tracebacks and redundant newline
+character from a failure message.
diff --git a/Misc/NEWS.d/next/Library/2025-05-29-06-53-40.gh-issue-134885.-_L22o.rst b/Misc/NEWS.d/next/Library/2025-05-29-06-53-40.gh-issue-134885.-_L22o.rst
new file mode 100644
index 00000000000..4b05d42c109
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-29-06-53-40.gh-issue-134885.-_L22o.rst
@@ -0,0 +1,2 @@
+Fix possible crash in the :mod:`compression.zstd` module related to setting
+parameter types. Patch by Jelle Zijlstra.
diff --git a/Misc/NEWS.d/next/Library/2025-05-29-17-39-13.gh-issue-108885.MegCRA.rst b/Misc/NEWS.d/next/Library/2025-05-29-17-39-13.gh-issue-108885.MegCRA.rst
new file mode 100644
index 00000000000..e37cf121f5f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-29-17-39-13.gh-issue-108885.MegCRA.rst
@@ -0,0 +1,3 @@
+Run each example as a subtest in unit tests synthesized by
+:func:`doctest.DocFileSuite` and :func:`doctest.DocTestSuite`.
+Add the :meth:`doctest.DocTestRunner.report_skip` method.
diff --git a/Misc/NEWS.d/next/Library/2025-05-30-09-46-21.gh-issue-134939.Pu3nnm.rst b/Misc/NEWS.d/next/Library/2025-05-30-09-46-21.gh-issue-134939.Pu3nnm.rst
new file mode 100644
index 00000000000..2bda69bff52
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-30-09-46-21.gh-issue-134939.Pu3nnm.rst
@@ -0,0 +1 @@
+Add the :mod:`concurrent.interpreters` module. See :pep:`734`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-30-13-07-29.gh-issue-134718.9Qvhxn.rst b/Misc/NEWS.d/next/Library/2025-05-30-13-07-29.gh-issue-134718.9Qvhxn.rst
new file mode 100644
index 00000000000..922ab168fdd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-30-13-07-29.gh-issue-134718.9Qvhxn.rst
@@ -0,0 +1,2 @@
+:func:`ast.dump` now only omits ``None`` and ``[]`` values if they are
+default values.
diff --git a/Misc/NEWS.d/next/Library/2025-05-30-18-13-48.gh-issue-134718.5FEspx.rst b/Misc/NEWS.d/next/Library/2025-05-30-18-13-48.gh-issue-134718.5FEspx.rst
new file mode 100644
index 00000000000..06c1d5583be
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-30-18-13-48.gh-issue-134718.5FEspx.rst
@@ -0,0 +1 @@
+By default, omit optional ``Load()`` values in :func:`ast.dump`.
diff --git a/Misc/NEWS.d/next/Library/2025-05-31-12-08-12.gh-issue-134970.lgSaxq.rst b/Misc/NEWS.d/next/Library/2025-05-31-12-08-12.gh-issue-134970.lgSaxq.rst
new file mode 100644
index 00000000000..20f53569ef4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-31-12-08-12.gh-issue-134970.lgSaxq.rst
@@ -0,0 +1,3 @@
+Fix the "unknown action" exception in
+:meth:`argparse.ArgumentParser.add_argument_group` to correctly replace the
+action class.
diff --git a/Misc/NEWS.d/next/Library/2025-05-31-15-49-46.gh-issue-134978.mXXuvW.rst b/Misc/NEWS.d/next/Library/2025-05-31-15-49-46.gh-issue-134978.mXXuvW.rst
new file mode 100644
index 00000000000..e75ce1622d6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-31-15-49-46.gh-issue-134978.mXXuvW.rst
@@ -0,0 +1,7 @@
+:mod:`hashlib`: Supporting the ``string`` keyword parameter in hash function
+constructors such as :func:`~hashlib.new` or the direct hash-named constructors
+such as :func:`~hashlib.md5` and :func:`~hashlib.sha256` is now deprecated and
+slated for removal in Python 3.19.
+Prefer passing the initial data as a positional argument for maximum backwards
+compatibility.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-01-14-18-48.gh-issue-135004.cq3-fp.rst b/Misc/NEWS.d/next/Library/2025-06-01-14-18-48.gh-issue-135004.cq3-fp.rst
new file mode 100644
index 00000000000..4c59b0f8e19
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-01-14-18-48.gh-issue-135004.cq3-fp.rst
@@ -0,0 +1,3 @@
+Rewrite and cleanup the internal :mod:`!_blake2` module. Some exception
+messages were changed but their types were left untouched. Patch by Bénédikt
+Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-01-15-13-07.gh-issue-66234.Jw7OdC.rst b/Misc/NEWS.d/next/Library/2025-06-01-15-13-07.gh-issue-66234.Jw7OdC.rst
new file mode 100644
index 00000000000..1defb9a72e0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-01-15-13-07.gh-issue-66234.Jw7OdC.rst
@@ -0,0 +1,3 @@
+Add the ``'m'`` flag for :func:`dbm.gnu.open` which allows to disable the
+use of :manpage:`mmap(2)`. This may harm performance, but improve crash
+tolerance.
diff --git a/Misc/NEWS.d/next/Library/2025-06-02-14-28-30.gh-issue-130662.EIgIR8.rst b/Misc/NEWS.d/next/Library/2025-06-02-14-28-30.gh-issue-130662.EIgIR8.rst
new file mode 100644
index 00000000000..e07200f9a3f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-02-14-28-30.gh-issue-130662.EIgIR8.rst
@@ -0,0 +1,3 @@
+Accept leading zeros in precision and width fields for
+:class:`~fractions.Fraction` formatting, for example ``format(Fraction(1,
+3), '.016f')``.
diff --git a/Misc/NEWS.d/next/Library/2025-06-02-14-36-28.gh-issue-130662.Gpr2GB.rst b/Misc/NEWS.d/next/Library/2025-06-02-14-36-28.gh-issue-130662.Gpr2GB.rst
new file mode 100644
index 00000000000..d97d937376a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-02-14-36-28.gh-issue-130662.Gpr2GB.rst
@@ -0,0 +1,3 @@
++Accept leading zeros in precision and width fields for
++:class:`~decimal.Decimal` formatting, for example ``format(Decimal(1.25),
+'.016f')``.
diff --git a/Misc/NEWS.d/next/Library/2025-06-03-12-59-17.gh-issue-135069.xop30V.rst b/Misc/NEWS.d/next/Library/2025-06-03-12-59-17.gh-issue-135069.xop30V.rst
new file mode 100644
index 00000000000..1affb5e2aad
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-03-12-59-17.gh-issue-135069.xop30V.rst
@@ -0,0 +1,3 @@
+Fix the "Invalid error handling" exception in
+:class:`!encodings.idna.IncrementalDecoder` to correctly replace the
+'errors' parameter.
diff --git a/Misc/NEWS.d/next/Library/2025-06-06-17-34-18.gh-issue-133934.yT1r68.rst b/Misc/NEWS.d/next/Library/2025-06-06-17-34-18.gh-issue-133934.yT1r68.rst
new file mode 100644
index 00000000000..4de7b4cceca
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-06-17-34-18.gh-issue-133934.yT1r68.rst
@@ -0,0 +1 @@
+Improve :mod:`sqlite3` CLI's ``.help`` message.
diff --git a/Misc/NEWS.d/next/Library/2025-06-08-10-22-22.gh-issue-135244.Y2SOTJ.rst b/Misc/NEWS.d/next/Library/2025-06-08-10-22-22.gh-issue-135244.Y2SOTJ.rst
new file mode 100644
index 00000000000..1f70358e64e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-08-10-22-22.gh-issue-135244.Y2SOTJ.rst
@@ -0,0 +1,4 @@
+:mod:`uuid`: when the MAC address cannot be determined, the 48-bit node
+ID is now generated with a cryptographically-secure pseudo-random number
+generator (CSPRNG) as per :rfc:`RFC 9562, §6.10.3 <9562#section-6.10-3>`.
+This affects :func:`~uuid.uuid1` and :func:`~uuid.uuid6`.
diff --git a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst
new file mode 100644
index 00000000000..e1c11e46735
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst
@@ -0,0 +1,3 @@
+:mod:`hashlib`: improve exception messages when an OpenSSL function failed.
+When memory allocation fails on OpenSSL's side, a :exc:`MemoryError` is
+raised instead of a :exc:`ValueError`. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst b/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst
new file mode 100644
index 00000000000..a8fbd48d08a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst
@@ -0,0 +1,6 @@
+Synchronized zipfile.Path with zipp 3.23, including improved performance of
+:meth:`zipfile.Path.open` for non-reading modes, rely on
+:func:`functools.cached_property` to cache values on the instance. Rely on
+``save_method_args`` to save the initialization method arguments. Fixed
+``.name``, ``.stem`` and other basename-based properties on Windows when
+working with a zipfile on disk.
diff --git a/Misc/NEWS.d/next/Library/2025-06-10-00-42-30.gh-issue-135321.UHh9jT.rst b/Misc/NEWS.d/next/Library/2025-06-10-00-42-30.gh-issue-135321.UHh9jT.rst
new file mode 100644
index 00000000000..9e63d8e28b7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-10-00-42-30.gh-issue-135321.UHh9jT.rst
@@ -0,0 +1 @@
+Raise a correct exception for values greater than 0x7fffffff for the ``BINSTRING`` opcode in the C implementation of :mod:`pickle`.
diff --git a/Misc/NEWS.d/next/Library/2025-06-10-16-11-00.gh-issue-133967.P0c24q.rst b/Misc/NEWS.d/next/Library/2025-06-10-16-11-00.gh-issue-133967.P0c24q.rst
new file mode 100644
index 00000000000..1976981727e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-10-16-11-00.gh-issue-133967.P0c24q.rst
@@ -0,0 +1 @@
+Do not normalize :mod:`locale` name 'C.UTF-8' to 'en_US.UTF-8'.
diff --git a/Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst b/Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst
new file mode 100644
index 00000000000..466ba0d232c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst
@@ -0,0 +1,2 @@
+:mod:`multiprocessing`: Flush ``stdout`` and ``stderr`` after preloading
+modules in the ``forkserver``.
diff --git a/Misc/NEWS.d/next/Library/2025-06-12-10-45-02.gh-issue-135368.OjWVHL.rst b/Misc/NEWS.d/next/Library/2025-06-12-10-45-02.gh-issue-135368.OjWVHL.rst
new file mode 100644
index 00000000000..b9973d88a85
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-12-10-45-02.gh-issue-135368.OjWVHL.rst
@@ -0,0 +1,2 @@
+Fix :class:`unittest.mock.Mock` generation on :func:`dataclasses.dataclass`
+objects. Now all special attributes are set as it was before :gh:`124429`.
diff --git a/Misc/NEWS.d/next/Library/2025-06-12-18-15-31.gh-issue-135429.mch75_.rst b/Misc/NEWS.d/next/Library/2025-06-12-18-15-31.gh-issue-135429.mch75_.rst
new file mode 100644
index 00000000000..b5213520a95
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-12-18-15-31.gh-issue-135429.mch75_.rst
@@ -0,0 +1 @@
+Fix the argument mismatch in ``_lsprof`` for ``PY_THROW`` event.
diff --git a/Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst b/Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst
new file mode 100644
index 00000000000..3ef51fa31df
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-14-12-06-55.gh-issue-135487.KdVFff.rst
@@ -0,0 +1,2 @@
+Fix :meth:`!reprlib.Repr.repr_int` when given integers with more than
+:func:`sys.get_int_max_str_digits` digits. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-14-14-19-13.gh-issue-135497.1pzwdA.rst b/Misc/NEWS.d/next/Library/2025-06-14-14-19-13.gh-issue-135497.1pzwdA.rst
new file mode 100644
index 00000000000..d3e81de9dbf
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-14-14-19-13.gh-issue-135497.1pzwdA.rst
@@ -0,0 +1 @@
+Fix :func:`os.getlogin` failing for longer usernames on BSD-based platforms.
diff --git a/Misc/NEWS.d/next/Library/2025-06-15-03-03-22.gh-issue-65697.COdwZd.rst b/Misc/NEWS.d/next/Library/2025-06-15-03-03-22.gh-issue-65697.COdwZd.rst
new file mode 100644
index 00000000000..d374220d02f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-15-03-03-22.gh-issue-65697.COdwZd.rst
@@ -0,0 +1 @@
+:class:`configparser`'s error message when attempting to write an invalid key is now more helpful.
diff --git a/Misc/NEWS.d/next/Library/2025-06-16-15-03-03.gh-issue-135561.mJCN8D.rst b/Misc/NEWS.d/next/Library/2025-06-16-15-03-03.gh-issue-135561.mJCN8D.rst
new file mode 100644
index 00000000000..ee743f16113
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-16-15-03-03.gh-issue-135561.mJCN8D.rst
@@ -0,0 +1,2 @@
+Fix a crash on DEBUG builds when an HACL* HMAC routine fails. Patch by
+Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-17-22-44-19.gh-issue-119180.Ogv8Nj.rst b/Misc/NEWS.d/next/Library/2025-06-17-22-44-19.gh-issue-119180.Ogv8Nj.rst
new file mode 100644
index 00000000000..c5e5d5b4f8d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-17-22-44-19.gh-issue-119180.Ogv8Nj.rst
@@ -0,0 +1,2 @@
+Only fetch globals and locals if necessary in
+:func:`annotationlib.get_annotations`
diff --git a/Misc/NEWS.d/next/Library/2025-06-17-23-13-56.gh-issue-135557.Bfcy4v.rst b/Misc/NEWS.d/next/Library/2025-06-17-23-13-56.gh-issue-135557.Bfcy4v.rst
new file mode 100644
index 00000000000..eabf5ea4aaa
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-17-23-13-56.gh-issue-135557.Bfcy4v.rst
@@ -0,0 +1,2 @@
+Fix races on :mod:`heapq` updates and :class:`list` reads on the :term:`free threaded <free threading>`
+build.
diff --git a/Misc/NEWS.d/next/Library/2025-06-18-11-43-17.gh-issue-135646.r7ekEn.rst b/Misc/NEWS.d/next/Library/2025-06-18-11-43-17.gh-issue-135646.r7ekEn.rst
new file mode 100644
index 00000000000..5fbd751467d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-18-11-43-17.gh-issue-135646.r7ekEn.rst
@@ -0,0 +1 @@
+Raise consistent :exc:`NameError` exceptions in :func:`annotationlib.ForwardRef.evaluate`
diff --git a/Misc/NEWS.d/next/Library/2025-06-18-13-58-13.gh-issue-135645.109nff.rst b/Misc/NEWS.d/next/Library/2025-06-18-13-58-13.gh-issue-135645.109nff.rst
new file mode 100644
index 00000000000..a7764a0105b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-18-13-58-13.gh-issue-135645.109nff.rst
@@ -0,0 +1,2 @@
+Added ``supports_isolated_interpreters`` field to
+:data:`sys.implementation`.
diff --git a/Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst b/Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst
new file mode 100644
index 00000000000..6f395024a9e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst
@@ -0,0 +1 @@
+Make concurrent iterations over :class:`itertools.chain` safe under :term:`free threading`.
diff --git a/Misc/NEWS.d/next/Library/2025-06-20-16-28-47.gh-issue-135759.jne0Zi.rst b/Misc/NEWS.d/next/Library/2025-06-20-16-28-47.gh-issue-135759.jne0Zi.rst
new file mode 100644
index 00000000000..268d7eccdab
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-20-16-28-47.gh-issue-135759.jne0Zi.rst
@@ -0,0 +1,4 @@
+:mod:`hashlib`: reject negative digest lengths in OpenSSL-based SHAKE objects
+by raising a :exc:`ValueError`. Previously, negative lengths were implicitly
+rejected by raising a :exc:`MemoryError` or a :exc:`SystemError`.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-20-17-06-59.gh-issue-90117.GYWVrn.rst b/Misc/NEWS.d/next/Library/2025-06-20-17-06-59.gh-issue-90117.GYWVrn.rst
new file mode 100644
index 00000000000..2bb15cb6d9c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-20-17-06-59.gh-issue-90117.GYWVrn.rst
@@ -0,0 +1 @@
+Speed up :mod:`pprint` for :class:`list` and :class:`tuple`.
diff --git a/Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst b/Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst
new file mode 100644
index 00000000000..ad217b57b4b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst
@@ -0,0 +1,4 @@
+Address bug where it was possible to call
+:func:`xml.etree.ElementTree.ElementTree.write` on an ElementTree object with
+an invalid root element. This behavior blanked the file passed to ``write``
+if it already existed.
diff --git a/Misc/NEWS.d/next/Library/2025-06-22-16-23-44.gh-issue-135815.0DandH.rst b/Misc/NEWS.d/next/Library/2025-06-22-16-23-44.gh-issue-135815.0DandH.rst
new file mode 100644
index 00000000000..0f4a68bf745
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-22-16-23-44.gh-issue-135815.0DandH.rst
@@ -0,0 +1,2 @@
+:mod:`netrc`: skip security checks if :func:`os.getuid` is missing.
+Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-22-22-03-06.gh-issue-135823.iDBg97.rst b/Misc/NEWS.d/next/Library/2025-06-22-22-03-06.gh-issue-135823.iDBg97.rst
new file mode 100644
index 00000000000..5b9d89caae7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-22-22-03-06.gh-issue-135823.iDBg97.rst
@@ -0,0 +1,3 @@
+:mod:`netrc`: improve the error message when the security check for the
+ownership of the default configuration file ``~/.netrc`` fails. Patch by
+Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-23-10-19-11.gh-issue-135855.-J0AGF.rst b/Misc/NEWS.d/next/Library/2025-06-23-10-19-11.gh-issue-135855.-J0AGF.rst
new file mode 100644
index 00000000000..fcf495bdceb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-23-10-19-11.gh-issue-135855.-J0AGF.rst
@@ -0,0 +1,3 @@
+Raise :exc:`TypeError` instead of :exc:`SystemError` when
+:func:`!_interpreters.set___main___attrs` is passed a non-dict object.
+Patch by Brian Schubert.
diff --git a/Misc/NEWS.d/next/Library/2025-06-23-11-04-25.gh-issue-135836.-C-c4v.rst b/Misc/NEWS.d/next/Library/2025-06-23-11-04-25.gh-issue-135836.-C-c4v.rst
new file mode 100644
index 00000000000..f93c9faee58
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-23-11-04-25.gh-issue-135836.-C-c4v.rst
@@ -0,0 +1 @@
+Fix :exc:`IndexError` in :meth:`asyncio.loop.create_connection` that could occur when the Happy Eyeballs algorithm resulted in an empty exceptions list during connection attempts.
diff --git a/Misc/NEWS.d/next/Library/2025-06-24-10-23-37.gh-issue-135853.6xDNOG.rst b/Misc/NEWS.d/next/Library/2025-06-24-10-23-37.gh-issue-135853.6xDNOG.rst
new file mode 100644
index 00000000000..3fea3bc3e7c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-24-10-23-37.gh-issue-135853.6xDNOG.rst
@@ -0,0 +1,2 @@
+:mod:`math`: expose C99 :func:`~math.signbit` function to determine whether
+the sign bit of a floating-point value is set. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-06-24-10-52-35.gh-issue-135836.s37351.rst b/Misc/NEWS.d/next/Library/2025-06-24-10-52-35.gh-issue-135836.s37351.rst
new file mode 100644
index 00000000000..1d1e7a2298c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-24-10-52-35.gh-issue-135836.s37351.rst
@@ -0,0 +1,3 @@
+Fix :exc:`IndexError` in :meth:`asyncio.loop.create_connection` that could
+occur when non-\ :exc:`OSError` exception is raised during connection and
+socket's ``close()`` raises :exc:`!OSError`.
diff --git a/Misc/NEWS.d/next/Library/2025-06-24-14-43-24.gh-issue-135878.Db4roX.rst b/Misc/NEWS.d/next/Library/2025-06-24-14-43-24.gh-issue-135878.Db4roX.rst
new file mode 100644
index 00000000000..969cf2dfa40
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-24-14-43-24.gh-issue-135878.Db4roX.rst
@@ -0,0 +1,3 @@
+Fixes a crash of :class:`types.SimpleNamespace` on :term:`free threading` builds,
+when several threads were calling its :meth:`~object.__repr__` method at the
+same time.
diff --git a/Misc/NEWS.d/next/Library/2025-06-26-11-52-40.gh-issue-53203.TMigBr.rst b/Misc/NEWS.d/next/Library/2025-06-26-11-52-40.gh-issue-53203.TMigBr.rst
new file mode 100644
index 00000000000..ba2fae49fdc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-26-11-52-40.gh-issue-53203.TMigBr.rst
@@ -0,0 +1,2 @@
+Fix :func:`time.strptime` for ``%c`` and ``%x`` formats on locales byn_ER,
+wal_ET and lzh_TW, and for ``%X`` format on locales ar_SA, bg_BG and lzh_TW.
diff --git a/Misc/NEWS.d/next/Library/2025-06-26-17-19-36.gh-issue-105456.eR9oHB.rst b/Misc/NEWS.d/next/Library/2025-06-26-17-19-36.gh-issue-105456.eR9oHB.rst
new file mode 100644
index 00000000000..772403a240a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-26-17-19-36.gh-issue-105456.eR9oHB.rst
@@ -0,0 +1,2 @@
+Removed :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse`
+modules.
diff --git a/Misc/NEWS.d/next/Library/2025-06-26-17-28-49.gh-issue-135995.pPrDCt.rst b/Misc/NEWS.d/next/Library/2025-06-26-17-28-49.gh-issue-135995.pPrDCt.rst
new file mode 100644
index 00000000000..998b3cd85b1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-26-17-28-49.gh-issue-135995.pPrDCt.rst
@@ -0,0 +1 @@
+In the palmos encoding, make byte ``0x9b`` decode to ``›`` (U+203A - SINGLE RIGHT-POINTING ANGLE QUOTATION MARK).
diff --git a/Misc/NEWS.d/next/Library/2025-06-27-09-26-04.gh-issue-87135.33z0UW.rst b/Misc/NEWS.d/next/Library/2025-06-27-09-26-04.gh-issue-87135.33z0UW.rst
new file mode 100644
index 00000000000..4b6bc74cad8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-27-09-26-04.gh-issue-87135.33z0UW.rst
@@ -0,0 +1,3 @@
+Acquiring a :class:`threading.Lock` or :class:`threading.RLock` at interpreter
+shutdown will raise :exc:`PythonFinalizationError` if Python can determine
+that it would otherwise deadlock.
diff --git a/Misc/NEWS.d/next/Library/2025-06-27-13-34-28.gh-issue-136028.RY727g.rst b/Misc/NEWS.d/next/Library/2025-06-27-13-34-28.gh-issue-136028.RY727g.rst
new file mode 100644
index 00000000000..9859df7cf6a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-27-13-34-28.gh-issue-136028.RY727g.rst
@@ -0,0 +1,3 @@
+Fix parsing month names containing "İ" (U+0130, LATIN CAPITAL LETTER I WITH
+DOT ABOVE) in :func:`time.strptime`. This affects locales az_AZ, ber_DZ,
+ber_MA and crh_UA.
diff --git a/Misc/NEWS.d/next/Library/2025-06-30-11-12-24.gh-issue-85702.0Lrbwu.rst b/Misc/NEWS.d/next/Library/2025-06-30-11-12-24.gh-issue-85702.0Lrbwu.rst
new file mode 100644
index 00000000000..fc13eb1d9e0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-30-11-12-24.gh-issue-85702.0Lrbwu.rst
@@ -0,0 +1,3 @@
+If ``zoneinfo._common.load_tzdata`` is given a package without a resource a
+:exc:`zoneinfo.ZoneInfoNotFoundError` is raised rather than a :exc:`PermissionError`.
+Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst b/Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst
new file mode 100644
index 00000000000..801115202d0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-07-02-10-48-21.gh-issue-136193.xfvras.rst
@@ -0,0 +1,2 @@
+Improve :exc:`TypeError` error message, when richcomparing two
+:class:`types.SimpleNamespace` objects.
diff --git a/Misc/NEWS.d/next/Security/2024-02-18-02-53-25.gh-issue-115322.Um2Sjx.rst b/Misc/NEWS.d/next/Security/2024-02-18-02-53-25.gh-issue-115322.Um2Sjx.rst
deleted file mode 100644
index 8eb5c3ed04e..00000000000
--- a/Misc/NEWS.d/next/Security/2024-02-18-02-53-25.gh-issue-115322.Um2Sjx.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-The underlying extension modules behind :mod:`readline`:, :mod:`subprocess`,
-and :mod:`ctypes` now raise audit events on previously uncovered code paths
-that could lead to file system access related to C function calling and
-external binary execution. The ``ctypes.call_function`` audit hook has also
-been fixed to use an unsigned value for its ``function pointer``.
diff --git a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst
new file mode 100644
index 00000000000..b57ec3e70dc
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst
@@ -0,0 +1,2 @@
+Short-circuit the processing of long IPv6 addresses early in :mod:`ipaddress` to prevent excessive
+memory consumption and a minor denial-of-service.
diff --git a/Misc/NEWS.d/next/Security/2025-05-07-22-49-27.gh-issue-133623.fgWkBm.rst b/Misc/NEWS.d/next/Security/2025-05-07-22-49-27.gh-issue-133623.fgWkBm.rst
new file mode 100644
index 00000000000..09279bbfb4f
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-05-07-22-49-27.gh-issue-133623.fgWkBm.rst
@@ -0,0 +1 @@
+Indicate through :data:`ssl.HAS_PSK_TLS13` whether the :mod:`ssl` module supports "External PSKs" in TLSv1.3, as described in RFC 9258. Patch by Will Childs-Klein.
diff --git a/Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst b/Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst
new file mode 100644
index 00000000000..39d2f1e1a89
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst
@@ -0,0 +1,2 @@
+Fix use-after-free in the "unicode-escape" decoder with a non-"strict" error
+handler.
diff --git a/Misc/NEWS.d/next/Security/2025-06-02-11-32-23.gh-issue-135034.RLGjbp.rst b/Misc/NEWS.d/next/Security/2025-06-02-11-32-23.gh-issue-135034.RLGjbp.rst
new file mode 100644
index 00000000000..08a0087e203
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-06-02-11-32-23.gh-issue-135034.RLGjbp.rst
@@ -0,0 +1,6 @@
+Fixes multiple issues that allowed ``tarfile`` extraction filters
+(``filter="data"`` and ``filter="tar"``) to be bypassed using crafted
+symlinks and hard links.
+
+Addresses :cve:`2024-12718`, :cve:`2025-4138`, :cve:`2025-4330`, and :cve:`2025-4517`.
+
diff --git a/Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst b/Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst
new file mode 100644
index 00000000000..cf9aa8dbdf2
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst
@@ -0,0 +1,4 @@
+Fix quadratic complexity in processing specially crafted input in
+:class:`html.parser.HTMLParser`. End-of-file errors are now handled according
+to the HTML5 specs -- comments and declarations are automatically closed,
+tags are ignored.
diff --git a/Misc/NEWS.d/next/Security/2025-06-25-14-13-39.gh-issue-135661.idjQ0B.rst b/Misc/NEWS.d/next/Security/2025-06-25-14-13-39.gh-issue-135661.idjQ0B.rst
new file mode 100644
index 00000000000..b6f9e104e44
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-06-25-14-13-39.gh-issue-135661.idjQ0B.rst
@@ -0,0 +1,25 @@
+Fix parsing start and end tags in :class:`html.parser.HTMLParser`
+according to the HTML5 standard.
+
+* Whitespaces no longer accepted between ``</`` and the tag name.
+ E.g. ``</ script>`` does not end the script section.
+
+* Vertical tabulation (``\v``) and non-ASCII whitespaces no longer recognized
+ as whitespaces. The only whitespaces are ``\t\n\r\f`` and space.
+
+* Null character (U+0000) no longer ends the tag name.
+
+* Attributes and slashes after the tag name in end tags are now ignored,
+ instead of terminating after the first ``>`` in quoted attribute value.
+ E.g. ``</script/foo=">"/>``.
+
+* Multiple slashes and whitespaces between the last attribute and closing ``>``
+ are now ignored in both start and end tags. E.g. ``<a foo=bar/ //>``.
+
+* Multiple ``=`` between attribute name and value are no longer collapsed.
+ E.g. ``<a foo==bar>`` produces attribute "foo" with value "=bar".
+
+* Whitespaces between the ``=`` separator and attribute name or value are no
+ longer ignored. E.g. ``<a foo =bar>`` produces two attributes "foo" and
+ "=bar", both with value None; ``<a foo= bar>`` produces two attributes:
+ "foo" with value "" and "bar" with value None.
diff --git a/Misc/NEWS.d/next/Security/2025-06-27-21-23-19.gh-issue-136053.QZxcee.rst b/Misc/NEWS.d/next/Security/2025-06-27-21-23-19.gh-issue-136053.QZxcee.rst
new file mode 100644
index 00000000000..93caed3aa3b
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2025-06-27-21-23-19.gh-issue-136053.QZxcee.rst
@@ -0,0 +1 @@
+:mod:`marshal`: fix a possible crash when deserializing :class:`slice` objects.
diff --git a/Misc/NEWS.d/next/Tests/2025-03-17-19-47-27.gh-issue-131290.NyCIXR.rst b/Misc/NEWS.d/next/Tests/2025-03-17-19-47-27.gh-issue-131290.NyCIXR.rst
deleted file mode 100644
index c6f0e0cc256..00000000000
--- a/Misc/NEWS.d/next/Tests/2025-03-17-19-47-27.gh-issue-131290.NyCIXR.rst
+++ /dev/null
@@ -1 +0,0 @@
-Tests in :file:`Lib/test` can now be correctly executed as standalone scripts.
diff --git a/Misc/NEWS.d/next/Tests/2025-04-18-14-00-38.gh-issue-132678.j_ZKf2.rst b/Misc/NEWS.d/next/Tests/2025-04-18-14-00-38.gh-issue-132678.j_ZKf2.rst
deleted file mode 100644
index e079cd46a7f..00000000000
--- a/Misc/NEWS.d/next/Tests/2025-04-18-14-00-38.gh-issue-132678.j_ZKf2.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Add ``--prioritize`` to ``-m test``. This option allows the user to specify
-which selected tests should execute first, even if the order is otherwise
-randomized. This is particularly useful for tests that run the longest.
diff --git a/Misc/NEWS.d/next/Tests/2025-04-23-02-23-37.gh-issue-109981.IX3k8p.rst b/Misc/NEWS.d/next/Tests/2025-04-23-02-23-37.gh-issue-109981.IX3k8p.rst
deleted file mode 100644
index 17561525830..00000000000
--- a/Misc/NEWS.d/next/Tests/2025-04-23-02-23-37.gh-issue-109981.IX3k8p.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-The test helper that counts the list of open file descriptors now uses the
-optimised ``/dev/fd`` approach on all Apple platforms, not just macOS.
-This avoids crashes caused by guarded file descriptors.
diff --git a/Misc/NEWS.d/next/Tests/2025-04-23-12-40-27.gh-issue-91048.WJQCdV.rst b/Misc/NEWS.d/next/Tests/2025-04-23-12-40-27.gh-issue-91048.WJQCdV.rst
deleted file mode 100644
index 11171739a6c..00000000000
--- a/Misc/NEWS.d/next/Tests/2025-04-23-12-40-27.gh-issue-91048.WJQCdV.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Add ability to externally inspect all pending asyncio tasks, even if no task
-is currently entered on the event loop.
diff --git a/Misc/NEWS.d/next/Tests/2025-04-29-14-56-37.gh-issue-133131.1pchjl.rst b/Misc/NEWS.d/next/Tests/2025-04-29-14-56-37.gh-issue-133131.1pchjl.rst
deleted file mode 100644
index 30b0f18f8dc..00000000000
--- a/Misc/NEWS.d/next/Tests/2025-04-29-14-56-37.gh-issue-133131.1pchjl.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-The iOS testbed will now select the most recently released "SE-class" device
-for testing if a device isn't explicitly specified.
diff --git a/Misc/NEWS.d/next/Tests/2025-05-08-15-06-01.gh-issue-133639.50-kbV.rst b/Misc/NEWS.d/next/Tests/2025-05-08-15-06-01.gh-issue-133639.50-kbV.rst
new file mode 100644
index 00000000000..68826cd95fa
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-05-08-15-06-01.gh-issue-133639.50-kbV.rst
@@ -0,0 +1,2 @@
+Fix ``TestPyReplAutoindent.test_auto_indent_default()`` doesn't run
+``input_code``.
diff --git a/Misc/NEWS.d/next/Tests/2025-05-09-04-11-06.gh-issue-133682.-_lwo3.rst b/Misc/NEWS.d/next/Tests/2025-05-09-04-11-06.gh-issue-133682.-_lwo3.rst
new file mode 100644
index 00000000000..ebd17f73ca5
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-05-09-04-11-06.gh-issue-133682.-_lwo3.rst
@@ -0,0 +1 @@
+Fixed test case ``test.test_annotationlib.TestStringFormat.test_displays`` which ensures proper handling of complex data structures (lists, sets, dictionaries, and tuples) in string annotations.
diff --git a/Misc/NEWS.d/next/Tests/2025-05-09-14-54-48.gh-issue-133744.LCquu0.rst b/Misc/NEWS.d/next/Tests/2025-05-09-14-54-48.gh-issue-133744.LCquu0.rst
new file mode 100644
index 00000000000..f19186db1ad
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-05-09-14-54-48.gh-issue-133744.LCquu0.rst
@@ -0,0 +1,3 @@
+Fix multiprocessing interrupt test. Add an event to synchronize the parent
+process with the child process: wait until the child process starts
+sleeping. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst b/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
new file mode 100644
index 00000000000..42e4a01c0cc
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
@@ -0,0 +1,2 @@
+Expose log formatter to users in TestCase.assertLogs.
+:func:`unittest.TestCase.assertLogs` will now optionally accept a formatter that will be used to format the strings in output if provided.
diff --git a/Misc/NEWS.d/next/Tests/2025-06-04-13-07-44.gh-issue-135120.NapnZT.rst b/Misc/NEWS.d/next/Tests/2025-06-04-13-07-44.gh-issue-135120.NapnZT.rst
new file mode 100644
index 00000000000..772173774b1
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-06-04-13-07-44.gh-issue-135120.NapnZT.rst
@@ -0,0 +1 @@
+Add :func:`!test.support.subTests`.
diff --git a/Misc/NEWS.d/next/Library/2025-04-13-19-17-14.gh-issue-70145.nJ2MKg.rst b/Misc/NEWS.d/next/Tests/2025-06-14-13-20-17.gh-issue-135489.Uh0yVO.rst
index 69833981c80..2c9ecc51829 100644
--- a/Misc/NEWS.d/next/Library/2025-04-13-19-17-14.gh-issue-70145.nJ2MKg.rst
+++ b/Misc/NEWS.d/next/Tests/2025-06-14-13-20-17.gh-issue-135489.Uh0yVO.rst
@@ -1,2 +1 @@
-Add support for channels in Bluetooth HCI protocol
-(:const:`~socket.BTPROTO_HCI`).
+Show verbose output for failing tests during PGO profiling step with --enable-optimizations.
diff --git a/Misc/NEWS.d/next/Tests/2025-06-17-08-48-08.gh-issue-132815.CY1Esu.rst b/Misc/NEWS.d/next/Tests/2025-06-17-08-48-08.gh-issue-132815.CY1Esu.rst
new file mode 100644
index 00000000000..5b7485ce2d6
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-06-17-08-48-08.gh-issue-132815.CY1Esu.rst
@@ -0,0 +1 @@
+Fix test__opcode: add ``JUMP_BACKWARD`` to specialization stats.
diff --git a/Misc/NEWS.d/next/Tests/2025-06-19-15-29-38.gh-issue-135494.FVl9a0.rst b/Misc/NEWS.d/next/Tests/2025-06-19-15-29-38.gh-issue-135494.FVl9a0.rst
new file mode 100644
index 00000000000..832d1fe033e
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-06-19-15-29-38.gh-issue-135494.FVl9a0.rst
@@ -0,0 +1,2 @@
+Fix regrtest to support excluding tests from ``--pgo`` tests. Patch by
+Victor Stinner.
diff --git a/Misc/NEWS.d/next/Tests/2025-06-26-15-15-35.gh-issue-135966.EBpF8Y.rst b/Misc/NEWS.d/next/Tests/2025-06-26-15-15-35.gh-issue-135966.EBpF8Y.rst
new file mode 100644
index 00000000000..8dc007431f3
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-06-26-15-15-35.gh-issue-135966.EBpF8Y.rst
@@ -0,0 +1 @@
+The iOS testbed now handles the ``app_packages`` folder as a site directory.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-02-16-19-00-00.gh-issue-130195.19274.rst b/Misc/NEWS.d/next/Tools-Demos/2025-02-16-19-00-00.gh-issue-130195.19274.rst
deleted file mode 100644
index 814f9090953..00000000000
--- a/Misc/NEWS.d/next/Tools-Demos/2025-02-16-19-00-00.gh-issue-130195.19274.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add warning messages when :program:`pygettext` unimplemented ``-a/--extract-all`` option is called.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-03-10-08-19-22.gh-issue-130453.9B0x8k.rst b/Misc/NEWS.d/next/Tools-Demos/2025-03-10-08-19-22.gh-issue-130453.9B0x8k.rst
deleted file mode 100644
index fdab48a2f7b..00000000000
--- a/Misc/NEWS.d/next/Tools-Demos/2025-03-10-08-19-22.gh-issue-130453.9B0x8k.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Allow passing multiple keyword arguments with the same function name in
-:program:`pygettext`.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst b/Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst
new file mode 100644
index 00000000000..546ed2a56b6
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2025-05-19-14-57-46.gh-issue-134215.sbdDK6.rst
@@ -0,0 +1 @@
+:term:`REPL` import autocomplete only suggests private modules when explicitly specified.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-06-11-12-14-06.gh-issue-135379.25ttXq.rst b/Misc/NEWS.d/next/Tools-Demos/2025-06-11-12-14-06.gh-issue-135379.25ttXq.rst
new file mode 100644
index 00000000000..25599a865b7
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2025-06-11-12-14-06.gh-issue-135379.25ttXq.rst
@@ -0,0 +1,4 @@
+The cases generator no longer accepts type annotations on stack items.
+Conversions to non-default types are now done explictly in bytecodes.c and
+optimizer_bytecodes.c. This will simplify code generation for top-of-stack
+caching and other future features.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-06-26-15-58-13.gh-issue-135968.C4v_-W.rst b/Misc/NEWS.d/next/Tools-Demos/2025-06-26-15-58-13.gh-issue-135968.C4v_-W.rst
new file mode 100644
index 00000000000..1c0b3825c71
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2025-06-26-15-58-13.gh-issue-135968.C4v_-W.rst
@@ -0,0 +1 @@
+Stubs for ``strip`` are now provided as part of an iOS install.
diff --git a/Misc/NEWS.d/next/Windows/2025-03-27-16-22-58.gh-issue-127405.aASs2Z.rst b/Misc/NEWS.d/next/Windows/2025-03-27-16-22-58.gh-issue-127405.aASs2Z.rst
deleted file mode 100644
index a6d5985ff4c..00000000000
--- a/Misc/NEWS.d/next/Windows/2025-03-27-16-22-58.gh-issue-127405.aASs2Z.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add ``ABIFLAGS`` to :func:`sysconfig.get_config_vars` on Windows. Patch by Xuehai Pan.
diff --git a/Misc/NEWS.d/next/Windows/2025-03-31-15-37-57.gh-issue-131942.jip_aL.rst b/Misc/NEWS.d/next/Windows/2025-03-31-15-37-57.gh-issue-131942.jip_aL.rst
new file mode 100644
index 00000000000..837f7265bba
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-03-31-15-37-57.gh-issue-131942.jip_aL.rst
@@ -0,0 +1 @@
+Use the Python-specific :c:macro:`Py_DEBUG` macro rather than :c:macro:`!_DEBUG` in Windows-related C code. Patch by Xuehai Pan.
diff --git a/Misc/NEWS.d/next/Windows/2025-04-25-13-34-27.gh-issue-132930.6MJumW.rst b/Misc/NEWS.d/next/Windows/2025-04-25-13-34-27.gh-issue-132930.6MJumW.rst
deleted file mode 100644
index bdf6cca2658..00000000000
--- a/Misc/NEWS.d/next/Windows/2025-04-25-13-34-27.gh-issue-132930.6MJumW.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Marks the installer for Windows as deprecated and updates documentation to
-cover the new Python install manager.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-07-08-19-15.gh-issue-133537.yzf963.rst b/Misc/NEWS.d/next/Windows/2025-05-07-08-19-15.gh-issue-133537.yzf963.rst
new file mode 100644
index 00000000000..94e45f9d8ee
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-07-08-19-15.gh-issue-133537.yzf963.rst
@@ -0,0 +1 @@
+Avoid using console I/O in WinAPI partitions that don’t support it
diff --git a/Misc/NEWS.d/next/Windows/2025-05-07-09-02-19.gh-issue-133562.lqqNW1.rst b/Misc/NEWS.d/next/Windows/2025-05-07-09-02-19.gh-issue-133562.lqqNW1.rst
new file mode 100644
index 00000000000..884425b839a
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-07-09-02-19.gh-issue-133562.lqqNW1.rst
@@ -0,0 +1 @@
+Disable handling of security descriptors by :func:`os.mkdir` with mode ``0o700`` on WinAPI partitions that do not support it. This only affects custom builds for specialized targets.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-07-11-25-29.gh-issue-133568.oYV0d8.rst b/Misc/NEWS.d/next/Windows/2025-05-07-11-25-29.gh-issue-133568.oYV0d8.rst
new file mode 100644
index 00000000000..1e2a5b582cb
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-07-11-25-29.gh-issue-133568.oYV0d8.rst
@@ -0,0 +1 @@
+Fix compile error when using a WinAPI partition that doesn't support the RPC runtime library.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-07-11-45-30.gh-issue-133572.Xc2zxH.rst b/Misc/NEWS.d/next/Windows/2025-05-07-11-45-30.gh-issue-133572.Xc2zxH.rst
new file mode 100644
index 00000000000..993eceab0b7
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-07-11-45-30.gh-issue-133572.Xc2zxH.rst
@@ -0,0 +1 @@
+Avoid LsaNtStatus to WinError conversion on unsupported WinAPI partitions.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-07-13-04-22.gh-issue-133580.jBMujJ.rst b/Misc/NEWS.d/next/Windows/2025-05-07-13-04-22.gh-issue-133580.jBMujJ.rst
new file mode 100644
index 00000000000..414a6f87e65
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-07-13-04-22.gh-issue-133580.jBMujJ.rst
@@ -0,0 +1 @@
+Fix :func:`sys.getwindowsversion` failing without setting an exception when called on some WinAPI partitions.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-08-19-07-26.gh-issue-133626.yFTKYK.rst b/Misc/NEWS.d/next/Windows/2025-05-08-19-07-26.gh-issue-133626.yFTKYK.rst
new file mode 100644
index 00000000000..6c80d96bb83
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-08-19-07-26.gh-issue-133626.yFTKYK.rst
@@ -0,0 +1,2 @@
+Ensures packages are not accidentally bundled into the traditional
+installer.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-13-13-25-27.gh-issue-133779.-YcTBz.rst b/Misc/NEWS.d/next/Windows/2025-05-13-13-25-27.gh-issue-133779.-YcTBz.rst
new file mode 100644
index 00000000000..550600d5eeb
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-13-13-25-27.gh-issue-133779.-YcTBz.rst
@@ -0,0 +1,6 @@
+Reverts the change to generate different :file:`pyconfig.h` files based on
+compiler settings, as it was frequently causing extension builds to break.
+In particular, the ``Py_GIL_DISABLED`` preprocessor variable must now always
+be defined explicitly when compiling for the experimental free-threaded
+runtime. The :func:`sysconfig.get_config_var` function can be used to
+determine whether the current runtime was compiled with that flag or not.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-19-03-02-04.gh-issue-76023.vHOf6M.rst b/Misc/NEWS.d/next/Windows/2025-05-19-03-02-04.gh-issue-76023.vHOf6M.rst
new file mode 100644
index 00000000000..958f4f4a440
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-19-03-02-04.gh-issue-76023.vHOf6M.rst
@@ -0,0 +1 @@
+Make :func:`os.path.realpath` ignore Windows error 1005 when in non-strict mode.
diff --git a/Misc/NEWS.d/next/Windows/2025-05-20-21-43-20.gh-issue-130727.-69t4D.rst b/Misc/NEWS.d/next/Windows/2025-05-20-21-43-20.gh-issue-130727.-69t4D.rst
new file mode 100644
index 00000000000..dc10b3e62c8
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-05-20-21-43-20.gh-issue-130727.-69t4D.rst
@@ -0,0 +1,2 @@
+Fix a race in internal calls into WMI that can result in an "invalid handle"
+exception under high load. Patch by Chris Eibl.
diff --git a/Misc/NEWS.d/next/Windows/2025-06-03-18-26-54.gh-issue-135099.Q9usKm.rst b/Misc/NEWS.d/next/Windows/2025-06-03-18-26-54.gh-issue-135099.Q9usKm.rst
new file mode 100644
index 00000000000..36e70b1c0d8
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2025-06-03-18-26-54.gh-issue-135099.Q9usKm.rst
@@ -0,0 +1,2 @@
+Fix a crash that could occur on Windows when a background thread waits on a
+:c:type:`PyMutex` while the main thread is shutting down the interpreter.
diff --git a/Misc/externals.spdx.json b/Misc/externals.spdx.json
index 13be10ca039..69f3beec82e 100644
--- a/Misc/externals.spdx.json
+++ b/Misc/externals.spdx.json
@@ -190,6 +190,27 @@
"name": "zlib-ng",
"primaryPackagePurpose": "SOURCE",
"versionInfo": "2.2.4"
+ },
+ {
+ "SPDXID": "SPDXRef-PACKAGE-zstd",
+ "checksums": [
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "f24b52470d12f466e9fa4fcc94e6c530625ada51d7b36de7fdc6ed7e6f499c8e"
+ }
+ ],
+ "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/zstd-1.5.7.tar.gz",
+ "externalRefs": [
+ {
+ "referenceCategory": "SECURITY",
+ "referenceLocator": "cpe:2.3:a:facebook:zstandard:1.5.7:*:*:*:*:*:*:*",
+ "referenceType": "cpe23Type"
+ }
+ ],
+ "licenseConcluded": "NOASSERTION",
+ "name": "zstd",
+ "primaryPackagePurpose": "SOURCE",
+ "versionInfo": "1.5.7"
}
],
"spdxVersion": "SPDX-2.3"
diff --git a/Misc/python.man b/Misc/python.man
index fa88a2586dc..612e2bbf568 100644
--- a/Misc/python.man
+++ b/Misc/python.man
@@ -344,6 +344,10 @@ Set implementation-specific option. The following options are available:
application. Typical usage is
\fBpython3 \-X importtime \-c 'import asyncio'\fR
+ \fB\-X importtime=2\fR enables additional output that indicates when an
+ imported module has already been loaded. In such cases, the string
+ \fBcached\fR will be printed in both time columns.
+
\fB\-X faulthandler\fR: enable faulthandler
\fB\-X frozen_modules=\fR[\fBon\fR|\fBoff\fR]: whether or not frozen modules
@@ -525,11 +529,11 @@ See also the \fB\-X frozen_modules\fR option.
If this variable is set to 1, the global interpreter lock (GIL) will be forced
on. Setting it to 0 forces the GIL off. Only available in builds configured
with \fB\-\-disable\-gil\fP.
+.IP
+This is equivalent to the \fB\-X gil\fR option.
.IP PYTHON_HISTORY
This environment variable can be used to set the location of a history file
(on Unix, it is \fI~/.python_history\fP by default).
-.IP
-This is equivalent to the \fB\-X gil\fR option.
.IP PYTHONNODEBUGRANGES
If this variable is set, it disables the inclusion of the tables mapping
extra location information (end line, start column offset and end column
@@ -648,9 +652,10 @@ See also the \fB\-X perf\fR option.
.IP PYTHONPLATLIBDIR
Override sys.platlibdir.
.IP PYTHONPROFILEIMPORTTIME
-If this environment variable is set to a non-empty string, Python will
-show how long each import takes. This is exactly equivalent to setting
-\fB\-X importtime\fP on the command line.
+If this environment variable is set to \fB1\fR, Python will show
+how long each import takes. If set to \fB2\fR, Python will include output for
+imported modules that have already been loaded.
+This is exactly equivalent to setting \fB\-X importtime\fP on the command line.
.IP PYTHONPYCACHEPREFIX
If this is set, Python will write \fB.pyc\fR files in a mirror directory tree
at this path, instead of in \fB__pycache__\fR directories within the source
diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json
index 4a697d047ca..738b3390885 100644
--- a/Misc/sbom.spdx.json
+++ b/Misc/sbom.spdx.json
@@ -314,11 +314,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "4b6e7696e8d84f322fb24b1fbb08ccb9b0e7d51b"
+ "checksumValue": "808af7ff8a2cb2b4ef3a9ce3dbfef58d90828c9f"
},
{
"algorithm": "SHA256",
- "checksumValue": "50a65a34a7a7569eedf7fa864a7892eeee5840a7fdf6fa8f1e87d42c65f6c877"
+ "checksumValue": "6a492aa586f2d10b1b300ce8ce4c72c976ff7548fee667aded2253f99969ac87"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_Blake2b.c"
@@ -342,11 +342,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "0f75e44a42775247a46acc2beaa6bae8f199a3d9"
+ "checksumValue": "1bb072f2be9e5d194274fdcc87825cb094e4b32e"
},
{
"algorithm": "SHA256",
- "checksumValue": "03b612c24193464ed6848aeebbf44f9266b78ec6eed2486056211cde8992c49a"
+ "checksumValue": "9eb22953ce60dde9dc970fec9dfce9d94235f4b7ccd8f0151cad4707dc835d1d"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_Blake2b_Simd256.c"
@@ -384,11 +384,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "65bf44140691b046dcfed3ab1576dbf8bbf96dc5"
+ "checksumValue": "cdd6e9ca86dbede92d1a47b9224d2af70c599a71"
},
{
"algorithm": "SHA256",
- "checksumValue": "0f98959dafffce039ade9d296f7a05bed151c9c512498f48e4b326a5523a240b"
+ "checksumValue": "f3204f3e60734d811b6630f879b69ce54eaf367f3fca5889c1026e7a1f3ee1e4"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_Blake2s.c"
@@ -412,11 +412,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "0da9782455923aede8d8dce9dfdc38f4fc1de572"
+ "checksumValue": "4056bb6e3ed184400d1610bdfd4260b3fd05ccbb"
},
{
"algorithm": "SHA256",
- "checksumValue": "2d17ae768fd3d7d6decddd8b4aaf23ce02a809ee62bb98da32c8a7f54acf92d0"
+ "checksumValue": "c93746df2f219cbb1634ee6fb0ab1c4cbd381d1f36c637114c68346c9935326d"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_Blake2s_Simd128.c"
@@ -454,11 +454,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "38e8d96ef1879480780494058a93cec181f8d6d7"
+ "checksumValue": "61f678cd9234c6eab5d4409ae66f470b068b959b"
},
{
"algorithm": "SHA256",
- "checksumValue": "61e77d2063cf60c96e9ce06af215efe5d42c43026833bffed5732326fe97ed1e"
+ "checksumValue": "5b91ed0339074e2e546119833398e2cdb7190c33a59c405bf43b2417c789547d"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_MD5.c"
@@ -482,11 +482,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "986dd5ba0b34d15f3e5e5c656979aea1b502e8aa"
+ "checksumValue": "c7fc5c9721caf37c5a24c9beff27b0ac2ed68cc9"
},
{
"algorithm": "SHA256",
- "checksumValue": "38d5f1f2e67a0eb30789f81fc56c07a6e7246e2b1be6c65485bcca1dcd0e0806"
+ "checksumValue": "ce08721d491f3b8a9bd4cde6c27bfcc8fc01471512ccca4bd3c0b764cb551d29"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_SHA1.c"
@@ -510,11 +510,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "f732a6710fe3e13cd28130f0f20504e347d1c412"
+ "checksumValue": "fffe8c4f67669ac8ccd87c2e0f95db2427481df1"
},
{
"algorithm": "SHA256",
- "checksumValue": "86cf32e4d1f3ba93a94108271923fdafe2204447792a918acf4a2250f352dbde"
+ "checksumValue": "f8af382de7e29a73c726f9c70770498ddd99e2c4702489ed6e634f0b68597c95"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_SHA2.c"
@@ -538,11 +538,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "50f75337b31f509b5bfcc7ebb3d066b82a0f1b33"
+ "checksumValue": "77d3d879dfa5949030bca0e8ee75d3d369ec54a7"
},
{
"algorithm": "SHA256",
- "checksumValue": "c9e1442899e5b902fa39f413f1a3131f7ab5c2283d5100dc8ac675a7d5ebbdf1"
+ "checksumValue": "8744f5b6e054c3e5c44f413a60f9154b76dd7230135dcee26fd063755ec64be1"
}
],
"fileName": "Modules/_hacl/Hacl_Hash_SHA3.c"
@@ -566,11 +566,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "8140310f505bb2619a749777a91487d666237bcf"
+ "checksumValue": "417e68ac8498cb2f93a06a19003ea1cc3f0f6753"
},
{
"algorithm": "SHA256",
- "checksumValue": "9d95e6a651c22185d9b7c38f363d30159f810e6fcdc2208f29492837ed891e82"
+ "checksumValue": "843db4bba78a476d4d53dabe4eed5c9235f490ccc9fdaf19e22af488af858920"
}
],
"fileName": "Modules/_hacl/Hacl_Streaming_HMAC.c"
@@ -608,11 +608,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "c9651ef21479c4d8a3b04c5baa1902866dbb1cdf"
+ "checksumValue": "0a0b7f3714167ad45ddf5a6a48d76f525a119c9c"
},
{
"algorithm": "SHA256",
- "checksumValue": "e039c82ba670606ca111573942baad800f75da467abbc74cd7d1fe175ebcdfaf"
+ "checksumValue": "135d4afb4812468885c963c9c87a55ba5fae9181df4431af5fbad08588dda229"
}
],
"fileName": "Modules/_hacl/Lib_Memzero0.c"
@@ -622,11 +622,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "eaa543c778300238dc23034aafeada0951154af1"
+ "checksumValue": "911c97a0f24067635b164fbca49da76055f192cd"
},
{
"algorithm": "SHA256",
- "checksumValue": "3fd2552d527a23110d61ad2811c774810efb1eaee008f136c2a0d609daa77f5b"
+ "checksumValue": "972b5111ebada8e11dd60df3119da1af505fd3e0b6c782ead6cab7f1daf134f1"
}
],
"fileName": "Modules/_hacl/include/krml/FStar_UInt128_Verified.h"
@@ -930,11 +930,11 @@
"checksums": [
{
"algorithm": "SHA1",
- "checksumValue": "4a0bdb9496d49bbfa3ad50bb7854d8f099e84891"
+ "checksumValue": "682b069d3888f3e8f8cc90f1a49bac9d1a5903d2"
},
{
"algorithm": "SHA256",
- "checksumValue": "b1a45149239ee7af7de769a3e9339950d47c199bb9eaa10edce8a00fde603b12"
+ "checksumValue": "8551d2c3fde03b92c6fab5febde00347bd9184ea0085077976863c7836e9669d"
}
],
"fileName": "Modules/_hacl/python_hacl_namespaces.h"
@@ -1752,14 +1752,14 @@
"checksums": [
{
"algorithm": "SHA256",
- "checksumValue": "02dfcf0c79d488b120d7f2c2a0f9206301c7927ed5106545e0b6f2aef88da76a"
+ "checksumValue": "39f6fd4f2fe98aecc995a2fd980fae0e0835cef6e563ebbf25f69d3d3102bafd"
}
],
- "downloadLocation": "https://github.com/hacl-star/hacl-star/archive/7720f6d4fc0468a99d5ea6120976bcc271e42727.zip",
+ "downloadLocation": "https://github.com/hacl-star/hacl-star/archive/4ef25b547b377dcef855db4289c6a00580e7221c.zip",
"externalRefs": [
{
"referenceCategory": "SECURITY",
- "referenceLocator": "cpe:2.3:a:hacl-star:hacl-star:7720f6d4fc0468a99d5ea6120976bcc271e42727:*:*:*:*:*:*:*",
+ "referenceLocator": "cpe:2.3:a:hacl-star:hacl-star:4ef25b547b377dcef855db4289c6a00580e7221c:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
}
],
@@ -1767,7 +1767,7 @@
"name": "hacl-star",
"originator": "Organization: HACL* Developers",
"primaryPackagePurpose": "SOURCE",
- "versionInfo": "7720f6d4fc0468a99d5ea6120976bcc271e42727"
+ "versionInfo": "4ef25b547b377dcef855db4289c6a00580e7221c"
},
{
"SPDXID": "SPDXRef-PACKAGE-macholib",
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index d3e1f0db057..1f323cc0397 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -888,6 +888,7 @@
added = '3.2'
[function.PyImport_ImportModuleNoBlock]
added = '3.2'
+ abi_only = true
[function.PyImport_ReloadModule]
added = '3.2'
[function.PyInterpreterState_Clear]
@@ -1462,14 +1463,18 @@
added = '3.2'
[function.PyUnicode_AsDecodedObject]
added = '3.2'
+ abi_only = true
[function.PyUnicode_AsDecodedUnicode]
added = '3.2'
+ abi_only = true
[function.PyUnicode_AsEncodedObject]
added = '3.2'
+ abi_only = true
[function.PyUnicode_AsEncodedString]
added = '3.2'
[function.PyUnicode_AsEncodedUnicode]
added = '3.2'
+ abi_only = true
[function.PyUnicode_AsLatin1String]
added = '3.2'
[function.PyUnicode_AsRawUnicodeEscapeString]
@@ -1632,18 +1637,24 @@
added = '3.2'
[function.Py_GetExecPrefix]
added = '3.2'
+ abi_only = true
[function.Py_GetPath]
added = '3.2'
+ abi_only = true
[function.Py_GetPlatform]
added = '3.2'
[function.Py_GetPrefix]
added = '3.2'
+ abi_only = true
[function.Py_GetProgramFullPath]
added = '3.2'
+ abi_only = true
[function.Py_GetProgramName]
added = '3.2'
+ abi_only = true
[function.Py_GetPythonHome]
added = '3.2'
+ abi_only = true
[function.Py_GetRecursionLimit]
added = '3.2'
[function.Py_GetVersion]
@@ -2564,3 +2575,11 @@
added = '3.14'
[function.Py_PACK_VERSION]
added = '3.14'
+[function.PySys_GetAttr]
+ added = '3.15'
+[function.PySys_GetAttrString]
+ added = '3.15'
+[function.PySys_GetOptionalAttr]
+ added = '3.15'
+[function.PySys_GetOptionalAttrString]
+ added = '3.15'
diff --git a/Modules/Setup b/Modules/Setup
index 65c22d48ba0..a066982df1a 100644
--- a/Modules/Setup
+++ b/Modules/Setup
@@ -201,6 +201,7 @@ PYTHONPATH=$(COREPYTHONPATH)
#_gdbm _gdbmmodule.c -lgdbm
#_lzma _lzmamodule.c -llzma
#_uuid _uuidmodule.c -luuid
+#_zstd _zstd/_zstdmodule.c -lzstd -I$(srcdir)/Modules/_zstd
#zlib zlibmodule.c -lz
# The readline module also supports libeditline (-leditline).
@@ -283,10 +284,10 @@ PYTHONPATH=$(COREPYTHONPATH)
#*shared*
#_ctypes_test _ctypes/_ctypes_test.c
+#_remote_debugging _remote_debugging_module.c
#_testcapi _testcapimodule.c
#_testimportmultiple _testimportmultiple.c
#_testmultiphase _testmultiphase.c
-#_testexternalinspection _testexternalinspection.c
#_testsinglephase _testsinglephase.c
# ---
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 33e60f37d19..3a38a60a152 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -41,6 +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
@MODULE__STRUCT_TRUE@_struct _struct.c
# build supports subinterpreters
@@ -64,10 +65,11 @@
@MODULE__DECIMAL_TRUE@_decimal _decimal/_decimal.c
# compression libs and binascii (optional CRC32 from zlib)
-# bindings need -lbz2, -lz, or -llzma, respectively
+# bindings need -lbz2, -llzma, -lzstd, or -lz, respectively
@MODULE_BINASCII_TRUE@binascii binascii.c
@MODULE__BZ2_TRUE@_bz2 _bz2module.c
@MODULE__LZMA_TRUE@_lzma _lzmamodule.c
+@MODULE__ZSTD_TRUE@_zstd _zstd/_zstdmodule.c _zstd/zstddict.c _zstd/compressor.c _zstd/decompressor.c
@MODULE_ZLIB_TRUE@zlib zlibmodule.c
# dbm/gdbm
@@ -186,7 +188,6 @@
@MODULE__TESTIMPORTMULTIPLE_TRUE@_testimportmultiple _testimportmultiple.c
@MODULE__TESTMULTIPHASE_TRUE@_testmultiphase _testmultiphase.c
@MODULE__TESTSINGLEPHASE_TRUE@_testsinglephase _testsinglephase.c
-@MODULE__TESTEXTERNALINSPECTION_TRUE@_testexternalinspection _testexternalinspection.c
@MODULE__CTYPES_TEST_TRUE@_ctypes_test _ctypes/_ctypes_test.c
# Limited API template modules; must be built as shared modules.
diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
index ad670293ec5..3ba48d5d9d3 100644
--- a/Modules/_collectionsmodule.c
+++ b/Modules/_collectionsmodule.c
@@ -5,6 +5,7 @@
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_pyatomic_ft_wrappers.h"
#include "pycore_typeobject.h" // _PyType_GetModuleState()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stddef.h>
@@ -1532,9 +1533,7 @@ deque_dealloc(PyObject *self)
Py_ssize_t i;
PyObject_GC_UnTrack(deque);
- if (deque->weakreflist != NULL) {
- PyObject_ClearWeakRefs(self);
- }
+ FT_CLEAR_WEAKREFS(self, deque->weakreflist);
if (deque->leftblock != NULL) {
(void)deque_clear(self);
assert(deque->leftblock != NULL);
diff --git a/Modules/_complex.h b/Modules/_complex.h
deleted file mode 100644
index 28d4a32794b..00000000000
--- a/Modules/_complex.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Workarounds for buggy complex number arithmetic implementations. */
-
-#ifndef Py_HAVE_C_COMPLEX
-# error "this header file should only be included if Py_HAVE_C_COMPLEX is defined"
-#endif
-
-#include <complex.h>
-
-/* Other compilers (than clang), that claims to
- implement C11 *and* define __STDC_IEC_559_COMPLEX__ - don't have
- issue with CMPLX(). This is specific to glibc & clang combination:
- https://sourceware.org/bugzilla/show_bug.cgi?id=26287
-
- Here we fallback to using __builtin_complex(), available in clang
- v12+. Else CMPLX implemented following C11 6.2.5p13: "Each complex type
- has the same representation and alignment requirements as an array
- type containing exactly two elements of the corresponding real type;
- the first element is equal to the real part, and the second element
- to the imaginary part, of the complex number.
- */
-#if !defined(CMPLX)
-# if defined(__clang__) && __has_builtin(__builtin_complex)
-# define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y))
-# define CMPLXF(x, y) __builtin_complex ((float) (x), (float) (y))
-# define CMPLXL(x, y) __builtin_complex ((long double) (x), (long double) (y))
-# else
-static inline double complex
-CMPLX(double real, double imag)
-{
- double complex z;
- ((double *)(&z))[0] = real;
- ((double *)(&z))[1] = imag;
- return z;
-}
-
-static inline float complex
-CMPLXF(float real, float imag)
-{
- float complex z;
- ((float *)(&z))[0] = real;
- ((float *)(&z))[1] = imag;
- return z;
-}
-
-static inline long double complex
-CMPLXL(long double real, long double imag)
-{
- long double complex z;
- ((long double *)(&z))[0] = real;
- ((long double *)(&z))[1] = imag;
- return z;
-}
-# endif
-#endif
diff --git a/Modules/_csv.c b/Modules/_csv.c
index e5ae853590b..2e04136e0ac 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -237,7 +237,7 @@ _set_int(const char *name, int *target, PyObject *src, int dflt)
int value;
if (!PyLong_CheckExact(src)) {
PyErr_Format(PyExc_TypeError,
- "\"%s\" must be an integer", name);
+ "\"%s\" must be an integer, not %T", name, src);
return -1;
}
value = PyLong_AsInt(src);
@@ -255,27 +255,29 @@ _set_char_or_none(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt
if (src == NULL) {
*target = dflt;
}
- else {
+ else if (src == Py_None) {
*target = NOT_SET;
- if (src != Py_None) {
- if (!PyUnicode_Check(src)) {
- PyErr_Format(PyExc_TypeError,
- "\"%s\" must be string or None, not %.200s", name,
- Py_TYPE(src)->tp_name);
- return -1;
- }
- Py_ssize_t len = PyUnicode_GetLength(src);
- if (len < 0) {
- return -1;
- }
- if (len != 1) {
- PyErr_Format(PyExc_TypeError,
- "\"%s\" must be a 1-character string",
- name);
- return -1;
- }
- *target = PyUnicode_READ_CHAR(src, 0);
+ }
+ else {
+ // similar to PyArg_Parse("C?")
+ if (!PyUnicode_Check(src)) {
+ PyErr_Format(PyExc_TypeError,
+ "\"%s\" must be a unicode character or None, not %T",
+ name, src);
+ return -1;
+ }
+ Py_ssize_t len = PyUnicode_GetLength(src);
+ if (len < 0) {
+ return -1;
}
+ if (len != 1) {
+ PyErr_Format(PyExc_TypeError,
+ "\"%s\" must be a unicode character or None, "
+ "not a string of length %zd",
+ name, len);
+ return -1;
+ }
+ *target = PyUnicode_READ_CHAR(src, 0);
}
return 0;
}
@@ -287,11 +289,12 @@ _set_char(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt)
*target = dflt;
}
else {
+ // similar to PyArg_Parse("C")
if (!PyUnicode_Check(src)) {
PyErr_Format(PyExc_TypeError,
- "\"%s\" must be string, not %.200s", name,
- Py_TYPE(src)->tp_name);
- return -1;
+ "\"%s\" must be a unicode character, not %T",
+ name, src);
+ return -1;
}
Py_ssize_t len = PyUnicode_GetLength(src);
if (len < 0) {
@@ -299,8 +302,9 @@ _set_char(const char *name, Py_UCS4 *target, PyObject *src, Py_UCS4 dflt)
}
if (len != 1) {
PyErr_Format(PyExc_TypeError,
- "\"%s\" must be a 1-character string",
- name);
+ "\"%s\" must be a unicode character, "
+ "not a string of length %zd",
+ name, len);
return -1;
}
*target = PyUnicode_READ_CHAR(src, 0);
@@ -314,16 +318,12 @@ _set_str(const char *name, PyObject **target, PyObject *src, const char *dflt)
if (src == NULL)
*target = PyUnicode_DecodeASCII(dflt, strlen(dflt), NULL);
else {
- if (src == Py_None)
- *target = NULL;
- else if (!PyUnicode_Check(src)) {
+ if (!PyUnicode_Check(src)) {
PyErr_Format(PyExc_TypeError,
- "\"%s\" must be a string", name);
+ "\"%s\" must be a string, not %T", name, src);
return -1;
}
- else {
- Py_XSETREF(*target, Py_NewRef(src));
- }
+ Py_XSETREF(*target, Py_NewRef(src));
}
return 0;
}
@@ -533,11 +533,6 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
/* validate options */
if (dialect_check_quoting(self->quoting))
goto err;
- if (self->delimiter == NOT_SET) {
- PyErr_SetString(PyExc_TypeError,
- "\"delimiter\" must be a 1-character string");
- goto err;
- }
if (quotechar == Py_None && quoting == NULL)
self->quoting = QUOTE_NONE;
if (self->quoting != QUOTE_NONE && self->quotechar == NOT_SET) {
@@ -545,10 +540,6 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
"quotechar must be set if quoting enabled");
goto err;
}
- if (self->lineterminator == NULL) {
- PyErr_SetString(PyExc_TypeError, "lineterminator must be set");
- goto err;
- }
if (dialect_check_char("delimiter", self->delimiter, self, true) ||
dialect_check_char("escapechar", self->escapechar, self,
!self->skipinitialspace) ||
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 1bb65e0a649..7e8a133caa7 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -576,8 +576,15 @@ _ctypes_CType_Type___sizeof___impl(PyObject *self, PyTypeObject *cls)
return PyLong_FromSsize_t(size);
}
+/*[clinic input]
+@getter
+_ctypes.CType_Type.__pointer_type__
+
+[clinic start generated code]*/
+
static PyObject *
-ctype_get_pointer_type(PyObject *self, void *Py_UNUSED(ignored))
+_ctypes_CType_Type___pointer_type___get_impl(PyObject *self)
+/*[clinic end generated code: output=718c9ff10b2b0012 input=ad12dc835943ceb8]*/
{
ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
StgInfo *info;
@@ -588,9 +595,12 @@ ctype_get_pointer_type(PyObject *self, void *Py_UNUSED(ignored))
PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
return NULL;
}
-
- if (info->pointer_type) {
- return Py_NewRef(info->pointer_type);
+ PyObject *pointer_type;
+ STGINFO_LOCK(info);
+ pointer_type = Py_XNewRef(info->pointer_type);
+ STGINFO_UNLOCK();
+ if (pointer_type) {
+ return pointer_type;
}
PyErr_Format(PyExc_AttributeError,
@@ -599,8 +609,15 @@ ctype_get_pointer_type(PyObject *self, void *Py_UNUSED(ignored))
return NULL;
}
+/*[clinic input]
+@setter
+_ctypes.CType_Type.__pointer_type__
+
+[clinic start generated code]*/
+
static int
-ctype_set_pointer_type(PyObject *self, PyObject *tp, void *Py_UNUSED(ignored))
+_ctypes_CType_Type___pointer_type___set_impl(PyObject *self, PyObject *value)
+/*[clinic end generated code: output=6259be8ea21693fa input=a05055fc7f4714b6]*/
{
ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
StgInfo *info;
@@ -611,8 +628,9 @@ ctype_set_pointer_type(PyObject *self, PyObject *tp, void *Py_UNUSED(ignored))
PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
return -1;
}
-
- Py_XSETREF(info->pointer_type, Py_XNewRef(tp));
+ STGINFO_LOCK(info);
+ Py_XSETREF(info->pointer_type, Py_XNewRef(value));
+ STGINFO_UNLOCK();
return 0;
}
@@ -626,8 +644,7 @@ static PyMethodDef ctype_methods[] = {
};
static PyGetSetDef ctype_getsets[] = {
- { "__pointer_type__", ctype_get_pointer_type, ctype_set_pointer_type,
- "pointer type", NULL },
+ _CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF
{ NULL, NULL }
};
@@ -1254,9 +1271,11 @@ PyCPointerType_SetProto(ctypes_state *st, PyObject *self, StgInfo *stginfo, PyOb
return -1;
}
Py_XSETREF(stginfo->proto, Py_NewRef(proto));
+ STGINFO_LOCK(info);
if (info->pointer_type == NULL) {
Py_XSETREF(info->pointer_type, Py_NewRef(self));
}
+ STGINFO_UNLOCK();
return 0;
}
@@ -3591,6 +3610,45 @@ generic_pycdata_new(ctypes_state *st,
PyCFuncPtr_Type
*/
+static inline void
+atomic_xsetref(PyObject **field, PyObject *value)
+{
+#ifdef Py_GIL_DISABLED
+ PyObject *old = *field;
+ _Py_atomic_store_ptr(field, value);
+ Py_XDECREF(old);
+#else
+ Py_XSETREF(*field, value);
+#endif
+}
+/*
+ This function atomically loads the reference from *field, and
+ tries to get a new reference to it. If the incref fails,
+ it acquires critical section of obj and returns a new reference to the *field.
+ In the general case, this avoids contention on acquiring the critical section.
+*/
+static inline PyObject *
+atomic_xgetref(PyObject *obj, PyObject **field)
+{
+#ifdef Py_GIL_DISABLED
+ PyObject *value = _Py_atomic_load_ptr(field);
+ if (value == NULL) {
+ return NULL;
+ }
+ if (_Py_TryIncrefCompare(field, value)) {
+ return value;
+ }
+ Py_BEGIN_CRITICAL_SECTION(obj);
+ value = Py_XNewRef(*field);
+ Py_END_CRITICAL_SECTION();
+ return value;
+#else
+ return Py_XNewRef(*field);
+#endif
+}
+
+
+
/*[clinic input]
@critical_section
@setter
@@ -3607,7 +3665,7 @@ _ctypes_CFuncPtr_errcheck_set_impl(PyCFuncPtrObject *self, PyObject *value)
return -1;
}
Py_XINCREF(value);
- Py_XSETREF(self->errcheck, value);
+ atomic_xsetref(&self->errcheck, value);
return 0;
}
@@ -3639,12 +3697,10 @@ static int
_ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value)
/*[clinic end generated code: output=0be0a086abbabf18 input=683c3bef4562ccc6]*/
{
- PyObject *checker, *oldchecker;
+ PyObject *checker;
if (value == NULL) {
- oldchecker = self->checker;
- self->checker = NULL;
- Py_CLEAR(self->restype);
- Py_XDECREF(oldchecker);
+ atomic_xsetref(&self->restype, NULL);
+ atomic_xsetref(&self->checker, NULL);
return 0;
}
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
@@ -3660,11 +3716,9 @@ _ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value)
if (PyObject_GetOptionalAttr(value, &_Py_ID(_check_retval_), &checker) < 0) {
return -1;
}
- oldchecker = self->checker;
- self->checker = checker;
Py_INCREF(value);
- Py_XSETREF(self->restype, value);
- Py_XDECREF(oldchecker);
+ atomic_xsetref(&self->checker, checker);
+ atomic_xsetref(&self->restype, value);
return 0;
}
@@ -3709,16 +3763,16 @@ _ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value)
PyObject *converters;
if (value == NULL || value == Py_None) {
- Py_CLEAR(self->converters);
- Py_CLEAR(self->argtypes);
+ atomic_xsetref(&self->argtypes, NULL);
+ atomic_xsetref(&self->converters, NULL);
} else {
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
converters = converters_from_argtypes(st, value);
if (!converters)
return -1;
- Py_XSETREF(self->converters, converters);
+ atomic_xsetref(&self->converters, converters);
Py_INCREF(value);
- Py_XSETREF(self->argtypes, value);
+ atomic_xsetref(&self->argtypes, value);
}
return 0;
}
@@ -4514,16 +4568,11 @@ _build_result(PyObject *result, PyObject *callargs,
}
static PyObject *
-PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
+PyCFuncPtr_call(PyObject *op, PyObject *inargs, PyObject *kwds)
{
- _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
- PyObject *restype;
- PyObject *converters;
- PyObject *checker;
- PyObject *argtypes;
- PyObject *result;
- PyObject *callargs;
- PyObject *errcheck;
+ PyObject *result = NULL;
+ PyObject *callargs = NULL;
+ PyObject *ret = NULL;
#ifdef MS_WIN32
IUnknown *piunk = NULL;
#endif
@@ -4541,13 +4590,24 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
}
assert(info); /* Cannot be NULL for PyCFuncPtrObject instances */
- restype = self->restype ? self->restype : info->restype;
- converters = self->converters ? self->converters : info->converters;
- checker = self->checker ? self->checker : info->checker;
- argtypes = self->argtypes ? self->argtypes : info->argtypes;
-/* later, we probably want to have an errcheck field in stginfo */
- errcheck = self->errcheck /* ? self->errcheck : info->errcheck */;
-
+ PyObject *restype = atomic_xgetref(op, &self->restype);
+ if (restype == NULL) {
+ restype = Py_XNewRef(info->restype);
+ }
+ PyObject *converters = atomic_xgetref(op, &self->converters);
+ if (converters == NULL) {
+ converters = Py_XNewRef(info->converters);
+ }
+ PyObject *checker = atomic_xgetref(op, &self->checker);
+ if (checker == NULL) {
+ checker = Py_XNewRef(info->checker);
+ }
+ PyObject *argtypes = atomic_xgetref(op, &self->argtypes);
+ if (argtypes == NULL) {
+ argtypes = Py_XNewRef(info->argtypes);
+ }
+ /* later, we probably want to have an errcheck field in stginfo */
+ PyObject *errcheck = atomic_xgetref(op, &self->errcheck);
pProc = *(void **)self->b_ptr;
#ifdef MS_WIN32
@@ -4558,25 +4618,25 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
if (!this) {
PyErr_SetString(PyExc_ValueError,
"native com method call without 'this' parameter");
- return NULL;
+ goto finally;
}
if (!CDataObject_Check(st, this)) {
PyErr_SetString(PyExc_TypeError,
"Expected a COM this pointer as first argument");
- return NULL;
+ goto finally;
}
/* there should be more checks? No, in Python */
/* First arg is a pointer to an interface instance */
if (!this->b_ptr || *(void **)this->b_ptr == NULL) {
PyErr_SetString(PyExc_ValueError,
"NULL COM pointer access");
- return NULL;
+ goto finally;
}
piunk = *(IUnknown **)this->b_ptr;
if (NULL == piunk->lpVtbl) {
PyErr_SetString(PyExc_ValueError,
"COM method call without VTable");
- return NULL;
+ goto finally;
}
pProc = ((void **)piunk->lpVtbl)[self->index - 0x1000];
}
@@ -4584,8 +4644,9 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
callargs = _build_callargs(st, self, argtypes,
inargs, kwds,
&outmask, &inoutmask, &numretvals);
- if (callargs == NULL)
- return NULL;
+ if (callargs == NULL) {
+ goto finally;
+ }
if (converters) {
int required = Py_SAFE_DOWNCAST(PyTuple_GET_SIZE(converters),
@@ -4604,7 +4665,7 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
required,
required == 1 ? "" : "s",
actual);
- return NULL;
+ goto finally;
}
} else if (required != actual) {
Py_DECREF(callargs);
@@ -4613,7 +4674,7 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
required,
required == 1 ? "" : "s",
actual);
- return NULL;
+ goto finally;
}
}
@@ -4644,23 +4705,19 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
if (v == NULL || v != callargs) {
Py_DECREF(result);
Py_DECREF(callargs);
- return v;
+ ret = v;
+ goto finally;
}
Py_DECREF(v);
}
-
- return _build_result(result, callargs,
- outmask, inoutmask, numretvals);
-}
-
-static PyObject *
-PyCFuncPtr_call(PyObject *op, PyObject *inargs, PyObject *kwds)
-{
- PyObject *result;
- Py_BEGIN_CRITICAL_SECTION(op);
- result = PyCFuncPtr_call_lock_held(op, inargs, kwds);
- Py_END_CRITICAL_SECTION();
- return result;
+ ret = _build_result(result, callargs, outmask, inoutmask, numretvals);
+finally:
+ Py_XDECREF(restype);
+ Py_XDECREF(converters);
+ Py_XDECREF(checker);
+ Py_XDECREF(argtypes);
+ Py_XDECREF(errcheck);
+ return ret;
}
static int
diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c
index 2268072545f..66338805007 100644
--- a/Modules/_ctypes/_ctypes_test.c
+++ b/Modules/_ctypes/_ctypes_test.c
@@ -23,8 +23,8 @@
# define _Py_thread_local __thread
#endif
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-# include "../_complex.h" // csqrt()
+#if defined(_Py_FFI_SUPPORT_C_COMPLEX)
+# include <complex.h> // csqrt()
# undef I // for _ctypes_test_generated.c.h
#endif
#include <stdio.h> // printf()
@@ -457,7 +457,7 @@ EXPORT(double) my_sqrt(double a)
return sqrt(a);
}
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
+#if defined(_Py_FFI_SUPPORT_C_COMPLEX)
EXPORT(double complex) my_csqrt(double complex a)
{
return csqrt(a);
diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c
index ec113e41d16..fd508ae61f2 100644
--- a/Modules/_ctypes/callbacks.c
+++ b/Modules/_ctypes/callbacks.c
@@ -11,18 +11,9 @@
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_runtime.h" // _Py_ID()
-#ifdef MS_WIN32
-# include <malloc.h>
-#endif
-
#include <ffi.h>
#include "ctypes.h"
-#ifdef HAVE_ALLOCA_H
-/* AIX needs alloca.h for alloca() */
-#include <alloca.h>
-#endif
-
/**************************************************************/
static int
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index fc7265cb63e..65a0f14e2b0 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -77,22 +77,14 @@ module _ctypes
#include <mach-o/dyld.h>
#endif
-#ifdef MS_WIN32
-#include <malloc.h>
-#endif
-
#include <ffi.h>
#include "ctypes.h"
-#ifdef HAVE_ALLOCA_H
-/* AIX needs alloca.h for alloca() */
-#include <alloca.h>
-#endif
#ifdef _Py_MEMORY_SANITIZER
#include <sanitizer/msan_interface.h>
#endif
-#if defined(_DEBUG) || defined(__MINGW32__)
+#if defined(Py_DEBUG) || defined(__MINGW32__)
/* Don't use structured exception handling on Windows if this is defined.
MingW, AFAIK, doesn't support it.
*/
@@ -103,9 +95,6 @@ module _ctypes
#include "pycore_global_objects.h"// _Py_ID()
#include "pycore_traceback.h" // _PyTraceback_Add()
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-#include "../_complex.h" // complex
-#endif
#define clinic_state() (get_module_state(module))
#include "clinic/callproc.c.h"
#undef clinic_state
@@ -652,11 +641,9 @@ union result {
double d;
float f;
void *p;
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
- double complex D;
- float complex F;
- long double complex G;
-#endif
+ double D[2];
+ float F[2];
+ long double G[2];
};
struct argument {
@@ -1394,7 +1381,7 @@ static PyObject *format_error(PyObject *self, PyObject *args)
code = GetLastError();
lpMsgBuf = FormatError(code);
if (lpMsgBuf) {
- result = PyUnicode_FromWideChar(lpMsgBuf, wcslen(lpMsgBuf));
+ result = PyUnicode_FromWideChar(lpMsgBuf, -1);
LocalFree(lpMsgBuf);
} else {
result = PyUnicode_FromString("<no description>");
diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index 580ea18af2a..547e2471a1c 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -14,10 +14,6 @@
#include <ffi.h>
#include "ctypes.h"
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-# include "../_complex.h" // complex
-#endif
-
#define CTYPES_CFIELD_CAPSULE_NAME_PYMEM "_ctypes/cfield.c pymem"
/*[clinic input]
@@ -763,18 +759,25 @@ d_get(void *ptr, Py_ssize_t size)
return PyFloat_FromDouble(val);
}
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
+#if defined(_Py_FFI_SUPPORT_C_COMPLEX)
+
+/* We don't use _Complex types here, using arrays instead, as the C11+
+ standard says: "Each complex type has the same representation and alignment
+ requirements as an array type containing exactly two elements of the
+ corresponding real type; the first element is equal to the real part, and
+ the second element to the imaginary part, of the complex number." */
+
/* D: double complex */
static PyObject *
D_set(void *ptr, PyObject *value, Py_ssize_t size)
{
- assert(NUM_BITS(size) || (size == sizeof(double complex)));
+ assert(NUM_BITS(size) || (size == 2*sizeof(double)));
Py_complex c = PyComplex_AsCComplex(value);
if (c.real == -1 && PyErr_Occurred()) {
return NULL;
}
- double complex x = CMPLX(c.real, c.imag);
+ double x[2] = {c.real, c.imag};
memcpy(ptr, &x, sizeof(x));
_RET(value);
}
@@ -782,24 +785,24 @@ D_set(void *ptr, PyObject *value, Py_ssize_t size)
static PyObject *
D_get(void *ptr, Py_ssize_t size)
{
- assert(NUM_BITS(size) || (size == sizeof(double complex)));
- double complex x;
+ assert(NUM_BITS(size) || (size == 2*sizeof(double)));
+ double x[2];
memcpy(&x, ptr, sizeof(x));
- return PyComplex_FromDoubles(creal(x), cimag(x));
+ return PyComplex_FromDoubles(x[0], x[1]);
}
/* F: float complex */
static PyObject *
F_set(void *ptr, PyObject *value, Py_ssize_t size)
{
- assert(NUM_BITS(size) || (size == sizeof(float complex)));
+ assert(NUM_BITS(size) || (size == 2*sizeof(float)));
Py_complex c = PyComplex_AsCComplex(value);
if (c.real == -1 && PyErr_Occurred()) {
return NULL;
}
- float complex x = CMPLXF((float)c.real, (float)c.imag);
+ float x[2] = {(float)c.real, (float)c.imag};
memcpy(ptr, &x, sizeof(x));
_RET(value);
}
@@ -807,24 +810,24 @@ F_set(void *ptr, PyObject *value, Py_ssize_t size)
static PyObject *
F_get(void *ptr, Py_ssize_t size)
{
- assert(NUM_BITS(size) || (size == sizeof(float complex)));
- float complex x;
+ assert(NUM_BITS(size) || (size == 2*sizeof(float)));
+ float x[2];
memcpy(&x, ptr, sizeof(x));
- return PyComplex_FromDoubles(crealf(x), cimagf(x));
+ return PyComplex_FromDoubles(x[0], x[1]);
}
/* G: long double complex */
static PyObject *
G_set(void *ptr, PyObject *value, Py_ssize_t size)
{
- assert(NUM_BITS(size) || (size == sizeof(long double complex)));
+ assert(NUM_BITS(size) || (size == 2*sizeof(long double)));
Py_complex c = PyComplex_AsCComplex(value);
if (c.real == -1 && PyErr_Occurred()) {
return NULL;
}
- long double complex x = CMPLXL(c.real, c.imag);
+ long double x[2] = {c.real, c.imag};
memcpy(ptr, &x, sizeof(x));
_RET(value);
}
@@ -832,11 +835,11 @@ G_set(void *ptr, PyObject *value, Py_ssize_t size)
static PyObject *
G_get(void *ptr, Py_ssize_t size)
{
- assert(NUM_BITS(size) || (size == sizeof(long double complex)));
- long double complex x;
+ assert(NUM_BITS(size) || (size == 2*sizeof(long double)));
+ long double x[2];
memcpy(&x, ptr, sizeof(x));
- return PyComplex_FromDoubles((double)creall(x), (double)cimagl(x));
+ return PyComplex_FromDoubles((double)x[0], (double)x[1]);
}
#endif
@@ -1250,7 +1253,7 @@ Z_get(void *ptr, Py_ssize_t size)
wchar_t *p;
p = *(wchar_t **)ptr;
if (p) {
- return PyUnicode_FromWideChar(p, wcslen(p));
+ return PyUnicode_FromWideChar(p, -1);
} else {
Py_RETURN_NONE;
}
@@ -1596,7 +1599,7 @@ for base_code, base_c_type in [
///////////////////////////////////////////////////////////////////////////
TABLE_ENTRY_SW(d, &ffi_type_double);
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
+#if defined(_Py_FFI_SUPPORT_C_COMPLEX)
if (Py_FFI_COMPLEX_AVAILABLE) {
TABLE_ENTRY(D, &ffi_type_complex_double);
TABLE_ENTRY(F, &ffi_type_complex_float);
diff --git a/Modules/_ctypes/clinic/_ctypes.c.h b/Modules/_ctypes/clinic/_ctypes.c.h
index 92dfb8f83b7..cf2e3fa2107 100644
--- a/Modules/_ctypes/clinic/_ctypes.c.h
+++ b/Modules/_ctypes/clinic/_ctypes.c.h
@@ -31,6 +31,48 @@ _ctypes_CType_Type___sizeof__(PyObject *self, PyTypeObject *cls, PyObject *const
return _ctypes_CType_Type___sizeof___impl(self, cls);
}
+#if !defined(_ctypes_CType_Type___pointer_type___DOCSTR)
+# define _ctypes_CType_Type___pointer_type___DOCSTR NULL
+#endif
+#if defined(_CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF)
+# undef _CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF
+# define _CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF {"__pointer_type__", (getter)_ctypes_CType_Type___pointer_type___get, (setter)_ctypes_CType_Type___pointer_type___set, _ctypes_CType_Type___pointer_type___DOCSTR},
+#else
+# define _CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF {"__pointer_type__", (getter)_ctypes_CType_Type___pointer_type___get, NULL, _ctypes_CType_Type___pointer_type___DOCSTR},
+#endif
+
+static PyObject *
+_ctypes_CType_Type___pointer_type___get_impl(PyObject *self);
+
+static PyObject *
+_ctypes_CType_Type___pointer_type___get(PyObject *self, void *Py_UNUSED(context))
+{
+ return _ctypes_CType_Type___pointer_type___get_impl(self);
+}
+
+#if !defined(_ctypes_CType_Type___pointer_type___DOCSTR)
+# define _ctypes_CType_Type___pointer_type___DOCSTR NULL
+#endif
+#if defined(_CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF)
+# undef _CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF
+# define _CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF {"__pointer_type__", (getter)_ctypes_CType_Type___pointer_type___get, (setter)_ctypes_CType_Type___pointer_type___set, _ctypes_CType_Type___pointer_type___DOCSTR},
+#else
+# define _CTYPES_CTYPE_TYPE___POINTER_TYPE___GETSETDEF {"__pointer_type__", NULL, (setter)_ctypes_CType_Type___pointer_type___set, NULL},
+#endif
+
+static int
+_ctypes_CType_Type___pointer_type___set_impl(PyObject *self, PyObject *value);
+
+static int
+_ctypes_CType_Type___pointer_type___set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
+{
+ int return_value;
+
+ return_value = _ctypes_CType_Type___pointer_type___set_impl(self, value);
+
+ return return_value;
+}
+
PyDoc_STRVAR(CDataType_from_address__doc__,
"from_address($self, value, /)\n"
"--\n"
@@ -1000,4 +1042,4 @@ Simple_from_outparm(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py
}
return Simple_from_outparm_impl(self, cls);
}
-/*[clinic end generated code: output=9fb75bf7e9a17df2 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=536c9bcf4e05913e input=a9049054013a1b77]*/
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index 9aceeceb88a..5b4f97d43b8 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -1,5 +1,17 @@
-#if defined (__SVR4) && defined (__sun)
+/* Get a definition of alloca(). */
+#if (defined (__SVR4) && defined (__sun)) || defined(HAVE_ALLOCA_H)
# include <alloca.h>
+#elif defined(MS_WIN32)
+# include <malloc.h>
+#endif
+
+/* If the system does not define alloca(), we have to hope for a compiler builtin. */
+#ifndef alloca
+# if defined __GNUC__ || (__clang_major__ >= 4)
+# define alloca __builtin_alloca
+# else
+# error "Could not define alloca() on your platform."
+# endif
#endif
#include <stdbool.h>
@@ -11,8 +23,7 @@
// Do we support C99 complex types in ffi?
// For Apple's libffi, this must be determined at runtime (see gh-128156).
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-# include "../_complex.h" // complex
+#if defined(_Py_FFI_SUPPORT_C_COMPLEX)
# if USING_APPLE_OS_LIBFFI && defined(__has_builtin)
# if __has_builtin(__builtin_available)
# define Py_FFI_COMPLEX_AVAILABLE __builtin_available(macOS 10.15, *)
@@ -494,11 +505,9 @@ struct tagPyCArgObject {
double d;
float f;
void *p;
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
- double complex D;
- float complex F;
- long double complex G;
-#endif
+ double D[2];
+ float F[2];
+ long double G[2];
} value;
PyObject *obj;
Py_ssize_t size; /* for the 'V' tag */
diff --git a/Modules/_curses_panel.c b/Modules/_curses_panel.c
index eecf7a1c8a1..d7acfc6a06a 100644
--- a/Modules/_curses_panel.c
+++ b/Modules/_curses_panel.c
@@ -17,6 +17,7 @@ static const char PyCursesVersion[] = "2.1";
#include "Python.h"
+#define CURSES_PANEL_MODULE
#include "py_curses.h"
#if defined(HAVE_NCURSESW_PANEL_H)
@@ -28,10 +29,12 @@ static const char PyCursesVersion[] = "2.1";
#endif
typedef struct {
- PyObject *PyCursesError;
+ PyObject *error;
PyTypeObject *PyCursesPanel_Type;
} _curses_panel_state;
+typedef struct PyCursesPanelObject PyCursesPanelObject;
+
static inline _curses_panel_state *
get_curses_panel_state(PyObject *module)
{
@@ -40,11 +43,30 @@ get_curses_panel_state(PyObject *module)
return (_curses_panel_state *)state;
}
+static inline _curses_panel_state *
+get_curses_panel_state_by_panel(PyCursesPanelObject *panel)
+{
+ /*
+ * Note: 'state' may be NULL if Py_TYPE(panel) is not a heap
+ * type associated with this module, but the compiler would
+ * have likely already complained with an "invalid pointer
+ * type" at compile-time.
+ *
+ * To make it more robust, all functions recovering a module's
+ * state from an object should expect to return NULL with an
+ * exception set (in contrast to functions recovering a module's
+ * state from a module itself).
+ */
+ void *state = PyType_GetModuleState(Py_TYPE(panel));
+ assert(state != NULL);
+ return (_curses_panel_state *)state;
+}
+
static int
_curses_panel_clear(PyObject *mod)
{
_curses_panel_state *state = get_curses_panel_state(mod);
- Py_CLEAR(state->PyCursesError);
+ Py_CLEAR(state->error);
Py_CLEAR(state->PyCursesPanel_Type);
return 0;
}
@@ -54,7 +76,7 @@ _curses_panel_traverse(PyObject *mod, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(mod));
_curses_panel_state *state = get_curses_panel_state(mod);
- Py_VISIT(state->PyCursesError);
+ Py_VISIT(state->error);
Py_VISIT(state->PyCursesPanel_Type);
return 0;
}
@@ -65,28 +87,149 @@ _curses_panel_free(void *mod)
(void)_curses_panel_clear((PyObject *)mod);
}
+/* Utility Error Procedures
+ *
+ * The naming and implementations are identical to those in _cursesmodule.c.
+ * Functions that are not yet needed (for instance, reporting an ERR value
+ * from a module-wide function, namely curses_panel_set_error()) are
+ * omitted and should only be added if needed.
+ */
+
+static void
+_curses_panel_format_error(_curses_panel_state *state,
+ const char *curses_funcname,
+ const char *python_funcname,
+ const char *return_value,
+ const char *default_message)
+{
+ assert(!PyErr_Occurred());
+ if (python_funcname == NULL && curses_funcname == NULL) {
+ PyErr_SetString(state->error, default_message);
+ }
+ else if (python_funcname == NULL) {
+ (void)PyErr_Format(state->error, CURSES_ERROR_FORMAT,
+ curses_funcname, return_value);
+ }
+ else {
+ assert(python_funcname != NULL);
+ (void)PyErr_Format(state->error, CURSES_ERROR_VERBOSE_FORMAT,
+ curses_funcname, python_funcname, return_value);
+ }
+}
+
+/*
+ * Format a curses error for a function that returned ERR.
+ *
+ * Specify a non-NULL 'python_funcname' when the latter differs from
+ * 'curses_funcname'. If both names are NULL, uses the 'catchall_ERR'
+ * message instead.
+ */
+static void
+_curses_panel_set_error(_curses_panel_state *state,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ _curses_panel_format_error(state, curses_funcname, python_funcname,
+ "ERR", catchall_ERR);
+}
+
+/*
+ * Format a curses error for a function that returned NULL.
+ *
+ * Specify a non-NULL 'python_funcname' when the latter differs from
+ * 'curses_funcname'. If both names are NULL, uses the 'catchall_NULL'
+ * message instead.
+ */
+static void
+_curses_panel_set_null_error(_curses_panel_state *state,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ _curses_panel_format_error(state, curses_funcname, python_funcname,
+ "NULL", catchall_NULL);
+}
+
+/* Same as _curses_panel_set_null_error() for a module object. */
+static void
+curses_panel_set_null_error(PyObject *module,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ _curses_panel_state *state = get_curses_panel_state(module);
+ _curses_panel_set_null_error(state, curses_funcname, python_funcname);
+}
+
+/* Same as _curses_panel_set_error() for a panel object. */
+static void
+curses_panel_panel_set_error(PyCursesPanelObject *panel,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ _curses_panel_state *state = get_curses_panel_state_by_panel(panel);
+ _curses_panel_set_error(state, curses_funcname, python_funcname);
+}
+
+/* Same as _curses_panel_set_null_error() for a panel object. */
+static void
+curses_panel_panel_set_null_error(PyCursesPanelObject *panel,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ _curses_panel_state *state = get_curses_panel_state_by_panel(panel);
+ _curses_panel_set_null_error(state, curses_funcname, python_funcname);
+}
+
+/*
+ * Indicate that a panel object couldn't be found.
+ *
+ * Use it for the following constructions:
+ *
+ * PROC caller_funcname:
+ * pan = called_funcname()
+ * find_po(panel)
+ *
+ * PROC caller_funcname:
+ * find_po(self->pan)
+*/
+static void
+curses_panel_notfound_error(const char *called_funcname,
+ const char *caller_funcname)
+{
+ assert(!(called_funcname == NULL && caller_funcname == NULL));
+ if (caller_funcname == NULL) {
+ (void)PyErr_Format(PyExc_RuntimeError,
+ "%s(): cannot find panel object",
+ called_funcname);
+ }
+ else {
+ (void)PyErr_Format(PyExc_RuntimeError,
+ "%s() (called by %s()): cannot find panel object",
+ called_funcname, caller_funcname);
+ }
+}
+
/* Utility Functions */
/*
- * Check the return code from a curses function and return None
- * or raise an exception as appropriate.
+ * Check the return code from a curses function, returning None
+ * on success and setting an exception on error.
*/
+/*
+ * Return None if 'code' is different from ERR (implementation-defined).
+ * Otherwise, set an exception using curses_panel_panel_set_error() and
+ * the remaining arguments, and return NULL.
+ */
static PyObject *
-PyCursesCheckERR(_curses_panel_state *state, int code, const char *fname)
+curses_panel_panel_check_err(PyCursesPanelObject *panel, int code,
+ const char *curses_funcname,
+ const char *python_funcname)
{
if (code != ERR) {
Py_RETURN_NONE;
}
- else {
- if (fname == NULL) {
- PyErr_SetString(state->PyCursesError, catchall_ERR);
- }
- else {
- PyErr_Format(state->PyCursesError, "%s() returned ERR", fname);
- }
- return NULL;
- }
+ curses_panel_panel_set_error(panel, curses_funcname, python_funcname);
+ return NULL;
}
/*****************************************************************************
@@ -95,7 +238,7 @@ PyCursesCheckERR(_curses_panel_state *state, int code, const char *fname)
/* Definition of the panel object and panel type */
-typedef struct {
+typedef struct PyCursesPanelObject {
PyObject_HEAD
PANEL *pan;
PyCursesWindowObject *wo; /* for reference counts */
@@ -144,8 +287,11 @@ insert_lop(PyCursesPanelObject *po)
return 0;
}
-/* Remove the panel object from lop */
-static void
+/* Remove the panel object from lop.
+ *
+ * Return -1 on error but do NOT set an exception; otherwise return 0.
+ */
+static int
remove_lop(PyCursesPanelObject *po)
{
list_of_panels *temp, *n;
@@ -154,25 +300,23 @@ remove_lop(PyCursesPanelObject *po)
if (temp->po == po) {
lop = temp->next;
PyMem_Free(temp);
- return;
+ return 0;
}
while (temp->next == NULL || temp->next->po != po) {
if (temp->next == NULL) {
- PyErr_SetString(PyExc_RuntimeError,
- "remove_lop: can't find Panel Object");
- return;
+ return -1;
}
temp = temp->next;
}
n = temp->next->next;
PyMem_Free(temp->next);
temp->next = n;
- return;
+ return 0;
}
/* Return the panel object that corresponds to pan */
static PyCursesPanelObject *
-find_po(PANEL *pan)
+find_po_impl(PANEL *pan)
{
list_of_panels *temp;
for (temp = lop; temp->po->pan != pan; temp = temp->next)
@@ -180,6 +324,17 @@ find_po(PANEL *pan)
return temp->po;
}
+/* Same as find_po_impl() but with caller context information. */
+static PyCursesPanelObject *
+find_po(PANEL *pan, const char *called_funcname, const char *caller_funcname)
+{
+ PyCursesPanelObject *res = find_po_impl(pan);
+ if (res == NULL) {
+ curses_panel_notfound_error(called_funcname, caller_funcname);
+ }
+ return res;
+}
+
/*[clinic input]
module _curses_panel
class _curses_panel.panel "PyCursesPanelObject *" "&PyCursesPanel_Type"
@@ -193,67 +348,59 @@ class _curses_panel.panel "PyCursesPanelObject *" "&PyCursesPanel_Type"
/*[clinic input]
_curses_panel.panel.bottom
- cls: defining_class
-
Push the panel to the bottom of the stack.
[clinic start generated code]*/
static PyObject *
-_curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=8ec7fbbc08554021 input=6b7d2c0578b5a1c4]*/
+_curses_panel_panel_bottom_impl(PyCursesPanelObject *self)
+/*[clinic end generated code: output=7aa7d14d7e1d1ce6 input=b6c920c071b61e2e]*/
{
- _curses_panel_state *state = PyType_GetModuleState(cls);
- return PyCursesCheckERR(state, bottom_panel(self->pan), "bottom");
+ int rtn = bottom_panel(self->pan);
+ return curses_panel_panel_check_err(self, rtn, "bottom_panel", "bottom");
}
/*[clinic input]
_curses_panel.panel.hide
- cls: defining_class
-
Hide the panel.
This does not delete the object, it just makes the window on screen invisible.
[clinic start generated code]*/
static PyObject *
-_curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=cc6ab7203cdc1450 input=1bfc741f473e6055]*/
+_curses_panel_panel_hide_impl(PyCursesPanelObject *self)
+/*[clinic end generated code: output=a7bbbd523e1eab49 input=f6ab884e99386118]*/
{
- _curses_panel_state *state = PyType_GetModuleState(cls);
- return PyCursesCheckERR(state, hide_panel(self->pan), "hide");
+ int rtn = hide_panel(self->pan);
+ return curses_panel_panel_check_err(self, rtn, "hide_panel", "hide");
}
/*[clinic input]
_curses_panel.panel.show
- cls: defining_class
-
Display the panel (which might have been hidden).
[clinic start generated code]*/
static PyObject *
-_curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=dc3421de375f0409 input=8122e80151cb4379]*/
+_curses_panel_panel_show_impl(PyCursesPanelObject *self)
+/*[clinic end generated code: output=6b4553ab45c97769 input=57b167bbefaa3755]*/
{
- _curses_panel_state *state = PyType_GetModuleState(cls);
- return PyCursesCheckERR(state, show_panel(self->pan), "show");
+ int rtn = show_panel(self->pan);
+ return curses_panel_panel_check_err(self, rtn, "show_panel", "show");
}
/*[clinic input]
_curses_panel.panel.top
- cls: defining_class
-
Push panel to the top of the stack.
[clinic start generated code]*/
static PyObject *
-_curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=10a072e511e873f7 input=1f372d597dda3379]*/
+_curses_panel_panel_top_impl(PyCursesPanelObject *self)
+/*[clinic end generated code: output=0f5f2f8cdd2d1777 input=be33975ec3ca0e9a]*/
{
- _curses_panel_state *state = PyType_GetModuleState(cls);
- return PyCursesCheckERR(state, top_panel(self->pan), "top");
+ int rtn = top_panel(self->pan);
+ return curses_panel_panel_check_err(self, rtn, "top_panel", "top");
}
/* Allocation and deallocation of Panel Objects */
@@ -287,13 +434,22 @@ PyCursesPanel_Dealloc(PyObject *self)
tp = (PyObject *) Py_TYPE(po);
obj = (PyObject *) panel_userptr(po->pan);
if (obj) {
- (void)set_panel_userptr(po->pan, NULL);
Py_DECREF(obj);
+ if (set_panel_userptr(po->pan, NULL) == ERR) {
+ curses_panel_panel_set_error(po, "set_panel_userptr", "__del__");
+ PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
+ }
+ }
+ if (del_panel(po->pan) == ERR && !PyErr_Occurred()) {
+ curses_panel_panel_set_error(po, "del_panel", "__del__");
+ PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
}
- (void)del_panel(po->pan);
if (po->wo != NULL) {
Py_DECREF(po->wo);
- remove_lop(po);
+ if (remove_lop(po) < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "__del__: no panel object to delete");
+ PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
+ }
}
PyObject_Free(po);
Py_DECREF(tp);
@@ -315,18 +471,11 @@ _curses_panel_panel_above_impl(PyCursesPanelObject *self)
PyCursesPanelObject *po;
pan = panel_above(self->pan);
-
- if (pan == NULL) { /* valid output, it means the calling panel
- is on top of the stack */
+ if (pan == NULL) { /* valid output: it means no panel exists yet */
Py_RETURN_NONE;
}
- po = find_po(pan);
- if (po == NULL) {
- PyErr_SetString(PyExc_RuntimeError,
- "panel_above: can't find Panel Object");
- return NULL;
- }
- return Py_NewRef(po);
+ po = find_po(pan, "panel_above", "above");
+ return Py_XNewRef(po);
}
/* panel_below(NULL) returns the top panel in the stack. To get
@@ -345,18 +494,11 @@ _curses_panel_panel_below_impl(PyCursesPanelObject *self)
PyCursesPanelObject *po;
pan = panel_below(self->pan);
-
- if (pan == NULL) { /* valid output, it means the calling panel
- is on the bottom of the stack */
+ if (pan == NULL) { /* valid output: it means no panel exists yet */
Py_RETURN_NONE;
}
- po = find_po(pan);
- if (po == NULL) {
- PyErr_SetString(PyExc_RuntimeError,
- "panel_below: can't find Panel Object");
- return NULL;
- }
- return Py_NewRef(po);
+ po = find_po(pan, "panel_below", "below");
+ return Py_XNewRef(po);
}
/*[clinic input]
@@ -378,7 +520,6 @@ _curses_panel_panel_hidden_impl(PyCursesPanelObject *self)
/*[clinic input]
_curses_panel.panel.move
- cls: defining_class
y: int
x: int
/
@@ -387,12 +528,11 @@ Move the panel to the screen coordinates (y, x).
[clinic start generated code]*/
static PyObject *
-_curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls,
- int y, int x)
-/*[clinic end generated code: output=ce546c93e56867da input=60a0e7912ff99849]*/
+_curses_panel_panel_move_impl(PyCursesPanelObject *self, int y, int x)
+/*[clinic end generated code: output=d867535a89777415 input=e0b36b78acc03fba]*/
{
- _curses_panel_state *state = PyType_GetModuleState(cls);
- return PyCursesCheckERR(state, move_panel(self->pan, y, x), "move_panel");
+ int rtn = move_panel(self->pan, y, x);
+ return curses_panel_panel_check_err(self, rtn, "move_panel", "move");
}
/*[clinic input]
@@ -411,7 +551,6 @@ _curses_panel_panel_window_impl(PyCursesPanelObject *self)
/*[clinic input]
_curses_panel.panel.replace
- cls: defining_class
win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
/
@@ -420,22 +559,17 @@ Change the window associated with the panel to the window win.
static PyObject *
_curses_panel_panel_replace_impl(PyCursesPanelObject *self,
- PyTypeObject *cls,
PyCursesWindowObject *win)
-/*[clinic end generated code: output=c71f95c212d58ae7 input=dbec7180ece41ff5]*/
+/*[clinic end generated code: output=2253a95f7b287255 input=4b1c4283987d9dfa]*/
{
- _curses_panel_state *state = PyType_GetModuleState(cls);
-
- PyCursesPanelObject *po = find_po(self->pan);
+ PyCursesPanelObject *po = find_po(self->pan, "replace", NULL);
if (po == NULL) {
- PyErr_SetString(PyExc_RuntimeError,
- "replace_panel: can't find Panel Object");
return NULL;
}
int rtn = replace_panel(self->pan, win->win);
if (rtn == ERR) {
- PyErr_SetString(state->PyCursesError, "replace_panel() returned ERR");
+ curses_panel_panel_set_error(self, "replace_panel", "replace");
return NULL;
}
Py_SETREF(po->wo, (PyCursesWindowObject*)Py_NewRef(win));
@@ -445,7 +579,6 @@ _curses_panel_panel_replace_impl(PyCursesPanelObject *self,
/*[clinic input]
_curses_panel.panel.set_userptr
- cls: defining_class
obj: object
/
@@ -454,8 +587,8 @@ Set the panel's user pointer to obj.
static PyObject *
_curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self,
- PyTypeObject *cls, PyObject *obj)
-/*[clinic end generated code: output=db74f3db07b28080 input=e3fee2ff7b1b8e48]*/
+ PyObject *obj)
+/*[clinic end generated code: output=7fa1fd23f69db71e input=d2c6a9dbefabbf39]*/
{
PyCursesInitialised;
Py_INCREF(obj);
@@ -464,34 +597,27 @@ _curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self,
if (rc == ERR) {
/* In case of an ncurses error, decref the new object again */
Py_DECREF(obj);
+ curses_panel_panel_set_error(self, "set_panel_userptr", "set_userptr");
+ return NULL;
}
- else {
- Py_XDECREF(oldobj);
- }
-
- _curses_panel_state *state = PyType_GetModuleState(cls);
- return PyCursesCheckERR(state, rc, "set_panel_userptr");
+ Py_XDECREF(oldobj);
+ Py_RETURN_NONE;
}
/*[clinic input]
_curses_panel.panel.userptr
- cls: defining_class
-
Return the user pointer for the panel.
[clinic start generated code]*/
static PyObject *
-_curses_panel_panel_userptr_impl(PyCursesPanelObject *self,
- PyTypeObject *cls)
-/*[clinic end generated code: output=eea6e6f39ffc0179 input=f22ca4f115e30a80]*/
+_curses_panel_panel_userptr_impl(PyCursesPanelObject *self)
+/*[clinic end generated code: output=e849c307b5dc9237 input=f78b7a47aef0fd50]*/
{
- _curses_panel_state *state = PyType_GetModuleState(cls);
-
PyCursesInitialised;
PyObject *obj = (PyObject *) panel_userptr(self->pan);
if (obj == NULL) {
- PyErr_SetString(state->PyCursesError, "no userptr set");
+ curses_panel_panel_set_null_error(self, "panel_userptr", "userptr");
return NULL;
}
@@ -552,18 +678,11 @@ _curses_panel_bottom_panel_impl(PyObject *module)
PyCursesInitialised;
pan = panel_above(NULL);
-
- if (pan == NULL) { /* valid output, it means
- there's no panel at all */
+ if (pan == NULL) { /* valid output: it means no panel exists yet */
Py_RETURN_NONE;
}
- po = find_po(pan);
- if (po == NULL) {
- PyErr_SetString(PyExc_RuntimeError,
- "panel_above: can't find Panel Object");
- return NULL;
- }
- return Py_NewRef(po);
+ po = find_po(pan, "panel_above", "bottom_panel");
+ return Py_XNewRef(po);
}
/*[clinic input]
@@ -579,14 +698,13 @@ static PyObject *
_curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win)
/*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/
{
- _curses_panel_state *state = get_curses_panel_state(module);
-
PANEL *pan = new_panel(win->win);
if (pan == NULL) {
- PyErr_SetString(state->PyCursesError, catchall_NULL);
+ curses_panel_set_null_error(module, "new_panel", NULL);
return NULL;
}
- return (PyObject *)PyCursesPanel_New(state, pan, win);
+ _curses_panel_state *state = get_curses_panel_state(module);
+ return PyCursesPanel_New(state, pan, win);
}
@@ -610,18 +728,11 @@ _curses_panel_top_panel_impl(PyObject *module)
PyCursesInitialised;
pan = panel_below(NULL);
-
- if (pan == NULL) { /* valid output, it means
- there's no panel at all */
+ if (pan == NULL) { /* valid output: it means no panel exists yet */
Py_RETURN_NONE;
}
- po = find_po(pan);
- if (po == NULL) {
- PyErr_SetString(PyExc_RuntimeError,
- "panel_below: can't find Panel Object");
- return NULL;
- }
- return Py_NewRef(po);
+ po = find_po(pan, "panel_below", "top_panel");
+ return Py_XNewRef(po);
}
/*[clinic input]
@@ -673,10 +784,10 @@ _curses_panel_exec(PyObject *mod)
}
/* For exception _curses_panel.error */
- state->PyCursesError = PyErr_NewException(
+ state->error = PyErr_NewException(
"_curses_panel.error", NULL, NULL);
- if (PyModule_AddObjectRef(mod, "error", state->PyCursesError) < 0) {
+ if (PyModule_AddObjectRef(mod, "error", state->error) < 0) {
return -1;
}
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index bf18cb51605..d7788ef7a58 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -108,7 +108,6 @@ static const char PyCursesVersion[] = "2.2";
#include "pycore_capsule.h" // _PyCapsule_SetTraverse()
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_structseq.h" // _PyStructSequence_NewType()
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
#include "pycore_fileutils.h" // _Py_set_inheritable
#ifdef __hpux
@@ -211,18 +210,116 @@ static int curses_start_color_called = FALSE;
static const char *curses_screen_encoding = NULL;
+/* Utility Error Procedures */
+
+static void
+_curses_format_error(cursesmodule_state *state,
+ const char *curses_funcname,
+ const char *python_funcname,
+ const char *return_value,
+ const char *default_message)
+{
+ assert(!PyErr_Occurred());
+ if (python_funcname == NULL && curses_funcname == NULL) {
+ PyErr_SetString(state->error, default_message);
+ }
+ else if (python_funcname == NULL) {
+ (void)PyErr_Format(state->error, CURSES_ERROR_FORMAT,
+ curses_funcname, return_value);
+ }
+ else {
+ assert(python_funcname != NULL);
+ (void)PyErr_Format(state->error, CURSES_ERROR_VERBOSE_FORMAT,
+ curses_funcname, python_funcname, return_value);
+ }
+}
+
+/*
+ * Format a curses error for a function that returned ERR.
+ *
+ * Specify a non-NULL 'python_funcname' when the latter differs from
+ * 'curses_funcname'. If both names are NULL, uses the 'catchall_ERR'
+ * message instead.
+ */
+static void
+_curses_set_error(cursesmodule_state *state,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ _curses_format_error(state, curses_funcname, python_funcname,
+ "ERR", catchall_ERR);
+}
+
+/*
+ * Format a curses error for a function that returned NULL.
+ *
+ * Specify a non-NULL 'python_funcname' when the latter differs from
+ * 'curses_funcname'. If both names are NULL, uses the 'catchall_NULL'
+ * message instead.
+ */
+static inline void
+_curses_set_null_error(cursesmodule_state *state,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ _curses_format_error(state, curses_funcname, python_funcname,
+ "NULL", catchall_NULL);
+}
+
+/* Same as _curses_set_error() for a module object. */
+static void
+curses_set_error(PyObject *module,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ cursesmodule_state *state = get_cursesmodule_state(module);
+ _curses_set_error(state, curses_funcname, python_funcname);
+}
+
+/* Same as _curses_set_null_error() for a module object. */
+static void
+curses_set_null_error(PyObject *module,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ cursesmodule_state *state = get_cursesmodule_state(module);
+ _curses_set_null_error(state, curses_funcname, python_funcname);
+}
+
+/* Same as _curses_set_error() for a Window object. */
+static void
+curses_window_set_error(PyCursesWindowObject *win,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ cursesmodule_state *state = get_cursesmodule_state_by_win(win);
+ _curses_set_error(state, curses_funcname, python_funcname);
+}
+
+/* Same as _curses_set_null_error() for a Window object. */
+static void
+curses_window_set_null_error(PyCursesWindowObject *win,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ cursesmodule_state *state = get_cursesmodule_state_by_win(win);
+ _curses_set_null_error(state, curses_funcname, python_funcname);
+}
+
/* Utility Checking Procedures */
/*
* Function to check that 'funcname' has been called by testing
- * the 'called' boolean. If an error occurs, a PyCursesError is
+ * the 'called' boolean. If an error occurs, an exception is
* set and this returns 0. Otherwise, this returns 1.
*
* Since this function can be called in functions that do not
* have a direct access to the module's state, '_curses.error'
* is imported on demand.
+ *
+ * Use _PyCursesStatefulCheckFunction() if the module is given.
*/
-static inline int
+static int
_PyCursesCheckFunction(int called, const char *funcname)
{
if (called == TRUE) {
@@ -230,7 +327,7 @@ _PyCursesCheckFunction(int called, const char *funcname)
}
PyObject *exc = PyImport_ImportModuleAttrString("_curses", "error");
if (exc != NULL) {
- PyErr_Format(exc, "must call %s() first", funcname);
+ PyErr_Format(exc, CURSES_ERROR_MUST_CALL_FORMAT, funcname);
Py_DECREF(exc);
}
assert(PyErr_Occurred());
@@ -244,14 +341,15 @@ _PyCursesCheckFunction(int called, const char *funcname)
*
* The exception type is obtained from the 'module' state.
*/
-static inline int
-_PyCursesStatefulCheckFunction(PyObject *module, int called, const char *funcname)
+static int
+_PyCursesStatefulCheckFunction(PyObject *module,
+ int called, const char *funcname)
{
if (called == TRUE) {
return 1;
}
cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_Format(state->error, "must call %s() first", funcname);
+ PyErr_Format(state->error, CURSES_ERROR_MUST_CALL_FORMAT, funcname);
return 0;
}
@@ -287,44 +385,39 @@ _PyCursesStatefulCheckFunction(PyObject *module, int called, const char *funcnam
/* Utility Functions */
-static inline void
-_PyCursesSetError(cursesmodule_state *state, const char *funcname)
-{
- if (funcname == NULL) {
- PyErr_SetString(state->error, catchall_ERR);
- }
- else {
- PyErr_Format(state->error, "%s() returned ERR", funcname);
- }
-}
-
/*
- * Check the return code from a curses function and return None
- * or raise an exception as appropriate.
+ * Check the return code from a curses function, returning None
+ * on success and setting an exception on error.
*/
+/*
+ * Return None if 'code' is different from ERR (implementation-defined).
+ * Otherwise, set an exception using curses_set_error() and the remaining
+ * arguments, and return NULL.
+ */
static PyObject *
-PyCursesCheckERR(PyObject *module, int code, const char *fname)
+curses_check_err(PyObject *module, int code,
+ const char *curses_funcname,
+ const char *python_funcname)
{
if (code != ERR) {
Py_RETURN_NONE;
- } else {
- cursesmodule_state *state = get_cursesmodule_state(module);
- _PyCursesSetError(state, fname);
- return NULL;
}
+ curses_set_error(module, curses_funcname, python_funcname);
+ return NULL;
}
+/* Same as curses_check_err() for a Window object. */
static PyObject *
-PyCursesCheckERR_ForWin(PyCursesWindowObject *win, int code, const char *fname)
+curses_window_check_err(PyCursesWindowObject *win, int code,
+ const char *curses_funcname,
+ const char *python_funcname)
{
if (code != ERR) {
Py_RETURN_NONE;
- } else {
- cursesmodule_state *state = get_cursesmodule_state_by_win(win);
- _PyCursesSetError(state, fname);
- return NULL;
}
+ curses_window_set_error(win, curses_funcname, python_funcname);
+ return NULL;
}
/* Convert an object to a byte (an integer of type chtype):
@@ -650,13 +743,16 @@ class component_converter(CConverter):
The Window Object
******************************************************************************/
-/* Function prototype macros for Window object
-
- X - function name
- TYPE - parameter Type
- ERGSTR - format string for construction of the return value
- PARSESTR - format string for argument parsing
-*/
+/*
+ * Macros for creating a PyCursesWindowObject object's method.
+ *
+ * Parameters
+ *
+ * X The name of the curses C function or macro to invoke.
+ * TYPE The function parameter(s) type.
+ * ERGSTR The format string for construction of the return value.
+ * PARSESTR The format string for argument parsing.
+ */
#define Window_NoArgNoReturnFunction(X) \
static PyObject *PyCursesWindow_ ## X \
@@ -664,7 +760,7 @@ class component_converter(CConverter):
{ \
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); \
int code = X(self->win); \
- return PyCursesCheckERR_ForWin(self, code, # X); \
+ return curses_window_check_err(self, code, # X, NULL); \
}
#define Window_NoArgTrueFalseFunction(X) \
@@ -717,7 +813,7 @@ class component_converter(CConverter):
} \
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); \
int code = X(self->win, arg1); \
- return PyCursesCheckERR_ForWin(self, code, # X); \
+ return curses_window_check_err(self, code, # X, NULL); \
}
#define Window_TwoArgNoReturnFunction(X, TYPE, PARSESTR) \
@@ -730,7 +826,7 @@ class component_converter(CConverter):
} \
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); \
int code = X(self->win, arg1, arg2); \
- return PyCursesCheckERR_ForWin(self, code, # X); \
+ return curses_window_check_err(self, code, # X, NULL); \
}
/* ------------- WINDOW routines --------------- */
@@ -787,7 +883,8 @@ Window_TwoArgNoReturnFunction(wresize, int, "ii;lines,columns")
static PyObject *
PyCursesWindow_New(cursesmodule_state *state,
- WINDOW *win, const char *encoding)
+ WINDOW *win, const char *encoding,
+ PyCursesWindowObject *orig)
{
if (encoding == NULL) {
#if defined(MS_WINDOWS)
@@ -821,6 +918,8 @@ PyCursesWindow_New(cursesmodule_state *state,
PyErr_NoMemory();
return NULL;
}
+ wo->orig = orig;
+ Py_XINCREF(orig);
PyObject_GC_Track((PyObject *)wo);
return (PyObject *)wo;
}
@@ -832,12 +931,15 @@ PyCursesWindow_dealloc(PyObject *self)
PyObject_GC_UnTrack(self);
PyCursesWindowObject *wo = (PyCursesWindowObject *)self;
if (wo->win != stdscr && wo->win != NULL) {
- // silently ignore errors in delwin(3)
- (void)delwin(wo->win);
+ if (delwin(wo->win) == ERR) {
+ curses_window_set_error(wo, "delwin", "__del__");
+ PyErr_FormatUnraisable("Exception ignored in delwin()");
+ }
}
if (wo->encoding != NULL) {
PyMem_Free(wo->encoding);
}
+ Py_XDECREF(wo->orig);
window_type->tp_free(self);
Py_DECREF(window_type);
}
@@ -846,6 +948,8 @@ static int
PyCursesWindow_traverse(PyObject *self, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(self));
+ PyCursesWindowObject *wo = (PyCursesWindowObject *)self;
+ Py_VISIT(wo->orig);
return 0;
}
@@ -897,13 +1001,19 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1,
#ifdef HAVE_NCURSESW
type = PyCurses_ConvertToCchar_t(self, ch, &cch, wstr);
if (type == 2) {
- funcname = "add_wch";
wstr[1] = L'\0';
- setcchar(&wcval, wstr, attr, PAIR_NUMBER(attr), NULL);
- if (coordinates_group)
+ rtn = setcchar(&wcval, wstr, attr, PAIR_NUMBER(attr), NULL);
+ if (rtn == ERR) {
+ curses_window_set_error(self, "setcchar", "addch");
+ return NULL;
+ }
+ if (coordinates_group) {
rtn = mvwadd_wch(self->win,y,x, &wcval);
+ funcname = "mvwadd_wch";
+ }
else {
rtn = wadd_wch(self->win, &wcval);
+ funcname = "wadd_wch";
}
}
else
@@ -911,17 +1021,40 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1,
type = PyCurses_ConvertToCchar_t(self, ch, &cch);
#endif
if (type == 1) {
- funcname = "addch";
- if (coordinates_group)
+ if (coordinates_group) {
rtn = mvwaddch(self->win,y,x, cch | (attr_t) attr);
+ funcname = "mvwaddch";
+ }
else {
rtn = waddch(self->win, cch | (attr_t) attr);
+ funcname = "waddch";
}
}
else {
return NULL;
}
- return PyCursesCheckERR_ForWin(self, rtn, funcname);
+ return curses_window_check_err(self, rtn, funcname, "addch");
+}
+
+#ifdef HAVE_NCURSESW
+#define curses_release_wstr(STRTYPE, WSTR) \
+ do { \
+ if ((STRTYPE) == 2) { \
+ PyMem_Free((WSTR)); \
+ } \
+ } while (0)
+#else
+#define curses_release_wstr(_STRTYPE, _WSTR)
+#endif
+
+static int
+curses_wattrset(PyCursesWindowObject *self, long attr, const char *funcname)
+{
+ if (wattrset(self->win, attr) == ERR) {
+ curses_window_set_error(self, "wattrset", funcname);
+ return -1;
+ }
+ return 0;
}
/*[clinic input]
@@ -977,31 +1110,46 @@ _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1,
}
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win,attr);
+ if (curses_wattrset(self, attr, "addstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
- funcname = "addwstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwaddwstr(self->win,y,x,wstr);
- else
+ funcname = "mvwaddwstr";
+ }
+ else {
rtn = waddwstr(self->win,wstr);
+ funcname = "waddwstr";
+ }
PyMem_Free(wstr);
}
else
#endif
{
const char *str = PyBytes_AS_STRING(bytesobj);
- funcname = "addstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwaddstr(self->win,y,x,str);
- else
+ funcname = "mvwaddstr";
+ }
+ else {
rtn = waddstr(self->win,str);
+ funcname = "waddstr";
+ }
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return PyCursesCheckERR_ForWin(self, rtn, funcname);
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "addstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "addstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -1060,31 +1208,46 @@ _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1,
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win,attr);
+ if (curses_wattrset(self, attr, "addnstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
- funcname = "addnwstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwaddnwstr(self->win,y,x,wstr,n);
- else
+ funcname = "mvwaddnwstr";
+ }
+ else {
rtn = waddnwstr(self->win,wstr,n);
+ funcname = "waddnwstr";
+ }
PyMem_Free(wstr);
}
else
#endif
{
const char *str = PyBytes_AS_STRING(bytesobj);
- funcname = "addnstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwaddnstr(self->win,y,x,str,n);
- else
+ funcname = "mvwaddnstr";
+ }
+ else {
rtn = waddnstr(self->win,str,n);
+ funcname = "waddnstr";
+ }
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return PyCursesCheckERR_ForWin(self, rtn, funcname);
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "addnstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "addnstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -1108,7 +1271,8 @@ _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, long attr)
if (!PyCurses_ConvertToChtype(self, ch, &bkgd))
return NULL;
- return PyCursesCheckERR_ForWin(self, wbkgd(self->win, bkgd | attr), "bkgd");
+ int rtn = wbkgd(self->win, bkgd | attr);
+ return curses_window_check_err(self, rtn, "wbkgd", "bkgd");
}
/*[clinic input]
@@ -1124,7 +1288,8 @@ static PyObject *
_curses_window_attroff_impl(PyCursesWindowObject *self, long attr)
/*[clinic end generated code: output=8a2fcd4df682fc64 input=786beedf06a7befe]*/
{
- return PyCursesCheckERR_ForWin(self, wattroff(self->win, (attr_t)attr), "attroff");
+ int rtn = wattroff(self->win, (attr_t)attr);
+ return curses_window_check_err(self, rtn, "wattroff", "attroff");
}
/*[clinic input]
@@ -1140,7 +1305,8 @@ static PyObject *
_curses_window_attron_impl(PyCursesWindowObject *self, long attr)
/*[clinic end generated code: output=7afea43b237fa870 input=5a88fba7b1524f32]*/
{
- return PyCursesCheckERR_ForWin(self, wattron(self->win, (attr_t)attr), "attron");
+ int rtn = wattron(self->win, (attr_t)attr);
+ return curses_window_check_err(self, rtn, "wattron", "attron");
}
/*[clinic input]
@@ -1156,7 +1322,8 @@ static PyObject *
_curses_window_attrset_impl(PyCursesWindowObject *self, long attr)
/*[clinic end generated code: output=84e379bff20c0433 input=42e400c0d0154ab5]*/
{
- return PyCursesCheckERR_ForWin(self, wattrset(self->win, (attr_t)attr), "attrset");
+ int rtn = wattrset(self->win, (attr_t)attr);
+ return curses_window_check_err(self, rtn, "wattrset", "attrset");
}
/*[clinic input]
@@ -1182,7 +1349,7 @@ _curses_window_bkgdset_impl(PyCursesWindowObject *self, PyObject *ch,
return NULL;
wbkgdset(self->win, bkgd | attr);
- return PyCursesCheckERR_ForWin(self, 0, "bkgdset");
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -1222,7 +1389,7 @@ _curses_window_border_impl(PyCursesWindowObject *self, PyObject *ls,
/*[clinic end generated code: output=670ef38d3d7c2aa3 input=e015f735d67a240b]*/
{
chtype ch[8];
- int i;
+ int i, rtn;
/* Clear the array of parameters */
for(i=0; i<8; i++)
@@ -1243,10 +1410,10 @@ _curses_window_border_impl(PyCursesWindowObject *self, PyObject *ls,
#undef CONVERTTOCHTYPE
- wborder(self->win,
- ch[0], ch[1], ch[2], ch[3],
- ch[4], ch[5], ch[6], ch[7]);
- Py_RETURN_NONE;
+ rtn = wborder(self->win,
+ ch[0], ch[1], ch[2], ch[3],
+ ch[4], ch[5], ch[6], ch[7]);
+ return curses_window_check_err(self, rtn, "wborder", "border");
}
/*[clinic input]
@@ -1280,8 +1447,7 @@ _curses_window_box_impl(PyCursesWindowObject *self, int group_right_1,
return NULL;
}
}
- box(self->win,ch1,ch2);
- Py_RETURN_NONE;
+ return curses_window_check_err(self, box(self->win, ch1, ch2), "box", NULL);
}
#if defined(HAVE_NCURSES_H) || defined(MVWDELCH_IS_EXPRESSION)
@@ -1306,38 +1472,34 @@ int py_mvwdelch(WINDOW *w, int y, int x)
/* chgat, added by Fabian Kreutz <fabian.kreutz at gmx.net> */
#ifdef HAVE_CURSES_WCHGAT
-/*[-clinic input]
-_curses.window.chgat
- [
- y: int
- Y-coordinate.
- x: int
- X-coordinate.
- ]
-
- n: int = -1
- Number of characters.
-
- attr: long
- Attributes for characters.
- /
+PyDoc_STRVAR(_curses_window_chgat__doc__,
+"chgat([y, x,] [n=-1,] attr)\n"
+"Set the attributes of characters.\n"
+"\n"
+" y\n"
+" Y-coordinate.\n"
+" x\n"
+" X-coordinate.\n"
+" n\n"
+" Number of characters.\n"
+" attr\n"
+" Attributes for characters.\n"
+"\n"
+"Set the attributes of num characters at the current cursor position, or at\n"
+"position (y, x) if supplied. If no value of num is given or num = -1, the\n"
+"attribute will be set on all the characters to the end of the line. This\n"
+"function does not move the cursor. The changed line will be touched using\n"
+"the touchline() method so that the contents will be redisplayed by the next\n"
+"window refresh.");
-Set the attributes of characters.
-
-Set the attributes of num characters at the current cursor position, or at
-position (y, x) if supplied. If no value of num is given or num = -1, the
-attribute will be set on all the characters to the end of the line. This
-function does not move the cursor. The changed line will be touched using
-the touchline() method so that the contents will be redisplayed by the next
-window refresh.
-[-clinic start generated code]*/
static PyObject *
PyCursesWindow_ChgAt(PyObject *op, PyObject *args)
{
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
int rtn;
+ const char *funcname;
int x, y;
int num = -1;
short color;
@@ -1357,19 +1519,20 @@ PyCursesWindow_ChgAt(PyObject *op, PyObject *args)
attr = lattr;
break;
case 3:
- if (!PyArg_ParseTuple(args,"iil;int,int,attr", &y, &x, &lattr))
+ if (!PyArg_ParseTuple(args,"iil;y,x,attr", &y, &x, &lattr))
return NULL;
attr = lattr;
use_xy = TRUE;
break;
case 4:
- if (!PyArg_ParseTuple(args,"iiil;int,int,n,attr", &y, &x, &num, &lattr))
+ if (!PyArg_ParseTuple(args,"iiil;y,x,n,attr", &y, &x, &num, &lattr))
return NULL;
attr = lattr;
use_xy = TRUE;
break;
default:
- PyErr_SetString(PyExc_TypeError, "chgat requires 1 to 4 arguments");
+ PyErr_SetString(PyExc_TypeError,
+ "_curses.window.chgat requires 1 to 4 arguments");
return NULL;
}
@@ -1378,13 +1541,18 @@ PyCursesWindow_ChgAt(PyObject *op, PyObject *args)
if (use_xy) {
rtn = mvwchgat(self->win,y,x,num,attr,color,NULL);
- touchline(self->win,y,1);
+ funcname = "mvwchgat";
} else {
getyx(self->win,y,x);
rtn = wchgat(self->win,num,attr,color,NULL);
- touchline(self->win,y,1);
+ funcname = "wchgat";
}
- return PyCursesCheckERR_ForWin(self, rtn, "chgat");
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "chgat");
+ return NULL;
+ }
+ rtn = touchline(self->win,y,1);
+ return curses_window_check_err(self, rtn, "touchline", "chgat");
}
#endif
@@ -1407,12 +1575,17 @@ _curses_window_delch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x)
/*[clinic end generated code: output=22e77bb9fa11b461 input=d2f79e630a4fc6d0]*/
{
+ int rtn;
+ const char *funcname;
if (!group_right_1) {
- return PyCursesCheckERR_ForWin(self, wdelch(self->win), "wdelch");
+ rtn = wdelch(self->win);
+ funcname = "wdelch";
}
else {
- return PyCursesCheckERR_ForWin(self, py_mvwdelch(self->win, y, x), "mvwdelch");
+ rtn = py_mvwdelch(self->win, y, x);
+ funcname = "mvwdelch";
}
+ return curses_window_check_err(self, rtn, funcname, "delch");
}
/*[clinic input]
@@ -1447,13 +1620,12 @@ _curses_window_derwin_impl(PyCursesWindowObject *self, int group_left_1,
win = derwin(self->win,nlines,ncols,begin_y,begin_x);
if (win == NULL) {
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error, catchall_NULL);
+ curses_window_set_null_error(self, "derwin", NULL);
return NULL;
}
cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- return PyCursesWindow_New(state, win, NULL);
+ return PyCursesWindow_New(state, win, NULL, self);
}
/*[clinic input]
@@ -1479,17 +1651,20 @@ _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch,
if (!PyCurses_ConvertToChtype(self, ch, &ch_))
return NULL;
+ int rtn;
+ const char *funcname;
#ifdef py_is_pad
if (py_is_pad(self->win)) {
- return PyCursesCheckERR_ForWin(self,
- pechochar(self->win, ch_ | (attr_t)attr),
- "echochar");
+ rtn = pechochar(self->win, ch_ | (attr_t)attr);
+ funcname = "pechochar";
}
else
#endif
- return PyCursesCheckERR_ForWin(self,
- wechochar(self->win, ch_ | (attr_t)attr),
- "echochar");
+ {
+ rtn = wechochar(self->win, ch_ | (attr_t)attr);
+ funcname = "wechochar";
+ }
+ return curses_window_check_err(self, rtn, funcname, "echochar");
}
#ifdef NCURSES_MOUSE_VERSION
@@ -1514,20 +1689,40 @@ _curses_window_enclose_impl(PyCursesWindowObject *self, int y, int x)
#endif
/*[clinic input]
-_curses.window.getbkgd -> long
+_curses.window.getbkgd
Return the window's current background character/attribute pair.
[clinic start generated code]*/
-static long
+static PyObject *
_curses_window_getbkgd_impl(PyCursesWindowObject *self)
-/*[clinic end generated code: output=c52b25dc16b215c3 input=a69db882fa35426c]*/
+/*[clinic end generated code: output=3ff953412b0e6028 input=7cf1f59a31f89df4]*/
{
- return (long) getbkgd(self->win);
+ chtype rtn = getbkgd(self->win);
+ if (rtn == (chtype)ERR) {
+ curses_window_set_error(self, "getbkgd", NULL);
+ return NULL;
+ }
+ return PyLong_FromLong(rtn);
+}
+
+static PyObject *
+curses_check_signals_on_input_error(PyCursesWindowObject *self,
+ const char *curses_funcname,
+ const char *python_funcname)
+{
+ assert(!PyErr_Occurred());
+ if (PyErr_CheckSignals()) {
+ return NULL;
+ }
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ PyErr_Format(state->error, "%s() (called by %s()): no input",
+ curses_funcname, python_funcname);
+ return NULL;
}
/*[clinic input]
-_curses.window.getch -> int
+_curses.window.getch
[
y: int
@@ -1544,10 +1739,10 @@ keypad keys and so on return numbers higher than 256. In no-delay mode, -1
is returned if there is no input, else getch() waits until a key is pressed.
[clinic start generated code]*/
-static int
+static PyObject *
_curses_window_getch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x)
-/*[clinic end generated code: output=980aa6af0c0ca387 input=bb24ebfb379f991f]*/
+/*[clinic end generated code: output=e1639e87d545e676 input=73f350336b1ee8c8]*/
{
int rtn;
@@ -1560,7 +1755,17 @@ _curses_window_getch_impl(PyCursesWindowObject *self, int group_right_1,
}
Py_END_ALLOW_THREADS
- return rtn;
+ if (rtn == ERR) {
+ // We suppress ERR returned by wgetch() in nodelay mode
+ // after we handled possible interruption signals.
+ if (PyErr_CheckSignals()) {
+ return NULL;
+ }
+ // ERR is an implementation detail, so to be on the safe side,
+ // we forcibly set the return value to -1 as documented above.
+ rtn = -1;
+ }
+ return PyLong_FromLong(rtn);
}
/*[clinic input]
@@ -1598,13 +1803,9 @@ _curses_window_getkey_impl(PyCursesWindowObject *self, int group_right_1,
Py_END_ALLOW_THREADS
if (rtn == ERR) {
- /* getch() returns ERR in nodelay mode */
- PyErr_CheckSignals();
- if (!PyErr_Occurred()) {
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error, "no input");
- }
- return NULL;
+ /* wgetch() returns ERR in nodelay mode */
+ const char *funcname = group_right_1 ? "mvwgetch" : "wgetch";
+ return curses_check_signals_on_input_error(self, funcname, "getkey");
} else if (rtn <= 255) {
#ifdef NCURSES_VERSION_MAJOR
#if NCURSES_VERSION_MAJOR*100+NCURSES_VERSION_MINOR <= 507
@@ -1657,13 +1858,9 @@ _curses_window_get_wch_impl(PyCursesWindowObject *self, int group_right_1,
Py_END_ALLOW_THREADS
if (ct == ERR) {
- if (PyErr_CheckSignals())
- return NULL;
-
- /* get_wch() returns ERR in nodelay mode */
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error, "no input");
- return NULL;
+ /* wget_wch() returns ERR in nodelay mode */
+ const char *funcname = group_right_1 ? "mvwget_wch" : "wget_wch";
+ return curses_check_signals_on_input_error(self, funcname, "get_wch");
}
if (ct == KEY_CODE_YES)
return PyLong_FromLong(rtn);
@@ -1672,84 +1869,102 @@ _curses_window_get_wch_impl(PyCursesWindowObject *self, int group_right_1,
}
#endif
-/*[-clinic input]
-_curses.window.getstr
-
- [
- y: int
- Y-coordinate.
- x: int
- X-coordinate.
- ]
- n: int = 1023
- Maximal number of characters.
- /
+/*
+ * Helper function for parsing parameters from getstr() and instr().
+ * This function is necessary because Argument Clinic does not know
+ * how to handle nested optional groups with default values inside.
+ *
+ * Return 1 on success and 0 on failure, similar to PyArg_ParseTuple().
+ */
+static int
+curses_clinic_parse_optional_xy_n(PyObject *args,
+ int *y, int *x, unsigned int *n, int *use_xy,
+ const char *qualname)
+{
+ switch (PyTuple_GET_SIZE(args)) {
+ case 0: {
+ *use_xy = 0;
+ return 1;
+ }
+ case 1: {
+ *use_xy = 0;
+ return PyArg_ParseTuple(args, "O&;n",
+ _PyLong_UnsignedInt_Converter, n);
+ }
+ case 2: {
+ *use_xy = 1;
+ return PyArg_ParseTuple(args, "ii;y,x", y, x);
+ }
+ case 3: {
+ *use_xy = 1;
+ return PyArg_ParseTuple(args, "iiO&;y,x,n", y, x,
+ _PyLong_UnsignedInt_Converter, n);
+ }
+ default: {
+ *use_xy = 0;
+ PyErr_Format(PyExc_TypeError, "%s requires 0 to 3 arguments",
+ qualname);
+ return 0;
+ }
+ }
+}
-Read a string from the user, with primitive line editing capacity.
-[-clinic start generated code]*/
+PyDoc_STRVAR(_curses_window_getstr__doc__,
+"getstr([[y, x,] n=2047])\n"
+"Read a string from the user, with primitive line editing capacity.\n"
+"\n"
+" y\n"
+" Y-coordinate.\n"
+" x\n"
+" X-coordinate.\n"
+" n\n"
+" Maximal number of characters.");
static PyObject *
-PyCursesWindow_GetStr(PyObject *op, PyObject *args)
+PyCursesWindow_getstr(PyObject *op, PyObject *args)
{
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
+ int rtn, use_xy = 0, y = 0, x = 0;
+ unsigned int max_buf_size = 2048;
+ unsigned int n = max_buf_size - 1;
+ PyObject *res;
- int x, y, n;
- char rtn[1024]; /* This should be big enough.. I hope */
- int rtn2;
+ if (!curses_clinic_parse_optional_xy_n(args, &y, &x, &n, &use_xy,
+ "_curses.window.instr"))
+ {
+ return NULL;
+ }
- switch (PyTuple_Size(args)) {
- case 0:
- Py_BEGIN_ALLOW_THREADS
- rtn2 = wgetnstr(self->win,rtn, 1023);
- Py_END_ALLOW_THREADS
- break;
- case 1:
- if (!PyArg_ParseTuple(args,"i;n", &n))
- return NULL;
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
- return NULL;
- }
- Py_BEGIN_ALLOW_THREADS
- rtn2 = wgetnstr(self->win, rtn, Py_MIN(n, 1023));
- Py_END_ALLOW_THREADS
- break;
- case 2:
- if (!PyArg_ParseTuple(args,"ii;y,x",&y,&x))
- return NULL;
+ n = Py_MIN(n, max_buf_size - 1);
+ res = PyBytes_FromStringAndSize(NULL, n + 1);
+ if (res == NULL) {
+ return NULL;
+ }
+ char *buf = PyBytes_AS_STRING(res);
+
+ if (use_xy) {
Py_BEGIN_ALLOW_THREADS
#ifdef STRICT_SYSV_CURSES
- rtn2 = wmove(self->win,y,x)==ERR ? ERR : wgetnstr(self->win, rtn, 1023);
+ rtn = wmove(self->win, y, x) == ERR
+ ? ERR
+ : wgetnstr(self->win, buf, n);
#else
- rtn2 = mvwgetnstr(self->win,y,x,rtn, 1023);
+ rtn = mvwgetnstr(self->win, y, x, buf, n);
#endif
Py_END_ALLOW_THREADS
- break;
- case 3:
- if (!PyArg_ParseTuple(args,"iii;y,x,n", &y, &x, &n))
- return NULL;
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
- return NULL;
- }
-#ifdef STRICT_SYSV_CURSES
- Py_BEGIN_ALLOW_THREADS
- rtn2 = wmove(self->win,y,x)==ERR ? ERR :
- wgetnstr(self->win, rtn, Py_MIN(n, 1023));
- Py_END_ALLOW_THREADS
-#else
+ }
+ else {
Py_BEGIN_ALLOW_THREADS
- rtn2 = mvwgetnstr(self->win, y, x, rtn, Py_MIN(n, 1023));
+ rtn = wgetnstr(self->win, buf, n);
Py_END_ALLOW_THREADS
-#endif
- break;
- default:
- PyErr_SetString(PyExc_TypeError, "getstr requires 0 to 3 arguments");
- return NULL;
}
- if (rtn2 == ERR)
- rtn[0] = 0;
- return PyBytes_FromString(rtn);
+
+ if (rtn == ERR) {
+ Py_DECREF(res);
+ return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
+ }
+ _PyBytes_Resize(&res, strlen(buf)); // 'res' is set to NULL on failure
+ return res;
}
/*[clinic input]
@@ -1788,10 +2003,12 @@ _curses_window_hline_impl(PyCursesWindowObject *self, int group_left_1,
return NULL;
if (group_left_1) {
if (wmove(self->win, y, x) == ERR) {
- return PyCursesCheckERR_ForWin(self, ERR, "wmove");
+ curses_window_set_error(self, "wmove", "hline");
+ return NULL;
}
}
- return PyCursesCheckERR_ForWin(self, whline(self->win, ch_ | (attr_t)attr, n), "hline");
+ int rtn = whline(self->win, ch_ | (attr_t)attr, n);
+ return curses_window_check_err(self, rtn, "whline", "hline");
}
/*[clinic input]
@@ -1831,18 +2048,21 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1,
if (!PyCurses_ConvertToChtype(self, ch, &ch_))
return NULL;
+ const char *funcname;
if (!group_left_1) {
rtn = winsch(self->win, ch_ | (attr_t)attr);
+ funcname = "winsch";
}
else {
rtn = mvwinsch(self->win, y, x, ch_ | (attr_t)attr);
+ funcname = "mvwwinsch";
}
- return PyCursesCheckERR_ForWin(self, rtn, "insch");
+ return curses_window_check_err(self, rtn, funcname, "insch");
}
/*[clinic input]
-_curses.window.inch -> unsigned_long
+_curses.window.inch
[
y: int
@@ -1857,86 +2077,80 @@ Return the character at the given position in the window.
The bottom 8 bits are the character proper, and upper bits are the attributes.
[clinic start generated code]*/
-static unsigned long
+static PyObject *
_curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x)
-/*[clinic end generated code: output=6c4719fe978fe86a input=fac23ee11e3b3a66]*/
+/*[clinic end generated code: output=97ca8581baaafd06 input=4b4fb43d85b177c3]*/
{
- unsigned long rtn;
+ chtype rtn;
+ const char *funcname;
if (!group_right_1) {
rtn = winch(self->win);
+ funcname = "winch";
}
else {
rtn = mvwinch(self->win, y, x);
+ funcname = "mvwinch";
}
-
- return rtn;
+ if (rtn == (chtype)ERR) {
+ curses_window_set_error(self, funcname, "inch");
+ return NULL;
+ }
+ return PyLong_FromUnsignedLong(rtn);
}
-/*[-clinic input]
-_curses.window.instr
+PyDoc_STRVAR(_curses_window_instr__doc__,
+"instr([y, x,] n=2047)\n"
+"Return a string of characters, extracted from the window.\n"
+"\n"
+" y\n"
+" Y-coordinate.\n"
+" x\n"
+" X-coordinate.\n"
+" n\n"
+" Maximal number of characters.\n"
+"\n"
+"Return a string of characters, extracted from the window starting at the\n"
+"current cursor position, or at y, x if specified. Attributes are stripped\n"
+"from the characters. If n is specified, instr() returns a string at most\n"
+"n characters long (exclusive of the trailing NUL).");
- [
- y: int
- Y-coordinate.
- x: int
- X-coordinate.
- ]
- n: int = 1023
- Maximal number of characters.
- /
-
-Return a string of characters, extracted from the window.
-
-Return a string of characters, extracted from the window starting at the
-current cursor position, or at y, x if specified. Attributes are stripped
-from the characters. If n is specified, instr() returns a string at most
-n characters long (exclusive of the trailing NUL).
-[-clinic start generated code]*/
static PyObject *
-PyCursesWindow_InStr(PyObject *op, PyObject *args)
+PyCursesWindow_instr(PyObject *op, PyObject *args)
{
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
+ int rtn, use_xy = 0, y = 0, x = 0;
+ unsigned int max_buf_size = 2048;
+ unsigned int n = max_buf_size - 1;
+ PyObject *res;
- int x, y, n;
- char rtn[1024]; /* This should be big enough.. I hope */
- int rtn2;
+ if (!curses_clinic_parse_optional_xy_n(args, &y, &x, &n, &use_xy,
+ "_curses.window.instr"))
+ {
+ return NULL;
+ }
- switch (PyTuple_Size(args)) {
- case 0:
- rtn2 = winnstr(self->win,rtn, 1023);
- break;
- case 1:
- if (!PyArg_ParseTuple(args,"i;n", &n))
- return NULL;
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
- return NULL;
- }
- rtn2 = winnstr(self->win, rtn, Py_MIN(n, 1023));
- break;
- case 2:
- if (!PyArg_ParseTuple(args,"ii;y,x",&y,&x))
- return NULL;
- rtn2 = mvwinnstr(self->win,y,x,rtn,1023);
- break;
- case 3:
- if (!PyArg_ParseTuple(args, "iii;y,x,n", &y, &x, &n))
- return NULL;
- if (n < 0) {
- PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
- return NULL;
- }
- rtn2 = mvwinnstr(self->win, y, x, rtn, Py_MIN(n,1023));
- break;
- default:
- PyErr_SetString(PyExc_TypeError, "instr requires 0 or 3 arguments");
+ n = Py_MIN(n, max_buf_size - 1);
+ res = PyBytes_FromStringAndSize(NULL, n + 1);
+ if (res == NULL) {
return NULL;
}
- if (rtn2 == ERR)
- rtn[0] = 0;
- return PyBytes_FromString(rtn);
+ char *buf = PyBytes_AS_STRING(res);
+
+ if (use_xy) {
+ rtn = mvwinnstr(self->win, y, x, buf, n);
+ }
+ else {
+ rtn = winnstr(self->win, buf, n);
+ }
+
+ if (rtn == ERR) {
+ Py_DECREF(res);
+ return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
+ }
+ _PyBytes_Resize(&res, strlen(buf)); // 'res' is set to NULL on failure
+ return res;
}
/*[clinic input]
@@ -1993,31 +2207,46 @@ _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1,
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win, (attr_t)attr);
+ if (curses_wattrset(self, attr, "insstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
- funcname = "inswstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwins_wstr(self->win,y,x,wstr);
- else
+ funcname = "mvwins_wstr";
+ }
+ else {
rtn = wins_wstr(self->win,wstr);
+ funcname = "wins_wstr";
+ }
PyMem_Free(wstr);
}
else
#endif
{
const char *str = PyBytes_AS_STRING(bytesobj);
- funcname = "insstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwinsstr(self->win,y,x,str);
- else
+ funcname = "mvwinsstr";
+ }
+ else {
rtn = winsstr(self->win,str);
+ funcname = "winsstr";
+ }
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return PyCursesCheckERR_ForWin(self, rtn, funcname);
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "insstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "insstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -2078,31 +2307,46 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1,
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win, (attr_t)attr);
+ if (curses_wattrset(self, attr, "insnstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
- funcname = "insn_wstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwins_nwstr(self->win,y,x,wstr,n);
- else
+ funcname = "mvwins_nwstr";
+ }
+ else {
rtn = wins_nwstr(self->win,wstr,n);
+ funcname = "wins_nwstr";
+ }
PyMem_Free(wstr);
}
else
#endif
{
const char *str = PyBytes_AS_STRING(bytesobj);
- funcname = "insnstr";
- if (use_xy)
+ if (use_xy) {
rtn = mvwinsnstr(self->win,y,x,str,n);
- else
+ funcname = "mvwinsnstr";
+ }
+ else {
rtn = winsnstr(self->win,str,n);
+ funcname = "winsnstr";
+ }
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return PyCursesCheckERR_ForWin(self, rtn, funcname);
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "insnstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "insnstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -2124,8 +2368,7 @@ _curses_window_is_linetouched_impl(PyCursesWindowObject *self, int line)
int erg;
erg = is_linetouched(self->win, line);
if (erg == ERR) {
- PyErr_SetString(PyExc_TypeError,
- "is_linetouched: line number outside of boundaries");
+ curses_window_set_error(self, "is_linetouched", NULL);
return NULL;
}
return PyBool_FromLong(erg);
@@ -2179,8 +2422,7 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject *self)
#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error,
+ PyErr_SetString(PyExc_TypeError,
"noutrefresh() called for a pad "
"requires 6 arguments");
return NULL;
@@ -2189,7 +2431,8 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject *self)
rtn = pnoutrefresh(self->win, pminrow, pmincol,
sminrow, smincol, smaxrow, smaxcol);
Py_END_ALLOW_THREADS
- return PyCursesCheckERR_ForWin(self, rtn, "pnoutrefresh");
+ return curses_window_check_err(self, rtn,
+ "pnoutrefresh", "noutrefresh");
}
if (group_right_1) {
PyErr_SetString(PyExc_TypeError,
@@ -2200,7 +2443,7 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject *self)
Py_BEGIN_ALLOW_THREADS
rtn = wnoutrefresh(self->win);
Py_END_ALLOW_THREADS
- return PyCursesCheckERR_ForWin(self, rtn, "wnoutrefresh");
+ return curses_window_check_err(self, rtn, "wnoutrefresh", "noutrefresh");
}
/*[clinic input]
@@ -2242,11 +2485,11 @@ _curses_window_overlay_impl(PyCursesWindowObject *self,
if (group_right_1) {
rtn = copywin(self->win, destwin->win, sminrow, smincol,
dminrow, dmincol, dmaxrow, dmaxcol, TRUE);
- return PyCursesCheckERR_ForWin(self, rtn, "copywin");
+ return curses_window_check_err(self, rtn, "copywin", "overlay");
}
else {
rtn = overlay(self->win, destwin->win);
- return PyCursesCheckERR_ForWin(self, rtn, "overlay");
+ return curses_window_check_err(self, rtn, "overlay", NULL);
}
}
@@ -2290,11 +2533,11 @@ _curses_window_overwrite_impl(PyCursesWindowObject *self,
if (group_right_1) {
rtn = copywin(self->win, destwin->win, sminrow, smincol,
dminrow, dmincol, dmaxrow, dmaxcol, FALSE);
- return PyCursesCheckERR_ForWin(self, rtn, "copywin");
+ return curses_window_check_err(self, rtn, "copywin", "overwrite");
}
else {
rtn = overwrite(self->win, destwin->win);
- return PyCursesCheckERR_ForWin(self, rtn, "overwrite");
+ return curses_window_check_err(self, rtn, "overwrite", NULL);
}
}
@@ -2323,7 +2566,7 @@ _curses_window_putwin_impl(PyCursesWindowObject *self, PyObject *file)
return PyErr_SetFromErrno(PyExc_OSError);
if (_Py_set_inheritable(fileno(fp), 0, NULL) < 0)
goto exit;
- res = PyCursesCheckERR_ForWin(self, putwin(self->win, fp), "putwin");
+ res = curses_window_check_err(self, putwin(self->win, fp), "putwin", NULL);
if (res == NULL)
goto exit;
fseek(fp, 0, 0);
@@ -2362,7 +2605,8 @@ static PyObject *
_curses_window_redrawln_impl(PyCursesWindowObject *self, int beg, int num)
/*[clinic end generated code: output=ea216e334f9ce1b4 input=152155e258a77a7a]*/
{
- return PyCursesCheckERR_ForWin(self, wredrawln(self->win,beg,num), "redrawln");
+ int rtn = wredrawln(self->win,beg, num);
+ return curses_window_check_err(self, rtn, "wredrawln", "redrawln");
}
/*[clinic input]
@@ -2404,8 +2648,7 @@ _curses_window_refresh_impl(PyCursesWindowObject *self, int group_right_1,
#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error,
+ PyErr_SetString(PyExc_TypeError,
"refresh() for a pad requires 6 arguments");
return NULL;
}
@@ -2413,7 +2656,7 @@ _curses_window_refresh_impl(PyCursesWindowObject *self, int group_right_1,
rtn = prefresh(self->win, pminrow, pmincol,
sminrow, smincol, smaxrow, smaxcol);
Py_END_ALLOW_THREADS
- return PyCursesCheckERR_ForWin(self, rtn, "prefresh");
+ return curses_window_check_err(self, rtn, "prefresh", "refresh");
}
#endif
if (group_right_1) {
@@ -2424,7 +2667,7 @@ _curses_window_refresh_impl(PyCursesWindowObject *self, int group_right_1,
Py_BEGIN_ALLOW_THREADS
rtn = wrefresh(self->win);
Py_END_ALLOW_THREADS
- return PyCursesCheckERR_ForWin(self, rtn, "prefresh");
+ return curses_window_check_err(self, rtn, "wrefresh", "refresh");
}
/*[clinic input]
@@ -2446,7 +2689,8 @@ _curses_window_setscrreg_impl(PyCursesWindowObject *self, int top,
int bottom)
/*[clinic end generated code: output=486ab5db218d2b1a input=1b517b986838bf0e]*/
{
- return PyCursesCheckERR_ForWin(self, wsetscrreg(self->win, top, bottom), "wsetscrreg");
+ int rtn = wsetscrreg(self->win, top, bottom);
+ return curses_window_check_err(self, rtn, "wsetscrreg", "setscrreg");
}
/*[clinic input]
@@ -2476,24 +2720,28 @@ _curses_window_subwin_impl(PyCursesWindowObject *self, int group_left_1,
/*[clinic end generated code: output=93e898afc348f59a input=2129fa47fd57721c]*/
{
WINDOW *win;
+ const char *funcname;
/* printf("Subwin: %i %i %i %i \n", nlines, ncols, begin_y, begin_x); */
#ifdef py_is_pad
if (py_is_pad(self->win)) {
win = subpad(self->win, nlines, ncols, begin_y, begin_x);
+ funcname = "subpad";
}
else
#endif
+ {
win = subwin(self->win, nlines, ncols, begin_y, begin_x);
+ funcname = "subwin";
+ }
if (win == NULL) {
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error, catchall_NULL);
+ curses_window_set_null_error(self, funcname, "subwin");
return NULL;
}
cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- return PyCursesWindow_New(state, win, self->encoding);
+ return PyCursesWindow_New(state, win, self->encoding, self);
}
/*[clinic input]
@@ -2515,12 +2763,17 @@ _curses_window_scroll_impl(PyCursesWindowObject *self, int group_right_1,
int lines)
/*[clinic end generated code: output=4541a8a11852d360 input=c969ca0cfabbdbec]*/
{
+ int rtn;
+ const char *funcname;
if (!group_right_1) {
- return PyCursesCheckERR_ForWin(self, scroll(self->win), "scroll");
+ rtn = scroll(self->win);
+ funcname = "scroll";
}
else {
- return PyCursesCheckERR_ForWin(self, wscrl(self->win, lines), "scroll");
+ rtn = wscrl(self->win, lines);
+ funcname = "wscrl";
}
+ return curses_window_check_err(self, rtn, funcname, "scroll");
}
/*[clinic input]
@@ -2544,12 +2797,17 @@ _curses_window_touchline_impl(PyCursesWindowObject *self, int start,
int count, int group_right_1, int changed)
/*[clinic end generated code: output=65d05b3f7438c61d input=a98aa4f79b6be845]*/
{
+ int rtn;
+ const char *funcname;
if (!group_right_1) {
- return PyCursesCheckERR_ForWin(self, touchline(self->win, start, count), "touchline");
+ rtn = touchline(self->win, start, count);
+ funcname = "touchline";
}
else {
- return PyCursesCheckERR_ForWin(self, wtouchln(self->win, start, count, changed), "touchline");
+ rtn = wtouchln(self->win, start, count, changed);
+ funcname = "wtouchln";
}
+ return curses_window_check_err(self, rtn, funcname, "touchline");
}
/*[clinic input]
@@ -2587,10 +2845,13 @@ _curses_window_vline_impl(PyCursesWindowObject *self, int group_left_1,
if (!PyCurses_ConvertToChtype(self, ch, &ch_))
return NULL;
if (group_left_1) {
- if (wmove(self->win, y, x) == ERR)
- return PyCursesCheckERR_ForWin(self, ERR, "wmove");
+ if (wmove(self->win, y, x) == ERR) {
+ curses_window_set_error(self, "wmove", "vline");
+ return NULL;
+ }
}
- return PyCursesCheckERR_ForWin(self, wvline(self->win, ch_ | (attr_t)attr, n), "vline");
+ int rtn = wvline(self->win, ch_ | (attr_t)attr, n);
+ return curses_window_check_err(self, rtn, "wvline", "vline");
}
static PyObject *
@@ -2647,7 +2908,10 @@ static PyMethodDef PyCursesWindow_methods[] = {
_CURSES_WINDOW_ATTRSET_METHODDEF
_CURSES_WINDOW_BKGD_METHODDEF
#ifdef HAVE_CURSES_WCHGAT
- {"chgat", PyCursesWindow_ChgAt, METH_VARARGS},
+ {
+ "chgat", PyCursesWindow_ChgAt, METH_VARARGS,
+ _curses_window_chgat__doc__
+ },
#endif
_CURSES_WINDOW_BKGDSET_METHODDEF
_CURSES_WINDOW_BORDER_METHODDEF
@@ -2670,7 +2934,10 @@ static PyMethodDef PyCursesWindow_methods[] = {
_CURSES_WINDOW_GET_WCH_METHODDEF
{"getmaxyx", PyCursesWindow_getmaxyx, METH_NOARGS},
{"getparyx", PyCursesWindow_getparyx, METH_NOARGS},
- {"getstr", PyCursesWindow_GetStr, METH_VARARGS},
+ {
+ "getstr", PyCursesWindow_getstr, METH_VARARGS,
+ _curses_window_getstr__doc__
+ },
{"getyx", PyCursesWindow_getyx, METH_NOARGS},
_CURSES_WINDOW_HLINE_METHODDEF
{"idcok", PyCursesWindow_idcok, METH_VARARGS},
@@ -2684,7 +2951,10 @@ static PyMethodDef PyCursesWindow_methods[] = {
{"insertln", PyCursesWindow_winsertln, METH_NOARGS},
_CURSES_WINDOW_INSNSTR_METHODDEF
_CURSES_WINDOW_INSSTR_METHODDEF
- {"instr", PyCursesWindow_InStr, METH_VARARGS},
+ {
+ "instr", PyCursesWindow_instr, METH_VARARGS,
+ _curses_window_instr__doc__
+ },
_CURSES_WINDOW_IS_LINETOUCHED_METHODDEF
{"is_wintouched", PyCursesWindow_is_wintouched, METH_NOARGS},
{"keypad", PyCursesWindow_keypad, METH_VARARGS},
@@ -2755,49 +3025,78 @@ static PyType_Spec PyCursesWindow_Type_spec = {
/* -------------------------------------------------------*/
-/* Function Body Macros - They are ugly but very, very useful. ;-)
-
- X - function name
- TYPE - parameter Type
- ERGSTR - format string for construction of the return value
- PARSESTR - format string for argument parsing
- */
-
-#define NoArgNoReturnFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return PyCursesCheckERR(module, X(), # X); }
+/*
+ * Macros for implementing simple module's methods.
+ *
+ * Parameters
+ *
+ * X The name of the curses C function or macro to invoke.
+ * FLAG When false, prefixes the function name with 'no' at runtime,
+ * This parameter is present in the signature and auto-generated
+ * by Argument Clinic.
+ *
+ * These macros should only be used for generating the body of
+ * the module's methods since they need a module reference.
+ *
+ * The Python function name must be the same as the curses function name (X).
+ */
-#define NoArgOrFlagNoReturnFunctionBody(X, flag) \
-{ \
- PyCursesStatefulInitialised(module); \
- if (flag) \
- return PyCursesCheckERR(module, X(), # X); \
- else \
- return PyCursesCheckERR(module, no ## X(), # X); \
+#define NoArgNoReturnFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ return curses_check_err(module, X(), # X, NULL); \
}
-#define NoArgReturnIntFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return PyLong_FromLong((long) X()); }
+#define NoArgOrFlagNoReturnFunctionBody(X, FLAG) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ int rtn; \
+ const char *funcname; \
+ if (FLAG) { \
+ rtn = X(); \
+ funcname = # X; \
+ } \
+ else { \
+ rtn = no ## X(); \
+ funcname = "no" # X; \
+ } \
+ return curses_check_err(module, rtn, funcname, # X); \
+}
+#define NoArgReturnIntFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ int rtn = X(); \
+ if (rtn == ERR) { \
+ curses_set_error(module, # X, NULL); \
+ return NULL; \
+ } \
+ return PyLong_FromLong(rtn); \
+}
-#define NoArgReturnStringFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return PyBytes_FromString(X()); }
+#define NoArgReturnStringFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ const char *res = X(); \
+ if (res == NULL) { \
+ curses_set_null_error(module, # X, NULL); \
+ return NULL; \
+ } \
+ return PyBytes_FromString(res); \
+}
-#define NoArgTrueFalseFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return PyBool_FromLong(X()); }
+#define NoArgTrueFalseFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ return PyBool_FromLong(X()); \
+}
-#define NoArgNoReturnVoidFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- X(); \
- Py_RETURN_NONE; }
+#define NoArgNoReturnVoidFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ X(); \
+ Py_RETURN_NONE; \
+}
/*********************************************************************
Global Functions
@@ -2897,9 +3196,8 @@ _curses_color_content_impl(PyObject *module, int color_number)
PyCursesStatefulInitialisedColor(module);
if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) == ERR) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_Format(state->error, "%s() returned ERR",
- Py_STRINGIFY(_COLOR_CONTENT_FUNC));
+ const char *funcname = Py_STRINGIFY(_COLOR_CONTENT_FUNC);
+ curses_set_error(module, funcname, "color_content");
return NULL;
}
@@ -2953,7 +3251,10 @@ _curses_curs_set_impl(PyObject *module, int visibility)
PyCursesStatefulInitialised(module);
erg = curs_set(visibility);
- if (erg == ERR) return PyCursesCheckERR(module, erg, "curs_set");
+ if (erg == ERR) {
+ curses_set_error(module, "curs_set", NULL);
+ return NULL;
+ }
return PyLong_FromLong((long) erg);
}
@@ -3004,7 +3305,7 @@ _curses_delay_output_impl(PyObject *module, int ms)
{
PyCursesStatefulInitialised(module);
- return PyCursesCheckERR(module, delay_output(ms), "delay_output");
+ return curses_check_err(module, delay_output(ms), "delay_output", NULL);
}
/*[clinic input]
@@ -3137,8 +3438,7 @@ _curses_getmouse_impl(PyObject *module)
rtn = getmouse( &event );
if (rtn == ERR) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, "getmouse() returned ERR");
+ curses_set_error(module, "getmouse", NULL);
return NULL;
}
return Py_BuildValue("(hiiik)",
@@ -3176,7 +3476,7 @@ _curses_ungetmouse_impl(PyObject *module, short id, int x, int y, int z,
event.y = y;
event.z = z;
event.bstate = bstate;
- return PyCursesCheckERR(module, ungetmouse(&event), "ungetmouse");
+ return curses_check_err(module, ungetmouse(&event), "ungetmouse", NULL);
}
#endif
@@ -3232,12 +3532,11 @@ _curses_getwin(PyObject *module, PyObject *file)
fseek(fp, 0, 0);
win = getwin(fp);
if (win == NULL) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, catchall_NULL);
+ curses_set_null_error(module, "getwin", NULL);
goto error;
}
cursesmodule_state *state = get_cursesmodule_state(module);
- res = PyCursesWindow_New(state, win, NULL);
+ res = PyCursesWindow_New(state, win, NULL, NULL);
error:
fclose(fp);
@@ -3262,7 +3561,7 @@ _curses_halfdelay_impl(PyObject *module, unsigned char tenths)
{
PyCursesStatefulInitialised(module);
- return PyCursesCheckERR(module, halfdelay(tenths), "halfdelay");
+ return curses_check_err(module, halfdelay(tenths), "halfdelay", NULL);
}
/*[clinic input]
@@ -3347,9 +3646,10 @@ _curses_init_color_impl(PyObject *module, int color_number, short r, short g,
PyCursesStatefulInitialised(module);
PyCursesStatefulInitialisedColor(module);
- return PyCursesCheckERR(module,
+ return curses_check_err(module,
_CURSES_INIT_COLOR_FUNC(color_number, r, g, b),
- Py_STRINGIFY(_CURSES_INIT_COLOR_FUNC));
+ Py_STRINGIFY(_CURSES_INIT_COLOR_FUNC),
+ NULL);
}
/*[clinic input]
@@ -3383,9 +3683,8 @@ _curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
COLOR_PAIRS - 1);
}
else {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_Format(state->error, "%s() returned ERR",
- Py_STRINGIFY(_CURSES_INIT_PAIR_FUNC));
+ const char *funcname = Py_STRINGIFY(_CURSES_INIT_PAIR_FUNC);
+ curses_set_error(module, funcname, "init_pair");
}
return NULL;
}
@@ -3408,16 +3707,19 @@ _curses_initscr_impl(PyObject *module)
WINDOW *win;
if (curses_initscr_called) {
- wrefresh(stdscr);
cursesmodule_state *state = get_cursesmodule_state(module);
- return PyCursesWindow_New(state, stdscr, NULL);
+ int code = wrefresh(stdscr);
+ if (code == ERR) {
+ _curses_set_null_error(state, "wrefresh", "initscr");
+ return NULL;
+ }
+ return PyCursesWindow_New(state, stdscr, NULL, NULL);
}
win = initscr();
if (win == NULL) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, catchall_NULL);
+ curses_set_null_error(module, "initscr", NULL);
return NULL;
}
@@ -3514,7 +3816,7 @@ _curses_initscr_impl(PyObject *module)
#undef SetDictInt
cursesmodule_state *state = get_cursesmodule_state(module);
- PyObject *winobj = PyCursesWindow_New(state, win, NULL);
+ PyObject *winobj = PyCursesWindow_New(state, win, NULL, NULL);
if (winobj == NULL) {
return NULL;
}
@@ -3544,7 +3846,7 @@ _curses_setupterm_impl(PyObject *module, const char *term, int fd)
if (fd == -1) {
PyObject* sys_stdout;
- if (_PySys_GetOptionalAttrString("stdout", &sys_stdout) < 0) {
+ if (PySys_GetOptionalAttrString("stdout", &sys_stdout) < 0) {
return NULL;
}
@@ -3622,7 +3924,7 @@ _curses_set_escdelay_impl(PyObject *module, int ms)
return NULL;
}
- return PyCursesCheckERR(module, set_escdelay(ms), "set_escdelay");
+ return curses_check_err(module, set_escdelay(ms), "set_escdelay", NULL);
}
/*[clinic input]
@@ -3661,7 +3963,7 @@ _curses_set_tabsize_impl(PyObject *module, int size)
return NULL;
}
- return PyCursesCheckERR(module, set_tabsize(size), "set_tabsize");
+ return curses_check_err(module, set_tabsize(size), "set_tabsize", NULL);
}
#endif
@@ -3679,7 +3981,7 @@ _curses_intrflush_impl(PyObject *module, int flag)
{
PyCursesStatefulInitialised(module);
- return PyCursesCheckERR(module, intrflush(NULL, flag), "intrflush");
+ return curses_check_err(module, intrflush(NULL, flag), "intrflush", NULL);
}
/*[clinic input]
@@ -3792,7 +4094,7 @@ _curses_meta_impl(PyObject *module, int yes)
{
PyCursesStatefulInitialised(module);
- return PyCursesCheckERR(module, meta(stdscr, yes), "meta");
+ return curses_check_err(module, meta(stdscr, yes), "meta", NULL);
}
#ifdef NCURSES_MOUSE_VERSION
@@ -3815,8 +4117,12 @@ _curses_mouseinterval_impl(PyObject *module, int interval)
/*[clinic end generated code: output=c4f5ff04354634c5 input=75aaa3f0db10ac4e]*/
{
PyCursesStatefulInitialised(module);
-
- return PyCursesCheckERR(module, mouseinterval(interval), "mouseinterval");
+ int value = mouseinterval(interval);
+ if (value == ERR) {
+ curses_set_error(module, "mouseinterval", NULL);
+ return NULL;
+ }
+ return PyLong_FromLong(value);
}
/*[clinic input]
@@ -3892,13 +4198,12 @@ _curses_newpad_impl(PyObject *module, int nlines, int ncols)
win = newpad(nlines, ncols);
if (win == NULL) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, catchall_NULL);
+ curses_set_null_error(module, "newpad", NULL);
return NULL;
}
cursesmodule_state *state = get_cursesmodule_state(module);
- return PyCursesWindow_New(state, win, NULL);
+ return PyCursesWindow_New(state, win, NULL, NULL);
}
/*[clinic input]
@@ -3933,13 +4238,12 @@ _curses_newwin_impl(PyObject *module, int nlines, int ncols,
win = newwin(nlines,ncols,begin_y,begin_x);
if (win == NULL) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, catchall_NULL);
+ curses_set_null_error(module, "newwin", NULL);
return NULL;
}
cursesmodule_state *state = get_cursesmodule_state(module);
- return PyCursesWindow_New(state, win, NULL);
+ return PyCursesWindow_New(state, win, NULL, NULL);
}
/*[clinic input]
@@ -4053,9 +4357,8 @@ _curses_pair_content_impl(PyObject *module, int pair_number)
COLOR_PAIRS - 1);
}
else {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_Format(state->error, "%s() returned ERR",
- Py_STRINGIFY(_CURSES_PAIR_CONTENT_FUNC));
+ const char *funcname = Py_STRINGIFY(_CURSES_PAIR_CONTENT_FUNC);
+ curses_set_error(module, funcname, "pair_content");
}
return NULL;
}
@@ -4099,7 +4402,7 @@ static PyObject *
_curses_putp_impl(PyObject *module, const char *string)
/*[clinic end generated code: output=e98081d1b8eb5816 input=1601faa828b44cb3]*/
{
- return PyCursesCheckERR(module, putp(string), "putp");
+ return curses_check_err(module, putp(string), "putp", NULL);
}
/*[clinic input]
@@ -4273,10 +4576,12 @@ _curses_resizeterm_impl(PyObject *module, short nlines, short ncols)
/*[clinic end generated code: output=4de3abab50c67f02 input=414e92a63e3e9899]*/
{
PyObject *result;
+ int code;
PyCursesStatefulInitialised(module);
- result = PyCursesCheckERR(module, resizeterm(nlines, ncols), "resizeterm");
+ code = resizeterm(nlines, ncols);
+ result = curses_check_err(module, code, "resizeterm", NULL);
if (!result)
return NULL;
if (!update_lines_cols(module)) {
@@ -4312,10 +4617,12 @@ _curses_resize_term_impl(PyObject *module, short nlines, short ncols)
/*[clinic end generated code: output=46c6d749fa291dbd input=276afa43d8ea7091]*/
{
PyObject *result;
+ int code;
PyCursesStatefulInitialised(module);
- result = PyCursesCheckERR(module, resize_term(nlines, ncols), "resize_term");
+ code = resize_term(nlines, ncols);
+ result = curses_check_err(module, code, "resize_term", NULL);
if (!result)
return NULL;
if (!update_lines_cols(module)) {
@@ -4384,8 +4691,7 @@ _curses_start_color_impl(PyObject *module)
PyCursesStatefulInitialised(module);
if (start_color() == ERR) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, "start_color() returned ERR");
+ curses_set_error(module, "start_color", NULL);
return NULL;
}
@@ -4537,8 +4843,7 @@ _curses_tparm_impl(PyObject *module, const char *str, int i1, int i2, int i3,
result = tparm((char *)str,i1,i2,i3,i4,i5,i6,i7,i8,i9);
if (!result) {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, "tparm() returned NULL");
+ curses_set_null_error(module, "tparm", NULL);
return NULL;
}
@@ -4564,7 +4869,7 @@ _curses_typeahead_impl(PyObject *module, int fd)
{
PyCursesStatefulInitialised(module);
- return PyCursesCheckERR(module, typeahead( fd ), "typeahead");
+ return curses_check_err(module, typeahead(fd), "typeahead", NULL);
}
#endif
@@ -4591,7 +4896,12 @@ _curses_unctrl(PyObject *module, PyObject *ch)
if (!PyCurses_ConvertToChtype(NULL, ch, &ch_))
return NULL;
- return PyBytes_FromString(unctrl(ch_));
+ const char *res = unctrl(ch_);
+ if (res == NULL) {
+ curses_set_null_error(module, "unctrl", NULL);
+ return NULL;
+ }
+ return PyBytes_FromString(res);
}
/*[clinic input]
@@ -4614,7 +4924,7 @@ _curses_ungetch(PyObject *module, PyObject *ch)
if (!PyCurses_ConvertToChtype(NULL, ch, &ch_))
return NULL;
- return PyCursesCheckERR(module, ungetch(ch_), "ungetch");
+ return curses_check_err(module, ungetch(ch_), "ungetch", NULL);
}
#ifdef HAVE_NCURSESW
@@ -4684,7 +4994,7 @@ _curses_unget_wch(PyObject *module, PyObject *ch)
if (!PyCurses_ConvertToWchar_t(ch, &wch))
return NULL;
- return PyCursesCheckERR(module, unget_wch(wch), "unget_wch");
+ return curses_check_err(module, unget_wch(wch), "unget_wch", NULL);
}
#endif
@@ -4720,15 +5030,12 @@ _curses_use_env_impl(PyObject *module, int flag)
/*[clinic input]
_curses.use_default_colors
-Allow use of default values for colors on terminals supporting this feature.
-
-Use this to support transparency in your application. The default color
-is assigned to the color number -1.
+Equivalent to assume_default_colors(-1, -1).
[clinic start generated code]*/
static PyObject *
_curses_use_default_colors_impl(PyObject *module)
-/*[clinic end generated code: output=a3b81ff71dd901be input=656844367470e8fc]*/
+/*[clinic end generated code: output=a3b81ff71dd901be input=99ff0b7c69834d1f]*/
{
int code;
@@ -4736,13 +5043,34 @@ _curses_use_default_colors_impl(PyObject *module)
PyCursesStatefulInitialisedColor(module);
code = use_default_colors();
- if (code != ERR) {
- Py_RETURN_NONE;
- } else {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, "use_default_colors() returned ERR");
- return NULL;
- }
+ return curses_check_err(module, code, "use_default_colors", NULL);
+}
+
+/*[clinic input]
+_curses.assume_default_colors
+ fg: int
+ bg: int
+ /
+
+Allow use of default values for colors on terminals supporting this feature.
+
+Assign terminal default foreground/background colors to color number -1.
+Change the definition of the color-pair 0 to (fg, bg).
+
+Use this to support transparency in your application.
+[clinic start generated code]*/
+
+static PyObject *
+_curses_assume_default_colors_impl(PyObject *module, int fg, int bg)
+/*[clinic end generated code: output=54985397a7d2b3a5 input=7fe301712ef3e9fb]*/
+{
+ int code;
+
+ PyCursesStatefulInitialised(module);
+ PyCursesStatefulInitialisedColor(module);
+
+ code = assume_default_colors(fg, bg);
+ return curses_check_err(module, code, "assume_default_colors", NULL);
}
#endif /* STRICT_SYSV_CURSES */
@@ -4902,6 +5230,7 @@ static PyMethodDef cursesmodule_methods[] = {
_CURSES_UNGET_WCH_METHODDEF
_CURSES_USE_ENV_METHODDEF
_CURSES_USE_DEFAULT_COLORS_METHODDEF
+ _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
{NULL, NULL} /* sentinel */
};
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 9bba0e3354b..eb90be81c8d 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -1088,6 +1088,7 @@ parse_isoformat_time(const char *dtstr, size_t dtlen, int *hour, int *minute,
// -3: Failed to parse time component
// -4: Failed to parse time separator
// -5: Malformed timezone string
+ // -6: Timezone fields are not in range
const char *p = dtstr;
const char *p_end = dtstr + dtlen;
@@ -1134,6 +1135,11 @@ parse_isoformat_time(const char *dtstr, size_t dtlen, int *hour, int *minute,
rv = parse_hh_mm_ss_ff(tzinfo_pos, p_end, &tzhour, &tzminute, &tzsecond,
tzmicrosecond);
+ // Check if timezone fields are in range
+ if (check_time_args(tzhour, tzminute, tzsecond, *tzmicrosecond, 0) < 0) {
+ return -6;
+ }
+
*tzoffset = tzsign * ((tzhour * 3600) + (tzminute * 60) + tzsecond);
*tzmicrosecond *= tzsign;
@@ -5039,6 +5045,9 @@ time_fromisoformat(PyObject *cls, PyObject *tstr) {
&tzoffset, &tzimicrosecond);
if (rv < 0) {
+ if (rv == -6) {
+ goto error;
+ }
goto invalid_string_error;
}
@@ -5075,6 +5084,9 @@ invalid_iso_midnight:
invalid_string_error:
PyErr_Format(PyExc_ValueError, "Invalid isoformat string: %R", tstr);
return NULL;
+
+error:
+ return NULL;
}
@@ -5539,8 +5551,9 @@ datetime_best_possible(PyObject *cls, TM_FUNC f, PyObject *tzinfo)
time_t secs;
int us;
- if (_PyTime_AsTimevalTime_t(ts, &secs, &us, _PyTime_ROUND_FLOOR) < 0)
+ if (_PyTime_AsTimevalTime_t(ts, &secs, &us, _PyTime_ROUND_HALF_EVEN) < 0) {
return NULL;
+ }
assert(0 <= us && us <= 999999);
return datetime_from_timet_and_us(cls, f, secs, us, tzinfo);
@@ -5927,6 +5940,9 @@ datetime_fromisoformat(PyObject *cls, PyObject *dtstr)
len -= (p - dt_ptr);
rv = parse_isoformat_time(p, len, &hour, &minute, &second,
&microsecond, &tzoffset, &tzusec);
+ if (rv == -6) {
+ goto error;
+ }
}
if (rv < 0) {
goto invalid_string_error;
diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c
index cc65cbd98d7..0cd0f043de4 100644
--- a/Modules/_dbmmodule.c
+++ b/Modules/_dbmmodule.c
@@ -69,6 +69,7 @@ typedef struct {
#include "clinic/_dbmmodule.c.h"
#define check_dbmobject_open(v, err) \
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((v)) \
if ((v)->di_dbm == NULL) { \
PyErr_SetString(err, "DBM object has already been closed"); \
return NULL; \
@@ -116,7 +117,7 @@ dbm_dealloc(PyObject *self)
}
static Py_ssize_t
-dbm_length(PyObject *self)
+dbm_length_lock_held(PyObject *self)
{
dbmobject *dp = dbmobject_CAST(self);
_dbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
@@ -138,8 +139,18 @@ dbm_length(PyObject *self)
return dp->di_size;
}
+static Py_ssize_t
+dbm_length(PyObject *self)
+{
+ Py_ssize_t result;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ result = dbm_length_lock_held(self);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
static int
-dbm_bool(PyObject *self)
+dbm_bool_lock_held(PyObject *self)
{
dbmobject *dp = dbmobject_CAST(self);
_dbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
@@ -170,8 +181,18 @@ dbm_bool(PyObject *self)
return 1;
}
+static int
+dbm_bool(PyObject *self)
+{
+ int result;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ result = dbm_bool_lock_held(self);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
static PyObject *
-dbm_subscript(PyObject *self, PyObject *key)
+dbm_subscript_lock_held(PyObject *self, PyObject *key)
{
datum drec, krec;
Py_ssize_t tmp_size;
@@ -197,8 +218,18 @@ dbm_subscript(PyObject *self, PyObject *key)
return PyBytes_FromStringAndSize(drec.dptr, drec.dsize);
}
+static PyObject *
+dbm_subscript(PyObject *self, PyObject *key)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ result = dbm_subscript_lock_held(self, key);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
static int
-dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w)
+dbm_ass_sub_lock_held(PyObject *self, PyObject *v, PyObject *w)
{
datum krec, drec;
Py_ssize_t tmp_size;
@@ -252,7 +283,18 @@ dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w)
return 0;
}
+static int
+dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w)
+{
+ int result;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ result = dbm_ass_sub_lock_held(self, v, w);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
/*[clinic input]
+@critical_section
_dbm.dbm.close
Close the database.
@@ -260,7 +302,7 @@ Close the database.
static PyObject *
_dbm_dbm_close_impl(dbmobject *self)
-/*[clinic end generated code: output=c8dc5b6709600b86 input=046db72377d51be8]*/
+/*[clinic end generated code: output=c8dc5b6709600b86 input=4a94f79facbc28ca]*/
{
if (self->di_dbm) {
dbm_close(self->di_dbm);
@@ -270,6 +312,7 @@ _dbm_dbm_close_impl(dbmobject *self)
}
/*[clinic input]
+@critical_section
_dbm.dbm.keys
cls: defining_class
@@ -279,7 +322,7 @@ Return a list of all keys in the database.
static PyObject *
_dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=f2a593b3038e5996 input=d3706a28fc051097]*/
+/*[clinic end generated code: output=f2a593b3038e5996 input=6ddefeadf2a80156]*/
{
PyObject *v, *item;
datum key;
@@ -310,7 +353,7 @@ _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls)
}
static int
-dbm_contains(PyObject *self, PyObject *arg)
+dbm_contains_lock_held(PyObject *self, PyObject *arg)
{
dbmobject *dp = dbmobject_CAST(self);
datum key, val;
@@ -343,7 +386,18 @@ dbm_contains(PyObject *self, PyObject *arg)
return val.dptr != NULL;
}
+static int
+dbm_contains(PyObject *self, PyObject *arg)
+{
+ int result;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ result = dbm_contains_lock_held(self, arg);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
/*[clinic input]
+@critical_section
_dbm.dbm.get
cls: defining_class
key: str(accept={str, robuffer}, zeroes=True)
@@ -356,7 +410,7 @@ Return the value for key if present, otherwise default.
static PyObject *
_dbm_dbm_get_impl(dbmobject *self, PyTypeObject *cls, const char *key,
Py_ssize_t key_length, PyObject *default_value)
-/*[clinic end generated code: output=b4e55f8b6d482bc4 input=66b993b8349fa8c1]*/
+/*[clinic end generated code: output=b4e55f8b6d482bc4 input=1d88a22bb5e55202]*/
{
datum dbm_key, val;
_dbm_state *state = PyType_GetModuleState(cls);
@@ -373,6 +427,7 @@ _dbm_dbm_get_impl(dbmobject *self, PyTypeObject *cls, const char *key,
}
/*[clinic input]
+@critical_section
_dbm.dbm.setdefault
cls: defining_class
key: str(accept={str, robuffer}, zeroes=True)
@@ -387,7 +442,7 @@ If key is not in the database, it is inserted with default as the value.
static PyObject *
_dbm_dbm_setdefault_impl(dbmobject *self, PyTypeObject *cls, const char *key,
Py_ssize_t key_length, PyObject *default_value)
-/*[clinic end generated code: output=9c2f6ea6d0fb576c input=126a3ff15c5f8232]*/
+/*[clinic end generated code: output=9c2f6ea6d0fb576c input=c01510ef7571e13b]*/
{
datum dbm_key, val;
Py_ssize_t tmp_size;
@@ -427,6 +482,7 @@ _dbm_dbm_setdefault_impl(dbmobject *self, PyTypeObject *cls, const char *key,
}
/*[clinic input]
+@critical_section
_dbm.dbm.clear
cls: defining_class
/
@@ -436,7 +492,7 @@ Remove all items from the database.
static PyObject *
_dbm_dbm_clear_impl(dbmobject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=8d126b9e1d01a434 input=43aa6ca1acb7f5f5]*/
+/*[clinic end generated code: output=8d126b9e1d01a434 input=a1aa5d99adfb9656]*/
{
_dbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 8c3efa36353..b9e12ab2026 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -17,6 +17,7 @@
#include "Python.h"
#include "pycore_pyhash.h" // _Py_HashSecret
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stddef.h> // offsetof()
#include "expat.h"
@@ -690,8 +691,7 @@ element_dealloc(PyObject *op)
/* bpo-31095: UnTrack is needed before calling any callbacks */
PyObject_GC_UnTrack(self);
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
/* element_gc_clear clears all references and deallocates extra
*/
@@ -811,6 +811,8 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
PyTypeObject *tp = Py_TYPE(self);
elementtreestate *st = get_elementtree_state_by_type(tp);
+ // The deepcopy() helper takes care of incrementing the refcount
+ // of the object to copy so to avoid use-after-frees.
tag = deepcopy(st, self->tag, memo);
if (!tag)
return NULL;
@@ -845,11 +847,13 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
assert(!element->extra || !element->extra->length);
if (self->extra) {
- if (element_resize(element, self->extra->length) < 0)
+ Py_ssize_t expected_count = self->extra->length;
+ if (element_resize(element, expected_count) < 0) {
+ assert(!element->extra->length);
goto error;
+ }
- // TODO(picnixz): check for an evil child's __deepcopy__ on 'self'
- for (i = 0; i < self->extra->length; i++) {
+ for (i = 0; self->extra && i < self->extra->length; i++) {
PyObject* child = deepcopy(st, self->extra->children[i], memo);
if (!child || !Element_Check(st, child)) {
if (child) {
@@ -859,11 +863,24 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
element->extra->length = i;
goto error;
}
+ if (self->extra && expected_count != self->extra->length) {
+ // 'self->extra' got mutated and 'element' may not have
+ // sufficient space to hold the next iteration's item.
+ expected_count = self->extra->length;
+ if (element_resize(element, expected_count) < 0) {
+ Py_DECREF(child);
+ element->extra->length = i;
+ goto error;
+ }
+ }
element->extra->children[i] = child;
}
assert(!element->extra->length);
- element->extra->length = self->extra->length;
+ // The original 'self->extra' may be gone at this point if deepcopy()
+ // has side-effects. However, 'i' is the number of copied items that
+ // we were able to successfully copy.
+ element->extra->length = i;
}
/* add object to memo dictionary (so deepcopy won't visit it again) */
@@ -906,13 +923,20 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject *memo)
break;
}
}
- if (simple)
+ if (simple) {
return PyDict_Copy(object);
+ }
/* Fall through to general case */
}
else if (Element_CheckExact(st, object)) {
- return _elementtree_Element___deepcopy___impl(
+ // The __deepcopy__() call may call arbitrary code even if the
+ // object to copy is a built-in XML element (one of its children
+ // any of its parents in its own __deepcopy__() implementation).
+ Py_INCREF(object);
+ PyObject *res = _elementtree_Element___deepcopy___impl(
(ElementObject *)object, memo);
+ Py_DECREF(object);
+ return res;
}
}
@@ -923,8 +947,11 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject *memo)
return NULL;
}
+ Py_INCREF(object);
PyObject *args[2] = {object, memo};
- return PyObject_Vectorcall(st->deepcopy_obj, args, 2, NULL);
+ PyObject *res = PyObject_Vectorcall(st->deepcopy_obj, args, 2, NULL);
+ Py_DECREF(object);
+ return res;
}
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index e6c454faf4b..d3dabd58b89 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -7,6 +7,7 @@
#include "pycore_pyatomic_ft_wrappers.h"
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_tuple.h" // _PyTuple_ITEMS()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "clinic/_functoolsmodule.c.h"
@@ -196,6 +197,19 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
return NULL;
}
+ /* keyword Placeholder prohibition */
+ if (kw != NULL) {
+ PyObject *key, *val;
+ Py_ssize_t pos = 0;
+ while (PyDict_Next(kw, &pos, &key, &val)) {
+ if (val == phold) {
+ PyErr_SetString(PyExc_TypeError,
+ "Placeholder cannot be passed as a keyword argument");
+ return NULL;
+ }
+ }
+ }
+
/* check wrapped function / object */
pto_args = pto_kw = NULL;
int res = PyObject_TypeCheck(func, state->partial_type);
@@ -338,9 +352,7 @@ partial_dealloc(PyObject *self)
PyTypeObject *tp = Py_TYPE(self);
/* bpo-31095: UnTrack is needed before calling any callbacks */
PyObject_GC_UnTrack(self);
- if (partialobject_CAST(self)->weakreflist != NULL) {
- PyObject_ClearWeakRefs(self);
- }
+ FT_CLEAR_WEAKREFS(self, partialobject_CAST(self)->weakreflist);
(void)partial_clear(self);
tp->tp_free(self);
Py_DECREF(tp);
@@ -1370,8 +1382,8 @@ bounded_lru_cache_update_lock_held(lru_cache_object *self,
this same key, then this setitem call will update the cache dict
with this new link, leaving the old link as an orphan (i.e. not
having a cache dict entry that refers to it). */
- if (_PyDict_SetItem_KnownHash(self->cache, key, (PyObject *)link,
- hash) < 0) {
+ if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)self->cache, key,
+ (PyObject *)link, hash) < 0) {
Py_DECREF(link);
return NULL;
}
@@ -1440,8 +1452,8 @@ bounded_lru_cache_update_lock_held(lru_cache_object *self,
for successful insertion in the cache dict before adding the
link to the linked list. Otherwise, the potentially reentrant
__eq__ call could cause the then orphan link to be visited. */
- if (_PyDict_SetItem_KnownHash(self->cache, key, (PyObject *)link,
- hash) < 0) {
+ if (_PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)self->cache, key,
+ (PyObject *)link, hash) < 0) {
/* Somehow the cache dict update failed. We no longer can
restore the old link. Let the error propagate upward and
leave the cache short one link. */
@@ -1608,9 +1620,7 @@ lru_cache_dealloc(PyObject *op)
PyTypeObject *tp = Py_TYPE(obj);
/* bpo-31095: UnTrack is needed before calling any callbacks */
PyObject_GC_UnTrack(obj);
- if (obj->weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, obj->weakreflist);
(void)lru_cache_tp_clear(op);
tp->tp_free(obj);
@@ -1676,7 +1686,13 @@ _functools__lru_cache_wrapper_cache_clear_impl(PyObject *self)
lru_list_elem *list = lru_cache_unlink_list(_self);
FT_ATOMIC_STORE_SSIZE_RELAXED(_self->hits, 0);
FT_ATOMIC_STORE_SSIZE_RELAXED(_self->misses, 0);
- PyDict_Clear(_self->cache);
+ if (_self->wrapper == bounded_lru_cache_wrapper) {
+ /* The critical section on the lru cache itself protects the dictionary
+ for bounded_lru_cache instances. */
+ _PyDict_Clear_LockHeld(_self->cache);
+ } else {
+ PyDict_Clear(_self->cache);
+ }
lru_cache_clear_list(list);
Py_RETURN_NONE;
}
diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c
index ab2ebdba924..6a4939512b2 100644
--- a/Modules/_gdbmmodule.c
+++ b/Modules/_gdbmmodule.c
@@ -81,6 +81,7 @@ typedef struct {
#include "clinic/_gdbmmodule.c.h"
#define check_gdbmobject_open(v, err) \
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((v)) \
if ((v)->di_dbm == NULL) { \
PyErr_SetString(err, "GDBM object has already been closed"); \
return NULL; \
@@ -142,7 +143,7 @@ gdbm_dealloc(PyObject *op)
}
static Py_ssize_t
-gdbm_length(PyObject *op)
+gdbm_length_lock_held(PyObject *op)
{
gdbmobject *dp = _gdbmobject_CAST(op);
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
@@ -188,8 +189,18 @@ gdbm_length(PyObject *op)
return dp->di_size;
}
+static Py_ssize_t
+gdbm_length(PyObject *op)
+{
+ Py_ssize_t result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = gdbm_length_lock_held(op);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
static int
-gdbm_bool(PyObject *op)
+gdbm_bool_lock_held(PyObject *op)
{
gdbmobject *dp = _gdbmobject_CAST(op);
_gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp));
@@ -218,6 +229,16 @@ gdbm_bool(PyObject *op)
return 1;
}
+static int
+gdbm_bool(PyObject *op)
+{
+ int result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = gdbm_bool_lock_held(op);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
// Wrapper function for PyArg_Parse(o, "s#", &d.dptr, &d.size).
// This function is needed to support PY_SSIZE_T_CLEAN.
// Return 1 on success, same to PyArg_Parse().
@@ -240,7 +261,7 @@ parse_datum(PyObject *o, datum *d, const char *failmsg)
}
static PyObject *
-gdbm_subscript(PyObject *op, PyObject *key)
+gdbm_subscript_lock_held(PyObject *op, PyObject *key)
{
PyObject *v;
datum drec, krec;
@@ -265,6 +286,16 @@ gdbm_subscript(PyObject *op, PyObject *key)
return v;
}
+static PyObject *
+gdbm_subscript(PyObject *op, PyObject *key)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = gdbm_subscript_lock_held(op, key);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
/*[clinic input]
_gdbm.gdbm.get
@@ -290,7 +321,7 @@ _gdbm_gdbm_get_impl(gdbmobject *self, PyObject *key, PyObject *default_value)
}
static int
-gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w)
+gdbm_ass_sub_lock_held(PyObject *op, PyObject *v, PyObject *w)
{
datum krec, drec;
const char *failmsg = "gdbm mappings have bytes or string indices only";
@@ -335,7 +366,18 @@ gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w)
return 0;
}
+static int
+gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w)
+{
+ int result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = gdbm_ass_sub_lock_held(op, v, w);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
/*[clinic input]
+@critical_section
_gdbm.gdbm.setdefault
key: object
@@ -348,7 +390,7 @@ Get value for key, or set it to default and return default if not present.
static PyObject *
_gdbm_gdbm_setdefault_impl(gdbmobject *self, PyObject *key,
PyObject *default_value)
-/*[clinic end generated code: output=f3246e880509f142 input=0db46b69e9680171]*/
+/*[clinic end generated code: output=f3246e880509f142 input=854374cd81ab51b6]*/
{
PyObject *res;
@@ -363,6 +405,7 @@ _gdbm_gdbm_setdefault_impl(gdbmobject *self, PyObject *key,
}
/*[clinic input]
+@critical_section
_gdbm.gdbm.close
Close the database.
@@ -370,7 +413,7 @@ Close the database.
static PyObject *
_gdbm_gdbm_close_impl(gdbmobject *self)
-/*[clinic end generated code: output=f5abb4d6bb9e52d5 input=0a203447379b45fd]*/
+/*[clinic end generated code: output=f5abb4d6bb9e52d5 input=56b604f4e77f533d]*/
{
if (self->di_dbm) {
gdbm_close(self->di_dbm);
@@ -381,6 +424,7 @@ _gdbm_gdbm_close_impl(gdbmobject *self)
/* XXX Should return a set or a set view */
/*[clinic input]
+@critical_section
_gdbm.gdbm.keys
cls: defining_class
@@ -390,7 +434,7 @@ Get a list of all keys in the database.
static PyObject *
_gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=c24b824e81404755 input=1428b7c79703d7d5]*/
+/*[clinic end generated code: output=c24b824e81404755 input=785988b1ea8f77e0]*/
{
PyObject *v, *item;
datum key, nextkey;
@@ -432,7 +476,7 @@ _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls)
}
static int
-gdbm_contains(PyObject *self, PyObject *arg)
+gdbm_contains_lock_held(PyObject *self, PyObject *arg)
{
gdbmobject *dp = (gdbmobject *)self;
datum key;
@@ -463,7 +507,18 @@ gdbm_contains(PyObject *self, PyObject *arg)
return gdbm_exists(dp->di_dbm, key);
}
+static int
+gdbm_contains(PyObject *self, PyObject *arg)
+{
+ int result;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ result = gdbm_contains_lock_held(self, arg);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
/*[clinic input]
+@critical_section
_gdbm.gdbm.firstkey
cls: defining_class
@@ -477,7 +532,7 @@ hash values, and won't be sorted by the key values.
static PyObject *
_gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=139275e9c8b60827 input=ed8782a029a5d299]*/
+/*[clinic end generated code: output=139275e9c8b60827 input=aad5a7c886c542f5]*/
{
PyObject *v;
datum key;
@@ -497,6 +552,7 @@ _gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls)
}
/*[clinic input]
+@critical_section
_gdbm.gdbm.nextkey
cls: defining_class
@@ -517,7 +573,7 @@ to create a list in memory that contains them all:
static PyObject *
_gdbm_gdbm_nextkey_impl(gdbmobject *self, PyTypeObject *cls, const char *key,
Py_ssize_t key_length)
-/*[clinic end generated code: output=c81a69300ef41766 input=365e297bc0b3db48]*/
+/*[clinic end generated code: output=c81a69300ef41766 input=181f1130d5bfeb1e]*/
{
PyObject *v;
datum dbm_key, nextkey;
@@ -539,6 +595,7 @@ _gdbm_gdbm_nextkey_impl(gdbmobject *self, PyTypeObject *cls, const char *key,
}
/*[clinic input]
+@critical_section
_gdbm.gdbm.reorganize
cls: defining_class
@@ -554,7 +611,7 @@ kept and reused as new (key,value) pairs are added.
static PyObject *
_gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=d77c69e8e3dd644a input=e1359faeef844e46]*/
+/*[clinic end generated code: output=d77c69e8e3dd644a input=3e3ca0d2ea787861]*/
{
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
@@ -573,6 +630,7 @@ _gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls)
}
/*[clinic input]
+@critical_section
_gdbm.gdbm.sync
cls: defining_class
@@ -585,7 +643,7 @@ any unwritten data to be written to the disk.
static PyObject *
_gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=bb680a2035c3f592 input=3d749235f79b6f2a]*/
+/*[clinic end generated code: output=bb680a2035c3f592 input=6054385b071d238a]*/
{
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
@@ -595,6 +653,7 @@ _gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls)
}
/*[clinic input]
+@critical_section
_gdbm.gdbm.clear
cls: defining_class
/
@@ -604,7 +663,7 @@ Remove all items from the database.
static PyObject *
_gdbm_gdbm_clear_impl(gdbmobject *self, PyTypeObject *cls)
-/*[clinic end generated code: output=673577c573318661 input=34136d52fcdd4210]*/
+/*[clinic end generated code: output=673577c573318661 input=b17467adfe62f23d]*/
{
_gdbm_state *state = PyType_GetModuleState(cls);
assert(state != NULL);
@@ -755,6 +814,11 @@ dbmopen_impl(PyObject *module, PyObject *filename, const char *flags,
iflags |= GDBM_NOLOCK;
break;
#endif
+#ifdef GDBM_NOMMAP
+ case 'm':
+ iflags |= GDBM_NOMMAP;
+ break;
+#endif
default:
PyErr_Format(state->gdbm_error,
"Flag '%c' is not supported.", (unsigned char)*flags);
@@ -788,6 +852,9 @@ static const char gdbmmodule_open_flags[] = "rwcn"
#ifdef GDBM_NOLOCK
"u"
#endif
+#ifdef GDBM_NOMMAP
+ "m"
+#endif
;
static PyMethodDef _gdbm_module_methods[] = {
diff --git a/Modules/_hacl/Hacl_Hash_Blake2b.c b/Modules/_hacl/Hacl_Hash_Blake2b.c
index 21ab2b88c79..a5b75d61798 100644
--- a/Modules/_hacl/Hacl_Hash_Blake2b.c
+++ b/Modules/_hacl/Hacl_Hash_Blake2b.c
@@ -544,11 +544,9 @@ void Hacl_Hash_Blake2b_init(uint64_t *hash, uint32_t kk, uint32_t nn)
uint64_t x = r;
os[i] = x;);
tmp[0U] =
- (uint64_t)nn1
- ^
- ((uint64_t)kk1
- << 8U
- ^ ((uint64_t)p.fanout << 16U ^ ((uint64_t)p.depth << 24U ^ (uint64_t)p.leaf_length << 32U)));
+ (uint64_t)nn1 ^
+ ((uint64_t)kk1 << 8U ^
+ ((uint64_t)p.fanout << 16U ^ ((uint64_t)p.depth << 24U ^ (uint64_t)p.leaf_length << 32U)));
tmp[1U] = p.node_offset;
tmp[2U] = (uint64_t)p.node_depth ^ (uint64_t)p.inner_length << 8U;
tmp[3U] = 0ULL;
@@ -860,14 +858,10 @@ static Hacl_Hash_Blake2b_state_t
uint64_t x = r4;
os[i0] = x;);
tmp[0U] =
- (uint64_t)nn1
- ^
- ((uint64_t)kk2
- << 8U
- ^
- ((uint64_t)pv.fanout
- << 16U
- ^ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
+ (uint64_t)nn1 ^
+ ((uint64_t)kk2 << 8U ^
+ ((uint64_t)pv.fanout << 16U ^
+ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
tmp[1U] = pv.node_offset;
tmp[2U] = (uint64_t)pv.node_depth ^ (uint64_t)pv.inner_length << 8U;
tmp[3U] = 0ULL;
@@ -1059,11 +1053,9 @@ static void reset_raw(Hacl_Hash_Blake2b_state_t *state, Hacl_Hash_Blake2b_params
uint64_t x = r;
os[i0] = x;);
tmp[0U] =
- (uint64_t)nn1
- ^
- ((uint64_t)kk2
- << 8U
- ^ ((uint64_t)pv.fanout << 16U ^ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
+ (uint64_t)nn1 ^
+ ((uint64_t)kk2 << 8U ^
+ ((uint64_t)pv.fanout << 16U ^ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
tmp[1U] = pv.node_offset;
tmp[2U] = (uint64_t)pv.node_depth ^ (uint64_t)pv.inner_length << 8U;
tmp[3U] = 0ULL;
@@ -1200,8 +1192,7 @@ Hacl_Hash_Blake2b_update(Hacl_Hash_Blake2b_state_t *state, uint8_t *chunk, uint3
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_state_t){
.block_state = block_state1,
@@ -1265,8 +1256,7 @@ Hacl_Hash_Blake2b_update(Hacl_Hash_Blake2b_state_t *state, uint8_t *chunk, uint3
nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_state_t){
.block_state = block_state1,
@@ -1296,8 +1286,7 @@ Hacl_Hash_Blake2b_update(Hacl_Hash_Blake2b_state_t *state, uint8_t *chunk, uint3
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_state_t){
.block_state = block_state10,
@@ -1359,8 +1348,7 @@ Hacl_Hash_Blake2b_update(Hacl_Hash_Blake2b_state_t *state, uint8_t *chunk, uint3
nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_state_t){
.block_state = block_state1,
@@ -1690,14 +1678,10 @@ Hacl_Hash_Blake2b_hash_with_key_and_params(
uint64_t x = r;
os[i] = x;);
tmp[0U] =
- (uint64_t)nn
- ^
- ((uint64_t)kk
- << 8U
- ^
- ((uint64_t)params.fanout
- << 16U
- ^ ((uint64_t)params.depth << 24U ^ (uint64_t)params.leaf_length << 32U)));
+ (uint64_t)nn ^
+ ((uint64_t)kk << 8U ^
+ ((uint64_t)params.fanout << 16U ^
+ ((uint64_t)params.depth << 24U ^ (uint64_t)params.leaf_length << 32U)));
tmp[1U] = params.node_offset;
tmp[2U] = (uint64_t)params.node_depth ^ (uint64_t)params.inner_length << 8U;
tmp[3U] = 0ULL;
diff --git a/Modules/_hacl/Hacl_Hash_Blake2b_Simd256.c b/Modules/_hacl/Hacl_Hash_Blake2b_Simd256.c
index c4d9b4a689d..f955f1c6115 100644
--- a/Modules/_hacl/Hacl_Hash_Blake2b_Simd256.c
+++ b/Modules/_hacl/Hacl_Hash_Blake2b_Simd256.c
@@ -274,11 +274,9 @@ Hacl_Hash_Blake2b_Simd256_init(Lib_IntVector_Intrinsics_vec256 *hash, uint32_t k
uint64_t x = r;
os[i] = x;);
tmp[0U] =
- (uint64_t)nn1
- ^
- ((uint64_t)kk1
- << 8U
- ^ ((uint64_t)p.fanout << 16U ^ ((uint64_t)p.depth << 24U ^ (uint64_t)p.leaf_length << 32U)));
+ (uint64_t)nn1 ^
+ ((uint64_t)kk1 << 8U ^
+ ((uint64_t)p.fanout << 16U ^ ((uint64_t)p.depth << 24U ^ (uint64_t)p.leaf_length << 32U)));
tmp[1U] = p.node_offset;
tmp[2U] = (uint64_t)p.node_depth ^ (uint64_t)p.inner_length << 8U;
tmp[3U] = 0ULL;
@@ -746,14 +744,10 @@ static Hacl_Hash_Blake2b_Simd256_state_t
uint64_t x = r4;
os[i0] = x;);
tmp[0U] =
- (uint64_t)nn1
- ^
- ((uint64_t)kk2
- << 8U
- ^
- ((uint64_t)pv.fanout
- << 16U
- ^ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
+ (uint64_t)nn1 ^
+ ((uint64_t)kk2 << 8U ^
+ ((uint64_t)pv.fanout << 16U ^
+ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
tmp[1U] = pv.node_offset;
tmp[2U] = (uint64_t)pv.node_depth ^ (uint64_t)pv.inner_length << 8U;
tmp[3U] = 0ULL;
@@ -936,11 +930,9 @@ reset_raw(Hacl_Hash_Blake2b_Simd256_state_t *state, Hacl_Hash_Blake2b_params_and
uint64_t x = r;
os[i0] = x;);
tmp[0U] =
- (uint64_t)nn1
- ^
- ((uint64_t)kk2
- << 8U
- ^ ((uint64_t)pv.fanout << 16U ^ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
+ (uint64_t)nn1 ^
+ ((uint64_t)kk2 << 8U ^
+ ((uint64_t)pv.fanout << 16U ^ ((uint64_t)pv.depth << 24U ^ (uint64_t)pv.leaf_length << 32U)));
tmp[1U] = pv.node_offset;
tmp[2U] = (uint64_t)pv.node_depth ^ (uint64_t)pv.inner_length << 8U;
tmp[3U] = 0ULL;
@@ -1075,8 +1067,7 @@ Hacl_Hash_Blake2b_Simd256_update(
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_Simd256_state_t){
.block_state = block_state1,
@@ -1140,8 +1131,7 @@ Hacl_Hash_Blake2b_Simd256_update(
nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_Simd256_state_t){
.block_state = block_state1,
@@ -1171,8 +1161,7 @@ Hacl_Hash_Blake2b_Simd256_update(
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_Simd256_state_t){
.block_state = block_state10,
@@ -1234,8 +1223,7 @@ Hacl_Hash_Blake2b_Simd256_update(
nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2b_Simd256_state_t){
.block_state = block_state1,
@@ -1578,14 +1566,10 @@ Hacl_Hash_Blake2b_Simd256_hash_with_key_and_params(
uint64_t x = r;
os[i] = x;);
tmp[0U] =
- (uint64_t)nn
- ^
- ((uint64_t)kk
- << 8U
- ^
- ((uint64_t)params.fanout
- << 16U
- ^ ((uint64_t)params.depth << 24U ^ (uint64_t)params.leaf_length << 32U)));
+ (uint64_t)nn ^
+ ((uint64_t)kk << 8U ^
+ ((uint64_t)params.fanout << 16U ^
+ ((uint64_t)params.depth << 24U ^ (uint64_t)params.leaf_length << 32U)));
tmp[1U] = params.node_offset;
tmp[2U] = (uint64_t)params.node_depth ^ (uint64_t)params.inner_length << 8U;
tmp[3U] = 0ULL;
diff --git a/Modules/_hacl/Hacl_Hash_Blake2s.c b/Modules/_hacl/Hacl_Hash_Blake2s.c
index 730ba135afb..0d4fcc7f395 100644
--- a/Modules/_hacl/Hacl_Hash_Blake2s.c
+++ b/Modules/_hacl/Hacl_Hash_Blake2s.c
@@ -543,13 +543,13 @@ void Hacl_Hash_Blake2s_init(uint32_t *hash, uint32_t kk, uint32_t nn)
uint32_t x = r;
os[i] = x;);
tmp[0U] =
- (uint32_t)(uint8_t)nn
- ^ ((uint32_t)(uint8_t)kk << 8U ^ ((uint32_t)p.fanout << 16U ^ (uint32_t)p.depth << 24U));
+ (uint32_t)(uint8_t)nn ^
+ ((uint32_t)(uint8_t)kk << 8U ^ ((uint32_t)p.fanout << 16U ^ (uint32_t)p.depth << 24U));
tmp[1U] = p.leaf_length;
tmp[2U] = (uint32_t)p.node_offset;
tmp[3U] =
- (uint32_t)(p.node_offset >> 32U)
- ^ ((uint32_t)p.node_depth << 16U ^ (uint32_t)p.inner_length << 24U);
+ (uint32_t)(p.node_offset >> 32U) ^
+ ((uint32_t)p.node_depth << 16U ^ (uint32_t)p.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
@@ -846,16 +846,14 @@ static Hacl_Hash_Blake2s_state_t
uint32_t x = r4;
os[i0] = x;);
tmp[0U] =
- (uint32_t)pv.digest_length
- ^
- ((uint32_t)pv.key_length
- << 8U
- ^ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
+ (uint32_t)pv.digest_length ^
+ ((uint32_t)pv.key_length << 8U ^
+ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
tmp[1U] = pv.leaf_length;
tmp[2U] = (uint32_t)pv.node_offset;
tmp[3U] =
- (uint32_t)(pv.node_offset >> 32U)
- ^ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
+ (uint32_t)(pv.node_offset >> 32U) ^
+ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
@@ -1042,13 +1040,13 @@ static void reset_raw(Hacl_Hash_Blake2s_state_t *state, Hacl_Hash_Blake2b_params
uint32_t x = r;
os[i0] = x;);
tmp[0U] =
- (uint32_t)pv.digest_length
- ^ ((uint32_t)pv.key_length << 8U ^ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
+ (uint32_t)pv.digest_length ^
+ ((uint32_t)pv.key_length << 8U ^ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
tmp[1U] = pv.leaf_length;
tmp[2U] = (uint32_t)pv.node_offset;
tmp[3U] =
- (uint32_t)(pv.node_offset >> 32U)
- ^ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
+ (uint32_t)(pv.node_offset >> 32U) ^
+ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
@@ -1182,8 +1180,7 @@ Hacl_Hash_Blake2s_update(Hacl_Hash_Blake2s_state_t *state, uint8_t *chunk, uint3
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_state_t){
.block_state = block_state1,
@@ -1237,8 +1234,7 @@ Hacl_Hash_Blake2s_update(Hacl_Hash_Blake2s_state_t *state, uint8_t *chunk, uint3
Hacl_Hash_Blake2s_update_multi(data1_len, wv, hash, total_len1, data1, nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_state_t){
.block_state = block_state1,
@@ -1268,8 +1264,7 @@ Hacl_Hash_Blake2s_update(Hacl_Hash_Blake2s_state_t *state, uint8_t *chunk, uint3
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_state_t){
.block_state = block_state10,
@@ -1321,8 +1316,7 @@ Hacl_Hash_Blake2s_update(Hacl_Hash_Blake2s_state_t *state, uint8_t *chunk, uint3
Hacl_Hash_Blake2s_update_multi(data1_len, wv, hash, total_len1, data1, nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_state_t){
.block_state = block_state1,
@@ -1639,16 +1633,14 @@ Hacl_Hash_Blake2s_hash_with_key_and_params(
uint32_t x = r;
os[i] = x;);
tmp[0U] =
- (uint32_t)params.digest_length
- ^
- ((uint32_t)params.key_length
- << 8U
- ^ ((uint32_t)params.fanout << 16U ^ (uint32_t)params.depth << 24U));
+ (uint32_t)params.digest_length ^
+ ((uint32_t)params.key_length << 8U ^
+ ((uint32_t)params.fanout << 16U ^ (uint32_t)params.depth << 24U));
tmp[1U] = params.leaf_length;
tmp[2U] = (uint32_t)params.node_offset;
tmp[3U] =
- (uint32_t)(params.node_offset >> 32U)
- ^ ((uint32_t)params.node_depth << 16U ^ (uint32_t)params.inner_length << 24U);
+ (uint32_t)(params.node_offset >> 32U) ^
+ ((uint32_t)params.node_depth << 16U ^ (uint32_t)params.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
diff --git a/Modules/_hacl/Hacl_Hash_Blake2s_Simd128.c b/Modules/_hacl/Hacl_Hash_Blake2s_Simd128.c
index 7e9cd79544f..fa46be045f3 100644
--- a/Modules/_hacl/Hacl_Hash_Blake2s_Simd128.c
+++ b/Modules/_hacl/Hacl_Hash_Blake2s_Simd128.c
@@ -271,13 +271,13 @@ Hacl_Hash_Blake2s_Simd128_init(Lib_IntVector_Intrinsics_vec128 *hash, uint32_t k
uint32_t x = r;
os[i] = x;);
tmp[0U] =
- (uint32_t)(uint8_t)nn
- ^ ((uint32_t)(uint8_t)kk << 8U ^ ((uint32_t)p.fanout << 16U ^ (uint32_t)p.depth << 24U));
+ (uint32_t)(uint8_t)nn ^
+ ((uint32_t)(uint8_t)kk << 8U ^ ((uint32_t)p.fanout << 16U ^ (uint32_t)p.depth << 24U));
tmp[1U] = p.leaf_length;
tmp[2U] = (uint32_t)p.node_offset;
tmp[3U] =
- (uint32_t)(p.node_offset >> 32U)
- ^ ((uint32_t)p.node_depth << 16U ^ (uint32_t)p.inner_length << 24U);
+ (uint32_t)(p.node_offset >> 32U) ^
+ ((uint32_t)p.node_depth << 16U ^ (uint32_t)p.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
@@ -736,16 +736,14 @@ static Hacl_Hash_Blake2s_Simd128_state_t
uint32_t x = r4;
os[i0] = x;);
tmp[0U] =
- (uint32_t)pv.digest_length
- ^
- ((uint32_t)pv.key_length
- << 8U
- ^ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
+ (uint32_t)pv.digest_length ^
+ ((uint32_t)pv.key_length << 8U ^
+ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
tmp[1U] = pv.leaf_length;
tmp[2U] = (uint32_t)pv.node_offset;
tmp[3U] =
- (uint32_t)(pv.node_offset >> 32U)
- ^ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
+ (uint32_t)(pv.node_offset >> 32U) ^
+ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
@@ -923,13 +921,13 @@ reset_raw(Hacl_Hash_Blake2s_Simd128_state_t *state, Hacl_Hash_Blake2b_params_and
uint32_t x = r;
os[i0] = x;);
tmp[0U] =
- (uint32_t)pv.digest_length
- ^ ((uint32_t)pv.key_length << 8U ^ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
+ (uint32_t)pv.digest_length ^
+ ((uint32_t)pv.key_length << 8U ^ ((uint32_t)pv.fanout << 16U ^ (uint32_t)pv.depth << 24U));
tmp[1U] = pv.leaf_length;
tmp[2U] = (uint32_t)pv.node_offset;
tmp[3U] =
- (uint32_t)(pv.node_offset >> 32U)
- ^ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
+ (uint32_t)(pv.node_offset >> 32U) ^
+ ((uint32_t)pv.node_depth << 16U ^ (uint32_t)pv.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
@@ -1061,8 +1059,7 @@ Hacl_Hash_Blake2s_Simd128_update(
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_Simd128_state_t){
.block_state = block_state1,
@@ -1116,8 +1113,7 @@ Hacl_Hash_Blake2s_Simd128_update(
Hacl_Hash_Blake2s_Simd128_update_multi(data1_len, wv, hash, total_len1, data1, nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_Simd128_state_t){
.block_state = block_state1,
@@ -1147,8 +1143,7 @@ Hacl_Hash_Blake2s_Simd128_update(
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_Simd128_state_t){
.block_state = block_state10,
@@ -1200,8 +1195,7 @@ Hacl_Hash_Blake2s_Simd128_update(
Hacl_Hash_Blake2s_Simd128_update_multi(data1_len, wv, hash, total_len1, data1, nb);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_Blake2s_Simd128_state_t){
.block_state = block_state1,
@@ -1531,16 +1525,14 @@ Hacl_Hash_Blake2s_Simd128_hash_with_key_and_params(
uint32_t x = r;
os[i] = x;);
tmp[0U] =
- (uint32_t)params.digest_length
- ^
- ((uint32_t)params.key_length
- << 8U
- ^ ((uint32_t)params.fanout << 16U ^ (uint32_t)params.depth << 24U));
+ (uint32_t)params.digest_length ^
+ ((uint32_t)params.key_length << 8U ^
+ ((uint32_t)params.fanout << 16U ^ (uint32_t)params.depth << 24U));
tmp[1U] = params.leaf_length;
tmp[2U] = (uint32_t)params.node_offset;
tmp[3U] =
- (uint32_t)(params.node_offset >> 32U)
- ^ ((uint32_t)params.node_depth << 16U ^ (uint32_t)params.inner_length << 24U);
+ (uint32_t)(params.node_offset >> 32U) ^
+ ((uint32_t)params.node_depth << 16U ^ (uint32_t)params.inner_length << 24U);
uint32_t tmp0 = tmp[0U];
uint32_t tmp1 = tmp[1U];
uint32_t tmp2 = tmp[2U];
diff --git a/Modules/_hacl/Hacl_Hash_MD5.c b/Modules/_hacl/Hacl_Hash_MD5.c
index 75ce8d2926e..305c75483c0 100644
--- a/Modules/_hacl/Hacl_Hash_MD5.c
+++ b/Modules/_hacl/Hacl_Hash_MD5.c
@@ -66,11 +66,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti0 = _t[0U];
uint32_t
v =
- vb0
- +
- ((va + ((vb0 & vc0) | (~vb0 & vd0)) + xk + ti0)
- << 7U
- | (va + ((vb0 & vc0) | (~vb0 & vd0)) + xk + ti0) >> 25U);
+ vb0 +
+ ((va + ((vb0 & vc0) | (~vb0 & vd0)) + xk + ti0) << 7U |
+ (va + ((vb0 & vc0) | (~vb0 & vd0)) + xk + ti0) >> 25U);
abcd[0U] = v;
uint32_t va0 = abcd[3U];
uint32_t vb1 = abcd[0U];
@@ -82,11 +80,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti1 = _t[1U];
uint32_t
v0 =
- vb1
- +
- ((va0 + ((vb1 & vc1) | (~vb1 & vd1)) + xk0 + ti1)
- << 12U
- | (va0 + ((vb1 & vc1) | (~vb1 & vd1)) + xk0 + ti1) >> 20U);
+ vb1 +
+ ((va0 + ((vb1 & vc1) | (~vb1 & vd1)) + xk0 + ti1) << 12U |
+ (va0 + ((vb1 & vc1) | (~vb1 & vd1)) + xk0 + ti1) >> 20U);
abcd[3U] = v0;
uint32_t va1 = abcd[2U];
uint32_t vb2 = abcd[3U];
@@ -98,11 +94,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti2 = _t[2U];
uint32_t
v1 =
- vb2
- +
- ((va1 + ((vb2 & vc2) | (~vb2 & vd2)) + xk1 + ti2)
- << 17U
- | (va1 + ((vb2 & vc2) | (~vb2 & vd2)) + xk1 + ti2) >> 15U);
+ vb2 +
+ ((va1 + ((vb2 & vc2) | (~vb2 & vd2)) + xk1 + ti2) << 17U |
+ (va1 + ((vb2 & vc2) | (~vb2 & vd2)) + xk1 + ti2) >> 15U);
abcd[2U] = v1;
uint32_t va2 = abcd[1U];
uint32_t vb3 = abcd[2U];
@@ -114,11 +108,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti3 = _t[3U];
uint32_t
v2 =
- vb3
- +
- ((va2 + ((vb3 & vc3) | (~vb3 & vd3)) + xk2 + ti3)
- << 22U
- | (va2 + ((vb3 & vc3) | (~vb3 & vd3)) + xk2 + ti3) >> 10U);
+ vb3 +
+ ((va2 + ((vb3 & vc3) | (~vb3 & vd3)) + xk2 + ti3) << 22U |
+ (va2 + ((vb3 & vc3) | (~vb3 & vd3)) + xk2 + ti3) >> 10U);
abcd[1U] = v2;
uint32_t va3 = abcd[0U];
uint32_t vb4 = abcd[1U];
@@ -130,11 +122,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti4 = _t[4U];
uint32_t
v3 =
- vb4
- +
- ((va3 + ((vb4 & vc4) | (~vb4 & vd4)) + xk3 + ti4)
- << 7U
- | (va3 + ((vb4 & vc4) | (~vb4 & vd4)) + xk3 + ti4) >> 25U);
+ vb4 +
+ ((va3 + ((vb4 & vc4) | (~vb4 & vd4)) + xk3 + ti4) << 7U |
+ (va3 + ((vb4 & vc4) | (~vb4 & vd4)) + xk3 + ti4) >> 25U);
abcd[0U] = v3;
uint32_t va4 = abcd[3U];
uint32_t vb5 = abcd[0U];
@@ -146,11 +136,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti5 = _t[5U];
uint32_t
v4 =
- vb5
- +
- ((va4 + ((vb5 & vc5) | (~vb5 & vd5)) + xk4 + ti5)
- << 12U
- | (va4 + ((vb5 & vc5) | (~vb5 & vd5)) + xk4 + ti5) >> 20U);
+ vb5 +
+ ((va4 + ((vb5 & vc5) | (~vb5 & vd5)) + xk4 + ti5) << 12U |
+ (va4 + ((vb5 & vc5) | (~vb5 & vd5)) + xk4 + ti5) >> 20U);
abcd[3U] = v4;
uint32_t va5 = abcd[2U];
uint32_t vb6 = abcd[3U];
@@ -162,11 +150,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti6 = _t[6U];
uint32_t
v5 =
- vb6
- +
- ((va5 + ((vb6 & vc6) | (~vb6 & vd6)) + xk5 + ti6)
- << 17U
- | (va5 + ((vb6 & vc6) | (~vb6 & vd6)) + xk5 + ti6) >> 15U);
+ vb6 +
+ ((va5 + ((vb6 & vc6) | (~vb6 & vd6)) + xk5 + ti6) << 17U |
+ (va5 + ((vb6 & vc6) | (~vb6 & vd6)) + xk5 + ti6) >> 15U);
abcd[2U] = v5;
uint32_t va6 = abcd[1U];
uint32_t vb7 = abcd[2U];
@@ -178,11 +164,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti7 = _t[7U];
uint32_t
v6 =
- vb7
- +
- ((va6 + ((vb7 & vc7) | (~vb7 & vd7)) + xk6 + ti7)
- << 22U
- | (va6 + ((vb7 & vc7) | (~vb7 & vd7)) + xk6 + ti7) >> 10U);
+ vb7 +
+ ((va6 + ((vb7 & vc7) | (~vb7 & vd7)) + xk6 + ti7) << 22U |
+ (va6 + ((vb7 & vc7) | (~vb7 & vd7)) + xk6 + ti7) >> 10U);
abcd[1U] = v6;
uint32_t va7 = abcd[0U];
uint32_t vb8 = abcd[1U];
@@ -194,11 +178,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti8 = _t[8U];
uint32_t
v7 =
- vb8
- +
- ((va7 + ((vb8 & vc8) | (~vb8 & vd8)) + xk7 + ti8)
- << 7U
- | (va7 + ((vb8 & vc8) | (~vb8 & vd8)) + xk7 + ti8) >> 25U);
+ vb8 +
+ ((va7 + ((vb8 & vc8) | (~vb8 & vd8)) + xk7 + ti8) << 7U |
+ (va7 + ((vb8 & vc8) | (~vb8 & vd8)) + xk7 + ti8) >> 25U);
abcd[0U] = v7;
uint32_t va8 = abcd[3U];
uint32_t vb9 = abcd[0U];
@@ -210,11 +192,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti9 = _t[9U];
uint32_t
v8 =
- vb9
- +
- ((va8 + ((vb9 & vc9) | (~vb9 & vd9)) + xk8 + ti9)
- << 12U
- | (va8 + ((vb9 & vc9) | (~vb9 & vd9)) + xk8 + ti9) >> 20U);
+ vb9 +
+ ((va8 + ((vb9 & vc9) | (~vb9 & vd9)) + xk8 + ti9) << 12U |
+ (va8 + ((vb9 & vc9) | (~vb9 & vd9)) + xk8 + ti9) >> 20U);
abcd[3U] = v8;
uint32_t va9 = abcd[2U];
uint32_t vb10 = abcd[3U];
@@ -226,11 +206,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti10 = _t[10U];
uint32_t
v9 =
- vb10
- +
- ((va9 + ((vb10 & vc10) | (~vb10 & vd10)) + xk9 + ti10)
- << 17U
- | (va9 + ((vb10 & vc10) | (~vb10 & vd10)) + xk9 + ti10) >> 15U);
+ vb10 +
+ ((va9 + ((vb10 & vc10) | (~vb10 & vd10)) + xk9 + ti10) << 17U |
+ (va9 + ((vb10 & vc10) | (~vb10 & vd10)) + xk9 + ti10) >> 15U);
abcd[2U] = v9;
uint32_t va10 = abcd[1U];
uint32_t vb11 = abcd[2U];
@@ -242,11 +220,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti11 = _t[11U];
uint32_t
v10 =
- vb11
- +
- ((va10 + ((vb11 & vc11) | (~vb11 & vd11)) + xk10 + ti11)
- << 22U
- | (va10 + ((vb11 & vc11) | (~vb11 & vd11)) + xk10 + ti11) >> 10U);
+ vb11 +
+ ((va10 + ((vb11 & vc11) | (~vb11 & vd11)) + xk10 + ti11) << 22U |
+ (va10 + ((vb11 & vc11) | (~vb11 & vd11)) + xk10 + ti11) >> 10U);
abcd[1U] = v10;
uint32_t va11 = abcd[0U];
uint32_t vb12 = abcd[1U];
@@ -258,11 +234,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti12 = _t[12U];
uint32_t
v11 =
- vb12
- +
- ((va11 + ((vb12 & vc12) | (~vb12 & vd12)) + xk11 + ti12)
- << 7U
- | (va11 + ((vb12 & vc12) | (~vb12 & vd12)) + xk11 + ti12) >> 25U);
+ vb12 +
+ ((va11 + ((vb12 & vc12) | (~vb12 & vd12)) + xk11 + ti12) << 7U |
+ (va11 + ((vb12 & vc12) | (~vb12 & vd12)) + xk11 + ti12) >> 25U);
abcd[0U] = v11;
uint32_t va12 = abcd[3U];
uint32_t vb13 = abcd[0U];
@@ -274,11 +248,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti13 = _t[13U];
uint32_t
v12 =
- vb13
- +
- ((va12 + ((vb13 & vc13) | (~vb13 & vd13)) + xk12 + ti13)
- << 12U
- | (va12 + ((vb13 & vc13) | (~vb13 & vd13)) + xk12 + ti13) >> 20U);
+ vb13 +
+ ((va12 + ((vb13 & vc13) | (~vb13 & vd13)) + xk12 + ti13) << 12U |
+ (va12 + ((vb13 & vc13) | (~vb13 & vd13)) + xk12 + ti13) >> 20U);
abcd[3U] = v12;
uint32_t va13 = abcd[2U];
uint32_t vb14 = abcd[3U];
@@ -290,11 +262,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti14 = _t[14U];
uint32_t
v13 =
- vb14
- +
- ((va13 + ((vb14 & vc14) | (~vb14 & vd14)) + xk13 + ti14)
- << 17U
- | (va13 + ((vb14 & vc14) | (~vb14 & vd14)) + xk13 + ti14) >> 15U);
+ vb14 +
+ ((va13 + ((vb14 & vc14) | (~vb14 & vd14)) + xk13 + ti14) << 17U |
+ (va13 + ((vb14 & vc14) | (~vb14 & vd14)) + xk13 + ti14) >> 15U);
abcd[2U] = v13;
uint32_t va14 = abcd[1U];
uint32_t vb15 = abcd[2U];
@@ -306,11 +276,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti15 = _t[15U];
uint32_t
v14 =
- vb15
- +
- ((va14 + ((vb15 & vc15) | (~vb15 & vd15)) + xk14 + ti15)
- << 22U
- | (va14 + ((vb15 & vc15) | (~vb15 & vd15)) + xk14 + ti15) >> 10U);
+ vb15 +
+ ((va14 + ((vb15 & vc15) | (~vb15 & vd15)) + xk14 + ti15) << 22U |
+ (va14 + ((vb15 & vc15) | (~vb15 & vd15)) + xk14 + ti15) >> 10U);
abcd[1U] = v14;
uint32_t va15 = abcd[0U];
uint32_t vb16 = abcd[1U];
@@ -322,11 +290,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti16 = _t[16U];
uint32_t
v15 =
- vb16
- +
- ((va15 + ((vb16 & vd16) | (vc16 & ~vd16)) + xk15 + ti16)
- << 5U
- | (va15 + ((vb16 & vd16) | (vc16 & ~vd16)) + xk15 + ti16) >> 27U);
+ vb16 +
+ ((va15 + ((vb16 & vd16) | (vc16 & ~vd16)) + xk15 + ti16) << 5U |
+ (va15 + ((vb16 & vd16) | (vc16 & ~vd16)) + xk15 + ti16) >> 27U);
abcd[0U] = v15;
uint32_t va16 = abcd[3U];
uint32_t vb17 = abcd[0U];
@@ -338,11 +304,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti17 = _t[17U];
uint32_t
v16 =
- vb17
- +
- ((va16 + ((vb17 & vd17) | (vc17 & ~vd17)) + xk16 + ti17)
- << 9U
- | (va16 + ((vb17 & vd17) | (vc17 & ~vd17)) + xk16 + ti17) >> 23U);
+ vb17 +
+ ((va16 + ((vb17 & vd17) | (vc17 & ~vd17)) + xk16 + ti17) << 9U |
+ (va16 + ((vb17 & vd17) | (vc17 & ~vd17)) + xk16 + ti17) >> 23U);
abcd[3U] = v16;
uint32_t va17 = abcd[2U];
uint32_t vb18 = abcd[3U];
@@ -354,11 +318,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti18 = _t[18U];
uint32_t
v17 =
- vb18
- +
- ((va17 + ((vb18 & vd18) | (vc18 & ~vd18)) + xk17 + ti18)
- << 14U
- | (va17 + ((vb18 & vd18) | (vc18 & ~vd18)) + xk17 + ti18) >> 18U);
+ vb18 +
+ ((va17 + ((vb18 & vd18) | (vc18 & ~vd18)) + xk17 + ti18) << 14U |
+ (va17 + ((vb18 & vd18) | (vc18 & ~vd18)) + xk17 + ti18) >> 18U);
abcd[2U] = v17;
uint32_t va18 = abcd[1U];
uint32_t vb19 = abcd[2U];
@@ -370,11 +332,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti19 = _t[19U];
uint32_t
v18 =
- vb19
- +
- ((va18 + ((vb19 & vd19) | (vc19 & ~vd19)) + xk18 + ti19)
- << 20U
- | (va18 + ((vb19 & vd19) | (vc19 & ~vd19)) + xk18 + ti19) >> 12U);
+ vb19 +
+ ((va18 + ((vb19 & vd19) | (vc19 & ~vd19)) + xk18 + ti19) << 20U |
+ (va18 + ((vb19 & vd19) | (vc19 & ~vd19)) + xk18 + ti19) >> 12U);
abcd[1U] = v18;
uint32_t va19 = abcd[0U];
uint32_t vb20 = abcd[1U];
@@ -386,11 +346,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti20 = _t[20U];
uint32_t
v19 =
- vb20
- +
- ((va19 + ((vb20 & vd20) | (vc20 & ~vd20)) + xk19 + ti20)
- << 5U
- | (va19 + ((vb20 & vd20) | (vc20 & ~vd20)) + xk19 + ti20) >> 27U);
+ vb20 +
+ ((va19 + ((vb20 & vd20) | (vc20 & ~vd20)) + xk19 + ti20) << 5U |
+ (va19 + ((vb20 & vd20) | (vc20 & ~vd20)) + xk19 + ti20) >> 27U);
abcd[0U] = v19;
uint32_t va20 = abcd[3U];
uint32_t vb21 = abcd[0U];
@@ -402,11 +360,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti21 = _t[21U];
uint32_t
v20 =
- vb21
- +
- ((va20 + ((vb21 & vd21) | (vc21 & ~vd21)) + xk20 + ti21)
- << 9U
- | (va20 + ((vb21 & vd21) | (vc21 & ~vd21)) + xk20 + ti21) >> 23U);
+ vb21 +
+ ((va20 + ((vb21 & vd21) | (vc21 & ~vd21)) + xk20 + ti21) << 9U |
+ (va20 + ((vb21 & vd21) | (vc21 & ~vd21)) + xk20 + ti21) >> 23U);
abcd[3U] = v20;
uint32_t va21 = abcd[2U];
uint32_t vb22 = abcd[3U];
@@ -418,11 +374,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti22 = _t[22U];
uint32_t
v21 =
- vb22
- +
- ((va21 + ((vb22 & vd22) | (vc22 & ~vd22)) + xk21 + ti22)
- << 14U
- | (va21 + ((vb22 & vd22) | (vc22 & ~vd22)) + xk21 + ti22) >> 18U);
+ vb22 +
+ ((va21 + ((vb22 & vd22) | (vc22 & ~vd22)) + xk21 + ti22) << 14U |
+ (va21 + ((vb22 & vd22) | (vc22 & ~vd22)) + xk21 + ti22) >> 18U);
abcd[2U] = v21;
uint32_t va22 = abcd[1U];
uint32_t vb23 = abcd[2U];
@@ -434,11 +388,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti23 = _t[23U];
uint32_t
v22 =
- vb23
- +
- ((va22 + ((vb23 & vd23) | (vc23 & ~vd23)) + xk22 + ti23)
- << 20U
- | (va22 + ((vb23 & vd23) | (vc23 & ~vd23)) + xk22 + ti23) >> 12U);
+ vb23 +
+ ((va22 + ((vb23 & vd23) | (vc23 & ~vd23)) + xk22 + ti23) << 20U |
+ (va22 + ((vb23 & vd23) | (vc23 & ~vd23)) + xk22 + ti23) >> 12U);
abcd[1U] = v22;
uint32_t va23 = abcd[0U];
uint32_t vb24 = abcd[1U];
@@ -450,11 +402,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti24 = _t[24U];
uint32_t
v23 =
- vb24
- +
- ((va23 + ((vb24 & vd24) | (vc24 & ~vd24)) + xk23 + ti24)
- << 5U
- | (va23 + ((vb24 & vd24) | (vc24 & ~vd24)) + xk23 + ti24) >> 27U);
+ vb24 +
+ ((va23 + ((vb24 & vd24) | (vc24 & ~vd24)) + xk23 + ti24) << 5U |
+ (va23 + ((vb24 & vd24) | (vc24 & ~vd24)) + xk23 + ti24) >> 27U);
abcd[0U] = v23;
uint32_t va24 = abcd[3U];
uint32_t vb25 = abcd[0U];
@@ -466,11 +416,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti25 = _t[25U];
uint32_t
v24 =
- vb25
- +
- ((va24 + ((vb25 & vd25) | (vc25 & ~vd25)) + xk24 + ti25)
- << 9U
- | (va24 + ((vb25 & vd25) | (vc25 & ~vd25)) + xk24 + ti25) >> 23U);
+ vb25 +
+ ((va24 + ((vb25 & vd25) | (vc25 & ~vd25)) + xk24 + ti25) << 9U |
+ (va24 + ((vb25 & vd25) | (vc25 & ~vd25)) + xk24 + ti25) >> 23U);
abcd[3U] = v24;
uint32_t va25 = abcd[2U];
uint32_t vb26 = abcd[3U];
@@ -482,11 +430,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti26 = _t[26U];
uint32_t
v25 =
- vb26
- +
- ((va25 + ((vb26 & vd26) | (vc26 & ~vd26)) + xk25 + ti26)
- << 14U
- | (va25 + ((vb26 & vd26) | (vc26 & ~vd26)) + xk25 + ti26) >> 18U);
+ vb26 +
+ ((va25 + ((vb26 & vd26) | (vc26 & ~vd26)) + xk25 + ti26) << 14U |
+ (va25 + ((vb26 & vd26) | (vc26 & ~vd26)) + xk25 + ti26) >> 18U);
abcd[2U] = v25;
uint32_t va26 = abcd[1U];
uint32_t vb27 = abcd[2U];
@@ -498,11 +444,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti27 = _t[27U];
uint32_t
v26 =
- vb27
- +
- ((va26 + ((vb27 & vd27) | (vc27 & ~vd27)) + xk26 + ti27)
- << 20U
- | (va26 + ((vb27 & vd27) | (vc27 & ~vd27)) + xk26 + ti27) >> 12U);
+ vb27 +
+ ((va26 + ((vb27 & vd27) | (vc27 & ~vd27)) + xk26 + ti27) << 20U |
+ (va26 + ((vb27 & vd27) | (vc27 & ~vd27)) + xk26 + ti27) >> 12U);
abcd[1U] = v26;
uint32_t va27 = abcd[0U];
uint32_t vb28 = abcd[1U];
@@ -514,11 +458,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti28 = _t[28U];
uint32_t
v27 =
- vb28
- +
- ((va27 + ((vb28 & vd28) | (vc28 & ~vd28)) + xk27 + ti28)
- << 5U
- | (va27 + ((vb28 & vd28) | (vc28 & ~vd28)) + xk27 + ti28) >> 27U);
+ vb28 +
+ ((va27 + ((vb28 & vd28) | (vc28 & ~vd28)) + xk27 + ti28) << 5U |
+ (va27 + ((vb28 & vd28) | (vc28 & ~vd28)) + xk27 + ti28) >> 27U);
abcd[0U] = v27;
uint32_t va28 = abcd[3U];
uint32_t vb29 = abcd[0U];
@@ -530,11 +472,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti29 = _t[29U];
uint32_t
v28 =
- vb29
- +
- ((va28 + ((vb29 & vd29) | (vc29 & ~vd29)) + xk28 + ti29)
- << 9U
- | (va28 + ((vb29 & vd29) | (vc29 & ~vd29)) + xk28 + ti29) >> 23U);
+ vb29 +
+ ((va28 + ((vb29 & vd29) | (vc29 & ~vd29)) + xk28 + ti29) << 9U |
+ (va28 + ((vb29 & vd29) | (vc29 & ~vd29)) + xk28 + ti29) >> 23U);
abcd[3U] = v28;
uint32_t va29 = abcd[2U];
uint32_t vb30 = abcd[3U];
@@ -546,11 +486,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti30 = _t[30U];
uint32_t
v29 =
- vb30
- +
- ((va29 + ((vb30 & vd30) | (vc30 & ~vd30)) + xk29 + ti30)
- << 14U
- | (va29 + ((vb30 & vd30) | (vc30 & ~vd30)) + xk29 + ti30) >> 18U);
+ vb30 +
+ ((va29 + ((vb30 & vd30) | (vc30 & ~vd30)) + xk29 + ti30) << 14U |
+ (va29 + ((vb30 & vd30) | (vc30 & ~vd30)) + xk29 + ti30) >> 18U);
abcd[2U] = v29;
uint32_t va30 = abcd[1U];
uint32_t vb31 = abcd[2U];
@@ -562,11 +500,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti31 = _t[31U];
uint32_t
v30 =
- vb31
- +
- ((va30 + ((vb31 & vd31) | (vc31 & ~vd31)) + xk30 + ti31)
- << 20U
- | (va30 + ((vb31 & vd31) | (vc31 & ~vd31)) + xk30 + ti31) >> 12U);
+ vb31 +
+ ((va30 + ((vb31 & vd31) | (vc31 & ~vd31)) + xk30 + ti31) << 20U |
+ (va30 + ((vb31 & vd31) | (vc31 & ~vd31)) + xk30 + ti31) >> 12U);
abcd[1U] = v30;
uint32_t va31 = abcd[0U];
uint32_t vb32 = abcd[1U];
@@ -578,11 +514,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti32 = _t[32U];
uint32_t
v31 =
- vb32
- +
- ((va31 + (vb32 ^ (vc32 ^ vd32)) + xk31 + ti32)
- << 4U
- | (va31 + (vb32 ^ (vc32 ^ vd32)) + xk31 + ti32) >> 28U);
+ vb32 +
+ ((va31 + (vb32 ^ (vc32 ^ vd32)) + xk31 + ti32) << 4U |
+ (va31 + (vb32 ^ (vc32 ^ vd32)) + xk31 + ti32) >> 28U);
abcd[0U] = v31;
uint32_t va32 = abcd[3U];
uint32_t vb33 = abcd[0U];
@@ -594,11 +528,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti33 = _t[33U];
uint32_t
v32 =
- vb33
- +
- ((va32 + (vb33 ^ (vc33 ^ vd33)) + xk32 + ti33)
- << 11U
- | (va32 + (vb33 ^ (vc33 ^ vd33)) + xk32 + ti33) >> 21U);
+ vb33 +
+ ((va32 + (vb33 ^ (vc33 ^ vd33)) + xk32 + ti33) << 11U |
+ (va32 + (vb33 ^ (vc33 ^ vd33)) + xk32 + ti33) >> 21U);
abcd[3U] = v32;
uint32_t va33 = abcd[2U];
uint32_t vb34 = abcd[3U];
@@ -610,11 +542,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti34 = _t[34U];
uint32_t
v33 =
- vb34
- +
- ((va33 + (vb34 ^ (vc34 ^ vd34)) + xk33 + ti34)
- << 16U
- | (va33 + (vb34 ^ (vc34 ^ vd34)) + xk33 + ti34) >> 16U);
+ vb34 +
+ ((va33 + (vb34 ^ (vc34 ^ vd34)) + xk33 + ti34) << 16U |
+ (va33 + (vb34 ^ (vc34 ^ vd34)) + xk33 + ti34) >> 16U);
abcd[2U] = v33;
uint32_t va34 = abcd[1U];
uint32_t vb35 = abcd[2U];
@@ -626,11 +556,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti35 = _t[35U];
uint32_t
v34 =
- vb35
- +
- ((va34 + (vb35 ^ (vc35 ^ vd35)) + xk34 + ti35)
- << 23U
- | (va34 + (vb35 ^ (vc35 ^ vd35)) + xk34 + ti35) >> 9U);
+ vb35 +
+ ((va34 + (vb35 ^ (vc35 ^ vd35)) + xk34 + ti35) << 23U |
+ (va34 + (vb35 ^ (vc35 ^ vd35)) + xk34 + ti35) >> 9U);
abcd[1U] = v34;
uint32_t va35 = abcd[0U];
uint32_t vb36 = abcd[1U];
@@ -642,11 +570,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti36 = _t[36U];
uint32_t
v35 =
- vb36
- +
- ((va35 + (vb36 ^ (vc36 ^ vd36)) + xk35 + ti36)
- << 4U
- | (va35 + (vb36 ^ (vc36 ^ vd36)) + xk35 + ti36) >> 28U);
+ vb36 +
+ ((va35 + (vb36 ^ (vc36 ^ vd36)) + xk35 + ti36) << 4U |
+ (va35 + (vb36 ^ (vc36 ^ vd36)) + xk35 + ti36) >> 28U);
abcd[0U] = v35;
uint32_t va36 = abcd[3U];
uint32_t vb37 = abcd[0U];
@@ -658,11 +584,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti37 = _t[37U];
uint32_t
v36 =
- vb37
- +
- ((va36 + (vb37 ^ (vc37 ^ vd37)) + xk36 + ti37)
- << 11U
- | (va36 + (vb37 ^ (vc37 ^ vd37)) + xk36 + ti37) >> 21U);
+ vb37 +
+ ((va36 + (vb37 ^ (vc37 ^ vd37)) + xk36 + ti37) << 11U |
+ (va36 + (vb37 ^ (vc37 ^ vd37)) + xk36 + ti37) >> 21U);
abcd[3U] = v36;
uint32_t va37 = abcd[2U];
uint32_t vb38 = abcd[3U];
@@ -674,11 +598,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti38 = _t[38U];
uint32_t
v37 =
- vb38
- +
- ((va37 + (vb38 ^ (vc38 ^ vd38)) + xk37 + ti38)
- << 16U
- | (va37 + (vb38 ^ (vc38 ^ vd38)) + xk37 + ti38) >> 16U);
+ vb38 +
+ ((va37 + (vb38 ^ (vc38 ^ vd38)) + xk37 + ti38) << 16U |
+ (va37 + (vb38 ^ (vc38 ^ vd38)) + xk37 + ti38) >> 16U);
abcd[2U] = v37;
uint32_t va38 = abcd[1U];
uint32_t vb39 = abcd[2U];
@@ -690,11 +612,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti39 = _t[39U];
uint32_t
v38 =
- vb39
- +
- ((va38 + (vb39 ^ (vc39 ^ vd39)) + xk38 + ti39)
- << 23U
- | (va38 + (vb39 ^ (vc39 ^ vd39)) + xk38 + ti39) >> 9U);
+ vb39 +
+ ((va38 + (vb39 ^ (vc39 ^ vd39)) + xk38 + ti39) << 23U |
+ (va38 + (vb39 ^ (vc39 ^ vd39)) + xk38 + ti39) >> 9U);
abcd[1U] = v38;
uint32_t va39 = abcd[0U];
uint32_t vb40 = abcd[1U];
@@ -706,11 +626,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti40 = _t[40U];
uint32_t
v39 =
- vb40
- +
- ((va39 + (vb40 ^ (vc40 ^ vd40)) + xk39 + ti40)
- << 4U
- | (va39 + (vb40 ^ (vc40 ^ vd40)) + xk39 + ti40) >> 28U);
+ vb40 +
+ ((va39 + (vb40 ^ (vc40 ^ vd40)) + xk39 + ti40) << 4U |
+ (va39 + (vb40 ^ (vc40 ^ vd40)) + xk39 + ti40) >> 28U);
abcd[0U] = v39;
uint32_t va40 = abcd[3U];
uint32_t vb41 = abcd[0U];
@@ -722,11 +640,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti41 = _t[41U];
uint32_t
v40 =
- vb41
- +
- ((va40 + (vb41 ^ (vc41 ^ vd41)) + xk40 + ti41)
- << 11U
- | (va40 + (vb41 ^ (vc41 ^ vd41)) + xk40 + ti41) >> 21U);
+ vb41 +
+ ((va40 + (vb41 ^ (vc41 ^ vd41)) + xk40 + ti41) << 11U |
+ (va40 + (vb41 ^ (vc41 ^ vd41)) + xk40 + ti41) >> 21U);
abcd[3U] = v40;
uint32_t va41 = abcd[2U];
uint32_t vb42 = abcd[3U];
@@ -738,11 +654,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti42 = _t[42U];
uint32_t
v41 =
- vb42
- +
- ((va41 + (vb42 ^ (vc42 ^ vd42)) + xk41 + ti42)
- << 16U
- | (va41 + (vb42 ^ (vc42 ^ vd42)) + xk41 + ti42) >> 16U);
+ vb42 +
+ ((va41 + (vb42 ^ (vc42 ^ vd42)) + xk41 + ti42) << 16U |
+ (va41 + (vb42 ^ (vc42 ^ vd42)) + xk41 + ti42) >> 16U);
abcd[2U] = v41;
uint32_t va42 = abcd[1U];
uint32_t vb43 = abcd[2U];
@@ -754,11 +668,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti43 = _t[43U];
uint32_t
v42 =
- vb43
- +
- ((va42 + (vb43 ^ (vc43 ^ vd43)) + xk42 + ti43)
- << 23U
- | (va42 + (vb43 ^ (vc43 ^ vd43)) + xk42 + ti43) >> 9U);
+ vb43 +
+ ((va42 + (vb43 ^ (vc43 ^ vd43)) + xk42 + ti43) << 23U |
+ (va42 + (vb43 ^ (vc43 ^ vd43)) + xk42 + ti43) >> 9U);
abcd[1U] = v42;
uint32_t va43 = abcd[0U];
uint32_t vb44 = abcd[1U];
@@ -770,11 +682,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti44 = _t[44U];
uint32_t
v43 =
- vb44
- +
- ((va43 + (vb44 ^ (vc44 ^ vd44)) + xk43 + ti44)
- << 4U
- | (va43 + (vb44 ^ (vc44 ^ vd44)) + xk43 + ti44) >> 28U);
+ vb44 +
+ ((va43 + (vb44 ^ (vc44 ^ vd44)) + xk43 + ti44) << 4U |
+ (va43 + (vb44 ^ (vc44 ^ vd44)) + xk43 + ti44) >> 28U);
abcd[0U] = v43;
uint32_t va44 = abcd[3U];
uint32_t vb45 = abcd[0U];
@@ -786,11 +696,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti45 = _t[45U];
uint32_t
v44 =
- vb45
- +
- ((va44 + (vb45 ^ (vc45 ^ vd45)) + xk44 + ti45)
- << 11U
- | (va44 + (vb45 ^ (vc45 ^ vd45)) + xk44 + ti45) >> 21U);
+ vb45 +
+ ((va44 + (vb45 ^ (vc45 ^ vd45)) + xk44 + ti45) << 11U |
+ (va44 + (vb45 ^ (vc45 ^ vd45)) + xk44 + ti45) >> 21U);
abcd[3U] = v44;
uint32_t va45 = abcd[2U];
uint32_t vb46 = abcd[3U];
@@ -802,11 +710,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti46 = _t[46U];
uint32_t
v45 =
- vb46
- +
- ((va45 + (vb46 ^ (vc46 ^ vd46)) + xk45 + ti46)
- << 16U
- | (va45 + (vb46 ^ (vc46 ^ vd46)) + xk45 + ti46) >> 16U);
+ vb46 +
+ ((va45 + (vb46 ^ (vc46 ^ vd46)) + xk45 + ti46) << 16U |
+ (va45 + (vb46 ^ (vc46 ^ vd46)) + xk45 + ti46) >> 16U);
abcd[2U] = v45;
uint32_t va46 = abcd[1U];
uint32_t vb47 = abcd[2U];
@@ -818,11 +724,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti47 = _t[47U];
uint32_t
v46 =
- vb47
- +
- ((va46 + (vb47 ^ (vc47 ^ vd47)) + xk46 + ti47)
- << 23U
- | (va46 + (vb47 ^ (vc47 ^ vd47)) + xk46 + ti47) >> 9U);
+ vb47 +
+ ((va46 + (vb47 ^ (vc47 ^ vd47)) + xk46 + ti47) << 23U |
+ (va46 + (vb47 ^ (vc47 ^ vd47)) + xk46 + ti47) >> 9U);
abcd[1U] = v46;
uint32_t va47 = abcd[0U];
uint32_t vb48 = abcd[1U];
@@ -834,11 +738,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti48 = _t[48U];
uint32_t
v47 =
- vb48
- +
- ((va47 + (vc48 ^ (vb48 | ~vd48)) + xk47 + ti48)
- << 6U
- | (va47 + (vc48 ^ (vb48 | ~vd48)) + xk47 + ti48) >> 26U);
+ vb48 +
+ ((va47 + (vc48 ^ (vb48 | ~vd48)) + xk47 + ti48) << 6U |
+ (va47 + (vc48 ^ (vb48 | ~vd48)) + xk47 + ti48) >> 26U);
abcd[0U] = v47;
uint32_t va48 = abcd[3U];
uint32_t vb49 = abcd[0U];
@@ -850,11 +752,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti49 = _t[49U];
uint32_t
v48 =
- vb49
- +
- ((va48 + (vc49 ^ (vb49 | ~vd49)) + xk48 + ti49)
- << 10U
- | (va48 + (vc49 ^ (vb49 | ~vd49)) + xk48 + ti49) >> 22U);
+ vb49 +
+ ((va48 + (vc49 ^ (vb49 | ~vd49)) + xk48 + ti49) << 10U |
+ (va48 + (vc49 ^ (vb49 | ~vd49)) + xk48 + ti49) >> 22U);
abcd[3U] = v48;
uint32_t va49 = abcd[2U];
uint32_t vb50 = abcd[3U];
@@ -866,11 +766,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti50 = _t[50U];
uint32_t
v49 =
- vb50
- +
- ((va49 + (vc50 ^ (vb50 | ~vd50)) + xk49 + ti50)
- << 15U
- | (va49 + (vc50 ^ (vb50 | ~vd50)) + xk49 + ti50) >> 17U);
+ vb50 +
+ ((va49 + (vc50 ^ (vb50 | ~vd50)) + xk49 + ti50) << 15U |
+ (va49 + (vc50 ^ (vb50 | ~vd50)) + xk49 + ti50) >> 17U);
abcd[2U] = v49;
uint32_t va50 = abcd[1U];
uint32_t vb51 = abcd[2U];
@@ -882,11 +780,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti51 = _t[51U];
uint32_t
v50 =
- vb51
- +
- ((va50 + (vc51 ^ (vb51 | ~vd51)) + xk50 + ti51)
- << 21U
- | (va50 + (vc51 ^ (vb51 | ~vd51)) + xk50 + ti51) >> 11U);
+ vb51 +
+ ((va50 + (vc51 ^ (vb51 | ~vd51)) + xk50 + ti51) << 21U |
+ (va50 + (vc51 ^ (vb51 | ~vd51)) + xk50 + ti51) >> 11U);
abcd[1U] = v50;
uint32_t va51 = abcd[0U];
uint32_t vb52 = abcd[1U];
@@ -898,11 +794,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti52 = _t[52U];
uint32_t
v51 =
- vb52
- +
- ((va51 + (vc52 ^ (vb52 | ~vd52)) + xk51 + ti52)
- << 6U
- | (va51 + (vc52 ^ (vb52 | ~vd52)) + xk51 + ti52) >> 26U);
+ vb52 +
+ ((va51 + (vc52 ^ (vb52 | ~vd52)) + xk51 + ti52) << 6U |
+ (va51 + (vc52 ^ (vb52 | ~vd52)) + xk51 + ti52) >> 26U);
abcd[0U] = v51;
uint32_t va52 = abcd[3U];
uint32_t vb53 = abcd[0U];
@@ -914,11 +808,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti53 = _t[53U];
uint32_t
v52 =
- vb53
- +
- ((va52 + (vc53 ^ (vb53 | ~vd53)) + xk52 + ti53)
- << 10U
- | (va52 + (vc53 ^ (vb53 | ~vd53)) + xk52 + ti53) >> 22U);
+ vb53 +
+ ((va52 + (vc53 ^ (vb53 | ~vd53)) + xk52 + ti53) << 10U |
+ (va52 + (vc53 ^ (vb53 | ~vd53)) + xk52 + ti53) >> 22U);
abcd[3U] = v52;
uint32_t va53 = abcd[2U];
uint32_t vb54 = abcd[3U];
@@ -930,11 +822,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti54 = _t[54U];
uint32_t
v53 =
- vb54
- +
- ((va53 + (vc54 ^ (vb54 | ~vd54)) + xk53 + ti54)
- << 15U
- | (va53 + (vc54 ^ (vb54 | ~vd54)) + xk53 + ti54) >> 17U);
+ vb54 +
+ ((va53 + (vc54 ^ (vb54 | ~vd54)) + xk53 + ti54) << 15U |
+ (va53 + (vc54 ^ (vb54 | ~vd54)) + xk53 + ti54) >> 17U);
abcd[2U] = v53;
uint32_t va54 = abcd[1U];
uint32_t vb55 = abcd[2U];
@@ -946,11 +836,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti55 = _t[55U];
uint32_t
v54 =
- vb55
- +
- ((va54 + (vc55 ^ (vb55 | ~vd55)) + xk54 + ti55)
- << 21U
- | (va54 + (vc55 ^ (vb55 | ~vd55)) + xk54 + ti55) >> 11U);
+ vb55 +
+ ((va54 + (vc55 ^ (vb55 | ~vd55)) + xk54 + ti55) << 21U |
+ (va54 + (vc55 ^ (vb55 | ~vd55)) + xk54 + ti55) >> 11U);
abcd[1U] = v54;
uint32_t va55 = abcd[0U];
uint32_t vb56 = abcd[1U];
@@ -962,11 +850,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti56 = _t[56U];
uint32_t
v55 =
- vb56
- +
- ((va55 + (vc56 ^ (vb56 | ~vd56)) + xk55 + ti56)
- << 6U
- | (va55 + (vc56 ^ (vb56 | ~vd56)) + xk55 + ti56) >> 26U);
+ vb56 +
+ ((va55 + (vc56 ^ (vb56 | ~vd56)) + xk55 + ti56) << 6U |
+ (va55 + (vc56 ^ (vb56 | ~vd56)) + xk55 + ti56) >> 26U);
abcd[0U] = v55;
uint32_t va56 = abcd[3U];
uint32_t vb57 = abcd[0U];
@@ -978,11 +864,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti57 = _t[57U];
uint32_t
v56 =
- vb57
- +
- ((va56 + (vc57 ^ (vb57 | ~vd57)) + xk56 + ti57)
- << 10U
- | (va56 + (vc57 ^ (vb57 | ~vd57)) + xk56 + ti57) >> 22U);
+ vb57 +
+ ((va56 + (vc57 ^ (vb57 | ~vd57)) + xk56 + ti57) << 10U |
+ (va56 + (vc57 ^ (vb57 | ~vd57)) + xk56 + ti57) >> 22U);
abcd[3U] = v56;
uint32_t va57 = abcd[2U];
uint32_t vb58 = abcd[3U];
@@ -994,11 +878,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti58 = _t[58U];
uint32_t
v57 =
- vb58
- +
- ((va57 + (vc58 ^ (vb58 | ~vd58)) + xk57 + ti58)
- << 15U
- | (va57 + (vc58 ^ (vb58 | ~vd58)) + xk57 + ti58) >> 17U);
+ vb58 +
+ ((va57 + (vc58 ^ (vb58 | ~vd58)) + xk57 + ti58) << 15U |
+ (va57 + (vc58 ^ (vb58 | ~vd58)) + xk57 + ti58) >> 17U);
abcd[2U] = v57;
uint32_t va58 = abcd[1U];
uint32_t vb59 = abcd[2U];
@@ -1010,11 +892,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti59 = _t[59U];
uint32_t
v58 =
- vb59
- +
- ((va58 + (vc59 ^ (vb59 | ~vd59)) + xk58 + ti59)
- << 21U
- | (va58 + (vc59 ^ (vb59 | ~vd59)) + xk58 + ti59) >> 11U);
+ vb59 +
+ ((va58 + (vc59 ^ (vb59 | ~vd59)) + xk58 + ti59) << 21U |
+ (va58 + (vc59 ^ (vb59 | ~vd59)) + xk58 + ti59) >> 11U);
abcd[1U] = v58;
uint32_t va59 = abcd[0U];
uint32_t vb60 = abcd[1U];
@@ -1026,11 +906,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti60 = _t[60U];
uint32_t
v59 =
- vb60
- +
- ((va59 + (vc60 ^ (vb60 | ~vd60)) + xk59 + ti60)
- << 6U
- | (va59 + (vc60 ^ (vb60 | ~vd60)) + xk59 + ti60) >> 26U);
+ vb60 +
+ ((va59 + (vc60 ^ (vb60 | ~vd60)) + xk59 + ti60) << 6U |
+ (va59 + (vc60 ^ (vb60 | ~vd60)) + xk59 + ti60) >> 26U);
abcd[0U] = v59;
uint32_t va60 = abcd[3U];
uint32_t vb61 = abcd[0U];
@@ -1042,11 +920,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti61 = _t[61U];
uint32_t
v60 =
- vb61
- +
- ((va60 + (vc61 ^ (vb61 | ~vd61)) + xk60 + ti61)
- << 10U
- | (va60 + (vc61 ^ (vb61 | ~vd61)) + xk60 + ti61) >> 22U);
+ vb61 +
+ ((va60 + (vc61 ^ (vb61 | ~vd61)) + xk60 + ti61) << 10U |
+ (va60 + (vc61 ^ (vb61 | ~vd61)) + xk60 + ti61) >> 22U);
abcd[3U] = v60;
uint32_t va61 = abcd[2U];
uint32_t vb62 = abcd[3U];
@@ -1058,11 +934,9 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti62 = _t[62U];
uint32_t
v61 =
- vb62
- +
- ((va61 + (vc62 ^ (vb62 | ~vd62)) + xk61 + ti62)
- << 15U
- | (va61 + (vc62 ^ (vb62 | ~vd62)) + xk61 + ti62) >> 17U);
+ vb62 +
+ ((va61 + (vc62 ^ (vb62 | ~vd62)) + xk61 + ti62) << 15U |
+ (va61 + (vc62 ^ (vb62 | ~vd62)) + xk61 + ti62) >> 17U);
abcd[2U] = v61;
uint32_t va62 = abcd[1U];
uint32_t vb = abcd[2U];
@@ -1074,11 +948,8 @@ static void update(uint32_t *abcd, uint8_t *x)
uint32_t ti = _t[63U];
uint32_t
v62 =
- vb
- +
- ((va62 + (vc ^ (vb | ~vd)) + xk62 + ti)
- << 21U
- | (va62 + (vc ^ (vb | ~vd)) + xk62 + ti) >> 11U);
+ vb +
+ ((va62 + (vc ^ (vb | ~vd)) + xk62 + ti) << 21U | (va62 + (vc ^ (vb | ~vd)) + xk62 + ti) >> 11U);
abcd[1U] = v62;
uint32_t a = abcd[0U];
uint32_t b = abcd[1U];
@@ -1282,8 +1153,7 @@ Hacl_Hash_MD5_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
@@ -1328,8 +1198,7 @@ Hacl_Hash_MD5_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t
Hacl_Hash_MD5_update_multi(block_state1, data1, data1_len / 64U);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
@@ -1359,8 +1228,7 @@ Hacl_Hash_MD5_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state10,
@@ -1403,8 +1271,7 @@ Hacl_Hash_MD5_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t
Hacl_Hash_MD5_update_multi(block_state1, data1, data1_len / 64U);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
diff --git a/Modules/_hacl/Hacl_Hash_SHA1.c b/Modules/_hacl/Hacl_Hash_SHA1.c
index 508e447bf27..97bd4f2204c 100644
--- a/Modules/_hacl/Hacl_Hash_SHA1.c
+++ b/Modules/_hacl/Hacl_Hash_SHA1.c
@@ -315,8 +315,7 @@ Hacl_Hash_SHA1_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
@@ -361,8 +360,7 @@ Hacl_Hash_SHA1_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_
Hacl_Hash_SHA1_update_multi(block_state1, data1, data1_len / 64U);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
@@ -392,8 +390,7 @@ Hacl_Hash_SHA1_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state10,
@@ -436,8 +433,7 @@ Hacl_Hash_SHA1_update(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_
Hacl_Hash_SHA1_update_multi(block_state1, data1, data1_len / 64U);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
diff --git a/Modules/_hacl/Hacl_Hash_SHA2.c b/Modules/_hacl/Hacl_Hash_SHA2.c
index d612bafa72c..d2ee0c9ef51 100644
--- a/Modules/_hacl/Hacl_Hash_SHA2.c
+++ b/Modules/_hacl/Hacl_Hash_SHA2.c
@@ -100,15 +100,14 @@ static inline void sha256_update(uint8_t *b, uint32_t *hash)
uint32_t k_e_t = k_t;
uint32_t
t1 =
- h02
- + ((e0 << 26U | e0 >> 6U) ^ ((e0 << 21U | e0 >> 11U) ^ (e0 << 7U | e0 >> 25U)))
- + ((e0 & f0) ^ (~e0 & g0))
+ h02 + ((e0 << 26U | e0 >> 6U) ^ ((e0 << 21U | e0 >> 11U) ^ (e0 << 7U | e0 >> 25U))) +
+ ((e0 & f0) ^ (~e0 & g0))
+ k_e_t
+ ws_t;
uint32_t
t2 =
- ((a0 << 30U | a0 >> 2U) ^ ((a0 << 19U | a0 >> 13U) ^ (a0 << 10U | a0 >> 22U)))
- + ((a0 & b0) ^ ((a0 & c0) ^ (b0 & c0)));
+ ((a0 << 30U | a0 >> 2U) ^ ((a0 << 19U | a0 >> 13U) ^ (a0 << 10U | a0 >> 22U))) +
+ ((a0 & b0) ^ ((a0 & c0) ^ (b0 & c0)));
uint32_t a1 = t1 + t2;
uint32_t b1 = a0;
uint32_t c1 = b0;
@@ -301,15 +300,14 @@ static inline void sha512_update(uint8_t *b, uint64_t *hash)
uint64_t k_e_t = k_t;
uint64_t
t1 =
- h02
- + ((e0 << 50U | e0 >> 14U) ^ ((e0 << 46U | e0 >> 18U) ^ (e0 << 23U | e0 >> 41U)))
- + ((e0 & f0) ^ (~e0 & g0))
+ h02 + ((e0 << 50U | e0 >> 14U) ^ ((e0 << 46U | e0 >> 18U) ^ (e0 << 23U | e0 >> 41U))) +
+ ((e0 & f0) ^ (~e0 & g0))
+ k_e_t
+ ws_t;
uint64_t
t2 =
- ((a0 << 36U | a0 >> 28U) ^ ((a0 << 30U | a0 >> 34U) ^ (a0 << 25U | a0 >> 39U)))
- + ((a0 & b0) ^ ((a0 & c0) ^ (b0 & c0)));
+ ((a0 << 36U | a0 >> 28U) ^ ((a0 << 30U | a0 >> 34U) ^ (a0 << 25U | a0 >> 39U))) +
+ ((a0 & b0) ^ ((a0 & c0) ^ (b0 & c0)));
uint64_t a1 = t1 + t2;
uint64_t b1 = a0;
uint64_t c1 = b0;
@@ -639,8 +637,7 @@ update_224_256(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t chunk
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
@@ -685,8 +682,7 @@ update_224_256(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t chunk
Hacl_Hash_SHA2_sha256_update_nblocks(data1_len / 64U * 64U, data1, block_state1);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
@@ -716,8 +712,7 @@ update_224_256(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t chunk
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state10,
@@ -760,8 +755,7 @@ update_224_256(Hacl_Streaming_MD_state_32 *state, uint8_t *chunk, uint32_t chunk
Hacl_Hash_SHA2_sha256_update_nblocks(data1_len / 64U * 64U, data1, block_state1);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_32){
.block_state = block_state1,
@@ -1205,8 +1199,7 @@ update_384_512(Hacl_Streaming_MD_state_64 *state, uint8_t *chunk, uint32_t chunk
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_64){
.block_state = block_state1,
@@ -1251,8 +1244,7 @@ update_384_512(Hacl_Streaming_MD_state_64 *state, uint8_t *chunk, uint32_t chunk
Hacl_Hash_SHA2_sha512_update_nblocks(data1_len / 128U * 128U, data1, block_state1);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_64){
.block_state = block_state1,
@@ -1282,8 +1274,7 @@ update_384_512(Hacl_Streaming_MD_state_64 *state, uint8_t *chunk, uint32_t chunk
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_64){
.block_state = block_state10,
@@ -1326,8 +1317,7 @@ update_384_512(Hacl_Streaming_MD_state_64 *state, uint8_t *chunk, uint32_t chunk
Hacl_Hash_SHA2_sha512_update_nblocks(data1_len / 128U * 128U, data1, block_state1);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_MD_state_64){
.block_state = block_state1,
diff --git a/Modules/_hacl/Hacl_Hash_SHA3.c b/Modules/_hacl/Hacl_Hash_SHA3.c
index 87638df9549..466d2b96c0c 100644
--- a/Modules/_hacl/Hacl_Hash_SHA3.c
+++ b/Modules/_hacl/Hacl_Hash_SHA3.c
@@ -866,8 +866,7 @@ Hacl_Hash_SHA3_update(Hacl_Hash_SHA3_state_t *state, uint8_t *chunk, uint32_t ch
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
((Hacl_Hash_SHA3_state_t){ .block_state = block_state1, .buf = buf, .total_len = total_len2 });
}
else if (sz == 0U)
@@ -910,8 +909,7 @@ Hacl_Hash_SHA3_update(Hacl_Hash_SHA3_state_t *state, uint8_t *chunk, uint32_t ch
Hacl_Hash_SHA3_update_multi_sha3(a1, s2, data1, data1_len / block_len(a1));
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_SHA3_state_t){
.block_state = block_state1,
@@ -941,8 +939,7 @@ Hacl_Hash_SHA3_update(Hacl_Hash_SHA3_state_t *state, uint8_t *chunk, uint32_t ch
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Hash_SHA3_state_t){
.block_state = block_state10,
@@ -972,10 +969,8 @@ Hacl_Hash_SHA3_update(Hacl_Hash_SHA3_state_t *state, uint8_t *chunk, uint32_t ch
uint32_t ite;
if
(
- (uint64_t)(chunk_len - diff)
- % (uint64_t)block_len(i)
- == 0ULL
- && (uint64_t)(chunk_len - diff) > 0ULL
+ (uint64_t)(chunk_len - diff) % (uint64_t)block_len(i) == 0ULL &&
+ (uint64_t)(chunk_len - diff) > 0ULL
)
{
ite = block_len(i);
@@ -994,8 +989,7 @@ Hacl_Hash_SHA3_update(Hacl_Hash_SHA3_state_t *state, uint8_t *chunk, uint32_t ch
Hacl_Hash_SHA3_update_multi_sha3(a1, s2, data1, data1_len / block_len(a1));
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Hash_SHA3_state_t){
.block_state = block_state1,
@@ -2422,9 +2416,7 @@ Hacl_Hash_SHA3_shake128_squeeze_nblocks(
5U,
1U,
_C[i] =
- state[i
- + 0U]
- ^ (state[i + 5U] ^ (state[i + 10U] ^ (state[i + 15U] ^ state[i + 20U]))););
+ state[i + 0U] ^ (state[i + 5U] ^ (state[i + 10U] ^ (state[i + 15U] ^ state[i + 20U]))););
KRML_MAYBE_FOR5(i2,
0U,
5U,
diff --git a/Modules/_hacl/Hacl_Streaming_HMAC.c b/Modules/_hacl/Hacl_Streaming_HMAC.c
index d28b39792af..8dd7e2c0bf3 100644
--- a/Modules/_hacl/Hacl_Streaming_HMAC.c
+++ b/Modules/_hacl/Hacl_Streaming_HMAC.c
@@ -198,8 +198,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- = ((Hacl_Agile_Hash_state_s){ .tag = Hacl_Agile_Hash_MD5_a, { .case_MD5_a = s1 } });
+ st[0U] = ((Hacl_Agile_Hash_state_s){ .tag = Hacl_Agile_Hash_MD5_a, { .case_MD5_a = s1 } });
}
if (st == NULL)
{
@@ -220,8 +219,8 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- = ((Hacl_Agile_Hash_state_s){ .tag = Hacl_Agile_Hash_SHA1_a, { .case_SHA1_a = s1 } });
+ st[0U] =
+ ((Hacl_Agile_Hash_state_s){ .tag = Hacl_Agile_Hash_SHA1_a, { .case_SHA1_a = s1 } });
}
if (st == NULL)
{
@@ -242,8 +241,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA2_224_a,
@@ -270,8 +268,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA2_256_a,
@@ -298,8 +295,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA2_384_a,
@@ -326,8 +322,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA2_512_a,
@@ -354,8 +349,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA3_224_a,
@@ -382,8 +376,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA3_256_a,
@@ -410,8 +403,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA3_384_a,
@@ -438,8 +430,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_SHA3_512_a,
@@ -466,8 +457,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_Blake2S_a,
@@ -495,8 +485,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_Blake2S_128_a,
@@ -531,8 +520,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_Blake2B_a,
@@ -560,8 +548,7 @@ static Hacl_Agile_Hash_state_s *malloc_(Hacl_Agile_Hash_impl a)
*st = (Hacl_Agile_Hash_state_s *)KRML_HOST_MALLOC(sizeof (Hacl_Agile_Hash_state_s));
if (st != NULL)
{
- st[0U]
- =
+ st[0U] =
(
(Hacl_Agile_Hash_state_s){
.tag = Hacl_Agile_Hash_Blake2B_256_a,
@@ -2059,8 +2046,8 @@ Hacl_Streaming_HMAC_update(
Hacl_Streaming_HMAC_Definitions_index i1 = Hacl_Streaming_HMAC_index_of_state(block_state);
if
(
- (uint64_t)chunk_len
- > max_input_len64(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) - total_len
+ (uint64_t)chunk_len >
+ max_input_len64(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) - total_len
)
{
return Hacl_Streaming_Types_MaximumLengthExceeded;
@@ -2068,9 +2055,7 @@ Hacl_Streaming_HMAC_update(
uint32_t sz;
if
(
- total_len
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
- == 0ULL
+ total_len % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) == 0ULL
&& total_len > 0ULL
)
{
@@ -2079,8 +2064,8 @@ Hacl_Streaming_HMAC_update(
else
{
sz =
- (uint32_t)(total_len
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)(total_len %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
if (chunk_len <= block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) - sz)
{
@@ -2091,9 +2076,7 @@ Hacl_Streaming_HMAC_update(
uint32_t sz1;
if
(
- total_len1
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
- == 0ULL
+ total_len1 % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) == 0ULL
&& total_len1 > 0ULL
)
{
@@ -2102,14 +2085,13 @@ Hacl_Streaming_HMAC_update(
else
{
sz1 =
- (uint32_t)(total_len1
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)(total_len1 %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
uint8_t *buf2 = buf + sz1;
memcpy(buf2, chunk, chunk_len * sizeof (uint8_t));
uint64_t total_len2 = total_len1 + (uint64_t)chunk_len;
- *state
- =
+ *state =
(
(Hacl_Streaming_HMAC_agile_state){
.block_state = block_state1,
@@ -2127,9 +2109,7 @@ Hacl_Streaming_HMAC_update(
uint32_t sz1;
if
(
- total_len1
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
- == 0ULL
+ total_len1 % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) == 0ULL
&& total_len1 > 0ULL
)
{
@@ -2138,8 +2118,8 @@ Hacl_Streaming_HMAC_update(
else
{
sz1 =
- (uint32_t)(total_len1
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)(total_len1 %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
if (!(sz1 == 0U))
{
@@ -2153,8 +2133,8 @@ Hacl_Streaming_HMAC_update(
uint32_t ite;
if
(
- (uint64_t)chunk_len
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
+ (uint64_t)chunk_len %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
== 0ULL
&& (uint64_t)chunk_len > 0ULL
)
@@ -2164,8 +2144,8 @@ Hacl_Streaming_HMAC_update(
else
{
ite =
- (uint32_t)((uint64_t)chunk_len
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)((uint64_t)chunk_len %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
uint32_t
n_blocks = (chunk_len - ite) / block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)));
@@ -2178,8 +2158,7 @@ Hacl_Streaming_HMAC_update(
update_multi(s11, total_len1, data1, data1_len);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_HMAC_agile_state){
.block_state = block_state1,
@@ -2200,9 +2179,8 @@ Hacl_Streaming_HMAC_update(
uint32_t sz10;
if
(
- total_len10
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
- == 0ULL
+ total_len10 % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) ==
+ 0ULL
&& total_len10 > 0ULL
)
{
@@ -2211,14 +2189,13 @@ Hacl_Streaming_HMAC_update(
else
{
sz10 =
- (uint32_t)(total_len10
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)(total_len10 %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
uint8_t *buf2 = buf0 + sz10;
memcpy(buf2, chunk1, diff * sizeof (uint8_t));
uint64_t total_len2 = total_len10 + (uint64_t)diff;
- *state
- =
+ *state =
(
(Hacl_Streaming_HMAC_agile_state){
.block_state = block_state10,
@@ -2233,9 +2210,7 @@ Hacl_Streaming_HMAC_update(
uint32_t sz1;
if
(
- total_len1
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
- == 0ULL
+ total_len1 % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) == 0ULL
&& total_len1 > 0ULL
)
{
@@ -2244,8 +2219,8 @@ Hacl_Streaming_HMAC_update(
else
{
sz1 =
- (uint32_t)(total_len1
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)(total_len1 %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
if (!(sz1 == 0U))
{
@@ -2259,8 +2234,8 @@ Hacl_Streaming_HMAC_update(
uint32_t ite;
if
(
- (uint64_t)(chunk_len - diff)
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
+ (uint64_t)(chunk_len - diff) %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
== 0ULL
&& (uint64_t)(chunk_len - diff) > 0ULL
)
@@ -2270,13 +2245,12 @@ Hacl_Streaming_HMAC_update(
else
{
ite =
- (uint32_t)((uint64_t)(chunk_len - diff)
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)((uint64_t)(chunk_len - diff) %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
uint32_t
n_blocks =
- (chunk_len - diff - ite)
- / block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)));
+ (chunk_len - diff - ite) / block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)));
uint32_t
data1_len = n_blocks * block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)));
uint32_t data2_len = chunk_len - diff - data1_len;
@@ -2286,8 +2260,7 @@ Hacl_Streaming_HMAC_update(
update_multi(s11, total_len1, data1, data1_len);
uint8_t *dst = buf;
memcpy(dst, data2, data2_len * sizeof (uint8_t));
- *state
- =
+ *state =
(
(Hacl_Streaming_HMAC_agile_state){
.block_state = block_state1,
@@ -2324,9 +2297,7 @@ Hacl_Streaming_HMAC_digest(
uint32_t r;
if
(
- total_len
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1)))
- == 0ULL
+ total_len % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))) == 0ULL
&& total_len > 0ULL
)
{
@@ -2335,8 +2306,8 @@ Hacl_Streaming_HMAC_digest(
else
{
r =
- (uint32_t)(total_len
- % (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
+ (uint32_t)(total_len %
+ (uint64_t)block_len(alg_of_impl(dfst__Hacl_Agile_Hash_impl_uint32_t(i1))));
}
uint8_t *buf_1 = buf_;
Hacl_Agile_Hash_state_s *s110 = malloc_(dfst__Hacl_Agile_Hash_impl_uint32_t(i1));
diff --git a/Modules/_hacl/Lib_Memzero0.c b/Modules/_hacl/Lib_Memzero0.c
index 28abd1aa4e2..f94e0e2254a 100644
--- a/Modules/_hacl/Lib_Memzero0.c
+++ b/Modules/_hacl/Lib_Memzero0.c
@@ -11,18 +11,18 @@
#if defined(__APPLE__) && defined(__MACH__)
#include <AvailabilityMacros.h>
// memset_s is available from macOS 10.9, iOS 7, watchOS 2, and on all tvOS and visionOS versions.
-# if (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && (MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9))
-# define APPLE_HAS_MEMSET_S 1
-# elif (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0))
-# define APPLE_HAS_MEMSET_S 1
+# if (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && defined(MAC_OS_X_VERSION_10_9) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9))
+# define APPLE_HAS_MEMSET_S
+# elif (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_7_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0))
+# define APPLE_HAS_MEMSET_S
# elif (defined(TARGET_OS_TV) && TARGET_OS_TV)
-# define APPLE_HAS_MEMSET_S 1
-# elif (defined(__WATCH_OS_VERSION_MIN_REQUIRED) && (__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_2_0))
-# define APPLE_HAS_MEMSET_S 1
+# define APPLE_HAS_MEMSET_S
+# elif (defined(__WATCH_OS_VERSION_MIN_REQUIRED) && defined(__WATCHOS_2_0) && (__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_2_0))
+# define APPLE_HAS_MEMSET_S
# elif (defined(TARGET_OS_VISION) && TARGET_OS_VISION)
-# define APPLE_HAS_MEMSET_S 1
+# define APPLE_HAS_MEMSET_S
# else
-# define APPLE_HAS_MEMSET_S 0
+# undef APPLE_HAS_MEMSET_S
# endif
#endif
@@ -55,7 +55,7 @@ void Lib_Memzero0_memzero0(void *dst, uint64_t len) {
#ifdef _WIN32
SecureZeroMemory(dst, len_);
- #elif defined(__APPLE__) && defined(__MACH__) && APPLE_HAS_MEMSET_S
+ #elif defined(__APPLE__) && defined(__MACH__) && defined(APPLE_HAS_MEMSET_S)
memset_s(dst, len_, 0, len_);
#elif (defined(__linux__) && !defined(LINUX_NO_EXPLICIT_BZERO)) || defined(__FreeBSD__) || defined(__OpenBSD__)
explicit_bzero(dst, len_);
diff --git a/Modules/_hacl/include/krml/FStar_UInt128_Verified.h b/Modules/_hacl/include/krml/FStar_UInt128_Verified.h
index d4a90220bea..f85982f3373 100644
--- a/Modules/_hacl/include/krml/FStar_UInt128_Verified.h
+++ b/Modules/_hacl/include/krml/FStar_UInt128_Verified.h
@@ -257,11 +257,11 @@ FStar_UInt128_gte_mask(FStar_UInt128_uint128 a, FStar_UInt128_uint128 b)
{
FStar_UInt128_uint128 lit;
lit.low =
- (FStar_UInt64_gte_mask(a.high, b.high) & ~FStar_UInt64_eq_mask(a.high, b.high))
- | (FStar_UInt64_eq_mask(a.high, b.high) & FStar_UInt64_gte_mask(a.low, b.low));
+ (FStar_UInt64_gte_mask(a.high, b.high) & ~FStar_UInt64_eq_mask(a.high, b.high)) |
+ (FStar_UInt64_eq_mask(a.high, b.high) & FStar_UInt64_gte_mask(a.low, b.low));
lit.high =
- (FStar_UInt64_gte_mask(a.high, b.high) & ~FStar_UInt64_eq_mask(a.high, b.high))
- | (FStar_UInt64_eq_mask(a.high, b.high) & FStar_UInt64_gte_mask(a.low, b.low));
+ (FStar_UInt64_gte_mask(a.high, b.high) & ~FStar_UInt64_eq_mask(a.high, b.high)) |
+ (FStar_UInt64_eq_mask(a.high, b.high) & FStar_UInt64_gte_mask(a.low, b.low));
return lit;
}
@@ -294,14 +294,12 @@ static inline FStar_UInt128_uint128 FStar_UInt128_mul32(uint64_t x, uint32_t y)
{
FStar_UInt128_uint128 lit;
lit.low =
- FStar_UInt128_u32_combine((x >> FStar_UInt128_u32_32)
- * (uint64_t)y
- + (FStar_UInt128_u64_mod_32(x) * (uint64_t)y >> FStar_UInt128_u32_32),
+ FStar_UInt128_u32_combine((x >> FStar_UInt128_u32_32) * (uint64_t)y +
+ (FStar_UInt128_u64_mod_32(x) * (uint64_t)y >> FStar_UInt128_u32_32),
FStar_UInt128_u64_mod_32(FStar_UInt128_u64_mod_32(x) * (uint64_t)y));
lit.high =
- ((x >> FStar_UInt128_u32_32)
- * (uint64_t)y
- + (FStar_UInt128_u64_mod_32(x) * (uint64_t)y >> FStar_UInt128_u32_32))
+ ((x >> FStar_UInt128_u32_32) * (uint64_t)y +
+ (FStar_UInt128_u64_mod_32(x) * (uint64_t)y >> FStar_UInt128_u32_32))
>> FStar_UInt128_u32_32;
return lit;
}
@@ -315,28 +313,19 @@ static inline FStar_UInt128_uint128 FStar_UInt128_mul_wide(uint64_t x, uint64_t
{
FStar_UInt128_uint128 lit;
lit.low =
- FStar_UInt128_u32_combine_(FStar_UInt128_u64_mod_32(x)
- * (y >> FStar_UInt128_u32_32)
- +
- FStar_UInt128_u64_mod_32((x >> FStar_UInt128_u32_32)
- * FStar_UInt128_u64_mod_32(y)
- + (FStar_UInt128_u64_mod_32(x) * FStar_UInt128_u64_mod_32(y) >> FStar_UInt128_u32_32)),
+ FStar_UInt128_u32_combine_(FStar_UInt128_u64_mod_32(x) * (y >> FStar_UInt128_u32_32) +
+ FStar_UInt128_u64_mod_32((x >> FStar_UInt128_u32_32) * FStar_UInt128_u64_mod_32(y) +
+ (FStar_UInt128_u64_mod_32(x) * FStar_UInt128_u64_mod_32(y) >> FStar_UInt128_u32_32)),
FStar_UInt128_u64_mod_32(FStar_UInt128_u64_mod_32(x) * FStar_UInt128_u64_mod_32(y)));
lit.high =
- (x >> FStar_UInt128_u32_32)
- * (y >> FStar_UInt128_u32_32)
- +
- (((x >> FStar_UInt128_u32_32)
- * FStar_UInt128_u64_mod_32(y)
- + (FStar_UInt128_u64_mod_32(x) * FStar_UInt128_u64_mod_32(y) >> FStar_UInt128_u32_32))
+ (x >> FStar_UInt128_u32_32) * (y >> FStar_UInt128_u32_32) +
+ (((x >> FStar_UInt128_u32_32) * FStar_UInt128_u64_mod_32(y) +
+ (FStar_UInt128_u64_mod_32(x) * FStar_UInt128_u64_mod_32(y) >> FStar_UInt128_u32_32))
>> FStar_UInt128_u32_32)
+
- ((FStar_UInt128_u64_mod_32(x)
- * (y >> FStar_UInt128_u32_32)
- +
- FStar_UInt128_u64_mod_32((x >> FStar_UInt128_u32_32)
- * FStar_UInt128_u64_mod_32(y)
- + (FStar_UInt128_u64_mod_32(x) * FStar_UInt128_u64_mod_32(y) >> FStar_UInt128_u32_32)))
+ ((FStar_UInt128_u64_mod_32(x) * (y >> FStar_UInt128_u32_32) +
+ FStar_UInt128_u64_mod_32((x >> FStar_UInt128_u32_32) * FStar_UInt128_u64_mod_32(y) +
+ (FStar_UInt128_u64_mod_32(x) * FStar_UInt128_u64_mod_32(y) >> FStar_UInt128_u32_32)))
>> FStar_UInt128_u32_32);
return lit;
}
diff --git a/Modules/_hacl/python_hacl_namespaces.h b/Modules/_hacl/python_hacl_namespaces.h
index 1c2f7fea5c8..d0b4500395e 100644
--- a/Modules/_hacl/python_hacl_namespaces.h
+++ b/Modules/_hacl/python_hacl_namespaces.h
@@ -2,95 +2,43 @@
#define _PYTHON_HACL_NAMESPACES_H
/*
- * C's excuse for namespaces: Use globally unique names to avoid linkage
- * conflicts with builds linking or dynamically loading other code potentially
- * using HACL* libraries.
+ * Use globally unique names to avoid linkage conflicts with builds linking
+ * or dynamically loading other code potentially using HACL* libraries.
*
- * Something like this to generate new entries for the list:
- *
- * nm *.o | grep Hacl | cut -c 20- | sort | uniq | grep -v _Py_LibHacl_ | egrep ^_ | sed 's/_\(.*\)/#define \1 _Py_LibHacl_\1/g'
- */
+ * Assuming that the current working directory is Modules/_hacl,
+ * use the following command to generate a list of candidates:
-#define Lib_Memzero0_memzero0 _Py_LibHacl_Lib_Memzero0_memzero0
+ nm -j *.o | grep -i hacl | grep -P '^[a-zA-Z_][a-zA-Z0-9_]+' \
+ | sed -e 's/^_Py_LibHacl_//g' \
+ | sed 's/\(.*\)/#define \1 _Py_LibHacl_\1/g' \
+ | sort -u
-#define Hacl_Hash_SHA2_state_sha2_224_s _Py_LibHacl_Hacl_Hash_SHA2_state_sha2_224_s
-#define Hacl_Hash_SHA2_state_sha2_224 _Py_LibHacl_Hacl_Hash_SHA2_state_sha2_224
-#define Hacl_Hash_SHA2_state_sha2_256 _Py_LibHacl_Hacl_Hash_SHA2_state_sha2_256
-#define Hacl_Hash_SHA2_state_sha2_384_s _Py_LibHacl_Hacl_Hash_SHA2_state_sha2_384_s
-#define Hacl_Hash_SHA2_state_sha2_384 _Py_LibHacl_Hacl_Hash_SHA2_state_sha2_384
-#define Hacl_Hash_SHA2_state_sha2_512 _Py_LibHacl_Hacl_Hash_SHA2_state_sha2_512
-#define Hacl_Hash_SHA2_malloc_256 _Py_LibHacl_Hacl_Hash_SHA2_malloc_256
-#define Hacl_Hash_SHA2_malloc_224 _Py_LibHacl_Hacl_Hash_SHA2_malloc_224
-#define Hacl_Hash_SHA2_malloc_512 _Py_LibHacl_Hacl_Hash_SHA2_malloc_512
-#define Hacl_Hash_SHA2_malloc_384 _Py_LibHacl_Hacl_Hash_SHA2_malloc_384
-#define Hacl_Hash_SHA2_copy_256 _Py_LibHacl_Hacl_Hash_SHA2_copy_256
-#define Hacl_Hash_SHA2_copy_224 _Py_LibHacl_Hacl_Hash_SHA2_copy_224
-#define Hacl_Hash_SHA2_copy_512 _Py_LibHacl_Hacl_Hash_SHA2_copy_512
-#define Hacl_Hash_SHA2_copy_384 _Py_LibHacl_Hacl_Hash_SHA2_copy_384
-#define Hacl_Hash_SHA2_init_256 _Py_LibHacl_Hacl_Hash_SHA2_init_256
-#define Hacl_Hash_SHA2_init_224 _Py_LibHacl_Hacl_Hash_SHA2_init_224
-#define Hacl_Hash_SHA2_init_512 _Py_LibHacl_Hacl_Hash_SHA2_init_512
-#define Hacl_Hash_SHA2_init_384 _Py_LibHacl_Hacl_Hash_SHA2_init_384
-#define Hacl_SHA2_Scalar32_sha512_init _Py_LibHacl_Hacl_SHA2_Scalar32_sha512_init
-#define Hacl_Hash_SHA2_update_256 _Py_LibHacl_Hacl_Hash_SHA2_update_256
-#define Hacl_Hash_SHA2_update_224 _Py_LibHacl_Hacl_Hash_SHA2_update_224
-#define Hacl_Hash_SHA2_update_512 _Py_LibHacl_Hacl_Hash_SHA2_update_512
-#define Hacl_Hash_SHA2_update_384 _Py_LibHacl_Hacl_Hash_SHA2_update_384
-#define Hacl_Hash_SHA2_digest_256 _Py_LibHacl_Hacl_Hash_SHA2_digest_256
-#define Hacl_Hash_SHA2_digest_224 _Py_LibHacl_Hacl_Hash_SHA2_digest_224
-#define Hacl_Hash_SHA2_digest_512 _Py_LibHacl_Hacl_Hash_SHA2_digest_512
-#define Hacl_Hash_SHA2_digest_384 _Py_LibHacl_Hacl_Hash_SHA2_digest_384
-#define Hacl_Hash_SHA2_free_256 _Py_LibHacl_Hacl_Hash_SHA2_free_256
-#define Hacl_Hash_SHA2_free_224 _Py_LibHacl_Hacl_Hash_SHA2_free_224
-#define Hacl_Hash_SHA2_free_512 _Py_LibHacl_Hacl_Hash_SHA2_free_512
-#define Hacl_Hash_SHA2_free_384 _Py_LibHacl_Hacl_Hash_SHA2_free_384
-#define Hacl_Hash_SHA2_sha256 _Py_LibHacl_Hacl_Hash_SHA2_sha256
-#define Hacl_Hash_SHA2_sha224 _Py_LibHacl_Hacl_Hash_SHA2_sha224
-#define Hacl_Hash_SHA2_sha512 _Py_LibHacl_Hacl_Hash_SHA2_sha512
-#define Hacl_Hash_SHA2_sha384 _Py_LibHacl_Hacl_Hash_SHA2_sha384
+ * Compare the entries to add as follows:
-#define Hacl_Hash_MD5_malloc _Py_LibHacl_Hacl_Hash_MD5_malloc
-#define Hacl_Hash_MD5_init _Py_LibHacl_Hacl_Hash_MD5_init
-#define Hacl_Hash_MD5_update _Py_LibHacl_Hacl_Hash_MD5_update
-#define Hacl_Hash_MD5_digest _Py_LibHacl_Hacl_Hash_MD5_digest
-#define Hacl_Hash_MD5_free _Py_LibHacl_Hacl_Hash_MD5_free
-#define Hacl_Hash_MD5_copy _Py_LibHacl_Hacl_Hash_MD5_copy
-#define Hacl_Hash_MD5_hash _Py_LibHacl_Hacl_Hash_MD5_hash
-
-#define Hacl_Hash_SHA1_malloc _Py_LibHacl_Hacl_Hash_SHA1_malloc
-#define Hacl_Hash_SHA1_init _Py_LibHacl_Hacl_Hash_SHA1_init
-#define Hacl_Hash_SHA1_update _Py_LibHacl_Hacl_Hash_SHA1_update
-#define Hacl_Hash_SHA1_digest _Py_LibHacl_Hacl_Hash_SHA1_digest
-#define Hacl_Hash_SHA1_free _Py_LibHacl_Hacl_Hash_SHA1_free
-#define Hacl_Hash_SHA1_copy _Py_LibHacl_Hacl_Hash_SHA1_copy
-#define Hacl_Hash_SHA1_hash _Py_LibHacl_Hacl_Hash_SHA1_hash
-
-#define Hacl_Hash_SHA3_update_last_sha3 _Py_LibHacl_Hacl_Hash_SHA3_update_last_sha3
-#define Hacl_Hash_SHA3_update_multi_sha3 _Py_LibHacl_Hacl_Hash_SHA3_update_multi_sha3
-#define Hacl_Impl_SHA3_absorb_inner _Py_LibHacl_Hacl_Impl_SHA3_absorb_inner
-#define Hacl_Impl_SHA3_keccak _Py_LibHacl_Hacl_Impl_SHA3_keccak
-#define Hacl_Impl_SHA3_loadState _Py_LibHacl_Hacl_Impl_SHA3_loadState
-#define Hacl_Impl_SHA3_squeeze _Py_LibHacl_Hacl_Impl_SHA3_squeeze
-#define Hacl_Impl_SHA3_state_permute _Py_LibHacl_Hacl_Impl_SHA3_state_permute
-#define Hacl_SHA3_sha3_224 _Py_LibHacl_Hacl_SHA3_sha3_224
-#define Hacl_SHA3_sha3_256 _Py_LibHacl_Hacl_SHA3_sha3_256
-#define Hacl_SHA3_sha3_384 _Py_LibHacl_Hacl_SHA3_sha3_384
-#define Hacl_SHA3_sha3_512 _Py_LibHacl_Hacl_SHA3_sha3_512
-#define Hacl_SHA3_shake128_hacl _Py_LibHacl_Hacl_SHA3_shake128_hacl
-#define Hacl_SHA3_shake256_hacl _Py_LibHacl_Hacl_SHA3_shake256_hacl
-#define Hacl_Hash_SHA3_block_len _Py_LibHacl_Hacl_Hash_SHA3_block_len
-#define Hacl_Hash_SHA3_copy _Py_LibHacl_Hacl_Hash_SHA3_copy
-#define Hacl_Hash_SHA3_digest _Py_LibHacl_Hacl_Hash_SHA3_digest
-#define Hacl_Hash_SHA3_free _Py_LibHacl_Hacl_Hash_SHA3_free
-#define Hacl_Hash_SHA3_get_alg _Py_LibHacl_Hacl_Hash_SHA3_get_alg
-#define Hacl_Hash_SHA3_hash_len _Py_LibHacl_Hacl_Hash_SHA3_hash_len
-#define Hacl_Hash_SHA3_is_shake _Py_LibHacl_Hacl_Hash_SHA3_is_shake
-#define Hacl_Hash_SHA3_init_ _Py_LibHacl_Hacl_Hash_SHA3_init_
-#define Hacl_Hash_SHA3_malloc _Py_LibHacl_Hacl_Hash_SHA3_malloc
-#define Hacl_Hash_SHA3_reset _Py_LibHacl_Hacl_Hash_SHA3_reset
-#define Hacl_Hash_SHA3_update _Py_LibHacl_Hacl_Hash_SHA3_update
-#define Hacl_Hash_SHA3_squeeze _Py_LibHacl_Hacl_Hash_SHA3_squeeze
+ diff -y --suppress-common-lines \
+ <(grep -P '^#define (?!_PY.+_H)' python_hacl_namespaces.h | sort -u) \
+ <(nm -j *.o | grep -i hacl | grep -P '^[a-zA-Z_][a-zA-Z0-9_]+' \
+ | sed -e 's/^_Py_LibHacl_//g' \
+ | sed 's/\(.*\)/#define \1 _Py_LibHacl_\1/g' | sort -u)
+ */
+// --- Utils ------------------------------------------------------------------
+#define Lib_Memzero0_memzero0 _Py_LibHacl_Lib_Memzero0_memzero0
+// --- HASH-BLAKE-2b ----------------------------------------------------------
+#define Hacl_Hash_Blake2b_copy _Py_LibHacl_Hacl_Hash_Blake2b_copy
+#define Hacl_Hash_Blake2b_digest _Py_LibHacl_Hacl_Hash_Blake2b_digest
+#define Hacl_Hash_Blake2b_finish _Py_LibHacl_Hacl_Hash_Blake2b_finish
+#define Hacl_Hash_Blake2b_free _Py_LibHacl_Hacl_Hash_Blake2b_free
+#define Hacl_Hash_Blake2b_hash_with_key _Py_LibHacl_Hacl_Hash_Blake2b_hash_with_key
+#define Hacl_Hash_Blake2b_hash_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2b_hash_with_key_and_params
+#define Hacl_Hash_Blake2b_info _Py_LibHacl_Hacl_Hash_Blake2b_info
+#define Hacl_Hash_Blake2b_init _Py_LibHacl_Hacl_Hash_Blake2b_init
+#define Hacl_Hash_Blake2b_malloc _Py_LibHacl_Hacl_Hash_Blake2b_malloc
+#define Hacl_Hash_Blake2b_malloc_with_key _Py_LibHacl_Hacl_Hash_Blake2b_malloc_with_key
+#define Hacl_Hash_Blake2b_malloc_with_params_and_key _Py_LibHacl_Hacl_Hash_Blake2b_malloc_with_params_and_key
+#define Hacl_Hash_Blake2b_reset _Py_LibHacl_Hacl_Hash_Blake2b_reset
+#define Hacl_Hash_Blake2b_reset_with_key _Py_LibHacl_Hacl_Hash_Blake2b_reset_with_key
+#define Hacl_Hash_Blake2b_reset_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2b_reset_with_key_and_params
#define Hacl_Hash_Blake2b_Simd256_copy _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_copy
#define Hacl_Hash_Blake2b_Simd256_copy_internal_state _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_copy_internal_state
#define Hacl_Hash_Blake2b_Simd256_digest _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_digest
@@ -104,7 +52,6 @@
#define Hacl_Hash_Blake2b_Simd256_malloc _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_malloc
#define Hacl_Hash_Blake2b_Simd256_malloc_internal_state_with_key _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_malloc_internal_state_with_key
#define Hacl_Hash_Blake2b_Simd256_malloc_with_key _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_malloc_with_key
-#define Hacl_Hash_Blake2b_Simd256_malloc_with_key0 _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_malloc_with_key0
#define Hacl_Hash_Blake2b_Simd256_malloc_with_params_and_key _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_malloc_with_params_and_key
#define Hacl_Hash_Blake2b_Simd256_reset _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_reset
#define Hacl_Hash_Blake2b_Simd256_reset_with_key _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_reset_with_key
@@ -115,23 +62,24 @@
#define Hacl_Hash_Blake2b_Simd256_update_last_no_inline _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_update_last_no_inline
#define Hacl_Hash_Blake2b_Simd256_update_multi _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_update_multi
#define Hacl_Hash_Blake2b_Simd256_update_multi_no_inline _Py_LibHacl_Hacl_Hash_Blake2b_Simd256_update_multi_no_inline
-#define Hacl_Hash_Blake2b_copy _Py_LibHacl_Hacl_Hash_Blake2b_copy
-#define Hacl_Hash_Blake2b_digest _Py_LibHacl_Hacl_Hash_Blake2b_digest
-#define Hacl_Hash_Blake2b_finish _Py_LibHacl_Hacl_Hash_Blake2b_finish
-#define Hacl_Hash_Blake2b_free _Py_LibHacl_Hacl_Hash_Blake2b_free
-#define Hacl_Hash_Blake2b_hash_with_key _Py_LibHacl_Hacl_Hash_Blake2b_hash_with_key
-#define Hacl_Hash_Blake2b_hash_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2b_hash_with_key_and_params
-#define Hacl_Hash_Blake2b_info _Py_LibHacl_Hacl_Hash_Blake2b_info
-#define Hacl_Hash_Blake2b_init _Py_LibHacl_Hacl_Hash_Blake2b_init
-#define Hacl_Hash_Blake2b_malloc _Py_LibHacl_Hacl_Hash_Blake2b_malloc
-#define Hacl_Hash_Blake2b_malloc_with_key _Py_LibHacl_Hacl_Hash_Blake2b_malloc_with_key
-#define Hacl_Hash_Blake2b_malloc_with_params_and_key _Py_LibHacl_Hacl_Hash_Blake2b_malloc_with_params_and_key
-#define Hacl_Hash_Blake2b_reset _Py_LibHacl_Hacl_Hash_Blake2b_reset
-#define Hacl_Hash_Blake2b_reset_with_key _Py_LibHacl_Hacl_Hash_Blake2b_reset_with_key
-#define Hacl_Hash_Blake2b_reset_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2b_reset_with_key_and_params
#define Hacl_Hash_Blake2b_update _Py_LibHacl_Hacl_Hash_Blake2b_update
#define Hacl_Hash_Blake2b_update_last _Py_LibHacl_Hacl_Hash_Blake2b_update_last
#define Hacl_Hash_Blake2b_update_multi _Py_LibHacl_Hacl_Hash_Blake2b_update_multi
+// --- HASH-BLAKE-2s ----------------------------------------------------------
+#define Hacl_Hash_Blake2s_copy _Py_LibHacl_Hacl_Hash_Blake2s_copy
+#define Hacl_Hash_Blake2s_digest _Py_LibHacl_Hacl_Hash_Blake2s_digest
+#define Hacl_Hash_Blake2s_finish _Py_LibHacl_Hacl_Hash_Blake2s_finish
+#define Hacl_Hash_Blake2s_free _Py_LibHacl_Hacl_Hash_Blake2s_free
+#define Hacl_Hash_Blake2s_hash_with_key _Py_LibHacl_Hacl_Hash_Blake2s_hash_with_key
+#define Hacl_Hash_Blake2s_hash_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2s_hash_with_key_and_params
+#define Hacl_Hash_Blake2s_info _Py_LibHacl_Hacl_Hash_Blake2s_info
+#define Hacl_Hash_Blake2s_init _Py_LibHacl_Hacl_Hash_Blake2s_init
+#define Hacl_Hash_Blake2s_malloc _Py_LibHacl_Hacl_Hash_Blake2s_malloc
+#define Hacl_Hash_Blake2s_malloc_with_key _Py_LibHacl_Hacl_Hash_Blake2s_malloc_with_key
+#define Hacl_Hash_Blake2s_malloc_with_params_and_key _Py_LibHacl_Hacl_Hash_Blake2s_malloc_with_params_and_key
+#define Hacl_Hash_Blake2s_reset _Py_LibHacl_Hacl_Hash_Blake2s_reset
+#define Hacl_Hash_Blake2s_reset_with_key _Py_LibHacl_Hacl_Hash_Blake2s_reset_with_key
+#define Hacl_Hash_Blake2s_reset_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2s_reset_with_key_and_params
#define Hacl_Hash_Blake2s_Simd128_copy _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_copy
#define Hacl_Hash_Blake2s_Simd128_copy_internal_state _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_copy_internal_state
#define Hacl_Hash_Blake2s_Simd128_digest _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_digest
@@ -145,7 +93,6 @@
#define Hacl_Hash_Blake2s_Simd128_malloc _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_malloc
#define Hacl_Hash_Blake2s_Simd128_malloc_internal_state_with_key _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_malloc_internal_state_with_key
#define Hacl_Hash_Blake2s_Simd128_malloc_with_key _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_malloc_with_key
-#define Hacl_Hash_Blake2s_Simd128_malloc_with_key0 _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_malloc_with_key0
#define Hacl_Hash_Blake2s_Simd128_malloc_with_params_and_key _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_malloc_with_params_and_key
#define Hacl_Hash_Blake2s_Simd128_reset _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_reset
#define Hacl_Hash_Blake2s_Simd128_reset_with_key _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_reset_with_key
@@ -156,37 +103,54 @@
#define Hacl_Hash_Blake2s_Simd128_update_last_no_inline _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_update_last_no_inline
#define Hacl_Hash_Blake2s_Simd128_update_multi _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_update_multi
#define Hacl_Hash_Blake2s_Simd128_update_multi_no_inline _Py_LibHacl_Hacl_Hash_Blake2s_Simd128_update_multi_no_inline
-#define Hacl_Hash_Blake2s_copy _Py_LibHacl_Hacl_Hash_Blake2s_copy
-#define Hacl_Hash_Blake2s_digest _Py_LibHacl_Hacl_Hash_Blake2s_digest
-#define Hacl_Hash_Blake2s_finish _Py_LibHacl_Hacl_Hash_Blake2s_finish
-#define Hacl_Hash_Blake2s_free _Py_LibHacl_Hacl_Hash_Blake2s_free
-#define Hacl_Hash_Blake2s_hash_with_key _Py_LibHacl_Hacl_Hash_Blake2s_hash_with_key
-#define Hacl_Hash_Blake2s_hash_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2s_hash_with_key_and_params
-#define Hacl_Hash_Blake2s_info _Py_LibHacl_Hacl_Hash_Blake2s_info
-#define Hacl_Hash_Blake2s_init _Py_LibHacl_Hacl_Hash_Blake2s_init
-#define Hacl_Hash_Blake2s_malloc _Py_LibHacl_Hacl_Hash_Blake2s_malloc
-#define Hacl_Hash_Blake2s_malloc_with_key _Py_LibHacl_Hacl_Hash_Blake2s_malloc_with_key
-#define Hacl_Hash_Blake2s_malloc_with_params_and_key _Py_LibHacl_Hacl_Hash_Blake2s_malloc_with_params_and_key
-#define Hacl_Hash_Blake2s_reset _Py_LibHacl_Hacl_Hash_Blake2s_reset
-#define Hacl_Hash_Blake2s_reset_with_key _Py_LibHacl_Hacl_Hash_Blake2s_reset_with_key
-#define Hacl_Hash_Blake2s_reset_with_key_and_params _Py_LibHacl_Hacl_Hash_Blake2s_reset_with_key_and_params
#define Hacl_Hash_Blake2s_update _Py_LibHacl_Hacl_Hash_Blake2s_update
#define Hacl_Hash_Blake2s_update_last _Py_LibHacl_Hacl_Hash_Blake2s_update_last
#define Hacl_Hash_Blake2s_update_multi _Py_LibHacl_Hacl_Hash_Blake2s_update_multi
+// --- HASH-MD5 ---------------------------------------------------------------
+#define Hacl_Hash_MD5_copy _Py_LibHacl_Hacl_Hash_MD5_copy
+#define Hacl_Hash_MD5_digest _Py_LibHacl_Hacl_Hash_MD5_digest
#define Hacl_Hash_MD5_finish _Py_LibHacl_Hacl_Hash_MD5_finish
+#define Hacl_Hash_MD5_free _Py_LibHacl_Hacl_Hash_MD5_free
+#define Hacl_Hash_MD5_hash _Py_LibHacl_Hacl_Hash_MD5_hash
#define Hacl_Hash_MD5_hash_oneshot _Py_LibHacl_Hacl_Hash_MD5_hash_oneshot
+#define Hacl_Hash_MD5_init _Py_LibHacl_Hacl_Hash_MD5_init
+#define Hacl_Hash_MD5_malloc _Py_LibHacl_Hacl_Hash_MD5_malloc
#define Hacl_Hash_MD5_reset _Py_LibHacl_Hacl_Hash_MD5_reset
+#define Hacl_Hash_MD5_update _Py_LibHacl_Hacl_Hash_MD5_update
#define Hacl_Hash_MD5_update_last _Py_LibHacl_Hacl_Hash_MD5_update_last
#define Hacl_Hash_MD5_update_multi _Py_LibHacl_Hacl_Hash_MD5_update_multi
+// --- HASH-SHA-1 -------------------------------------------------------------
+#define Hacl_Hash_SHA1_copy _Py_LibHacl_Hacl_Hash_SHA1_copy
+#define Hacl_Hash_SHA1_digest _Py_LibHacl_Hacl_Hash_SHA1_digest
#define Hacl_Hash_SHA1_finish _Py_LibHacl_Hacl_Hash_SHA1_finish
+#define Hacl_Hash_SHA1_free _Py_LibHacl_Hacl_Hash_SHA1_free
+#define Hacl_Hash_SHA1_hash _Py_LibHacl_Hacl_Hash_SHA1_hash
#define Hacl_Hash_SHA1_hash_oneshot _Py_LibHacl_Hacl_Hash_SHA1_hash_oneshot
+#define Hacl_Hash_SHA1_init _Py_LibHacl_Hacl_Hash_SHA1_init
+#define Hacl_Hash_SHA1_malloc _Py_LibHacl_Hacl_Hash_SHA1_malloc
#define Hacl_Hash_SHA1_reset _Py_LibHacl_Hacl_Hash_SHA1_reset
+#define Hacl_Hash_SHA1_update _Py_LibHacl_Hacl_Hash_SHA1_update
#define Hacl_Hash_SHA1_update_last _Py_LibHacl_Hacl_Hash_SHA1_update_last
#define Hacl_Hash_SHA1_update_multi _Py_LibHacl_Hacl_Hash_SHA1_update_multi
+// --- HASH-SHA-2 -------------------------------------------------------------
+#define Hacl_Hash_SHA2_copy_256 _Py_LibHacl_Hacl_Hash_SHA2_copy_256
+#define Hacl_Hash_SHA2_copy_512 _Py_LibHacl_Hacl_Hash_SHA2_copy_512
+#define Hacl_Hash_SHA2_digest_224 _Py_LibHacl_Hacl_Hash_SHA2_digest_224
+#define Hacl_Hash_SHA2_digest_256 _Py_LibHacl_Hacl_Hash_SHA2_digest_256
+#define Hacl_Hash_SHA2_digest_384 _Py_LibHacl_Hacl_Hash_SHA2_digest_384
+#define Hacl_Hash_SHA2_digest_512 _Py_LibHacl_Hacl_Hash_SHA2_digest_512
+#define Hacl_Hash_SHA2_free_224 _Py_LibHacl_Hacl_Hash_SHA2_free_224
+#define Hacl_Hash_SHA2_free_256 _Py_LibHacl_Hacl_Hash_SHA2_free_256
+#define Hacl_Hash_SHA2_free_384 _Py_LibHacl_Hacl_Hash_SHA2_free_384
+#define Hacl_Hash_SHA2_free_512 _Py_LibHacl_Hacl_Hash_SHA2_free_512
#define Hacl_Hash_SHA2_hash_224 _Py_LibHacl_Hacl_Hash_SHA2_hash_224
#define Hacl_Hash_SHA2_hash_256 _Py_LibHacl_Hacl_Hash_SHA2_hash_256
#define Hacl_Hash_SHA2_hash_384 _Py_LibHacl_Hacl_Hash_SHA2_hash_384
#define Hacl_Hash_SHA2_hash_512 _Py_LibHacl_Hacl_Hash_SHA2_hash_512
+#define Hacl_Hash_SHA2_malloc_224 _Py_LibHacl_Hacl_Hash_SHA2_malloc_224
+#define Hacl_Hash_SHA2_malloc_256 _Py_LibHacl_Hacl_Hash_SHA2_malloc_256
+#define Hacl_Hash_SHA2_malloc_384 _Py_LibHacl_Hacl_Hash_SHA2_malloc_384
+#define Hacl_Hash_SHA2_malloc_512 _Py_LibHacl_Hacl_Hash_SHA2_malloc_512
#define Hacl_Hash_SHA2_reset_224 _Py_LibHacl_Hacl_Hash_SHA2_reset_224
#define Hacl_Hash_SHA2_reset_256 _Py_LibHacl_Hacl_Hash_SHA2_reset_256
#define Hacl_Hash_SHA2_reset_384 _Py_LibHacl_Hacl_Hash_SHA2_reset_384
@@ -207,10 +171,25 @@
#define Hacl_Hash_SHA2_sha512_init _Py_LibHacl_Hacl_Hash_SHA2_sha512_init
#define Hacl_Hash_SHA2_sha512_update_last _Py_LibHacl_Hacl_Hash_SHA2_sha512_update_last
#define Hacl_Hash_SHA2_sha512_update_nblocks _Py_LibHacl_Hacl_Hash_SHA2_sha512_update_nblocks
+#define Hacl_Hash_SHA2_update_224 _Py_LibHacl_Hacl_Hash_SHA2_update_224
+#define Hacl_Hash_SHA2_update_256 _Py_LibHacl_Hacl_Hash_SHA2_update_256
+#define Hacl_Hash_SHA2_update_384 _Py_LibHacl_Hacl_Hash_SHA2_update_384
+#define Hacl_Hash_SHA2_update_512 _Py_LibHacl_Hacl_Hash_SHA2_update_512
+// --- HASH-SHA-3 -------------------------------------------------------------
#define Hacl_Hash_SHA3_absorb_inner_32 _Py_LibHacl_Hacl_Hash_SHA3_absorb_inner_32
+#define Hacl_Hash_SHA3_block_len _Py_LibHacl_Hacl_Hash_SHA3_block_len
+#define Hacl_Hash_SHA3_copy _Py_LibHacl_Hacl_Hash_SHA3_copy
+#define Hacl_Hash_SHA3_digest _Py_LibHacl_Hacl_Hash_SHA3_digest
+#define Hacl_Hash_SHA3_free _Py_LibHacl_Hacl_Hash_SHA3_free
+#define Hacl_Hash_SHA3_get_alg _Py_LibHacl_Hacl_Hash_SHA3_get_alg
+#define Hacl_Hash_SHA3_hash_len _Py_LibHacl_Hacl_Hash_SHA3_hash_len
+#define Hacl_Hash_SHA3_init_ _Py_LibHacl_Hacl_Hash_SHA3_init_
+#define Hacl_Hash_SHA3_is_shake _Py_LibHacl_Hacl_Hash_SHA3_is_shake
#define Hacl_Hash_SHA3_keccak_piln _Py_LibHacl_Hacl_Hash_SHA3_keccak_piln
#define Hacl_Hash_SHA3_keccak_rndc _Py_LibHacl_Hacl_Hash_SHA3_keccak_rndc
#define Hacl_Hash_SHA3_keccak_rotc _Py_LibHacl_Hacl_Hash_SHA3_keccak_rotc
+#define Hacl_Hash_SHA3_malloc _Py_LibHacl_Hacl_Hash_SHA3_malloc
+#define Hacl_Hash_SHA3_reset _Py_LibHacl_Hacl_Hash_SHA3_reset
#define Hacl_Hash_SHA3_sha3_224 _Py_LibHacl_Hacl_Hash_SHA3_sha3_224
#define Hacl_Hash_SHA3_sha3_256 _Py_LibHacl_Hacl_Hash_SHA3_sha3_256
#define Hacl_Hash_SHA3_sha3_384 _Py_LibHacl_Hacl_Hash_SHA3_sha3_384
@@ -220,37 +199,39 @@
#define Hacl_Hash_SHA3_shake128_absorb_nblocks _Py_LibHacl_Hacl_Hash_SHA3_shake128_absorb_nblocks
#define Hacl_Hash_SHA3_shake128_squeeze_nblocks _Py_LibHacl_Hacl_Hash_SHA3_shake128_squeeze_nblocks
#define Hacl_Hash_SHA3_shake256 _Py_LibHacl_Hacl_Hash_SHA3_shake256
+#define Hacl_Hash_SHA3_squeeze _Py_LibHacl_Hacl_Hash_SHA3_squeeze
#define Hacl_Hash_SHA3_state_free _Py_LibHacl_Hacl_Hash_SHA3_state_free
#define Hacl_Hash_SHA3_state_malloc _Py_LibHacl_Hacl_Hash_SHA3_state_malloc
-
-// Streaming HMAC
+#define Hacl_Hash_SHA3_update _Py_LibHacl_Hacl_Hash_SHA3_update
+#define Hacl_Hash_SHA3_update_last_sha3 _Py_LibHacl_Hacl_Hash_SHA3_update_last_sha3
+#define Hacl_Hash_SHA3_update_multi_sha3 _Py_LibHacl_Hacl_Hash_SHA3_update_multi_sha3
+// --- STREAMING-MAC ----------------------------------------------------------
+#define Hacl_Streaming_HMAC_copy _Py_LibHacl_Hacl_Streaming_HMAC_copy
+#define Hacl_Streaming_HMAC_digest _Py_LibHacl_Hacl_Streaming_HMAC_digest
+#define Hacl_Streaming_HMAC_free _Py_LibHacl_Hacl_Streaming_HMAC_free
+#define Hacl_Streaming_HMAC_get_impl _Py_LibHacl_Hacl_Streaming_HMAC_get_impl
#define Hacl_Streaming_HMAC_index_of_state _Py_LibHacl_Hacl_Streaming_HMAC_index_of_state
#define Hacl_Streaming_HMAC_malloc_ _Py_LibHacl_Hacl_Streaming_HMAC_malloc_
-#define Hacl_Streaming_HMAC_get_impl _Py_LibHacl_Hacl_Streaming_HMAC_get_impl
#define Hacl_Streaming_HMAC_reset _Py_LibHacl_Hacl_Streaming_HMAC_reset
-#define Hacl_Streaming_HMAC_update _Py_LibHacl_Hacl_Streaming_HMAC_update
-#define Hacl_Streaming_HMAC_digest _Py_LibHacl_Hacl_Streaming_HMAC_digest
-#define Hacl_Streaming_HMAC_copy _Py_LibHacl_Hacl_Streaming_HMAC_copy
-#define Hacl_Streaming_HMAC_free _Py_LibHacl_Hacl_Streaming_HMAC_free
#define Hacl_Streaming_HMAC_s1 _Py_LibHacl_Hacl_Streaming_HMAC_s1
#define Hacl_Streaming_HMAC_s2 _Py_LibHacl_Hacl_Streaming_HMAC_s2
-
-// HMAC-MD5
+#define Hacl_Streaming_HMAC_update _Py_LibHacl_Hacl_Streaming_HMAC_update
+// --- HMAC-MD5 ---------------------------------------------------------------
#define Hacl_HMAC_compute_md5 _Py_LibHacl_Hacl_HMAC_compute_md5
-// HMAC-SHA-1
+// --- HMAC-SHA-1 -------------------------------------------------------------
#define Hacl_HMAC_compute_sha1 _Py_LibHacl_Hacl_HMAC_compute_sha1
-// HMAC-SHA-2
+// --- HMAC-SHA-2 -------------------------------------------------------------
#define Hacl_HMAC_compute_sha2_224 _Py_LibHacl_Hacl_HMAC_compute_sha2_224
#define Hacl_HMAC_compute_sha2_256 _Py_LibHacl_Hacl_HMAC_compute_sha2_256
#define Hacl_HMAC_compute_sha2_384 _Py_LibHacl_Hacl_HMAC_compute_sha2_384
#define Hacl_HMAC_compute_sha2_512 _Py_LibHacl_Hacl_HMAC_compute_sha2_512
-// HMAC-SHA-3
+// --- HMAC-SHA-3 -------------------------------------------------------------
#define Hacl_HMAC_compute_sha3_224 _Py_LibHacl_Hacl_HMAC_compute_sha3_224
#define Hacl_HMAC_compute_sha3_256 _Py_LibHacl_Hacl_HMAC_compute_sha3_256
#define Hacl_HMAC_compute_sha3_384 _Py_LibHacl_Hacl_HMAC_compute_sha3_384
#define Hacl_HMAC_compute_sha3_512 _Py_LibHacl_Hacl_HMAC_compute_sha3_512
-// HMAC-BLAKE
-#define Hacl_HMAC_compute_blake2s_32 _Py_LibHacl_Hacl_HMAC_compute_blake2s_32
+// --- HMAC-BLAKE-2 -----------------------------------------------------------
#define Hacl_HMAC_compute_blake2b_32 _Py_LibHacl_Hacl_HMAC_compute_blake2b_32
+#define Hacl_HMAC_compute_blake2s_32 _Py_LibHacl_Hacl_HMAC_compute_blake2s_32
#endif // _PYTHON_HACL_NAMESPACES_H
diff --git a/Modules/_hacl/refresh.sh b/Modules/_hacl/refresh.sh
index d91650b44bb..a6776282423 100755
--- a/Modules/_hacl/refresh.sh
+++ b/Modules/_hacl/refresh.sh
@@ -22,7 +22,7 @@ fi
# Update this when updating to a new version after verifying that the changes
# the update brings in are good.
-expected_hacl_star_rev=7720f6d4fc0468a99d5ea6120976bcc271e42727
+expected_hacl_star_rev=4ef25b547b377dcef855db4289c6a00580e7221c
hacl_dir="$(realpath "$1")"
cd "$(dirname "$0")"
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 48eed5eac97..90a7391ebb0 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -38,6 +38,10 @@
#include <stdbool.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+# define Py_HAS_OPENSSL3_SUPPORT
+#endif
+
#ifndef OPENSSL_THREADS
# error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL"
#endif
@@ -55,7 +59,7 @@
#define PY_OPENSSL_HAS_BLAKE2 1
#endif
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#ifdef Py_HAS_OPENSSL3_SUPPORT
#define PY_EVP_MD EVP_MD
#define PY_EVP_MD_fetch(algorithm, properties) EVP_MD_fetch(NULL, algorithm, properties)
#define PY_EVP_MD_up_ref(md) EVP_MD_up_ref(md)
@@ -77,12 +81,12 @@
* py_alias as keys.
*/
-enum Py_hash_type {
- Py_ht_evp, // usedforsecurity=True / default
- Py_ht_evp_nosecurity, // usedforsecurity=False
- Py_ht_mac, // HMAC
- Py_ht_pbkdf2, // PKBDF2
-};
+typedef enum Py_hash_type {
+ Py_ht_evp, // usedforsecurity=True / default
+ Py_ht_evp_nosecurity, // usedforsecurity=False
+ Py_ht_mac, // HMAC
+ Py_ht_pbkdf2, // PKBDF2
+} Py_hash_type;
typedef struct {
const char *py_name;
@@ -251,14 +255,15 @@ py_hashentry_table_new(void) {
return NULL;
}
-/* Module state */
+// --- Module state -----------------------------------------------------------
+
static PyModuleDef _hashlibmodule;
typedef struct {
- PyTypeObject *EVPtype;
- PyTypeObject *HMACtype;
+ PyTypeObject *HASH_type; // based on EVP_MD
+ PyTypeObject *HMAC_type;
#ifdef PY_OPENSSL_HAS_SHAKE
- PyTypeObject *EVPXOFtype;
+ PyTypeObject *HASHXOF_type; // based on EVP_MD
#endif
PyObject *constructs;
PyObject *unsupported_digestmod_error;
@@ -273,42 +278,41 @@ get_hashlib_state(PyObject *module)
return (_hashlibstate *)state;
}
+// --- Module objects ---------------------------------------------------------
+
typedef struct {
- PyObject_HEAD
- EVP_MD_CTX *ctx; /* OpenSSL message digest context */
- // Prevents undefined behavior via multiple threads entering the C API.
- bool use_mutex;
- PyMutex mutex; /* OpenSSL context lock */
-} EVPobject;
+ HASHLIB_OBJECT_HEAD
+ EVP_MD_CTX *ctx; /* OpenSSL message digest context */
+} HASHobject;
-#define EVPobject_CAST(op) ((EVPobject *)(op))
+#define HASHobject_CAST(op) ((HASHobject *)(op))
typedef struct {
- PyObject_HEAD
+ HASHLIB_OBJECT_HEAD
HMAC_CTX *ctx; /* OpenSSL hmac context */
- // Prevents undefined behavior via multiple threads entering the C API.
- bool use_mutex;
- PyMutex mutex; /* HMAC context lock */
} HMACobject;
#define HMACobject_CAST(op) ((HMACobject *)(op))
-#include "clinic/_hashopenssl.c.h"
+// --- Module clinic configuration --------------------------------------------
+
/*[clinic input]
module _hashlib
-class _hashlib.HASH "EVPobject *" "((_hashlibstate *)PyModule_GetState(module))->EVPtype"
-class _hashlib.HASHXOF "EVPobject *" "((_hashlibstate *)PyModule_GetState(module))->EVPXOFtype"
-class _hashlib.HMAC "HMACobject *" "((_hashlibstate *)PyModule_GetState(module))->HMACtype"
+class _hashlib.HASH "HASHobject *" "&PyType_Type"
+class _hashlib.HASHXOF "HASHobject *" "&PyType_Type"
+class _hashlib.HMAC "HMACobject *" "&PyType_Type"
[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7df1bcf6f75cb8ef]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6b5c9ce5c28bdc58]*/
+#include "clinic/_hashopenssl.c.h"
/* LCOV_EXCL_START */
/* Set an exception of given type using the given OpenSSL error code. */
static void
-set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode)
+set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode)
{
+ assert(exc_type != NULL);
assert(errcode != 0);
/* ERR_ERROR_STRING(3) ensures that the messages below are ASCII */
@@ -317,13 +321,29 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode)
const char *reason = ERR_reason_error_string(errcode);
if (lib && func) {
- PyErr_Format(exc, "[%s: %s] %s", lib, func, reason);
+ PyErr_Format(exc_type, "[%s: %s] %s", lib, func, reason);
}
else if (lib) {
- PyErr_Format(exc, "[%s] %s", lib, reason);
+ PyErr_Format(exc_type, "[%s] %s", lib, reason);
}
else {
- PyErr_SetString(exc, reason);
+ PyErr_SetString(exc_type, reason);
+ }
+}
+
+/*
+ * Get an appropriate exception type for the given OpenSSL error code.
+ *
+ * The exception type depends on the error code reason.
+ */
+static PyObject *
+get_smart_ssl_exception_type(unsigned long errcode, PyObject *default_exc_type)
+{
+ switch (ERR_GET_REASON(errcode)) {
+ case ERR_R_MALLOC_FAILURE:
+ return PyExc_MemoryError;
+ default:
+ return default_exc_type;
}
}
@@ -331,73 +351,171 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode)
* Set an exception of given type.
*
* By default, the exception's message is constructed by using the last SSL
- * error that occurred. If no error occurred, the 'fallback_format' is used
- * to create a C-style formatted fallback message.
+ * error that occurred. If no error occurred, the 'fallback_message' is used
+ * to create an exception message.
*/
static void
-raise_ssl_error(PyObject *exc, const char *fallback_format, ...)
+raise_ssl_error(PyObject *exc_type, const char *fallback_message)
+{
+ assert(fallback_message != NULL);
+ unsigned long errcode = ERR_peek_last_error();
+ if (errcode) {
+ ERR_clear_error();
+ set_ssl_exception_from_errcode(exc_type, errcode);
+ }
+ else {
+ PyErr_SetString(exc_type, fallback_message);
+ }
+}
+
+/* Same as raise_ssl_error() but with a C-style formatted message. */
+static void
+raise_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...)
{
assert(fallback_format != NULL);
unsigned long errcode = ERR_peek_last_error();
if (errcode) {
ERR_clear_error();
- set_ssl_exception_from_errcode(exc, errcode);
+ set_ssl_exception_from_errcode(exc_type, errcode);
}
else {
va_list vargs;
va_start(vargs, fallback_format);
- PyErr_FormatV(exc, fallback_format, vargs);
+ PyErr_FormatV(exc_type, fallback_format, vargs);
+ va_end(vargs);
+ }
+}
+
+/* Same as raise_ssl_error_f() with smart exception types. */
+static void
+raise_smart_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...)
+{
+ unsigned long errcode = ERR_peek_last_error();
+ if (errcode) {
+ ERR_clear_error();
+ exc_type = get_smart_ssl_exception_type(errcode, exc_type);
+ set_ssl_exception_from_errcode(exc_type, errcode);
+ }
+ else {
+ va_list vargs;
+ va_start(vargs, fallback_format);
+ PyErr_FormatV(exc_type, fallback_format, vargs);
va_end(vargs);
}
}
/*
- * Set an exception with a generic default message after an error occurred.
- *
- * It can also be used without previous calls to SSL built-in functions,
- * in which case a generic error message is provided.
+ * Raise a ValueError with a default message after an error occurred.
+ * It can also be used without previous calls to SSL built-in functions.
*/
static inline void
-notify_ssl_error_occurred(void)
+notify_ssl_error_occurred(const char *message)
{
- raise_ssl_error(PyExc_ValueError, "no reason supplied");
+ raise_ssl_error(PyExc_ValueError, message);
}
-/* LCOV_EXCL_STOP */
-static PyObject*
-py_digest_name(const EVP_MD *md)
+/* Same as notify_ssl_error_occurred() for failed OpenSSL functions. */
+static inline void
+notify_ssl_error_occurred_in(const char *funcname)
{
- assert(md != NULL);
- int nid = EVP_MD_nid(md);
- const char *name = NULL;
- const py_hashentry_t *h;
+ raise_ssl_error_f(PyExc_ValueError,
+ "error in OpenSSL function %s()", funcname);
+}
+
+/* Same as notify_ssl_error_occurred_in() with smart exception types. */
+static inline void
+notify_smart_ssl_error_occurred_in(const char *funcname)
+{
+ raise_smart_ssl_error_f(PyExc_ValueError,
+ "error in OpenSSL function %s()", funcname);
+}
+/* LCOV_EXCL_STOP */
+
+/*
+ * OpenSSL provides a way to go from NIDs to digest names for hash functions
+ * but lacks this granularity for MAC objects where it is not possible to get
+ * the underlying digest name (only the block size and digest size are allowed
+ * to be recovered).
+ *
+ * In addition, OpenSSL aliases pollute the list of known digest names
+ * as OpenSSL appears to have its own definition of alias. In particular,
+ * the resulting list still contains duplicate and alternate names for several
+ * algorithms.
+ *
+ * Therefore, digest names, whether they are used by hash functions or HMAC,
+ * are handled through EVP_MD objects or directly by using some NID.
+ */
- for (h = py_hashes; h->py_name != NULL; h++) {
+/* Get a cached entry by OpenSSL NID. */
+static const py_hashentry_t *
+get_hashentry_by_nid(int nid)
+{
+ for (const py_hashentry_t *h = py_hashes; h->py_name != NULL; h++) {
if (h->ossl_nid == nid) {
- name = h->py_name;
- break;
+ return h;
}
}
+ return NULL;
+}
+
+/*
+ * Convert the NID to a string via OBJ_nid2*() functions.
+ *
+ * If 'nid' cannot be resolved, set an exception and return NULL.
+ */
+static const char *
+get_asn1_utf8name_by_nid(int nid)
+{
+ const char *name = OBJ_nid2ln(nid);
if (name == NULL) {
- /* Ignore aliased names and only use long, lowercase name. The aliases
- * pollute the list and OpenSSL appears to have its own definition of
- * alias as the resulting list still contains duplicate and alternate
- * names for several algorithms.
- */
- name = OBJ_nid2ln(nid);
- if (name == NULL)
- name = OBJ_nid2sn(nid);
+ // In OpenSSL 3.0 and later, OBJ_nid*() are thread-safe and may raise.
+ assert(ERR_peek_last_error() != 0);
+ if (ERR_GET_REASON(ERR_peek_last_error()) != OBJ_R_UNKNOWN_NID) {
+ goto error;
+ }
+ // fallback to short name and unconditionally propagate errors
+ name = OBJ_nid2sn(nid);
+ if (name == NULL) {
+ goto error;
+ }
}
+ return name;
- return PyUnicode_FromString(name);
+error:
+ raise_ssl_error_f(PyExc_ValueError, "cannot resolve NID %d", nid);
+ return NULL;
}
-/* Get EVP_MD by HID and purpose */
-static PY_EVP_MD*
-py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
+/*
+ * Convert the NID to an OpenSSL digest name.
+ *
+ * On error, set an exception and return NULL.
+ */
+static const char *
+get_hashlib_utf8name_by_nid(int nid)
{
- PY_EVP_MD *digest = NULL;
- PY_EVP_MD *other_digest = NULL;
+ const py_hashentry_t *e = get_hashentry_by_nid(nid);
+ return e ? e->py_name : get_asn1_utf8name_by_nid(nid);
+}
+
+/* Same as get_hashlib_utf8name_by_nid() but using an EVP_MD object. */
+static const char *
+get_hashlib_utf8name_by_evp_md(const EVP_MD *md)
+{
+ assert(md != NULL);
+ return get_hashlib_utf8name_by_nid(EVP_MD_nid(md));
+}
+
+/*
+ * Get a new reference to an EVP_MD object described by name and purpose.
+ *
+ * If 'name' is an OpenSSL indexed name, the return value is cached.
+ */
+static PY_EVP_MD *
+get_openssl_evp_md_by_utf8name(PyObject *module, const char *name,
+ Py_hash_type py_ht)
+{
+ PY_EVP_MD *digest = NULL, *other_digest = NULL;
_hashlibstate *state = get_hashlib_state(module);
py_hashentry_t *entry = (py_hashentry_t *)_Py_hashtable_get(
state->hashtable, (const void*)name
@@ -431,15 +549,16 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
#endif
}
break;
+ default:
+ goto invalid_hash_type;
}
// if another thread same thing at same time make sure we got same ptr
assert(other_digest == NULL || other_digest == digest);
- if (digest != NULL) {
- if (other_digest == NULL) {
- PY_EVP_MD_up_ref(digest);
- }
+ if (digest != NULL && other_digest == NULL) {
+ PY_EVP_MD_up_ref(digest);
}
- } else {
+ }
+ else {
// Fall back for looking up an unindexed OpenSSL specific name.
switch (py_ht) {
case Py_ht_evp:
@@ -450,66 +569,94 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
case Py_ht_evp_nosecurity:
digest = PY_EVP_MD_fetch(name, "-fips");
break;
+ default:
+ goto invalid_hash_type;
}
}
if (digest == NULL) {
- raise_ssl_error(state->unsupported_digestmod_error,
- "unsupported hash type %s", name);
+ raise_ssl_error_f(state->unsupported_digestmod_error,
+ "unsupported digest name: %s", name);
return NULL;
}
return digest;
+
+invalid_hash_type:
+ assert(digest == NULL);
+ PyErr_Format(PyExc_SystemError, "unsupported hash type %d", py_ht);
+ return NULL;
}
-/* Get digest EVP from object
+/*
+ * Raise an exception indicating that 'digestmod' is not supported.
+ */
+static void
+raise_unsupported_digestmod_error(PyObject *module, PyObject *digestmod)
+{
+ _hashlibstate *state = get_hashlib_state(module);
+ PyErr_Format(state->unsupported_digestmod_error,
+ "Unsupported digestmod %R", digestmod);
+}
+
+/*
+ * Get a new reference to an EVP_MD described by 'digestmod' and purpose.
+ *
+ * On error, set an exception and return NULL.
*
- * * string
- * * _hashopenssl builtin function
+ * Parameters
*
- * on error returns NULL with exception set.
+ * digestmod A digest name or a _hashopenssl builtin function
+ * py_ht The message digest purpose.
*/
-static PY_EVP_MD*
-py_digest_by_digestmod(PyObject *module, PyObject *digestmod, enum Py_hash_type py_ht) {
- PyObject *name_obj = NULL;
+static PY_EVP_MD *
+get_openssl_evp_md(PyObject *module, PyObject *digestmod, Py_hash_type py_ht)
+{
const char *name;
-
if (PyUnicode_Check(digestmod)) {
- name_obj = digestmod;
- } else {
- _hashlibstate *state = get_hashlib_state(module);
- // borrowed ref
- name_obj = PyDict_GetItemWithError(state->constructs, digestmod);
+ name = PyUnicode_AsUTF8(digestmod);
+ }
+ else {
+ PyObject *dict = get_hashlib_state(module)->constructs;
+ assert(dict != NULL);
+ PyObject *borrowed_ref = PyDict_GetItemWithError(dict, digestmod);
+ name = borrowed_ref == NULL ? NULL : PyUnicode_AsUTF8(borrowed_ref);
}
- if (name_obj == NULL) {
+ if (name == NULL) {
if (!PyErr_Occurred()) {
- _hashlibstate *state = get_hashlib_state(module);
- PyErr_Format(
- state->unsupported_digestmod_error,
- "Unsupported digestmod %R", digestmod);
+ raise_unsupported_digestmod_error(module, digestmod);
}
return NULL;
}
+ return get_openssl_evp_md_by_utf8name(module, name, py_ht);
+}
- name = PyUnicode_AsUTF8(name_obj);
- if (name == NULL) {
+// --- OpenSSL HASH wrappers --------------------------------------------------
+
+/* Thin wrapper around EVP_MD_CTX_new() which sets an exception on failure. */
+static EVP_MD_CTX *
+py_wrapper_EVP_MD_CTX_new(void)
+{
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ if (ctx == NULL) {
+ PyErr_NoMemory();
return NULL;
}
-
- return py_digest_by_name(module, name, py_ht);
+ return ctx;
}
-static EVPobject *
-newEVPobject(PyTypeObject *type)
+// --- HASH interface ---------------------------------------------------------
+
+static HASHobject *
+new_hash_object(PyTypeObject *type)
{
- EVPobject *retval = PyObject_New(EVPobject, type);
+ HASHobject *retval = PyObject_New(HASHobject, type);
if (retval == NULL) {
return NULL;
}
HASHLIB_INIT_MUTEX(retval);
- retval->ctx = EVP_MD_CTX_new();
+ retval->ctx = py_wrapper_EVP_MD_CTX_new();
if (retval->ctx == NULL) {
Py_DECREF(retval);
- PyErr_NoMemory();
return NULL;
}
@@ -517,7 +664,7 @@ newEVPobject(PyTypeObject *type)
}
static int
-EVP_hash(EVPobject *self, const void *vp, Py_ssize_t len)
+_hashlib_HASH_hash(HASHobject *self, const void *vp, Py_ssize_t len)
{
unsigned int process;
const unsigned char *cp = (const unsigned char *)vp;
@@ -527,7 +674,7 @@ EVP_hash(EVPobject *self, const void *vp, Py_ssize_t len)
else
process = Py_SAFE_DOWNCAST(len, Py_ssize_t, unsigned int);
if (!EVP_DigestUpdate(self->ctx, (const void*)cp, process)) {
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestUpdate));
return -1;
}
len -= process;
@@ -539,9 +686,9 @@ EVP_hash(EVPobject *self, const void *vp, Py_ssize_t len)
/* Internal methods for a hash object */
static void
-EVP_dealloc(PyObject *op)
+_hashlib_HASH_dealloc(PyObject *op)
{
- EVPobject *self = EVPobject_CAST(op);
+ HASHobject *self = HASHobject_CAST(op);
PyTypeObject *tp = Py_TYPE(self);
EVP_MD_CTX_free(self->ctx);
PyObject_Free(self);
@@ -549,120 +696,98 @@ EVP_dealloc(PyObject *op)
}
static int
-locked_EVP_MD_CTX_copy(EVP_MD_CTX *new_ctx_p, EVPobject *self)
+_hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p)
{
int result;
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
result = EVP_MD_CTX_copy(new_ctx_p, self->ctx);
- LEAVE_HASHLIB(self);
- return result;
+ HASHLIB_RELEASE_LOCK(self);
+ if (result == 0) {
+ notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy));
+ return -1;
+ }
+ return 0;
}
/* External methods for a hash object */
/*[clinic input]
-_hashlib.HASH.copy as EVP_copy
+_hashlib.HASH.copy
Return a copy of the hash object.
[clinic start generated code]*/
static PyObject *
-EVP_copy_impl(EVPobject *self)
-/*[clinic end generated code: output=b370c21cdb8ca0b4 input=31455b6a3e638069]*/
+_hashlib_HASH_copy_impl(HASHobject *self)
+/*[clinic end generated code: output=2545541af18d53d7 input=814b19202cd08a26]*/
{
- EVPobject *newobj;
+ HASHobject *newobj;
- if ((newobj = newEVPobject(Py_TYPE(self))) == NULL)
+ if ((newobj = new_hash_object(Py_TYPE(self))) == NULL)
return NULL;
- if (!locked_EVP_MD_CTX_copy(newobj->ctx, self)) {
+ if (_hashlib_HASH_copy_locked(self, newobj->ctx) < 0) {
Py_DECREF(newobj);
- notify_ssl_error_occurred();
return NULL;
}
return (PyObject *)newobj;
}
-/*[clinic input]
-_hashlib.HASH.digest as EVP_digest
-
-Return the digest value as a bytes object.
-[clinic start generated code]*/
-
-static PyObject *
-EVP_digest_impl(EVPobject *self)
-/*[clinic end generated code: output=0f6a3a0da46dc12d input=03561809a419bf00]*/
+static Py_ssize_t
+_hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest)
{
- unsigned char digest[EVP_MAX_MD_SIZE];
- EVP_MD_CTX *temp_ctx;
- PyObject *retval;
- unsigned int digest_size;
-
- temp_ctx = EVP_MD_CTX_new();
- if (temp_ctx == NULL) {
- PyErr_NoMemory();
- return NULL;
+ EVP_MD_CTX *ctx = py_wrapper_EVP_MD_CTX_new();
+ if (ctx == NULL) {
+ return -1;
}
-
- if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
+ if (_hashlib_HASH_copy_locked(self, ctx) < 0) {
goto error;
}
- digest_size = EVP_MD_CTX_size(temp_ctx);
- if (!EVP_DigestFinal(temp_ctx, digest, NULL)) {
+ Py_ssize_t digest_size = EVP_MD_CTX_size(ctx);
+ if (!EVP_DigestFinal(ctx, digest, NULL)) {
+ notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinal));
goto error;
}
-
- retval = PyBytes_FromStringAndSize((const char *)digest, digest_size);
- EVP_MD_CTX_free(temp_ctx);
- return retval;
+ EVP_MD_CTX_free(ctx);
+ return digest_size;
error:
- EVP_MD_CTX_free(temp_ctx);
- notify_ssl_error_occurred();
- return NULL;
+ EVP_MD_CTX_free(ctx);
+ return -1;
}
/*[clinic input]
-_hashlib.HASH.hexdigest as EVP_hexdigest
+_hashlib.HASH.digest
-Return the digest value as a string of hexadecimal digits.
+Return the digest value as a bytes object.
[clinic start generated code]*/
static PyObject *
-EVP_hexdigest_impl(EVPobject *self)
-/*[clinic end generated code: output=18e6decbaf197296 input=aff9cf0e4c741a9a]*/
+_hashlib_HASH_digest_impl(HASHobject *self)
+/*[clinic end generated code: output=3fc6f9671d712850 input=d8d528d6e50af0de]*/
{
unsigned char digest[EVP_MAX_MD_SIZE];
- EVP_MD_CTX *temp_ctx;
- unsigned int digest_size;
-
- temp_ctx = EVP_MD_CTX_new();
- if (temp_ctx == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
-
- /* Get the raw (binary) digest value */
- if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- goto error;
- }
- digest_size = EVP_MD_CTX_size(temp_ctx);
- if (!EVP_DigestFinal(temp_ctx, digest, NULL)) {
- goto error;
- }
+ Py_ssize_t n = _hashlib_HASH_digest_compute(self, digest);
+ return n < 0 ? NULL : PyBytes_FromStringAndSize((const char *)digest, n);
+}
- EVP_MD_CTX_free(temp_ctx);
+/*[clinic input]
+_hashlib.HASH.hexdigest
- return _Py_strhex((const char *)digest, (Py_ssize_t)digest_size);
+Return the digest value as a string of hexadecimal digits.
+[clinic start generated code]*/
-error:
- EVP_MD_CTX_free(temp_ctx);
- notify_ssl_error_occurred();
- return NULL;
+static PyObject *
+_hashlib_HASH_hexdigest_impl(HASHobject *self)
+/*[clinic end generated code: output=1b8e60d9711e7f4d input=ae7553f78f8372d8]*/
+{
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ Py_ssize_t n = _hashlib_HASH_digest_compute(self, digest);
+ return n < 0 ? NULL : _Py_strhex((const char *)digest, n);
}
/*[clinic input]
-_hashlib.HASH.update as EVP_update
+_hashlib.HASH.update
obj: object
/
@@ -671,82 +796,70 @@ Update this hash object's state with the provided string.
[clinic start generated code]*/
static PyObject *
-EVP_update_impl(EVPobject *self, PyObject *obj)
-/*[clinic end generated code: output=d56f91c68348f95f input=9b30ec848f015501]*/
+_hashlib_HASH_update_impl(HASHobject *self, PyObject *obj)
+/*[clinic end generated code: output=62ad989754946b86 input=aa1ce20e3f92ceb6]*/
{
int result;
Py_buffer view;
-
GET_BUFFER_VIEW_OR_ERROUT(obj, &view);
-
- if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- result = EVP_hash(self, view.buf, view.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- result = EVP_hash(self, view.buf, view.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, view.len,
+ result = _hashlib_HASH_hash(self, view.buf, view.len)
+ );
PyBuffer_Release(&view);
-
- if (result == -1)
- return NULL;
- Py_RETURN_NONE;
+ return result < 0 ? NULL : Py_None;
}
-static PyMethodDef EVP_methods[] = {
- EVP_UPDATE_METHODDEF
- EVP_DIGEST_METHODDEF
- EVP_HEXDIGEST_METHODDEF
- EVP_COPY_METHODDEF
+static PyMethodDef HASH_methods[] = {
+ _HASHLIB_HASH_COPY_METHODDEF
+ _HASHLIB_HASH_DIGEST_METHODDEF
+ _HASHLIB_HASH_HEXDIGEST_METHODDEF
+ _HASHLIB_HASH_UPDATE_METHODDEF
{NULL, NULL} /* sentinel */
};
static PyObject *
-EVP_get_block_size(PyObject *op, void *Py_UNUSED(closure))
+_hashlib_HASH_get_blocksize(PyObject *op, void *Py_UNUSED(closure))
{
- EVPobject *self = EVPobject_CAST(op);
+ HASHobject *self = HASHobject_CAST(op);
long block_size = EVP_MD_CTX_block_size(self->ctx);
return PyLong_FromLong(block_size);
}
static PyObject *
-EVP_get_digest_size(PyObject *op, void *Py_UNUSED(closure))
+_hashlib_HASH_get_digestsize(PyObject *op, void *Py_UNUSED(closure))
{
- EVPobject *self = EVPobject_CAST(op);
+ HASHobject *self = HASHobject_CAST(op);
long size = EVP_MD_CTX_size(self->ctx);
return PyLong_FromLong(size);
}
static PyObject *
-EVP_get_name(PyObject *op, void *Py_UNUSED(closure))
+_hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure))
{
- EVPobject *self = EVPobject_CAST(op);
+ HASHobject *self = HASHobject_CAST(op);
const EVP_MD *md = EVP_MD_CTX_md(self->ctx);
if (md == NULL) {
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred("missing EVP_MD for HASH context");
return NULL;
}
- return py_digest_name(md);
+ const char *name = get_hashlib_utf8name_by_evp_md(md);
+ assert(name != NULL || PyErr_Occurred());
+ return name == NULL ? NULL : PyUnicode_FromString(name);
}
-static PyGetSetDef EVP_getseters[] = {
- {"digest_size", EVP_get_digest_size, NULL, NULL, NULL},
- {"block_size", EVP_get_block_size, NULL, NULL, NULL},
- {"name", EVP_get_name, NULL, NULL, PyDoc_STR("algorithm name.")},
+static PyGetSetDef HASH_getsets[] = {
+ {"digest_size", _hashlib_HASH_get_digestsize, NULL, NULL, NULL},
+ {"block_size", _hashlib_HASH_get_blocksize, NULL, NULL, NULL},
+ {"name", _hashlib_HASH_get_name, NULL, NULL, PyDoc_STR("algorithm name.")},
{NULL} /* Sentinel */
};
static PyObject *
-EVP_repr(PyObject *self)
+_hashlib_HASH_repr(PyObject *self)
{
- PyObject *name = EVP_get_name(self, NULL);
+ PyObject *name = _hashlib_HASH_get_name(self, NULL);
if (name == NULL) {
return NULL;
}
@@ -756,7 +869,7 @@ EVP_repr(PyObject *self)
return repr;
}
-PyDoc_STRVAR(hashtype_doc,
+PyDoc_STRVAR(HASHobject_type_doc,
"HASH(name, string=b\'\')\n"
"--\n"
"\n"
@@ -774,27 +887,31 @@ PyDoc_STRVAR(hashtype_doc,
"name -- the hash algorithm being used by this object\n"
"digest_size -- number of bytes in this hashes output");
-static PyType_Slot EVPtype_slots[] = {
- {Py_tp_dealloc, EVP_dealloc},
- {Py_tp_repr, EVP_repr},
- {Py_tp_doc, (char *)hashtype_doc},
- {Py_tp_methods, EVP_methods},
- {Py_tp_getset, EVP_getseters},
+static PyType_Slot HASHobject_type_slots[] = {
+ {Py_tp_dealloc, _hashlib_HASH_dealloc},
+ {Py_tp_repr, _hashlib_HASH_repr},
+ {Py_tp_doc, (char *)HASHobject_type_doc},
+ {Py_tp_methods, HASH_methods},
+ {Py_tp_getset, HASH_getsets},
{0, 0},
};
-static PyType_Spec EVPtype_spec = {
- "_hashlib.HASH", /*tp_name*/
- sizeof(EVPobject), /*tp_basicsize*/
- 0, /*tp_itemsize*/
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE,
- EVPtype_slots
+static PyType_Spec HASHobject_type_spec = {
+ .name = "_hashlib.HASH",
+ .basicsize = sizeof(HASHobject),
+ .flags = (
+ Py_TPFLAGS_DEFAULT
+ | Py_TPFLAGS_BASETYPE
+ | Py_TPFLAGS_DISALLOW_INSTANTIATION
+ | Py_TPFLAGS_IMMUTABLETYPE
+ ),
+ .slots = HASHobject_type_slots
};
#ifdef PY_OPENSSL_HAS_SHAKE
/*[clinic input]
-_hashlib.HASHXOF.digest as EVPXOF_digest
+_hashlib.HASHXOF.digest
length: Py_ssize_t
@@ -802,30 +919,40 @@ Return the digest value as a bytes object.
[clinic start generated code]*/
static PyObject *
-EVPXOF_digest_impl(EVPobject *self, Py_ssize_t length)
-/*[clinic end generated code: output=ef9320c23280efad input=816a6537cea3d1db]*/
+_hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length)
+/*[clinic end generated code: output=dcb09335dd2fe908 input=3eb034ce03c55b21]*/
{
EVP_MD_CTX *temp_ctx;
- PyObject *retval = PyBytes_FromStringAndSize(NULL, length);
+ PyObject *retval;
+ if (length < 0) {
+ PyErr_SetString(PyExc_ValueError, "negative digest length");
+ return NULL;
+ }
+
+ if (length == 0) {
+ return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
+ }
+
+ retval = PyBytes_FromStringAndSize(NULL, length);
if (retval == NULL) {
return NULL;
}
- temp_ctx = EVP_MD_CTX_new();
+ temp_ctx = py_wrapper_EVP_MD_CTX_new();
if (temp_ctx == NULL) {
Py_DECREF(retval);
- PyErr_NoMemory();
return NULL;
}
- if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
+ if (_hashlib_HASH_copy_locked(self, temp_ctx) < 0) {
goto error;
}
if (!EVP_DigestFinalXOF(temp_ctx,
(unsigned char*)PyBytes_AS_STRING(retval),
length))
{
+ notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinalXOF));
goto error;
}
@@ -835,12 +962,11 @@ EVPXOF_digest_impl(EVPobject *self, Py_ssize_t length)
error:
Py_DECREF(retval);
EVP_MD_CTX_free(temp_ctx);
- notify_ssl_error_occurred();
return NULL;
}
/*[clinic input]
-_hashlib.HASHXOF.hexdigest as EVPXOF_hexdigest
+_hashlib.HASHXOF.hexdigest
length: Py_ssize_t
@@ -848,31 +974,40 @@ Return the digest value as a string of hexadecimal digits.
[clinic start generated code]*/
static PyObject *
-EVPXOF_hexdigest_impl(EVPobject *self, Py_ssize_t length)
-/*[clinic end generated code: output=eb3e6ee7788bf5b2 input=5f9d6a8f269e34df]*/
+_hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length)
+/*[clinic end generated code: output=519431cafa014f39 input=0e58f7238adb7ab8]*/
{
unsigned char *digest;
EVP_MD_CTX *temp_ctx;
PyObject *retval;
+ if (length < 0) {
+ PyErr_SetString(PyExc_ValueError, "negative digest length");
+ return NULL;
+ }
+
+ if (length == 0) {
+ return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
+ }
+
digest = (unsigned char*)PyMem_Malloc(length);
if (digest == NULL) {
- PyErr_NoMemory();
+ (void)PyErr_NoMemory();
return NULL;
}
- temp_ctx = EVP_MD_CTX_new();
+ temp_ctx = py_wrapper_EVP_MD_CTX_new();
if (temp_ctx == NULL) {
PyMem_Free(digest);
- PyErr_NoMemory();
return NULL;
}
/* Get the raw (binary) digest value */
- if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
+ if (_hashlib_HASH_copy_locked(self, temp_ctx) < 0) {
goto error;
}
if (!EVP_DigestFinalXOF(temp_ctx, digest, length)) {
+ notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinalXOF));
goto error;
}
@@ -885,29 +1020,29 @@ EVPXOF_hexdigest_impl(EVPobject *self, Py_ssize_t length)
error:
PyMem_Free(digest);
EVP_MD_CTX_free(temp_ctx);
- notify_ssl_error_occurred();
return NULL;
}
-static PyMethodDef EVPXOF_methods[] = {
- EVPXOF_DIGEST_METHODDEF
- EVPXOF_HEXDIGEST_METHODDEF
+static PyMethodDef HASHXOFobject_methods[] = {
+ _HASHLIB_HASHXOF_DIGEST_METHODDEF
+ _HASHLIB_HASHXOF_HEXDIGEST_METHODDEF
{NULL, NULL} /* sentinel */
};
static PyObject *
-EVPXOF_get_digest_size(PyObject *Py_UNUSED(self), void *Py_UNUSED(closure))
+_hashlib_HASHXOF_digest_size(PyObject *Py_UNUSED(self),
+ void *Py_UNUSED(closure))
{
return PyLong_FromLong(0);
}
-static PyGetSetDef EVPXOF_getseters[] = {
- {"digest_size", EVPXOF_get_digest_size, NULL, NULL, NULL},
+static PyGetSetDef HASHXOFobject_getsets[] = {
+ {"digest_size", _hashlib_HASHXOF_digest_size, NULL, NULL, NULL},
{NULL} /* Sentinel */
};
-PyDoc_STRVAR(hashxoftype_doc,
+PyDoc_STRVAR(HASHXOFobject_type_doc,
"HASHXOF(name, string=b\'\')\n"
"--\n"
"\n"
@@ -925,38 +1060,42 @@ PyDoc_STRVAR(hashxoftype_doc,
"name -- the hash algorithm being used by this object\n"
"digest_size -- number of bytes in this hashes output");
-static PyType_Slot EVPXOFtype_slots[] = {
- {Py_tp_doc, (char *)hashxoftype_doc},
- {Py_tp_methods, EVPXOF_methods},
- {Py_tp_getset, EVPXOF_getseters},
+static PyType_Slot HASHXOFobject_type_slots[] = {
+ {Py_tp_doc, (char *)HASHXOFobject_type_doc},
+ {Py_tp_methods, HASHXOFobject_methods},
+ {Py_tp_getset, HASHXOFobject_getsets},
{0, 0},
};
-static PyType_Spec EVPXOFtype_spec = {
- "_hashlib.HASHXOF", /*tp_name*/
- sizeof(EVPobject), /*tp_basicsize*/
- 0, /*tp_itemsize*/
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE,
- EVPXOFtype_slots
+static PyType_Spec HASHXOFobject_type_spec = {
+ .name = "_hashlib.HASHXOF",
+ .basicsize = sizeof(HASHobject),
+ .flags = (
+ Py_TPFLAGS_DEFAULT
+ | Py_TPFLAGS_BASETYPE
+ | Py_TPFLAGS_DISALLOW_INSTANTIATION
+ | Py_TPFLAGS_IMMUTABLETYPE
+ ),
+ .slots = HASHXOFobject_type_slots
};
#endif
-static PyObject*
-py_evp_fromname(PyObject *module, const char *digestname, PyObject *data_obj,
- int usedforsecurity)
+static PyObject *
+_hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj,
+ int usedforsecurity)
{
Py_buffer view = { 0 };
PY_EVP_MD *digest = NULL;
PyTypeObject *type;
- EVPobject *self = NULL;
+ HASHobject *self = NULL;
if (data_obj != NULL) {
GET_BUFFER_VIEW_OR_ERROUT(data_obj, &view);
}
- digest = py_digest_by_name(
+ digest = get_openssl_evp_md_by_utf8name(
module, digestname, usedforsecurity ? Py_ht_evp : Py_ht_evp_nosecurity
);
if (digest == NULL) {
@@ -964,12 +1103,12 @@ py_evp_fromname(PyObject *module, const char *digestname, PyObject *data_obj,
}
if ((EVP_MD_flags(digest) & EVP_MD_FLAG_XOF) == EVP_MD_FLAG_XOF) {
- type = get_hashlib_state(module)->EVPXOFtype;
+ type = get_hashlib_state(module)->HASHXOF_type;
} else {
- type = get_hashlib_state(module)->EVPtype;
+ type = get_hashlib_state(module)->HASH_type;
}
- self = newEVPobject(type);
+ self = new_hash_object(type);
if (self == NULL) {
goto exit;
}
@@ -984,21 +1123,18 @@ py_evp_fromname(PyObject *module, const char *digestname, PyObject *data_obj,
int result = EVP_DigestInit_ex(self->ctx, digest, NULL);
if (!result) {
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestInit_ex));
Py_CLEAR(self);
goto exit;
}
if (view.buf && view.len) {
- if (view.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- result = EVP_hash(self, view.buf, view.len);
- Py_END_ALLOW_THREADS
- } else {
- result = EVP_hash(self, view.buf, view.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ view.len,
+ result = _hashlib_HASH_hash(self, view.buf, view.len)
+ );
if (result == -1) {
assert(PyErr_Occurred());
Py_CLEAR(self);
@@ -1017,16 +1153,25 @@ exit:
return (PyObject *)self;
}
+#define CALL_HASHLIB_NEW(MODULE, NAME, DATA, STRING, USEDFORSECURITY) \
+ do { \
+ PyObject *data_obj; \
+ if (_Py_hashlib_data_argument(&data_obj, DATA, STRING) < 0) { \
+ return NULL; \
+ } \
+ return _hashlib_HASH(MODULE, NAME, data_obj, USEDFORSECURITY); \
+ } while (0)
/* The module-level function: new() */
/*[clinic input]
-_hashlib.new as EVP_new
+_hashlib.new as _hashlib_HASH_new
- name as name_obj: object
- string as data_obj: object(c_default="NULL") = b''
+ name: str
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Return a new hash object using the named algorithm.
@@ -1037,136 +1182,137 @@ The MD5 and SHA1 algorithms are always supported.
[clinic start generated code]*/
static PyObject *
-EVP_new_impl(PyObject *module, PyObject *name_obj, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=ddd5053f92dffe90 input=c24554d0337be1b0]*/
+_hashlib_HASH_new_impl(PyObject *module, const char *name, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=b905aaf9840c1bbd input=c34af6c6e696d44e]*/
{
- char *name;
- if (!PyArg_Parse(name_obj, "s", &name)) {
- PyErr_SetString(PyExc_TypeError, "name must be a string");
- return NULL;
- }
- return py_evp_fromname(module, name, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, name, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_md5
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a md5 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_md5_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=87b0186440a44f8c input=990e36d5e689b16e]*/
+_hashlib_openssl_md5_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=ca8cf184d90f7432 input=e7c0adbd6a867db1]*/
{
- return py_evp_fromname(module, Py_hash_md5, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_md5, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha1
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha1 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha1_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=6813024cf690670d input=948f2f4b6deabc10]*/
+_hashlib_openssl_sha1_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=1736fb7b310d64be input=f7e5bb1711e952d8]*/
{
- return py_evp_fromname(module, Py_hash_sha1, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha1, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha224
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha224 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha224_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=a2dfe7cc4eb14ebb input=f9272821fadca505]*/
+_hashlib_openssl_sha224_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=0d6ff57be5e5c140 input=3820fff7ed3a53b8]*/
{
- return py_evp_fromname(module, Py_hash_sha224, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha224, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha256
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha256 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha256_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=1f874a34870f0a68 input=549fad9d2930d4c5]*/
+_hashlib_openssl_sha256_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=412ea7111555b6e7 input=9a2f115cf1f7e0eb]*/
{
- return py_evp_fromname(module, Py_hash_sha256, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha256, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha384
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha384 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha384_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=58529eff9ca457b2 input=48601a6e3bf14ad7]*/
+_hashlib_openssl_sha384_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=2e0dc395b59ed726 input=1ea48f6f01e77cfb]*/
{
- return py_evp_fromname(module, Py_hash_sha384, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha384, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha512
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha512 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=2c744c9e4a40d5f6 input=c5c46a2a817aa98f]*/
+_hashlib_openssl_sha512_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=4bdd760388dbfc0f input=3cf56903e07d1f5c]*/
{
- return py_evp_fromname(module, Py_hash_sha512, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha512, data, string, usedforsecurity);
}
@@ -1175,77 +1321,81 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
/*[clinic input]
_hashlib.openssl_sha3_224
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha3-224 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha3_224_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=144641c1d144b974 input=e3a01b2888916157]*/
+_hashlib_openssl_sha3_224_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=6d8dc2a924f3ba35 input=7f14f16a9f6a3158]*/
{
- return py_evp_fromname(module, Py_hash_sha3_224, data_obj, usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha3_224, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha3_256
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha3-256 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha3_256_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=c61f1ab772d06668 input=e2908126c1b6deed]*/
+_hashlib_openssl_sha3_256_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=9e520f537b3a4622 input=7987150939d5e352]*/
{
- return py_evp_fromname(module, Py_hash_sha3_256, data_obj , usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha3_256, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha3_384
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha3-384 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha3_384_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=f68e4846858cf0ee input=ec0edf5c792f8252]*/
+_hashlib_openssl_sha3_384_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=d239ba0463fd6138 input=fc943401f67e3b81]*/
{
- return py_evp_fromname(module, Py_hash_sha3_384, data_obj , usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha3_384, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_sha3_512
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a sha3-512 hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_sha3_512_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=2eede478c159354a input=64e2cc0c094d56f4]*/
+_hashlib_openssl_sha3_512_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=17662f21038c2278 input=6601ddd2c6c1516d]*/
{
- return py_evp_fromname(module, Py_hash_sha3_512, data_obj , usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_sha3_512, data, string, usedforsecurity);
}
#endif /* PY_OPENSSL_HAS_SHA3 */
@@ -1253,42 +1403,46 @@ _hashlib_openssl_sha3_512_impl(PyObject *module, PyObject *data_obj,
/*[clinic input]
_hashlib.openssl_shake_128
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a shake-128 variable hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_shake_128_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=bc49cdd8ada1fa97 input=6c9d67440eb33ec8]*/
+_hashlib_openssl_shake_128_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=4e6afed8d18980ad input=373c3f1c93d87b37]*/
{
- return py_evp_fromname(module, Py_hash_shake_128, data_obj , usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_shake_128, data, string, usedforsecurity);
}
/*[clinic input]
_hashlib.openssl_shake_256
- string as data_obj: object(py_default="b''") = NULL
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Returns a shake-256 variable hash object; optionally initialized with a string
[clinic start generated code]*/
static PyObject *
-_hashlib_openssl_shake_256_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity)
-/*[clinic end generated code: output=358d213be8852df7 input=479cbe9fefd4a9f8]*/
+_hashlib_openssl_shake_256_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string)
+/*[clinic end generated code: output=62481bce4a77d16c input=101c139ea2ddfcbf]*/
{
- return py_evp_fromname(module, Py_hash_shake_256, data_obj , usedforsecurity);
+ CALL_HASHLIB_NEW(module, Py_hash_shake_256, data, string, usedforsecurity);
}
#endif /* PY_OPENSSL_HAS_SHAKE */
+#undef CALL_HASHLIB_NEW
+
/*[clinic input]
_hashlib.pbkdf2_hmac as pbkdf2_hmac
@@ -1312,7 +1466,7 @@ pbkdf2_hmac_impl(PyObject *module, const char *hash_name,
long dklen;
int retval;
- PY_EVP_MD *digest = py_digest_by_name(module, hash_name, Py_ht_pbkdf2);
+ PY_EVP_MD *digest = get_openssl_evp_md_by_utf8name(module, hash_name, Py_ht_pbkdf2);
if (digest == NULL) {
goto end;
}
@@ -1375,7 +1529,7 @@ pbkdf2_hmac_impl(PyObject *module, const char *hash_name,
if (!retval) {
Py_CLEAR(key_obj);
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(PKCS5_PBKDF2_HMAC));
goto end;
}
@@ -1451,8 +1605,8 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
/* let OpenSSL validate the rest */
retval = EVP_PBE_scrypt(NULL, 0, NULL, 0, n, r, p, maxmem, NULL, 0);
if (!retval) {
- raise_ssl_error(PyExc_ValueError,
- "Invalid parameter combination for n, r, p, maxmem.");
+ notify_ssl_error_occurred(
+ "Invalid parameter combination for n, r, p, maxmem.");
return NULL;
}
@@ -1473,7 +1627,7 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
if (!retval) {
Py_CLEAR(key_obj);
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_PBE_scrypt));
return NULL;
}
return key_obj;
@@ -1514,7 +1668,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
return NULL;
}
- evp = py_digest_by_digestmod(module, digest, Py_ht_mac);
+ evp = get_openssl_evp_md(module, digest, Py_ht_mac);
if (evp == NULL) {
return NULL;
}
@@ -1530,7 +1684,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
PY_EVP_MD_free(evp);
if (result == NULL) {
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC));
return NULL;
}
return PyBytes_FromStringAndSize((const char*)md, md_len);
@@ -1539,6 +1693,18 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
/* OpenSSL-based HMAC implementation
*/
+/* Thin wrapper around HMAC_CTX_new() which sets an exception on failure. */
+static HMAC_CTX *
+py_openssl_wrapper_HMAC_CTX_new(void)
+{
+ HMAC_CTX *ctx = HMAC_CTX_new();
+ if (ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ return ctx;
+}
+
static int _hmac_update(HMACobject*, PyObject*);
static const EVP_MD *
@@ -1546,7 +1712,7 @@ _hashlib_hmac_get_md(HMACobject *self)
{
const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
if (md == NULL) {
- raise_ssl_error(PyExc_ValueError, "missing EVP_MD for HMAC context");
+ notify_ssl_error_occurred("missing EVP_MD for HMAC context");
}
return md;
}
@@ -1583,27 +1749,26 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
return NULL;
}
- digest = py_digest_by_digestmod(module, digestmod, Py_ht_mac);
+ digest = get_openssl_evp_md(module, digestmod, Py_ht_mac);
if (digest == NULL) {
return NULL;
}
- ctx = HMAC_CTX_new();
+ ctx = py_openssl_wrapper_HMAC_CTX_new();
if (ctx == NULL) {
PY_EVP_MD_free(digest);
- PyErr_NoMemory();
goto error;
}
r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */);
PY_EVP_MD_free(digest);
if (r == 0) {
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex));
goto error;
}
_hashlibstate *state = get_hashlib_state(module);
- self = PyObject_New(HMACobject, state->HMACtype);
+ self = PyObject_New(HMACobject, state->HMAC_type);
if (self == NULL) {
goto error;
}
@@ -1630,10 +1795,14 @@ static int
locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
{
int result;
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
result = HMAC_CTX_copy(new_ctx_p, self->ctx);
- LEAVE_HASHLIB(self);
- return result;
+ HASHLIB_RELEASE_LOCK(self);
+ if (result == 0) {
+ notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy));
+ return -1;
+ }
+ return 0;
}
/* returning 0 means that an error occurred and an exception is set */
@@ -1647,7 +1816,7 @@ _hashlib_hmac_digest_size(HMACobject *self)
unsigned int digest_size = EVP_MD_size(md);
assert(digest_size <= EVP_MAX_MD_SIZE);
if (digest_size == 0) {
- raise_ssl_error(PyExc_ValueError, "invalid digest size");
+ notify_ssl_error_occurred("invalid digest size");
}
return digest_size;
}
@@ -1659,28 +1828,16 @@ _hmac_update(HMACobject *self, PyObject *obj)
Py_buffer view = {0};
GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0);
-
- if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- r = HMAC_Update(self->ctx,
- (const unsigned char *)view.buf,
- (size_t)view.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- r = HMAC_Update(self->ctx,
- (const unsigned char *)view.buf,
- (size_t)view.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, view.len,
+ r = HMAC_Update(
+ self->ctx, (const unsigned char *)view.buf, (size_t)view.len
+ )
+ );
PyBuffer_Release(&view);
if (r == 0) {
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Update));
return 0;
}
return 1;
@@ -1698,13 +1855,12 @@ _hashlib_HMAC_copy_impl(HMACobject *self)
{
HMACobject *retval;
- HMAC_CTX *ctx = HMAC_CTX_new();
+ HMAC_CTX *ctx = py_openssl_wrapper_HMAC_CTX_new();
if (ctx == NULL) {
- return PyErr_NoMemory();
+ return NULL;
}
- if (!locked_HMAC_CTX_copy(ctx, self)) {
+ if (locked_HMAC_CTX_copy(ctx, self) < 0) {
HMAC_CTX_free(ctx);
- notify_ssl_error_occurred();
return NULL;
}
@@ -1735,20 +1891,15 @@ _hmac_dealloc(PyObject *op)
static PyObject *
_hmac_repr(PyObject *op)
{
+ const char *digest_name;
HMACobject *self = HMACobject_CAST(op);
const EVP_MD *md = _hashlib_hmac_get_md(self);
- if (md == NULL) {
- return NULL;
- }
- PyObject *digest_name = py_digest_name(md);
+ digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md);
if (digest_name == NULL) {
+ assert(PyErr_Occurred());
return NULL;
}
- PyObject *repr = PyUnicode_FromFormat(
- "<%U HMAC object @ %p>", digest_name, self
- );
- Py_DECREF(digest_name);
- return repr;
+ return PyUnicode_FromFormat("<%s HMAC object @ %p>", digest_name, self);
}
/*[clinic input]
@@ -1771,20 +1922,18 @@ _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg)
static int
_hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len)
{
- HMAC_CTX *temp_ctx = HMAC_CTX_new();
+ HMAC_CTX *temp_ctx = py_openssl_wrapper_HMAC_CTX_new();
if (temp_ctx == NULL) {
- (void)PyErr_NoMemory();
return 0;
}
- if (!locked_HMAC_CTX_copy(temp_ctx, self)) {
+ if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) {
HMAC_CTX_free(temp_ctx);
- notify_ssl_error_occurred();
return 0;
}
int r = HMAC_Final(temp_ctx, buf, &len);
HMAC_CTX_free(temp_ctx);
if (r == 0) {
- notify_ssl_error_occurred();
+ notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Final));
return 0;
}
return 1;
@@ -1860,13 +2009,12 @@ _hashlib_hmac_get_name(PyObject *op, void *Py_UNUSED(closure))
if (md == NULL) {
return NULL;
}
- PyObject *digest_name = py_digest_name(md);
+ const char *digest_name = get_hashlib_utf8name_by_evp_md(md);
if (digest_name == NULL) {
+ assert(PyErr_Occurred());
return NULL;
}
- PyObject *name = PyUnicode_FromFormat("hmac-%U", digest_name);
- Py_DECREF(digest_name);
- return name;
+ return PyUnicode_FromFormat("hmac-%s", digest_name);
}
static PyMethodDef HMAC_methods[] = {
@@ -1926,7 +2074,7 @@ typedef struct _internal_name_mapper_state {
/* A callback function to pass to OpenSSL's OBJ_NAME_do_all(...) */
static void
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#ifdef Py_HAS_OPENSSL3_SUPPORT
_openssl_hash_name_mapper(EVP_MD *md, void *arg)
#else
_openssl_hash_name_mapper(const EVP_MD *md, const char *from,
@@ -1942,7 +2090,9 @@ _openssl_hash_name_mapper(const EVP_MD *md, const char *from,
return;
}
- py_name = py_digest_name(md);
+ const char *name = get_hashlib_utf8name_by_evp_md(md);
+ assert(name != NULL || PyErr_Occurred());
+ py_name = name == NULL ? NULL : PyUnicode_FromString(name);
if (py_name == NULL) {
state->error = 1;
} else {
@@ -1966,7 +2116,7 @@ hashlib_md_meth_names(PyObject *module)
return -1;
}
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#ifdef Py_HAS_OPENSSL3_SUPPORT
// get algorithms from all activated providers in default context
EVP_MD_do_all_provided(NULL, &_openssl_hash_name_mapper, &state);
#else
@@ -1999,21 +2149,18 @@ _hashlib_get_fips_mode_impl(PyObject *module)
/*[clinic end generated code: output=87eece1bab4d3fa9 input=2db61538c41c6fef]*/
{
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#ifdef Py_HAS_OPENSSL3_SUPPORT
return EVP_default_properties_is_fips_enabled(NULL);
#else
ERR_clear_error();
int result = FIPS_mode();
- if (result == 0) {
+ if (result == 0 && ERR_peek_last_error()) {
// "If the library was built without support of the FIPS Object Module,
// then the function will return 0 with an error code of
// CRYPTO_R_FIPS_MODE_NOT_SUPPORTED (0x0f06d065)."
// But 0 is also a valid result value.
- unsigned long errcode = ERR_peek_last_error();
- if (errcode) {
- notify_ssl_error_occurred();
- return -1;
- }
+ notify_ssl_error_occurred_in(Py_STRINGIFY(FIPS_mode));
+ return -1;
}
return result;
#endif
@@ -2134,7 +2281,7 @@ _hashlib_compare_digest_impl(PyObject *module, PyObject *a, PyObject *b)
/* List of functions exported by this module */
static struct PyMethodDef EVP_functions[] = {
- EVP_NEW_METHODDEF
+ _HASHLIB_HASH_NEW_METHODDEF
PBKDF2_HMAC_METHODDEF
_HASHLIB_SCRYPT_METHODDEF
_HASHLIB_GET_FIPS_MODE_METHODDEF
@@ -2163,10 +2310,10 @@ static int
hashlib_traverse(PyObject *m, visitproc visit, void *arg)
{
_hashlibstate *state = get_hashlib_state(m);
- Py_VISIT(state->EVPtype);
- Py_VISIT(state->HMACtype);
+ Py_VISIT(state->HASH_type);
+ Py_VISIT(state->HMAC_type);
#ifdef PY_OPENSSL_HAS_SHAKE
- Py_VISIT(state->EVPXOFtype);
+ Py_VISIT(state->HASHXOF_type);
#endif
Py_VISIT(state->constructs);
Py_VISIT(state->unsupported_digestmod_error);
@@ -2177,10 +2324,10 @@ static int
hashlib_clear(PyObject *m)
{
_hashlibstate *state = get_hashlib_state(m);
- Py_CLEAR(state->EVPtype);
- Py_CLEAR(state->HMACtype);
+ Py_CLEAR(state->HASH_type);
+ Py_CLEAR(state->HMAC_type);
#ifdef PY_OPENSSL_HAS_SHAKE
- Py_CLEAR(state->EVPXOFtype);
+ Py_CLEAR(state->HASHXOF_type);
#endif
Py_CLEAR(state->constructs);
Py_CLEAR(state->unsupported_digestmod_error);
@@ -2214,37 +2361,37 @@ hashlib_init_hashtable(PyObject *module)
}
static int
-hashlib_init_evptype(PyObject *module)
+hashlib_init_HASH_type(PyObject *module)
{
_hashlibstate *state = get_hashlib_state(module);
- state->EVPtype = (PyTypeObject *)PyType_FromSpec(&EVPtype_spec);
- if (state->EVPtype == NULL) {
+ state->HASH_type = (PyTypeObject *)PyType_FromSpec(&HASHobject_type_spec);
+ if (state->HASH_type == NULL) {
return -1;
}
- if (PyModule_AddType(module, state->EVPtype) < 0) {
+ if (PyModule_AddType(module, state->HASH_type) < 0) {
return -1;
}
return 0;
}
static int
-hashlib_init_evpxoftype(PyObject *module)
+hashlib_init_HASHXOF_type(PyObject *module)
{
#ifdef PY_OPENSSL_HAS_SHAKE
_hashlibstate *state = get_hashlib_state(module);
- if (state->EVPtype == NULL) {
+ if (state->HASH_type == NULL) {
return -1;
}
- state->EVPXOFtype = (PyTypeObject *)PyType_FromSpecWithBases(
- &EVPXOFtype_spec, (PyObject *)state->EVPtype
+ state->HASHXOF_type = (PyTypeObject *)PyType_FromSpecWithBases(
+ &HASHXOFobject_type_spec, (PyObject *)state->HASH_type
);
- if (state->EVPXOFtype == NULL) {
+ if (state->HASHXOF_type == NULL) {
return -1;
}
- if (PyModule_AddType(module, state->EVPXOFtype) < 0) {
+ if (PyModule_AddType(module, state->HASHXOF_type) < 0) {
return -1;
}
#endif
@@ -2256,11 +2403,11 @@ hashlib_init_hmactype(PyObject *module)
{
_hashlibstate *state = get_hashlib_state(module);
- state->HMACtype = (PyTypeObject *)PyType_FromSpec(&HMACtype_spec);
- if (state->HMACtype == NULL) {
+ state->HMAC_type = (PyTypeObject *)PyType_FromSpec(&HMACtype_spec);
+ if (state->HMAC_type == NULL) {
return -1;
}
- if (PyModule_AddType(module, state->HMACtype) < 0) {
+ if (PyModule_AddType(module, state->HMAC_type) < 0) {
return -1;
}
return 0;
@@ -2341,8 +2488,8 @@ hashlib_constants(PyObject *module)
static PyModuleDef_Slot hashlib_slots[] = {
{Py_mod_exec, hashlib_init_hashtable},
- {Py_mod_exec, hashlib_init_evptype},
- {Py_mod_exec, hashlib_init_evpxoftype},
+ {Py_mod_exec, hashlib_init_HASH_type},
+ {Py_mod_exec, hashlib_init_HASHXOF_type},
{Py_mod_exec, hashlib_init_hmactype},
{Py_mod_exec, hashlib_md_meth_names},
{Py_mod_exec, hashlib_init_constructors},
diff --git a/Modules/_heapqmodule.c b/Modules/_heapqmodule.c
index 80fe9cff985..05d01acd771 100644
--- a/Modules/_heapqmodule.c
+++ b/Modules/_heapqmodule.c
@@ -11,7 +11,8 @@ annotated by François Pinard, and converted to C by Raymond Hettinger.
#endif
#include "Python.h"
-#include "pycore_list.h" // _PyList_ITEMS()
+#include "pycore_list.h" // _PyList_ITEMS(), _PyList_AppendTakeRef()
+#include "pycore_pyatomic_ft_wrappers.h"
#include "clinic/_heapqmodule.c.h"
@@ -59,8 +60,8 @@ siftdown(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
arr = _PyList_ITEMS(heap);
parent = arr[parentpos];
newitem = arr[pos];
- arr[parentpos] = newitem;
- arr[pos] = parent;
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[parentpos], newitem);
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[pos], parent);
pos = parentpos;
}
return 0;
@@ -108,8 +109,8 @@ siftup(PyListObject *heap, Py_ssize_t pos)
/* Move the smaller child up. */
tmp1 = arr[childpos];
tmp2 = arr[pos];
- arr[childpos] = tmp2;
- arr[pos] = tmp1;
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[childpos], tmp2);
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[pos], tmp1);
pos = childpos;
}
/* Bubble it up to its final resting place (by sifting its parents down). */
@@ -117,6 +118,7 @@ siftup(PyListObject *heap, Py_ssize_t pos)
}
/*[clinic input]
+@critical_section heap
_heapq.heappush
heap: object(subclass_of='&PyList_Type')
@@ -128,13 +130,22 @@ Push item onto heap, maintaining the heap invariant.
static PyObject *
_heapq_heappush_impl(PyObject *module, PyObject *heap, PyObject *item)
-/*[clinic end generated code: output=912c094f47663935 input=7c69611f3698aceb]*/
+/*[clinic end generated code: output=912c094f47663935 input=f7a4f03ef8d52e67]*/
{
- if (PyList_Append(heap, item))
+ if (item == NULL) {
+ PyErr_BadInternalCall();
return NULL;
+ }
- if (siftdown((PyListObject *)heap, 0, PyList_GET_SIZE(heap)-1))
+ // In a free-threaded build, the heap is locked at this point.
+ // Therefore, calling _PyList_AppendTakeRef() is safe and no overhead.
+ if (_PyList_AppendTakeRef((PyListObject *)heap, Py_NewRef(item))) {
return NULL;
+ }
+
+ if (siftdown((PyListObject *)heap, 0, PyList_GET_SIZE(heap)-1)) {
+ return NULL;
+ }
Py_RETURN_NONE;
}
@@ -162,8 +173,9 @@ heappop_internal(PyObject *heap, int siftup_func(PyListObject *, Py_ssize_t))
if (!n)
return lastelt;
returnitem = PyList_GET_ITEM(heap, 0);
- PyList_SET_ITEM(heap, 0, lastelt);
- if (siftup_func((PyListObject *)heap, 0)) {
+ PyListObject *list = _PyList_CAST(heap);
+ FT_ATOMIC_STORE_PTR_RELAXED(list->ob_item[0], lastelt);
+ if (siftup_func(list, 0)) {
Py_DECREF(returnitem);
return NULL;
}
@@ -171,6 +183,7 @@ heappop_internal(PyObject *heap, int siftup_func(PyListObject *, Py_ssize_t))
}
/*[clinic input]
+@critical_section heap
_heapq.heappop
heap: object(subclass_of='&PyList_Type')
@@ -181,7 +194,7 @@ Pop the smallest item off the heap, maintaining the heap invariant.
static PyObject *
_heapq_heappop_impl(PyObject *module, PyObject *heap)
-/*[clinic end generated code: output=96dfe82d37d9af76 input=91487987a583c856]*/
+/*[clinic end generated code: output=96dfe82d37d9af76 input=ed396461b153dd51]*/
{
return heappop_internal(heap, siftup);
}
@@ -197,8 +210,9 @@ heapreplace_internal(PyObject *heap, PyObject *item, int siftup_func(PyListObjec
}
returnitem = PyList_GET_ITEM(heap, 0);
- PyList_SET_ITEM(heap, 0, Py_NewRef(item));
- if (siftup_func((PyListObject *)heap, 0)) {
+ PyListObject *list = _PyList_CAST(heap);
+ FT_ATOMIC_STORE_PTR_RELAXED(list->ob_item[0], Py_NewRef(item));
+ if (siftup_func(list, 0)) {
Py_DECREF(returnitem);
return NULL;
}
@@ -207,6 +221,7 @@ heapreplace_internal(PyObject *heap, PyObject *item, int siftup_func(PyListObjec
/*[clinic input]
+@critical_section heap
_heapq.heapreplace
heap: object(subclass_of='&PyList_Type')
@@ -226,12 +241,13 @@ this routine unless written as part of a conditional replacement:
static PyObject *
_heapq_heapreplace_impl(PyObject *module, PyObject *heap, PyObject *item)
-/*[clinic end generated code: output=82ea55be8fbe24b4 input=719202ac02ba10c8]*/
+/*[clinic end generated code: output=82ea55be8fbe24b4 input=9be1678b817ef1a9]*/
{
return heapreplace_internal(heap, item, siftup);
}
/*[clinic input]
+@critical_section heap
_heapq.heappushpop
heap: object(subclass_of='&PyList_Type')
@@ -246,7 +262,7 @@ a separate call to heappop().
static PyObject *
_heapq_heappushpop_impl(PyObject *module, PyObject *heap, PyObject *item)
-/*[clinic end generated code: output=67231dc98ed5774f input=5dc701f1eb4a4aa7]*/
+/*[clinic end generated code: output=67231dc98ed5774f input=db05c81b1dd92c44]*/
{
PyObject *returnitem;
int cmp;
@@ -271,8 +287,9 @@ _heapq_heappushpop_impl(PyObject *module, PyObject *heap, PyObject *item)
}
returnitem = PyList_GET_ITEM(heap, 0);
- PyList_SET_ITEM(heap, 0, Py_NewRef(item));
- if (siftup((PyListObject *)heap, 0)) {
+ PyListObject *list = _PyList_CAST(heap);
+ FT_ATOMIC_STORE_PTR_RELAXED(list->ob_item[0], Py_NewRef(item));
+ if (siftup(list, 0)) {
Py_DECREF(returnitem);
return NULL;
}
@@ -371,6 +388,7 @@ heapify_internal(PyObject *heap, int siftup_func(PyListObject *, Py_ssize_t))
}
/*[clinic input]
+@critical_section heap
_heapq.heapify
heap: object(subclass_of='&PyList_Type')
@@ -381,7 +399,7 @@ Transform list into a heap, in-place, in O(len(heap)) time.
static PyObject *
_heapq_heapify_impl(PyObject *module, PyObject *heap)
-/*[clinic end generated code: output=e63a636fcf83d6d0 input=53bb7a2166febb73]*/
+/*[clinic end generated code: output=e63a636fcf83d6d0 input=aaaaa028b9b6af08]*/
{
return heapify_internal(heap, siftup);
}
@@ -423,8 +441,8 @@ siftdown_max(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
arr = _PyList_ITEMS(heap);
parent = arr[parentpos];
newitem = arr[pos];
- arr[parentpos] = newitem;
- arr[pos] = parent;
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[parentpos], newitem);
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[pos], parent);
pos = parentpos;
}
return 0;
@@ -445,11 +463,11 @@ siftup_max(PyListObject *heap, Py_ssize_t pos)
return -1;
}
- /* Bubble up the smaller child until hitting a leaf. */
+ /* Bubble up the larger child until hitting a leaf. */
arr = _PyList_ITEMS(heap);
limit = endpos >> 1; /* smallest pos that has no child */
while (pos < limit) {
- /* Set childpos to index of smaller child. */
+ /* Set childpos to index of larger child. */
childpos = 2*pos + 1; /* leftmost child position */
if (childpos + 1 < endpos) {
PyObject* a = arr[childpos + 1];
@@ -469,20 +487,53 @@ siftup_max(PyListObject *heap, Py_ssize_t pos)
return -1;
}
}
- /* Move the smaller child up. */
+ /* Move the larger child up. */
tmp1 = arr[childpos];
tmp2 = arr[pos];
- arr[childpos] = tmp2;
- arr[pos] = tmp1;
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[childpos], tmp2);
+ FT_ATOMIC_STORE_PTR_RELAXED(arr[pos], tmp1);
pos = childpos;
}
/* Bubble it up to its final resting place (by sifting its parents down). */
return siftdown_max(heap, startpos, pos);
}
+/*[clinic input]
+@critical_section heap
+_heapq.heappush_max
+
+ heap: object(subclass_of='&PyList_Type')
+ item: object
+ /
+
+Push item onto max heap, maintaining the heap invariant.
+[clinic start generated code]*/
+
+static PyObject *
+_heapq_heappush_max_impl(PyObject *module, PyObject *heap, PyObject *item)
+/*[clinic end generated code: output=c869d5f9deb08277 input=c437e3d1ff8dcb70]*/
+{
+ if (item == NULL) {
+ PyErr_BadInternalCall();
+ return NULL;
+ }
+
+ // In a free-threaded build, the heap is locked at this point.
+ // Therefore, calling _PyList_AppendTakeRef() is safe and no overhead.
+ if (_PyList_AppendTakeRef((PyListObject *)heap, Py_NewRef(item))) {
+ return NULL;
+ }
+
+ if (siftdown_max((PyListObject *)heap, 0, PyList_GET_SIZE(heap)-1)) {
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
/*[clinic input]
-_heapq._heappop_max
+@critical_section heap
+_heapq.heappop_max
heap: object(subclass_of='&PyList_Type')
/
@@ -491,14 +542,15 @@ Maxheap variant of heappop.
[clinic start generated code]*/
static PyObject *
-_heapq__heappop_max_impl(PyObject *module, PyObject *heap)
-/*[clinic end generated code: output=9e77aadd4e6a8760 input=362c06e1c7484793]*/
+_heapq_heappop_max_impl(PyObject *module, PyObject *heap)
+/*[clinic end generated code: output=2f051195ab404b77 input=5d70c997798aec64]*/
{
return heappop_internal(heap, siftup_max);
}
/*[clinic input]
-_heapq._heapreplace_max
+@critical_section heap
+_heapq.heapreplace_max
heap: object(subclass_of='&PyList_Type')
item: object
@@ -508,15 +560,15 @@ Maxheap variant of heapreplace.
[clinic start generated code]*/
static PyObject *
-_heapq__heapreplace_max_impl(PyObject *module, PyObject *heap,
- PyObject *item)
-/*[clinic end generated code: output=8ad7545e4a5e8adb input=f2dd27cbadb948d7]*/
+_heapq_heapreplace_max_impl(PyObject *module, PyObject *heap, PyObject *item)
+/*[clinic end generated code: output=8770778b5a9cbe9b input=fe70175356e4a649]*/
{
return heapreplace_internal(heap, item, siftup_max);
}
/*[clinic input]
-_heapq._heapify_max
+@critical_section heap
+_heapq.heapify_max
heap: object(subclass_of='&PyList_Type')
/
@@ -525,21 +577,76 @@ Maxheap variant of heapify.
[clinic start generated code]*/
static PyObject *
-_heapq__heapify_max_impl(PyObject *module, PyObject *heap)
-/*[clinic end generated code: output=2cb028beb4a8b65e input=c1f765ee69f124b8]*/
+_heapq_heapify_max_impl(PyObject *module, PyObject *heap)
+/*[clinic end generated code: output=8401af3856529807 input=4eee63231e7d1573]*/
{
return heapify_internal(heap, siftup_max);
}
+/*[clinic input]
+@critical_section heap
+_heapq.heappushpop_max
+
+ heap: object(subclass_of='&PyList_Type')
+ item: object
+ /
+
+Maxheap variant of heappushpop.
+
+The combined action runs more efficiently than heappush_max() followed by
+a separate call to heappop_max().
+[clinic start generated code]*/
+
+static PyObject *
+_heapq_heappushpop_max_impl(PyObject *module, PyObject *heap, PyObject *item)
+/*[clinic end generated code: output=ff0019f0941aca0d input=24d0defa6fd6df4a]*/
+{
+ PyObject *returnitem;
+ int cmp;
+
+ if (PyList_GET_SIZE(heap) == 0) {
+ return Py_NewRef(item);
+ }
+
+ PyObject *top = PyList_GET_ITEM(heap, 0);
+ Py_INCREF(top);
+ cmp = PyObject_RichCompareBool(item, top, Py_LT);
+ Py_DECREF(top);
+ if (cmp < 0) {
+ return NULL;
+ }
+ if (cmp == 0) {
+ return Py_NewRef(item);
+ }
+
+ if (PyList_GET_SIZE(heap) == 0) {
+ PyErr_SetString(PyExc_IndexError, "index out of range");
+ return NULL;
+ }
+
+ returnitem = PyList_GET_ITEM(heap, 0);
+ PyListObject *list = _PyList_CAST(heap);
+ FT_ATOMIC_STORE_PTR_RELAXED(list->ob_item[0], Py_NewRef(item));
+ if (siftup_max(list, 0) < 0) {
+ Py_DECREF(returnitem);
+ return NULL;
+ }
+ return returnitem;
+}
+
static PyMethodDef heapq_methods[] = {
_HEAPQ_HEAPPUSH_METHODDEF
_HEAPQ_HEAPPUSHPOP_METHODDEF
_HEAPQ_HEAPPOP_METHODDEF
_HEAPQ_HEAPREPLACE_METHODDEF
_HEAPQ_HEAPIFY_METHODDEF
- _HEAPQ__HEAPPOP_MAX_METHODDEF
- _HEAPQ__HEAPIFY_MAX_METHODDEF
- _HEAPQ__HEAPREPLACE_MAX_METHODDEF
+
+ _HEAPQ_HEAPPUSH_MAX_METHODDEF
+ _HEAPQ_HEAPPUSHPOP_MAX_METHODDEF
+ _HEAPQ_HEAPPOP_MAX_METHODDEF
+ _HEAPQ_HEAPREPLACE_MAX_METHODDEF
+ _HEAPQ_HEAPIFY_MAX_METHODDEF
+
{NULL, NULL} /* sentinel */
};
diff --git a/Modules/_interpchannelsmodule.c b/Modules/_interpchannelsmodule.c
index 172cebcaa48..9c1f8615161 100644
--- a/Modules/_interpchannelsmodule.c
+++ b/Modules/_interpchannelsmodule.c
@@ -20,9 +20,11 @@
#endif
#define REGISTERS_HEAP_TYPES
+#define HAS_FALLBACK
#define HAS_UNBOUND_ITEMS
#include "_interpreters_common.h"
#undef HAS_UNBOUND_ITEMS
+#undef HAS_FALLBACK
#undef REGISTERS_HEAP_TYPES
@@ -218,6 +220,22 @@ wait_for_lock(PyThread_type_lock mutex, PY_TIMEOUT_T timeout)
return 0;
}
+static int
+ensure_highlevel_module_loaded(void)
+{
+ PyObject *highlevel =
+ PyImport_ImportModule("concurrent.interpreters._channels");
+ if (highlevel == NULL) {
+ PyErr_Clear();
+ highlevel = PyImport_ImportModule("test.support.channels");
+ if (highlevel == NULL) {
+ return -1;
+ }
+ }
+ Py_DECREF(highlevel);
+ return 0;
+}
+
/* module state *************************************************************/
@@ -252,10 +270,10 @@ _get_current_module_state(void)
{
PyObject *mod = _get_current_module();
if (mod == NULL) {
- // XXX import it?
- PyErr_SetString(PyExc_RuntimeError,
- MODULE_NAME_STR " module not imported yet");
- return NULL;
+ mod = PyImport_ImportModule(MODULE_NAME_STR);
+ if (mod == NULL) {
+ return NULL;
+ }
}
module_state *state = get_module_state(mod);
Py_DECREF(mod);
@@ -523,7 +541,7 @@ typedef struct _channelitem {
int64_t interpid;
_PyXIData_t *data;
_waiting_t *waiting;
- int unboundop;
+ unboundop_t unboundop;
struct _channelitem *next;
} _channelitem;
@@ -536,7 +554,7 @@ _channelitem_ID(_channelitem *item)
static void
_channelitem_init(_channelitem *item,
int64_t interpid, _PyXIData_t *data,
- _waiting_t *waiting, int unboundop)
+ _waiting_t *waiting, unboundop_t unboundop)
{
if (interpid < 0) {
interpid = _get_interpid(data);
@@ -583,7 +601,7 @@ _channelitem_clear(_channelitem *item)
static _channelitem *
_channelitem_new(int64_t interpid, _PyXIData_t *data,
- _waiting_t *waiting, int unboundop)
+ _waiting_t *waiting, unboundop_t unboundop)
{
_channelitem *item = GLOBAL_MALLOC(_channelitem);
if (item == NULL) {
@@ -694,7 +712,7 @@ _channelqueue_free(_channelqueue *queue)
static int
_channelqueue_put(_channelqueue *queue,
int64_t interpid, _PyXIData_t *data,
- _waiting_t *waiting, int unboundop)
+ _waiting_t *waiting, unboundop_t unboundop)
{
_channelitem *item = _channelitem_new(interpid, data, waiting, unboundop);
if (item == NULL) {
@@ -798,7 +816,7 @@ _channelqueue_remove(_channelqueue *queue, _channelitem_id_t itemid,
}
queue->count -= 1;
- int unboundop;
+ unboundop_t unboundop;
_channelitem_popped(item, p_data, p_waiting, &unboundop);
}
@@ -1083,16 +1101,18 @@ typedef struct _channel {
PyThread_type_lock mutex;
_channelqueue *queue;
_channelends *ends;
- struct {
- int unboundop;
+ struct _channeldefaults {
+ unboundop_t unboundop;
+ xidata_fallback_t fallback;
} defaults;
int open;
struct _channel_closing *closing;
} _channel_state;
static _channel_state *
-_channel_new(PyThread_type_lock mutex, int unboundop)
+_channel_new(PyThread_type_lock mutex, struct _channeldefaults defaults)
{
+ assert(check_unbound(defaults.unboundop));
_channel_state *chan = GLOBAL_MALLOC(_channel_state);
if (chan == NULL) {
return NULL;
@@ -1109,7 +1129,7 @@ _channel_new(PyThread_type_lock mutex, int unboundop)
GLOBAL_FREE(chan);
return NULL;
}
- chan->defaults.unboundop = unboundop;
+ chan->defaults = defaults;
chan->open = 1;
chan->closing = NULL;
return chan;
@@ -1130,7 +1150,7 @@ _channel_free(_channel_state *chan)
static int
_channel_add(_channel_state *chan, int64_t interpid,
- _PyXIData_t *data, _waiting_t *waiting, int unboundop)
+ _PyXIData_t *data, _waiting_t *waiting, unboundop_t unboundop)
{
int res = -1;
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
@@ -1611,7 +1631,7 @@ done:
struct channel_id_and_info {
int64_t id;
- int unboundop;
+ struct _channeldefaults defaults;
};
static struct channel_id_and_info *
@@ -1628,7 +1648,7 @@ _channels_list_all(_channels *channels, int64_t *count)
for (int64_t i=0; ref != NULL; ref = ref->next, i++) {
ids[i] = (struct channel_id_and_info){
.id = ref->cid,
- .unboundop = ref->chan->defaults.unboundop,
+ .defaults = ref->chan->defaults,
};
}
*count = channels->numopen;
@@ -1714,13 +1734,13 @@ _channel_finish_closing(_channel_state *chan) {
// Create a new channel.
static int64_t
-channel_create(_channels *channels, int unboundop)
+channel_create(_channels *channels, struct _channeldefaults defaults)
{
PyThread_type_lock mutex = PyThread_allocate_lock();
if (mutex == NULL) {
return ERR_CHANNEL_MUTEX_INIT;
}
- _channel_state *chan = _channel_new(mutex, unboundop);
+ _channel_state *chan = _channel_new(mutex, defaults);
if (chan == NULL) {
PyThread_free_lock(mutex);
return -1;
@@ -1752,7 +1772,7 @@ channel_destroy(_channels *channels, int64_t cid)
// Optionally request to be notified when it is received.
static int
channel_send(_channels *channels, int64_t cid, PyObject *obj,
- _waiting_t *waiting, int unboundop)
+ _waiting_t *waiting, unboundop_t unboundop, xidata_fallback_t fallback)
{
PyThreadState *tstate = _PyThreadState_GET();
PyInterpreterState *interp = tstate->interp;
@@ -1779,7 +1799,7 @@ channel_send(_channels *channels, int64_t cid, PyObject *obj,
PyThread_release_lock(mutex);
return -1;
}
- if (_PyObject_GetXIData(tstate, obj, data) != 0) {
+ if (_PyObject_GetXIData(tstate, obj, fallback, data) != 0) {
PyThread_release_lock(mutex);
GLOBAL_FREE(data);
return -1;
@@ -1823,7 +1843,8 @@ channel_clear_sent(_channels *channels, int64_t cid, _waiting_t *waiting)
// Like channel_send(), but strictly wait for the object to be received.
static int
channel_send_wait(_channels *channels, int64_t cid, PyObject *obj,
- int unboundop, PY_TIMEOUT_T timeout)
+ unboundop_t unboundop, PY_TIMEOUT_T timeout,
+ xidata_fallback_t fallback)
{
// We use a stack variable here, so we must ensure that &waiting
// is not held by any channel item at the point this function exits.
@@ -1834,7 +1855,7 @@ channel_send_wait(_channels *channels, int64_t cid, PyObject *obj,
}
/* Queue up the object. */
- int res = channel_send(channels, cid, obj, &waiting, unboundop);
+ int res = channel_send(channels, cid, obj, &waiting, unboundop, fallback);
if (res < 0) {
assert(waiting.status == WAITING_NO_STATUS);
goto finally;
@@ -2006,6 +2027,20 @@ channel_is_associated(_channels *channels, int64_t cid, int64_t interpid,
}
static int
+channel_get_defaults(_channels *channels, int64_t cid, struct _channeldefaults *defaults)
+{
+ PyThread_type_lock mutex = NULL;
+ _channel_state *channel = NULL;
+ int err = _channels_lookup(channels, cid, &mutex, &channel);
+ if (err != 0) {
+ return err;
+ }
+ *defaults = channel->defaults;
+ PyThread_release_lock(mutex);
+ return 0;
+}
+
+static int
_channel_get_count(_channels *channels, int64_t cid, Py_ssize_t *p_count)
{
PyThread_type_lock mutex = NULL;
@@ -2694,7 +2729,7 @@ add_channelid_type(PyObject *mod)
Py_DECREF(cls);
return NULL;
}
- if (ensure_xid_class(cls, _channelid_shared) < 0) {
+ if (ensure_xid_class(cls, GETDATA(_channelid_shared)) < 0) {
Py_DECREF(cls);
return NULL;
}
@@ -2723,15 +2758,9 @@ _get_current_channelend_type(int end)
}
if (cls == NULL) {
// Force the module to be loaded, to register the type.
- PyObject *highlevel = PyImport_ImportModule("interpreters.channels");
- if (highlevel == NULL) {
- PyErr_Clear();
- highlevel = PyImport_ImportModule("test.support.interpreters.channels");
- if (highlevel == NULL) {
- return NULL;
- }
+ if (ensure_highlevel_module_loaded() < 0) {
+ return NULL;
}
- Py_DECREF(highlevel);
if (end == CHANNEL_SEND) {
cls = state->send_channel_type;
}
@@ -2797,12 +2826,12 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv)
// Add and register the types.
state->send_channel_type = (PyTypeObject *)Py_NewRef(send);
state->recv_channel_type = (PyTypeObject *)Py_NewRef(recv);
- if (ensure_xid_class(send, _channelend_shared) < 0) {
+ if (ensure_xid_class(send, GETDATA(_channelend_shared)) < 0) {
Py_CLEAR(state->send_channel_type);
Py_CLEAR(state->recv_channel_type);
return -1;
}
- if (ensure_xid_class(recv, _channelend_shared) < 0) {
+ if (ensure_xid_class(recv, GETDATA(_channelend_shared)) < 0) {
(void)clear_xid_class(state->send_channel_type);
Py_CLEAR(state->send_channel_type);
Py_CLEAR(state->recv_channel_type);
@@ -2881,20 +2910,27 @@ clear_interpreter(void *data)
static PyObject *
channelsmod_create(PyObject *self, PyObject *args, PyObject *kwds)
{
- static char *kwlist[] = {"unboundop", NULL};
- int unboundop;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:create", kwlist,
- &unboundop))
+ static char *kwlist[] = {"unboundop", "fallback", NULL};
+ int unboundarg = -1;
+ int fallbackarg = -1;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ii:create", kwlist,
+ &unboundarg, &fallbackarg))
{
return NULL;
}
- if (!check_unbound(unboundop)) {
- PyErr_Format(PyExc_ValueError,
- "unsupported unboundop %d", unboundop);
+ struct _channeldefaults defaults = {0};
+ if (resolve_unboundop(unboundarg, UNBOUND_REPLACE,
+ &defaults.unboundop) < 0)
+ {
+ return NULL;
+ }
+ if (resolve_fallback(fallbackarg, _PyXIDATA_FULL_FALLBACK,
+ &defaults.fallback) < 0)
+ {
return NULL;
}
- int64_t cid = channel_create(&_globals.channels, unboundop);
+ int64_t cid = channel_create(&_globals.channels, defaults);
if (cid < 0) {
(void)handle_channel_error(-1, self, cid);
return NULL;
@@ -2987,7 +3023,9 @@ channelsmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
}
assert(cidobj != NULL);
- PyObject *item = Py_BuildValue("Oi", cidobj, cur->unboundop);
+ PyObject *item = Py_BuildValue("Oii", cidobj,
+ cur->defaults.unboundop,
+ cur->defaults.fallback);
Py_DECREF(cidobj);
if (item == NULL) {
Py_SETREF(ids, NULL);
@@ -3075,40 +3113,54 @@ receive end.");
static PyObject *
channelsmod_send(PyObject *self, PyObject *args, PyObject *kwds)
{
- static char *kwlist[] = {"cid", "obj", "unboundop", "blocking", "timeout",
- NULL};
+ static char *kwlist[] = {"cid", "obj", "unboundop", "fallback",
+ "blocking", "timeout", NULL};
struct channel_id_converter_data cid_data = {
.module = self,
};
PyObject *obj;
- int unboundop = UNBOUND_REPLACE;
+ int unboundarg = -1;
+ int fallbackarg = -1;
int blocking = 1;
PyObject *timeout_obj = NULL;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O|i$pO:channel_send", kwlist,
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O&O|ii$pO:channel_send", kwlist,
channel_id_converter, &cid_data, &obj,
- &unboundop, &blocking, &timeout_obj))
+ &unboundarg, &fallbackarg,
+ &blocking, &timeout_obj))
{
return NULL;
}
- if (!check_unbound(unboundop)) {
- PyErr_Format(PyExc_ValueError,
- "unsupported unboundop %d", unboundop);
- return NULL;
- }
-
int64_t cid = cid_data.cid;
PY_TIMEOUT_T timeout;
if (PyThread_ParseTimeoutArg(timeout_obj, blocking, &timeout) < 0) {
return NULL;
}
+ struct _channeldefaults defaults = {-1, -1};
+ if (unboundarg < 0 || fallbackarg < 0) {
+ int err = channel_get_defaults(&_globals.channels, cid, &defaults);
+ if (handle_channel_error(err, self, cid)) {
+ return NULL;
+ }
+ }
+ unboundop_t unboundop;
+ if (resolve_unboundop(unboundarg, defaults.unboundop, &unboundop) < 0) {
+ return NULL;
+ }
+ xidata_fallback_t fallback;
+ if (resolve_fallback(fallbackarg, defaults.fallback, &fallback) < 0) {
+ return NULL;
+ }
/* Queue up the object. */
int err = 0;
if (blocking) {
- err = channel_send_wait(&_globals.channels, cid, obj, unboundop, timeout);
+ err = channel_send_wait(
+ &_globals.channels, cid, obj, unboundop, timeout, fallback);
}
else {
- err = channel_send(&_globals.channels, cid, obj, NULL, unboundop);
+ err = channel_send(
+ &_globals.channels, cid, obj, NULL, unboundop, fallback);
}
if (handle_channel_error(err, self, cid)) {
return NULL;
@@ -3126,32 +3178,44 @@ By default this waits for the object to be received.");
static PyObject *
channelsmod_send_buffer(PyObject *self, PyObject *args, PyObject *kwds)
{
- static char *kwlist[] = {"cid", "obj", "unboundop", "blocking", "timeout",
- NULL};
+ static char *kwlist[] = {"cid", "obj", "unboundop", "fallback",
+ "blocking", "timeout", NULL};
struct channel_id_converter_data cid_data = {
.module = self,
};
PyObject *obj;
- int unboundop = UNBOUND_REPLACE;
- int blocking = 1;
+ int unboundarg = -1;
+ int fallbackarg = -1;
+ int blocking = -1;
PyObject *timeout_obj = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "O&O|i$pO:channel_send_buffer", kwlist,
+ "O&O|ii$pO:channel_send_buffer", kwlist,
channel_id_converter, &cid_data, &obj,
- &unboundop, &blocking, &timeout_obj)) {
- return NULL;
- }
- if (!check_unbound(unboundop)) {
- PyErr_Format(PyExc_ValueError,
- "unsupported unboundop %d", unboundop);
+ &unboundarg, &fallbackarg,
+ &blocking, &timeout_obj))
+ {
return NULL;
}
-
int64_t cid = cid_data.cid;
PY_TIMEOUT_T timeout;
if (PyThread_ParseTimeoutArg(timeout_obj, blocking, &timeout) < 0) {
return NULL;
}
+ struct _channeldefaults defaults = {-1, -1};
+ if (unboundarg < 0 || fallbackarg < 0) {
+ int err = channel_get_defaults(&_globals.channels, cid, &defaults);
+ if (handle_channel_error(err, self, cid)) {
+ return NULL;
+ }
+ }
+ unboundop_t unboundop;
+ if (resolve_unboundop(unboundarg, defaults.unboundop, &unboundop) < 0) {
+ return NULL;
+ }
+ xidata_fallback_t fallback;
+ if (resolve_fallback(fallbackarg, defaults.fallback, &fallback) < 0) {
+ return NULL;
+ }
PyObject *tempobj = PyMemoryView_FromObject(obj);
if (tempobj == NULL) {
@@ -3162,10 +3226,11 @@ channelsmod_send_buffer(PyObject *self, PyObject *args, PyObject *kwds)
int err = 0;
if (blocking) {
err = channel_send_wait(
- &_globals.channels, cid, tempobj, unboundop, timeout);
+ &_globals.channels, cid, tempobj, unboundop, timeout, fallback);
}
else {
- err = channel_send(&_globals.channels, cid, tempobj, NULL, unboundop);
+ err = channel_send(
+ &_globals.channels, cid, tempobj, NULL, unboundop, fallback);
}
Py_DECREF(tempobj);
if (handle_channel_error(err, self, cid)) {
@@ -3197,7 +3262,7 @@ channelsmod_recv(PyObject *self, PyObject *args, PyObject *kwds)
cid = cid_data.cid;
PyObject *obj = NULL;
- int unboundop = 0;
+ unboundop_t unboundop = 0;
int err = channel_recv(&_globals.channels, cid, &obj, &unboundop);
if (err == ERR_CHANNEL_EMPTY && dflt != NULL) {
// Use the default.
@@ -3388,17 +3453,14 @@ channelsmod_get_channel_defaults(PyObject *self, PyObject *args, PyObject *kwds)
}
int64_t cid = cid_data.cid;
- PyThread_type_lock mutex = NULL;
- _channel_state *channel = NULL;
- int err = _channels_lookup(&_globals.channels, cid, &mutex, &channel);
+ struct _channeldefaults defaults = {0};
+ int err = channel_get_defaults(&_globals.channels, cid, &defaults);
if (handle_channel_error(err, self, cid)) {
return NULL;
}
- int unboundop = channel->defaults.unboundop;
- PyThread_release_lock(mutex);
- PyObject *defaults = Py_BuildValue("i", unboundop);
- return defaults;
+ PyObject *res = Py_BuildValue("ii", defaults.unboundop, defaults.fallback);
+ return res;
}
PyDoc_STRVAR(channelsmod_get_channel_defaults_doc,
@@ -3552,8 +3614,7 @@ module_traverse(PyObject *mod, visitproc visit, void *arg)
{
module_state *state = get_module_state(mod);
assert(state != NULL);
- (void)traverse_module_state(state, visit, arg);
- return 0;
+ return traverse_module_state(state, visit, arg);
}
static int
@@ -3563,8 +3624,7 @@ module_clear(PyObject *mod)
assert(state != NULL);
// Now we clear the module state.
- (void)clear_module_state(state);
- return 0;
+ return clear_module_state(state);
}
static void
diff --git a/Modules/_interpqueuesmodule.c b/Modules/_interpqueuesmodule.c
index 526249a0e1a..e5afe746f90 100644
--- a/Modules/_interpqueuesmodule.c
+++ b/Modules/_interpqueuesmodule.c
@@ -9,9 +9,11 @@
#include "pycore_crossinterp.h" // _PyXIData_t
#define REGISTERS_HEAP_TYPES
+#define HAS_FALLBACK
#define HAS_UNBOUND_ITEMS
#include "_interpreters_common.h"
#undef HAS_UNBOUND_ITEMS
+#undef HAS_FALLBACK
#undef REGISTERS_HEAP_TYPES
@@ -134,13 +136,10 @@ idarg_int64_converter(PyObject *arg, void *ptr)
static int
ensure_highlevel_module_loaded(void)
{
- PyObject *highlevel = PyImport_ImportModule("interpreters.queues");
+ PyObject *highlevel =
+ PyImport_ImportModule("concurrent.interpreters._queues");
if (highlevel == NULL) {
- PyErr_Clear();
- highlevel = PyImport_ImportModule("test.support.interpreters.queues");
- if (highlevel == NULL) {
- return -1;
- }
+ return -1;
}
Py_DECREF(highlevel);
return 0;
@@ -297,7 +296,7 @@ add_QueueError(PyObject *mod)
{
module_state *state = get_module_state(mod);
-#define PREFIX "test.support.interpreters."
+#define PREFIX "concurrent.interpreters."
#define ADD_EXCTYPE(NAME, BASE, DOC) \
assert(state->NAME == NULL); \
if (add_exctype(mod, &state->NAME, PREFIX #NAME, DOC, BASE) < 0) { \
@@ -401,14 +400,13 @@ typedef struct _queueitem {
meaning the interpreter has been destroyed. */
int64_t interpid;
_PyXIData_t *data;
- int fmt;
- int unboundop;
+ unboundop_t unboundop;
struct _queueitem *next;
} _queueitem;
static void
_queueitem_init(_queueitem *item,
- int64_t interpid, _PyXIData_t *data, int fmt, int unboundop)
+ int64_t interpid, _PyXIData_t *data, unboundop_t unboundop)
{
if (interpid < 0) {
interpid = _get_interpid(data);
@@ -422,7 +420,6 @@ _queueitem_init(_queueitem *item,
*item = (_queueitem){
.interpid = interpid,
.data = data,
- .fmt = fmt,
.unboundop = unboundop,
};
}
@@ -446,14 +443,14 @@ _queueitem_clear(_queueitem *item)
}
static _queueitem *
-_queueitem_new(int64_t interpid, _PyXIData_t *data, int fmt, int unboundop)
+_queueitem_new(int64_t interpid, _PyXIData_t *data, int unboundop)
{
_queueitem *item = GLOBAL_MALLOC(_queueitem);
if (item == NULL) {
PyErr_NoMemory();
return NULL;
}
- _queueitem_init(item, interpid, data, fmt, unboundop);
+ _queueitem_init(item, interpid, data, unboundop);
return item;
}
@@ -476,10 +473,9 @@ _queueitem_free_all(_queueitem *item)
static void
_queueitem_popped(_queueitem *item,
- _PyXIData_t **p_data, int *p_fmt, int *p_unboundop)
+ _PyXIData_t **p_data, unboundop_t *p_unboundop)
{
*p_data = item->data;
- *p_fmt = item->fmt;
*p_unboundop = item->unboundop;
// We clear them here, so they won't be released in _queueitem_clear().
item->data = NULL;
@@ -527,16 +523,16 @@ typedef struct _queue {
_queueitem *first;
_queueitem *last;
} items;
- struct {
- int fmt;
+ struct _queuedefaults {
+ xidata_fallback_t fallback;
int unboundop;
} defaults;
} _queue;
static int
-_queue_init(_queue *queue, Py_ssize_t maxsize, int fmt, int unboundop)
+_queue_init(_queue *queue, Py_ssize_t maxsize, struct _queuedefaults defaults)
{
- assert(check_unbound(unboundop));
+ assert(check_unbound(defaults.unboundop));
PyThread_type_lock mutex = PyThread_allocate_lock();
if (mutex == NULL) {
return ERR_QUEUE_ALLOC;
@@ -547,10 +543,7 @@ _queue_init(_queue *queue, Py_ssize_t maxsize, int fmt, int unboundop)
.items = {
.maxsize = maxsize,
},
- .defaults = {
- .fmt = fmt,
- .unboundop = unboundop,
- },
+ .defaults = defaults,
};
return 0;
}
@@ -631,8 +624,7 @@ _queue_unlock(_queue *queue)
}
static int
-_queue_add(_queue *queue, int64_t interpid, _PyXIData_t *data,
- int fmt, int unboundop)
+_queue_add(_queue *queue, int64_t interpid, _PyXIData_t *data, int unboundop)
{
int err = _queue_lock(queue);
if (err < 0) {
@@ -648,7 +640,7 @@ _queue_add(_queue *queue, int64_t interpid, _PyXIData_t *data,
return ERR_QUEUE_FULL;
}
- _queueitem *item = _queueitem_new(interpid, data, fmt, unboundop);
+ _queueitem *item = _queueitem_new(interpid, data, unboundop);
if (item == NULL) {
_queue_unlock(queue);
return -1;
@@ -668,8 +660,7 @@ _queue_add(_queue *queue, int64_t interpid, _PyXIData_t *data,
}
static int
-_queue_next(_queue *queue,
- _PyXIData_t **p_data, int *p_fmt, int *p_unboundop)
+_queue_next(_queue *queue, _PyXIData_t **p_data, int *p_unboundop)
{
int err = _queue_lock(queue);
if (err < 0) {
@@ -688,7 +679,7 @@ _queue_next(_queue *queue,
}
queue->items.count -= 1;
- _queueitem_popped(item, p_data, p_fmt, p_unboundop);
+ _queueitem_popped(item, p_data, p_unboundop);
_queue_unlock(queue);
return 0;
@@ -716,8 +707,11 @@ _queue_is_full(_queue *queue, int *p_is_full)
return err;
}
- assert(queue->items.count <= queue->items.maxsize);
- *p_is_full = queue->items.count == queue->items.maxsize;
+ assert(queue->items.maxsize <= 0
+ || queue->items.count <= queue->items.maxsize);
+ *p_is_full = queue->items.maxsize > 0
+ ? queue->items.count == queue->items.maxsize
+ : 0;
_queue_unlock(queue);
return 0;
@@ -1035,8 +1029,7 @@ finally:
struct queue_id_and_info {
int64_t id;
- int fmt;
- int unboundop;
+ struct _queuedefaults defaults;
};
static struct queue_id_and_info *
@@ -1053,8 +1046,7 @@ _queues_list_all(_queues *queues, int64_t *p_count)
for (int64_t i=0; ref != NULL; ref = ref->next, i++) {
ids[i].id = ref->qid;
assert(ref->queue != NULL);
- ids[i].fmt = ref->queue->defaults.fmt;
- ids[i].unboundop = ref->queue->defaults.unboundop;
+ ids[i].defaults = ref->queue->defaults;
}
*p_count = queues->count;
@@ -1090,13 +1082,14 @@ _queue_free(_queue *queue)
// Create a new queue.
static int64_t
-queue_create(_queues *queues, Py_ssize_t maxsize, int fmt, int unboundop)
+queue_create(_queues *queues, Py_ssize_t maxsize,
+ struct _queuedefaults defaults)
{
_queue *queue = GLOBAL_MALLOC(_queue);
if (queue == NULL) {
return ERR_QUEUE_ALLOC;
}
- int err = _queue_init(queue, maxsize, fmt, unboundop);
+ int err = _queue_init(queue, maxsize, defaults);
if (err < 0) {
GLOBAL_FREE(queue);
return (int64_t)err;
@@ -1125,7 +1118,8 @@ queue_destroy(_queues *queues, int64_t qid)
// Push an object onto the queue.
static int
-queue_put(_queues *queues, int64_t qid, PyObject *obj, int fmt, int unboundop)
+queue_put(_queues *queues, int64_t qid, PyObject *obj, unboundop_t unboundop,
+ xidata_fallback_t fallback)
{
PyThreadState *tstate = PyThreadState_Get();
@@ -1138,27 +1132,27 @@ queue_put(_queues *queues, int64_t qid, PyObject *obj, int fmt, int unboundop)
assert(queue != NULL);
// Convert the object to cross-interpreter data.
- _PyXIData_t *data = _PyXIData_New();
- if (data == NULL) {
+ _PyXIData_t *xidata = _PyXIData_New();
+ if (xidata == NULL) {
_queue_unmark_waiter(queue, queues->mutex);
return -1;
}
- if (_PyObject_GetXIData(tstate, obj, data) != 0) {
+ if (_PyObject_GetXIData(tstate, obj, fallback, xidata) != 0) {
_queue_unmark_waiter(queue, queues->mutex);
- GLOBAL_FREE(data);
+ GLOBAL_FREE(xidata);
return -1;
}
- assert(_PyXIData_INTERPID(data) ==
+ assert(_PyXIData_INTERPID(xidata) ==
PyInterpreterState_GetID(tstate->interp));
// Add the data to the queue.
int64_t interpid = -1; // _queueitem_init() will set it.
- int res = _queue_add(queue, interpid, data, fmt, unboundop);
+ int res = _queue_add(queue, interpid, xidata, unboundop);
_queue_unmark_waiter(queue, queues->mutex);
if (res != 0) {
// We may chain an exception here:
- (void)_release_xid_data(data, 0);
- GLOBAL_FREE(data);
+ (void)_release_xid_data(xidata, 0);
+ GLOBAL_FREE(xidata);
return res;
}
@@ -1169,7 +1163,7 @@ queue_put(_queues *queues, int64_t qid, PyObject *obj, int fmt, int unboundop)
// XXX Support a "wait" mutex?
static int
queue_get(_queues *queues, int64_t qid,
- PyObject **res, int *p_fmt, int *p_unboundop)
+ PyObject **res, int *p_unboundop)
{
int err;
*res = NULL;
@@ -1185,7 +1179,7 @@ queue_get(_queues *queues, int64_t qid,
// Pop off the next item from the queue.
_PyXIData_t *data = NULL;
- err = _queue_next(queue, &data, p_fmt, p_unboundop);
+ err = _queue_next(queue, &data, p_unboundop);
_queue_unmark_waiter(queue, queues->mutex);
if (err != 0) {
return err;
@@ -1217,6 +1211,20 @@ queue_get(_queues *queues, int64_t qid,
}
static int
+queue_get_defaults(_queues *queues, int64_t qid,
+ struct _queuedefaults *p_defaults)
+{
+ _queue *queue = NULL;
+ int err = _queues_lookup(queues, qid, &queue);
+ if (err != 0) {
+ return err;
+ }
+ *p_defaults = queue->defaults;
+ _queue_unmark_waiter(queue, queues->mutex);
+ return 0;
+}
+
+static int
queue_get_maxsize(_queues *queues, int64_t qid, Py_ssize_t *p_maxsize)
{
_queue *queue = NULL;
@@ -1270,7 +1278,7 @@ set_external_queue_type(module_state *state, PyTypeObject *queue_type)
}
// Add and register the new type.
- if (ensure_xid_class(queue_type, _queueobj_shared) < 0) {
+ if (ensure_xid_class(queue_type, GETDATA(_queueobj_shared)) < 0) {
return -1;
}
state->queue_type = (PyTypeObject *)Py_NewRef(queue_type);
@@ -1348,10 +1356,10 @@ _queueobj_from_xid(_PyXIData_t *data)
PyObject *mod = _get_current_module();
if (mod == NULL) {
- // XXX import it?
- PyErr_SetString(PyExc_RuntimeError,
- MODULE_NAME_STR " module not imported yet");
- return NULL;
+ mod = PyImport_ImportModule(MODULE_NAME_STR);
+ if (mod == NULL) {
+ return NULL;
+ }
}
PyTypeObject *cls = get_external_queue_type(mod);
@@ -1474,22 +1482,28 @@ qidarg_converter(PyObject *arg, void *ptr)
static PyObject *
queuesmod_create(PyObject *self, PyObject *args, PyObject *kwds)
{
- static char *kwlist[] = {"maxsize", "fmt", "unboundop", NULL};
+ static char *kwlist[] = {"maxsize", "unboundop", "fallback", NULL};
Py_ssize_t maxsize;
- int fmt;
- int unboundop;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "nii:create", kwlist,
- &maxsize, &fmt, &unboundop))
+ int unboundarg = -1;
+ int fallbackarg = -1;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "n|ii:create", kwlist,
+ &maxsize, &unboundarg, &fallbackarg))
{
return NULL;
}
- if (!check_unbound(unboundop)) {
- PyErr_Format(PyExc_ValueError,
- "unsupported unboundop %d", unboundop);
+ struct _queuedefaults defaults = {0};
+ if (resolve_unboundop(unboundarg, UNBOUND_REPLACE,
+ &defaults.unboundop) < 0)
+ {
+ return NULL;
+ }
+ if (resolve_fallback(fallbackarg, _PyXIDATA_FULL_FALLBACK,
+ &defaults.fallback) < 0)
+ {
return NULL;
}
- int64_t qid = queue_create(&_globals.queues, maxsize, fmt, unboundop);
+ int64_t qid = queue_create(&_globals.queues, maxsize, defaults);
if (qid < 0) {
(void)handle_queue_error((int)qid, self, qid);
return NULL;
@@ -1511,7 +1525,7 @@ queuesmod_create(PyObject *self, PyObject *args, PyObject *kwds)
}
PyDoc_STRVAR(queuesmod_create_doc,
-"create(maxsize, fmt, unboundop) -> qid\n\
+"create(maxsize, unboundop, fallback) -> qid\n\
\n\
Create a new cross-interpreter queue and return its unique generated ID.\n\
It is a new reference as though bind() had been called on the queue.\n\
@@ -1560,8 +1574,9 @@ queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
}
struct queue_id_and_info *cur = qids;
for (int64_t i=0; i < count; cur++, i++) {
- PyObject *item = Py_BuildValue("Lii", cur->id, cur->fmt,
- cur->unboundop);
+ PyObject *item = Py_BuildValue("Lii", cur->id,
+ cur->defaults.unboundop,
+ cur->defaults.fallback);
if (item == NULL) {
Py_SETREF(ids, NULL);
break;
@@ -1575,34 +1590,44 @@ finally:
}
PyDoc_STRVAR(queuesmod_list_all_doc,
-"list_all() -> [(qid, fmt)]\n\
+"list_all() -> [(qid, unboundop, fallback)]\n\
\n\
Return the list of IDs for all queues.\n\
-Each corresponding default format is also included.");
+Each corresponding default unbound op and fallback is also included.");
static PyObject *
queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds)
{
- static char *kwlist[] = {"qid", "obj", "fmt", "unboundop", NULL};
+ static char *kwlist[] = {"qid", "obj", "unboundop", "fallback", NULL};
qidarg_converter_data qidarg = {0};
PyObject *obj;
- int fmt;
- int unboundop;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&Oii:put", kwlist,
- qidarg_converter, &qidarg, &obj, &fmt,
- &unboundop))
+ int unboundarg = -1;
+ int fallbackarg = -1;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O|ii$p:put", kwlist,
+ qidarg_converter, &qidarg, &obj,
+ &unboundarg, &fallbackarg))
{
return NULL;
}
int64_t qid = qidarg.id;
- if (!check_unbound(unboundop)) {
- PyErr_Format(PyExc_ValueError,
- "unsupported unboundop %d", unboundop);
+ struct _queuedefaults defaults = {-1, -1};
+ if (unboundarg < 0 || fallbackarg < 0) {
+ int err = queue_get_defaults(&_globals.queues, qid, &defaults);
+ if (handle_queue_error(err, self, qid)) {
+ return NULL;
+ }
+ }
+ unboundop_t unboundop;
+ if (resolve_unboundop(unboundarg, defaults.unboundop, &unboundop) < 0) {
+ return NULL;
+ }
+ xidata_fallback_t fallback;
+ if (resolve_fallback(fallbackarg, defaults.fallback, &fallback) < 0) {
return NULL;
}
/* Queue up the object. */
- int err = queue_put(&_globals.queues, qid, obj, fmt, unboundop);
+ int err = queue_put(&_globals.queues, qid, obj, unboundop, fallback);
// This is the only place that raises QueueFull.
if (handle_queue_error(err, self, qid)) {
return NULL;
@@ -1612,7 +1637,7 @@ queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds)
}
PyDoc_STRVAR(queuesmod_put_doc,
-"put(qid, obj, fmt)\n\
+"put(qid, obj)\n\
\n\
Add the object's data to the queue.");
@@ -1628,27 +1653,26 @@ queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds)
int64_t qid = qidarg.id;
PyObject *obj = NULL;
- int fmt = 0;
int unboundop = 0;
- int err = queue_get(&_globals.queues, qid, &obj, &fmt, &unboundop);
+ int err = queue_get(&_globals.queues, qid, &obj, &unboundop);
// This is the only place that raises QueueEmpty.
if (handle_queue_error(err, self, qid)) {
return NULL;
}
if (obj == NULL) {
- return Py_BuildValue("Oii", Py_None, fmt, unboundop);
+ return Py_BuildValue("Oi", Py_None, unboundop);
}
- PyObject *res = Py_BuildValue("OiO", obj, fmt, Py_None);
+ PyObject *res = Py_BuildValue("OO", obj, Py_None);
Py_DECREF(obj);
return res;
}
PyDoc_STRVAR(queuesmod_get_doc,
-"get(qid) -> (obj, fmt)\n\
+"get(qid) -> (obj, unboundop)\n\
\n\
Return a new object from the data at the front of the queue.\n\
-The object's format is also returned.\n\
+The unbound op is also returned.\n\
\n\
If there is nothing to receive then raise QueueEmpty.");
@@ -1748,17 +1772,14 @@ queuesmod_get_queue_defaults(PyObject *self, PyObject *args, PyObject *kwds)
}
int64_t qid = qidarg.id;
- _queue *queue = NULL;
- int err = _queues_lookup(&_globals.queues, qid, &queue);
+ struct _queuedefaults defaults = {0};
+ int err = queue_get_defaults(&_globals.queues, qid, &defaults);
if (handle_queue_error(err, self, qid)) {
return NULL;
}
- int fmt = queue->defaults.fmt;
- int unboundop = queue->defaults.unboundop;
- _queue_unmark_waiter(queue, _globals.queues.mutex);
- PyObject *defaults = Py_BuildValue("ii", fmt, unboundop);
- return defaults;
+ PyObject *res = Py_BuildValue("ii", defaults.unboundop, defaults.fallback);
+ return res;
}
PyDoc_STRVAR(queuesmod_get_queue_defaults_doc,
@@ -1931,8 +1952,7 @@ static int
module_traverse(PyObject *mod, visitproc visit, void *arg)
{
module_state *state = get_module_state(mod);
- (void)traverse_module_state(state, visit, arg);
- return 0;
+ return traverse_module_state(state, visit, arg);
}
static int
@@ -1941,8 +1961,7 @@ module_clear(PyObject *mod)
module_state *state = get_module_state(mod);
// Now we clear the module state.
- (void)clear_module_state(state);
- return 0;
+ return clear_module_state(state);
}
static void
diff --git a/Modules/_interpreters_common.h b/Modules/_interpreters_common.h
index edd65577284..40fd51d752e 100644
--- a/Modules/_interpreters_common.h
+++ b/Modules/_interpreters_common.h
@@ -5,8 +5,10 @@
_RESOLVE_MODINIT_FUNC_NAME(NAME)
+#define GETDATA(FUNC) ((_PyXIData_getdata_t){.basic=FUNC})
+
static int
-ensure_xid_class(PyTypeObject *cls, xidatafunc getdata)
+ensure_xid_class(PyTypeObject *cls, _PyXIData_getdata_t getdata)
{
PyThreadState *tstate = PyThreadState_Get();
return _PyXIData_RegisterClass(tstate, cls, getdata);
@@ -37,10 +39,37 @@ _get_interpid(_PyXIData_t *data)
}
+#ifdef HAS_FALLBACK
+static int
+resolve_fallback(int arg, xidata_fallback_t dflt,
+ xidata_fallback_t *p_fallback)
+{
+ if (arg < 0) {
+ *p_fallback = dflt;
+ return 0;
+ }
+ xidata_fallback_t fallback;
+ if (arg == _PyXIDATA_XIDATA_ONLY) {
+ fallback =_PyXIDATA_XIDATA_ONLY;
+ }
+ else if (arg == _PyXIDATA_FULL_FALLBACK) {
+ fallback = _PyXIDATA_FULL_FALLBACK;
+ }
+ else {
+ PyErr_Format(PyExc_ValueError, "unsupported fallback %d", arg);
+ return -1;
+ }
+ *p_fallback = fallback;
+ return 0;
+}
+#endif
+
+
/* unbound items ************************************************************/
#ifdef HAS_UNBOUND_ITEMS
+typedef int unboundop_t;
#define UNBOUND_REMOVE 1
#define UNBOUND_ERROR 2
#define UNBOUND_REPLACE 3
@@ -51,6 +80,7 @@ _get_interpid(_PyXIData_t *data)
// object is released but the underlying data is copied (with the "raw"
// allocator) and used when the item is popped off the queue.
+#ifndef NDEBUG
static int
check_unbound(int unboundop)
{
@@ -63,5 +93,31 @@ check_unbound(int unboundop)
return 0;
}
}
+#endif
+
+static int
+resolve_unboundop(int arg, unboundop_t dflt, unboundop_t *p_unboundop)
+{
+ if (arg < 0) {
+ *p_unboundop = dflt;
+ return 0;
+ }
+ unboundop_t op;
+ if (arg == UNBOUND_REMOVE) {
+ op = UNBOUND_REMOVE;
+ }
+ else if (arg == UNBOUND_ERROR) {
+ op = UNBOUND_ERROR;
+ }
+ else if (arg == UNBOUND_REPLACE) {
+ op = UNBOUND_REPLACE;
+ }
+ else {
+ PyErr_Format(PyExc_ValueError, "unsupported unboundop %d", arg);
+ return -1;
+ }
+ *p_unboundop = op;
+ return 0;
+}
#endif
diff --git a/Modules/_interpretersmodule.c b/Modules/_interpretersmodule.c
index 77678f7c126..e7feaa7f186 100644
--- a/Modules/_interpretersmodule.c
+++ b/Modules/_interpretersmodule.c
@@ -8,6 +8,7 @@
#include "Python.h"
#include "pycore_code.h" // _PyCode_HAS_EXECUTORS()
#include "pycore_crossinterp.h" // _PyXIData_t
+#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
#include "pycore_interp.h" // _PyInterpreterState_IDIncref()
#include "pycore_modsupport.h" // _PyArg_BadArgument()
#include "pycore_namespace.h" // _PyNamespace_New()
@@ -71,6 +72,22 @@ is_running_main(PyInterpreterState *interp)
}
+static inline int
+is_notshareable_raised(PyThreadState *tstate)
+{
+ PyObject *exctype = _PyXIData_GetNotShareableErrorType(tstate);
+ return _PyErr_ExceptionMatches(tstate, exctype);
+}
+
+static void
+unwrap_not_shareable(PyThreadState *tstate, _PyXI_failure *failure)
+{
+ if (_PyXI_UnwrapNotShareableError(tstate, failure) < 0) {
+ _PyErr_Clear(tstate);
+ }
+}
+
+
/* Cross-interpreter Buffer Views *******************************************/
/* When a memoryview object is "shared" between interpreters,
@@ -286,7 +303,7 @@ register_memoryview_xid(PyObject *mod, PyTypeObject **p_state)
*p_state = cls;
// Register XID for the builtin memoryview type.
- if (ensure_xid_class(&PyMemoryView_Type, _pybuffer_shared) < 0) {
+ if (ensure_xid_class(&PyMemoryView_Type, GETDATA(_pybuffer_shared)) < 0) {
return -1;
}
// We don't ever bother un-registering memoryview.
@@ -319,10 +336,10 @@ _get_current_module_state(void)
{
PyObject *mod = _get_current_module();
if (mod == NULL) {
- // XXX import it?
- PyErr_SetString(PyExc_RuntimeError,
- MODULE_NAME_STR " module not imported yet");
- return NULL;
+ mod = PyImport_ImportModule(MODULE_NAME_STR);
+ if (mod == NULL) {
+ return NULL;
+ }
}
module_state *state = get_module_state(mod);
Py_DECREF(mod);
@@ -359,96 +376,6 @@ _get_current_xibufferview_type(void)
}
-/* Python code **************************************************************/
-
-static const char *
-check_code_str(PyUnicodeObject *text)
-{
- assert(text != NULL);
- if (PyUnicode_GET_LENGTH(text) == 0) {
- return "too short";
- }
-
- // XXX Verify that it parses?
-
- return NULL;
-}
-
-static const char *
-check_code_object(PyCodeObject *code)
-{
- assert(code != NULL);
- if (code->co_argcount > 0
- || code->co_posonlyargcount > 0
- || code->co_kwonlyargcount > 0
- || code->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
- {
- return "arguments not supported";
- }
- if (code->co_ncellvars > 0) {
- return "closures not supported";
- }
- // We trust that no code objects under co_consts have unbound cell vars.
-
- if (_PyCode_HAS_EXECUTORS(code) || _PyCode_HAS_INSTRUMENTATION(code)) {
- return "only basic functions are supported";
- }
- if (code->_co_monitoring != NULL) {
- return "only basic functions are supported";
- }
- if (code->co_extra != NULL) {
- return "only basic functions are supported";
- }
-
- return NULL;
-}
-
-#define RUN_TEXT 1
-#define RUN_CODE 2
-
-static const char *
-get_code_str(PyObject *arg, Py_ssize_t *len_p, PyObject **bytes_p, int *flags_p)
-{
- const char *codestr = NULL;
- Py_ssize_t len = -1;
- PyObject *bytes_obj = NULL;
- int flags = 0;
-
- if (PyUnicode_Check(arg)) {
- assert(PyUnicode_Check(arg)
- && (check_code_str((PyUnicodeObject *)arg) == NULL));
- codestr = PyUnicode_AsUTF8AndSize(arg, &len);
- if (codestr == NULL) {
- return NULL;
- }
- if (strlen(codestr) != (size_t)len) {
- PyErr_SetString(PyExc_ValueError,
- "source code string cannot contain null bytes");
- return NULL;
- }
- flags = RUN_TEXT;
- }
- else {
- assert(PyCode_Check(arg)
- && (check_code_object((PyCodeObject *)arg) == NULL));
- flags = RUN_CODE;
-
- // Serialize the code object.
- bytes_obj = PyMarshal_WriteObjectToString(arg, Py_MARSHAL_VERSION);
- if (bytes_obj == NULL) {
- return NULL;
- }
- codestr = PyBytes_AS_STRING(bytes_obj);
- len = PyBytes_GET_SIZE(bytes_obj);
- }
-
- *flags_p = flags;
- *bytes_p = bytes_obj;
- *len_p = len;
- return codestr;
-}
-
-
/* interpreter-specific code ************************************************/
static int
@@ -511,73 +438,290 @@ config_from_object(PyObject *configobj, PyInterpreterConfig *config)
}
+struct interp_call {
+ _PyXIData_t *func;
+ _PyXIData_t *args;
+ _PyXIData_t *kwargs;
+ struct {
+ _PyXIData_t func;
+ _PyXIData_t args;
+ _PyXIData_t kwargs;
+ } _preallocated;
+};
+
+static void
+_interp_call_clear(struct interp_call *call)
+{
+ if (call->func != NULL) {
+ _PyXIData_Clear(NULL, call->func);
+ }
+ if (call->args != NULL) {
+ _PyXIData_Clear(NULL, call->args);
+ }
+ if (call->kwargs != NULL) {
+ _PyXIData_Clear(NULL, call->kwargs);
+ }
+ *call = (struct interp_call){0};
+}
+
static int
-_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
+_interp_call_pack(PyThreadState *tstate, struct interp_call *call,
+ PyObject *func, PyObject *args, PyObject *kwargs)
{
- PyObject *result = NULL;
- if (flags & RUN_TEXT) {
- result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
- }
- else if (flags & RUN_CODE) {
- PyObject *code = PyMarshal_ReadObjectFromString(codestr, codestrlen);
- if (code != NULL) {
- result = PyEval_EvalCode(code, ns, ns);
- Py_DECREF(code);
+ xidata_fallback_t fallback = _PyXIDATA_FULL_FALLBACK;
+ assert(call->func == NULL);
+ assert(call->args == NULL);
+ assert(call->kwargs == NULL);
+ // Handle the func.
+ if (!PyCallable_Check(func)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "expected a callable, got %R", func);
+ return -1;
+ }
+ if (_PyFunction_GetXIData(tstate, func, &call->_preallocated.func) < 0) {
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ if (_PyPickle_GetXIData(tstate, func, &call->_preallocated.func) < 0) {
+ _PyErr_SetRaisedException(tstate, exc);
+ return -1;
}
+ Py_DECREF(exc);
+ }
+ call->func = &call->_preallocated.func;
+ // Handle the args.
+ if (args == NULL || args == Py_None) {
+ // Leave it empty.
}
else {
- Py_UNREACHABLE();
+ assert(PyTuple_Check(args));
+ if (PyTuple_GET_SIZE(args) > 0) {
+ if (_PyObject_GetXIData(
+ tstate, args, fallback, &call->_preallocated.args) < 0)
+ {
+ _interp_call_clear(call);
+ return -1;
+ }
+ call->args = &call->_preallocated.args;
+ }
}
- if (result == NULL) {
- return -1;
+ // Handle the kwargs.
+ if (kwargs == NULL || kwargs == Py_None) {
+ // Leave it empty.
+ }
+ else {
+ assert(PyDict_Check(kwargs));
+ if (PyDict_GET_SIZE(kwargs) > 0) {
+ if (_PyObject_GetXIData(
+ tstate, kwargs, fallback, &call->_preallocated.kwargs) < 0)
+ {
+ _interp_call_clear(call);
+ return -1;
+ }
+ call->kwargs = &call->_preallocated.kwargs;
+ }
}
- Py_DECREF(result); // We throw away the result.
return 0;
}
+static void
+wrap_notshareable(PyThreadState *tstate, const char *label)
+{
+ if (!is_notshareable_raised(tstate)) {
+ return;
+ }
+ assert(label != NULL && strlen(label) > 0);
+ PyObject *cause = _PyErr_GetRaisedException(tstate);
+ _PyXIData_FormatNotShareableError(tstate, "%s not shareable", label);
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ PyException_SetCause(exc, cause);
+ _PyErr_SetRaisedException(tstate, exc);
+}
+
static int
-_run_in_interpreter(PyInterpreterState *interp,
- const char *codestr, Py_ssize_t codestrlen,
- PyObject *shareables, int flags,
- PyObject **p_excinfo)
+_interp_call_unpack(struct interp_call *call,
+ PyObject **p_func, PyObject **p_args, PyObject **p_kwargs)
{
- assert(!PyErr_Occurred());
- _PyXI_session session = {0};
+ PyThreadState *tstate = PyThreadState_Get();
- // Prep and switch interpreters.
- if (_PyXI_Enter(&session, interp, shareables) < 0) {
- if (PyErr_Occurred()) {
- // If an error occured at this step, it means that interp
- // was not prepared and switched.
+ // Unpack the func.
+ PyObject *func = _PyXIData_NewObject(call->func);
+ if (func == NULL) {
+ wrap_notshareable(tstate, "func");
+ return -1;
+ }
+ // Unpack the args.
+ PyObject *args;
+ if (call->args == NULL) {
+ args = PyTuple_New(0);
+ if (args == NULL) {
+ Py_DECREF(func);
return -1;
}
- // Now, apply the error from another interpreter:
- PyObject *excinfo = _PyXI_ApplyError(session.error);
- if (excinfo != NULL) {
- *p_excinfo = excinfo;
+ }
+ else {
+ args = _PyXIData_NewObject(call->args);
+ if (args == NULL) {
+ wrap_notshareable(tstate, "args");
+ Py_DECREF(func);
+ return -1;
}
- assert(PyErr_Occurred());
+ assert(PyTuple_Check(args));
+ }
+ // Unpack the kwargs.
+ PyObject *kwargs = NULL;
+ if (call->kwargs != NULL) {
+ kwargs = _PyXIData_NewObject(call->kwargs);
+ if (kwargs == NULL) {
+ wrap_notshareable(tstate, "kwargs");
+ Py_DECREF(func);
+ Py_DECREF(args);
+ return -1;
+ }
+ assert(PyDict_Check(kwargs));
+ }
+ *p_func = func;
+ *p_args = args;
+ *p_kwargs = kwargs;
+ return 0;
+}
+
+static int
+_make_call(struct interp_call *call,
+ PyObject **p_result, _PyXI_failure *failure)
+{
+ assert(call != NULL && call->func != NULL);
+ PyThreadState *tstate = _PyThreadState_GET();
+
+ // Get the func and args.
+ PyObject *func = NULL, *args = NULL, *kwargs = NULL;
+ if (_interp_call_unpack(call, &func, &args, &kwargs) < 0) {
+ assert(func == NULL);
+ assert(args == NULL);
+ assert(kwargs == NULL);
+ _PyXI_InitFailure(failure, _PyXI_ERR_OTHER, NULL);
+ unwrap_not_shareable(tstate, failure);
return -1;
}
+ assert(!_PyErr_Occurred(tstate));
- // Run the script.
- int res = _run_script(session.main_ns, codestr, codestrlen, flags);
+ // Make the call.
+ PyObject *resobj = PyObject_Call(func, args, kwargs);
+ Py_DECREF(func);
+ Py_XDECREF(args);
+ Py_XDECREF(kwargs);
+ if (resobj == NULL) {
+ return -1;
+ }
+ *p_result = resobj;
+ return 0;
+}
- // Clean up and switch back.
- _PyXI_Exit(&session);
+static int
+_run_script(_PyXIData_t *script, PyObject *ns, _PyXI_failure *failure)
+{
+ PyObject *code = _PyXIData_NewObject(script);
+ if (code == NULL) {
+ _PyXI_InitFailure(failure, _PyXI_ERR_NOT_SHAREABLE, NULL);
+ return -1;
+ }
+ PyObject *result = PyEval_EvalCode(code, ns, ns);
+ Py_DECREF(code);
+ if (result == NULL) {
+ _PyXI_InitFailure(failure, _PyXI_ERR_UNCAUGHT_EXCEPTION, NULL);
+ return -1;
+ }
+ assert(result == Py_None);
+ Py_DECREF(result); // We throw away the result.
+ return 0;
+}
- // Propagate any exception out to the caller.
- assert(!PyErr_Occurred());
- if (res < 0) {
- PyObject *excinfo = _PyXI_ApplyCapturedException(&session);
- if (excinfo != NULL) {
- *p_excinfo = excinfo;
+struct run_result {
+ PyObject *result;
+ PyObject *excinfo;
+};
+
+static void
+_run_result_clear(struct run_result *runres)
+{
+ Py_CLEAR(runres->result);
+ Py_CLEAR(runres->excinfo);
+}
+
+static int
+_run_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp,
+ _PyXIData_t *script, struct interp_call *call,
+ PyObject *shareables, struct run_result *runres)
+{
+ assert(!_PyErr_Occurred(tstate));
+ int res = -1;
+ _PyXI_failure *failure = _PyXI_NewFailure();
+ if (failure == NULL) {
+ return -1;
+ }
+ _PyXI_session *session = _PyXI_NewSession();
+ if (session == NULL) {
+ _PyXI_FreeFailure(failure);
+ return -1;
+ }
+ _PyXI_session_result result = {0};
+
+ // Prep and switch interpreters.
+ if (_PyXI_Enter(session, interp, shareables, &result) < 0) {
+ // If an error occured at this step, it means that interp
+ // was not prepared and switched.
+ _PyXI_FreeSession(session);
+ _PyXI_FreeFailure(failure);
+ assert(result.excinfo == NULL);
+ return -1;
+ }
+
+ // Run in the interpreter.
+ if (script != NULL) {
+ assert(call == NULL);
+ PyObject *mainns = _PyXI_GetMainNamespace(session, failure);
+ if (mainns == NULL) {
+ goto finally;
}
+ res = _run_script(script, mainns, failure);
}
else {
- assert(!_PyXI_HasCapturedException(&session));
+ assert(call != NULL);
+ PyObject *resobj;
+ res = _make_call(call, &resobj, failure);
+ if (res == 0) {
+ res = _PyXI_Preserve(session, "resobj", resobj, failure);
+ Py_DECREF(resobj);
+ if (res < 0) {
+ goto finally;
+ }
+ }
}
+finally:
+ // Clean up and switch back.
+ (void)res;
+ int exitres = _PyXI_Exit(session, failure, &result);
+ assert(res == 0 || exitres != 0);
+ _PyXI_FreeSession(session);
+ _PyXI_FreeFailure(failure);
+
+ res = exitres;
+ if (_PyErr_Occurred(tstate)) {
+ // It's a directly propagated exception.
+ assert(res < 0);
+ }
+ else if (res < 0) {
+ assert(result.excinfo != NULL);
+ runres->excinfo = Py_NewRef(result.excinfo);
+ res = -1;
+ }
+ else {
+ assert(result.excinfo == NULL);
+ runres->result = _PyXI_GetPreserved(&result, "resobj");
+ if (_PyErr_Occurred(tstate)) {
+ res = -1;
+ }
+ }
+ _PyXI_ClearResult(&result);
return res;
}
@@ -895,8 +1039,8 @@ interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
PyObject *id, *updates;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
- "OO|$p:" MODULE_NAME_STR ".set___main___attrs",
- kwlist, &id, &updates, &restricted))
+ "OO!|$p:" MODULE_NAME_STR ".set___main___attrs",
+ kwlist, &id, &PyDict_Type, &updates, &restricted))
{
return NULL;
}
@@ -910,34 +1054,39 @@ interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
}
// Check the updates.
- if (updates != Py_None) {
- Py_ssize_t size = PyObject_Size(updates);
- if (size < 0) {
- return NULL;
- }
- if (size == 0) {
- PyErr_SetString(PyExc_ValueError,
- "arg 2 must be a non-empty mapping");
- return NULL;
- }
+ Py_ssize_t size = PyDict_Size(updates);
+ if (size < 0) {
+ return NULL;
+ }
+ if (size == 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "arg 2 must be a non-empty dict");
+ return NULL;
}
- _PyXI_session session = {0};
+ _PyXI_session *session = _PyXI_NewSession();
+ if (session == NULL) {
+ return NULL;
+ }
// Prep and switch interpreters, including apply the updates.
- if (_PyXI_Enter(&session, interp, updates) < 0) {
- if (!PyErr_Occurred()) {
- _PyXI_ApplyCapturedException(&session);
- assert(PyErr_Occurred());
- }
- else {
- assert(!_PyXI_HasCapturedException(&session));
- }
+ if (_PyXI_Enter(session, interp, updates, NULL) < 0) {
+ _PyXI_FreeSession(session);
return NULL;
}
// Clean up and switch back.
- _PyXI_Exit(&session);
+ assert(!PyErr_Occurred());
+ int res = _PyXI_Exit(session, NULL, NULL);
+ _PyXI_FreeSession(session);
+ assert(res == 0);
+ if (res < 0) {
+ // unreachable
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(PyExc_RuntimeError, "unresolved error");
+ }
+ return NULL;
+ }
Py_RETURN_NONE;
}
@@ -948,122 +1097,31 @@ PyDoc_STRVAR(set___main___attrs_doc,
Bind the given attributes in the interpreter's __main__ module.");
-static PyUnicodeObject *
-convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
- const char *expected)
-{
- PyUnicodeObject *str = NULL;
- if (PyUnicode_CheckExact(arg)) {
- str = (PyUnicodeObject *)Py_NewRef(arg);
- }
- else if (PyUnicode_Check(arg)) {
- // XXX str = PyUnicode_FromObject(arg);
- str = (PyUnicodeObject *)Py_NewRef(arg);
- }
- else {
- _PyArg_BadArgument(fname, displayname, expected, arg);
- return NULL;
- }
-
- const char *err = check_code_str(str);
- if (err != NULL) {
- Py_DECREF(str);
- PyErr_Format(PyExc_ValueError,
- "%.200s(): bad script text (%s)", fname, err);
- return NULL;
- }
-
- return str;
-}
-
-static PyCodeObject *
-convert_code_arg(PyObject *arg, const char *fname, const char *displayname,
- const char *expected)
+static PyObject *
+_handle_script_error(struct run_result *runres)
{
- const char *kind = NULL;
- PyCodeObject *code = NULL;
- if (PyFunction_Check(arg)) {
- if (PyFunction_GetClosure(arg) != NULL) {
- PyErr_Format(PyExc_ValueError,
- "%.200s(): closures not supported", fname);
- return NULL;
- }
- code = (PyCodeObject *)PyFunction_GetCode(arg);
- if (code == NULL) {
- if (PyErr_Occurred()) {
- // This chains.
- PyErr_Format(PyExc_ValueError,
- "%.200s(): bad func", fname);
- }
- else {
- PyErr_Format(PyExc_ValueError,
- "%.200s(): func.__code__ missing", fname);
- }
- return NULL;
- }
- Py_INCREF(code);
- kind = "func";
- }
- else if (PyCode_Check(arg)) {
- code = (PyCodeObject *)Py_NewRef(arg);
- kind = "code object";
- }
- else {
- _PyArg_BadArgument(fname, displayname, expected, arg);
- return NULL;
- }
-
- const char *err = check_code_object(code);
- if (err != NULL) {
- Py_DECREF(code);
- PyErr_Format(PyExc_ValueError,
- "%.200s(): bad %s (%s)", fname, kind, err);
+ assert(runres->result == NULL);
+ if (runres->excinfo == NULL) {
+ assert(PyErr_Occurred());
return NULL;
}
-
- return code;
-}
-
-static int
-_interp_exec(PyObject *self, PyInterpreterState *interp,
- PyObject *code_arg, PyObject *shared_arg, PyObject **p_excinfo)
-{
- if (shared_arg != NULL && !PyDict_CheckExact(shared_arg)) {
- PyErr_SetString(PyExc_TypeError, "expected 'shared' to be a dict");
- return -1;
- }
-
- // Extract code.
- Py_ssize_t codestrlen = -1;
- PyObject *bytes_obj = NULL;
- int flags = 0;
- const char *codestr = get_code_str(code_arg,
- &codestrlen, &bytes_obj, &flags);
- if (codestr == NULL) {
- return -1;
- }
-
- // Run the code in the interpreter.
- int res = _run_in_interpreter(interp, codestr, codestrlen,
- shared_arg, flags, p_excinfo);
- Py_XDECREF(bytes_obj);
- if (res < 0) {
- return -1;
- }
-
- return 0;
+ assert(!PyErr_Occurred());
+ return runres->excinfo;
}
static PyObject *
interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
{
+#define FUNCNAME MODULE_NAME_STR ".exec"
+ PyThreadState *tstate = _PyThreadState_GET();
static char *kwlist[] = {"id", "code", "shared", "restrict", NULL};
PyObject *id, *code;
PyObject *shared = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "OO|O$p:" MODULE_NAME_STR ".exec", kwlist,
- &id, &code, &shared, &restricted))
+ "OO|O!$p:" FUNCNAME, kwlist,
+ &id, &code, &PyDict_Type, &shared,
+ &restricted))
{
return NULL;
}
@@ -1075,27 +1133,24 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds)
return NULL;
}
- const char *expected = "a string, a function, or a code object";
- if (PyUnicode_Check(code)) {
- code = (PyObject *)convert_script_arg(code, MODULE_NAME_STR ".exec",
- "argument 2", expected);
- }
- else {
- code = (PyObject *)convert_code_arg(code, MODULE_NAME_STR ".exec",
- "argument 2", expected);
- }
- if (code == NULL) {
+ // We don't need the script to be "pure", which means it can use
+ // global variables. They will be resolved against __main__.
+ _PyXIData_t xidata = {0};
+ if (_PyCode_GetScriptXIData(tstate, code, &xidata) < 0) {
+ unwrap_not_shareable(tstate, NULL);
return NULL;
}
- PyObject *excinfo = NULL;
- int res = _interp_exec(self, interp, code, shared, &excinfo);
- Py_DECREF(code);
+ struct run_result runres = {0};
+ int res = _run_in_interpreter(
+ tstate, interp, &xidata, NULL, shared, &runres);
+ _PyXIData_Release(&xidata);
if (res < 0) {
- assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
- return excinfo;
+ return _handle_script_error(&runres);
}
+ assert(runres.result == NULL);
Py_RETURN_NONE;
+#undef FUNCNAME
}
PyDoc_STRVAR(exec_doc,
@@ -1118,13 +1173,16 @@ is ignored, including its __globals__ dict.");
static PyObject *
interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
{
+#define FUNCNAME MODULE_NAME_STR ".run_string"
+ PyThreadState *tstate = _PyThreadState_GET();
static char *kwlist[] = {"id", "script", "shared", "restrict", NULL};
PyObject *id, *script;
PyObject *shared = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "OU|O$p:" MODULE_NAME_STR ".run_string",
- kwlist, &id, &script, &shared, &restricted))
+ "OU|O!$p:" FUNCNAME, kwlist,
+ &id, &script, &PyDict_Type, &shared,
+ &restricted))
{
return NULL;
}
@@ -1136,20 +1194,27 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
return NULL;
}
- script = (PyObject *)convert_script_arg(script, MODULE_NAME_STR ".run_string",
- "argument 2", "a string");
- if (script == NULL) {
+ if (PyFunction_Check(script) || PyCode_Check(script)) {
+ _PyArg_BadArgument(FUNCNAME, "argument 2", "a string", script);
+ return NULL;
+ }
+
+ _PyXIData_t xidata = {0};
+ if (_PyCode_GetScriptXIData(tstate, script, &xidata) < 0) {
+ unwrap_not_shareable(tstate, NULL);
return NULL;
}
- PyObject *excinfo = NULL;
- int res = _interp_exec(self, interp, script, shared, &excinfo);
- Py_DECREF(script);
+ struct run_result runres = {0};
+ int res = _run_in_interpreter(
+ tstate, interp, &xidata, NULL, shared, &runres);
+ _PyXIData_Release(&xidata);
if (res < 0) {
- assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
- return excinfo;
+ return _handle_script_error(&runres);
}
+ assert(runres.result == NULL);
Py_RETURN_NONE;
+#undef FUNCNAME
}
PyDoc_STRVAR(run_string_doc,
@@ -1162,13 +1227,16 @@ Execute the provided string in the identified interpreter.\n\
static PyObject *
interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
{
+#define FUNCNAME MODULE_NAME_STR ".run_func"
+ PyThreadState *tstate = _PyThreadState_GET();
static char *kwlist[] = {"id", "func", "shared", "restrict", NULL};
PyObject *id, *func;
PyObject *shared = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "OO|O$p:" MODULE_NAME_STR ".run_func",
- kwlist, &id, &func, &shared, &restricted))
+ "OO|O!$p:" FUNCNAME, kwlist,
+ &id, &func, &PyDict_Type, &shared,
+ &restricted))
{
return NULL;
}
@@ -1180,21 +1248,36 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds)
return NULL;
}
- PyCodeObject *code = convert_code_arg(func, MODULE_NAME_STR ".exec",
- "argument 2",
- "a function or a code object");
- if (code == NULL) {
+ // We don't worry about checking globals. They will be resolved
+ // against __main__.
+ PyObject *code;
+ if (PyFunction_Check(func)) {
+ code = PyFunction_GET_CODE(func);
+ }
+ else if (PyCode_Check(func)) {
+ code = func;
+ }
+ else {
+ _PyArg_BadArgument(FUNCNAME, "argument 2", "a function", func);
return NULL;
}
- PyObject *excinfo = NULL;
- int res = _interp_exec(self, interp, (PyObject *)code, shared, &excinfo);
- Py_DECREF(code);
+ _PyXIData_t xidata = {0};
+ if (_PyCode_GetScriptXIData(tstate, code, &xidata) < 0) {
+ unwrap_not_shareable(tstate, NULL);
+ return NULL;
+ }
+
+ struct run_result runres = {0};
+ int res = _run_in_interpreter(
+ tstate, interp, &xidata, NULL, shared, &runres);
+ _PyXIData_Release(&xidata);
if (res < 0) {
- assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
- return excinfo;
+ return _handle_script_error(&runres);
}
+ assert(runres.result == NULL);
Py_RETURN_NONE;
+#undef FUNCNAME
}
PyDoc_STRVAR(run_func_doc,
@@ -1209,16 +1292,21 @@ are not supported. Methods and other callables are not supported either.\n\
static PyObject *
interp_call(PyObject *self, PyObject *args, PyObject *kwds)
{
+#define FUNCNAME MODULE_NAME_STR ".call"
+ PyThreadState *tstate = _PyThreadState_GET();
static char *kwlist[] = {"id", "callable", "args", "kwargs",
- "restrict", NULL};
+ "preserve_exc", "restrict", NULL};
PyObject *id, *callable;
PyObject *args_obj = NULL;
PyObject *kwargs_obj = NULL;
+ int preserve_exc = 0;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "OO|OO$p:" MODULE_NAME_STR ".call", kwlist,
- &id, &callable, &args_obj, &kwargs_obj,
- &restricted))
+ "OO|O!O!$pp:" FUNCNAME, kwlist,
+ &id, &callable,
+ &PyTuple_Type, &args_obj,
+ &PyDict_Type, &kwargs_obj,
+ &preserve_exc, &restricted))
{
return NULL;
}
@@ -1230,42 +1318,37 @@ interp_call(PyObject *self, PyObject *args, PyObject *kwds)
return NULL;
}
- if (args_obj != NULL) {
- PyErr_SetString(PyExc_ValueError, "got unexpected args");
- return NULL;
- }
- if (kwargs_obj != NULL) {
- PyErr_SetString(PyExc_ValueError, "got unexpected kwargs");
+ struct interp_call call = {0};
+ if (_interp_call_pack(tstate, &call, callable, args_obj, kwargs_obj) < 0) {
return NULL;
}
- PyObject *code = (PyObject *)convert_code_arg(callable, MODULE_NAME_STR ".call",
- "argument 2", "a function");
- if (code == NULL) {
- return NULL;
+ PyObject *res_and_exc = NULL;
+ struct run_result runres = {0};
+ if (_run_in_interpreter(tstate, interp, NULL, &call, NULL, &runres) < 0) {
+ if (runres.excinfo == NULL) {
+ assert(_PyErr_Occurred(tstate));
+ goto finally;
+ }
+ assert(!_PyErr_Occurred(tstate));
}
+ assert(runres.result == NULL || runres.excinfo == NULL);
+ res_and_exc = Py_BuildValue("OO",
+ (runres.result ? runres.result : Py_None),
+ (runres.excinfo ? runres.excinfo : Py_None));
- PyObject *excinfo = NULL;
- int res = _interp_exec(self, interp, code, NULL, &excinfo);
- Py_DECREF(code);
- if (res < 0) {
- assert((excinfo == NULL) != (PyErr_Occurred() == NULL));
- return excinfo;
- }
- Py_RETURN_NONE;
+finally:
+ _interp_call_clear(&call);
+ _run_result_clear(&runres);
+ return res_and_exc;
+#undef FUNCNAME
}
PyDoc_STRVAR(call_doc,
"call(id, callable, args=None, kwargs=None, *, restrict=False)\n\
\n\
Call the provided object in the identified interpreter.\n\
-Pass the given args and kwargs, if possible.\n\
-\n\
-\"callable\" may be a plain function with no free vars that takes\n\
-no arguments.\n\
-\n\
-The function's code object is used and all its state\n\
-is ignored, including its __globals__ dict.");
+Pass the given args and kwargs, if possible.");
static PyObject *
@@ -1472,16 +1555,16 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
}
PyObject *captured = NULL;
- _PyXI_excinfo info = {0};
- if (_PyXI_InitExcInfo(&info, exc) < 0) {
+ _PyXI_excinfo *info = _PyXI_NewExcInfo(exc);
+ if (info == NULL) {
goto finally;
}
- captured = _PyXI_ExcInfoAsObject(&info);
+ captured = _PyXI_ExcInfoAsObject(info);
if (captured == NULL) {
goto finally;
}
- PyObject *formatted = _PyXI_FormatExcInfo(&info);
+ PyObject *formatted = _PyXI_FormatExcInfo(info);
if (formatted == NULL) {
Py_CLEAR(captured);
goto finally;
@@ -1494,7 +1577,7 @@ capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
}
finally:
- _PyXI_ClearExcInfo(&info);
+ _PyXI_FreeExcInfo(info);
if (exc != exc_arg) {
if (PyErr_Occurred()) {
PyErr_SetRaisedException(exc);
@@ -1623,8 +1706,7 @@ module_traverse(PyObject *mod, visitproc visit, void *arg)
{
module_state *state = get_module_state(mod);
assert(state != NULL);
- (void)traverse_module_state(state, visit, arg);
- return 0;
+ return traverse_module_state(state, visit, arg);
}
static int
@@ -1632,8 +1714,7 @@ module_clear(PyObject *mod)
{
module_state *state = get_module_state(mod);
assert(state != NULL);
- (void)clear_module_state(state);
- return 0;
+ return clear_module_state(state);
}
static void
diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c
index 2189b1f3800..25c8bf8b3d5 100644
--- a/Modules/_io/bufferedio.c
+++ b/Modules/_io/bufferedio.c
@@ -13,6 +13,7 @@
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pyerrors.h" // _Py_FatalErrorFormat()
#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "_iomodule.h"
@@ -421,8 +422,7 @@ buffered_dealloc(PyObject *op)
return;
_PyObject_GC_UNTRACK(self);
self->ok = 0;
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
if (self->buffer) {
PyMem_Free(self->buffer);
self->buffer = NULL;
@@ -2312,8 +2312,7 @@ bufferedrwpair_dealloc(PyObject *op)
rwpair *self = rwpair_CAST(op);
PyTypeObject *tp = Py_TYPE(self);
_PyObject_GC_UNTRACK(self);
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
(void)bufferedrwpair_clear(op);
tp->tp_free(self);
Py_DECREF(tp);
@@ -2555,8 +2554,7 @@ static PyMethodDef bufferedreader_methods[] = {
_IO__BUFFERED_TRUNCATE_METHODDEF
_IO__BUFFERED___SIZEOF___METHODDEF
- {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
- {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
+ {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
{NULL, NULL}
};
@@ -2615,8 +2613,7 @@ static PyMethodDef bufferedwriter_methods[] = {
_IO__BUFFERED_TELL_METHODDEF
_IO__BUFFERED___SIZEOF___METHODDEF
- {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
- {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
+ {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
{NULL, NULL}
};
@@ -2733,8 +2730,7 @@ static PyMethodDef bufferedrandom_methods[] = {
_IO_BUFFEREDWRITER_WRITE_METHODDEF
_IO__BUFFERED___SIZEOF___METHODDEF
- {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
- {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
+ {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
{NULL, NULL}
};
diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c
index e45a2d1a16d..1c71bce4fbb 100644
--- a/Modules/_io/bytesio.c
+++ b/Modules/_io/bytesio.c
@@ -1,8 +1,11 @@
#include "Python.h"
+#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()
#include "pycore_object.h"
-#include "pycore_sysmodule.h" // _PySys_GetSizeOf()
+#include "pycore_pyatomic_ft_wrappers.h"
+#include "pycore_sysmodule.h" // _PySys_GetSizeOf()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
-#include <stddef.h> // offsetof()
+#include <stddef.h> // offsetof()
#include "_iomodule.h"
/*[clinic input]
@@ -50,7 +53,7 @@ check_closed(bytesio *self)
static int
check_exports(bytesio *self)
{
- if (self->exports > 0) {
+ if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) {
PyErr_SetString(PyExc_BufferError,
"Existing exports of data: object cannot be re-sized");
return 1;
@@ -68,15 +71,17 @@ check_exports(bytesio *self)
return NULL; \
}
-#define SHARED_BUF(self) (Py_REFCNT((self)->buf) > 1)
+#define SHARED_BUF(self) (!_PyObject_IsUniquelyReferenced((self)->buf))
/* Internal routine to get a line from the buffer of a BytesIO
object. Returns the length between the current position to the
next newline character. */
static Py_ssize_t
-scan_eol(bytesio *self, Py_ssize_t len)
+scan_eol_lock_held(bytesio *self, Py_ssize_t len)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
const char *start, *n;
Py_ssize_t maxlen;
@@ -109,11 +114,13 @@ scan_eol(bytesio *self, Py_ssize_t len)
The caller should ensure that the 'size' argument is non-negative and
not lesser than self->string_size. Returns 0 on success, -1 otherwise. */
static int
-unshare_buffer(bytesio *self, size_t size)
+unshare_buffer_lock_held(bytesio *self, size_t size)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
PyObject *new_buf;
assert(SHARED_BUF(self));
- assert(self->exports == 0);
+ assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0);
assert(size >= (size_t)self->string_size);
new_buf = PyBytes_FromStringAndSize(NULL, size);
if (new_buf == NULL)
@@ -128,10 +135,12 @@ unshare_buffer(bytesio *self, size_t size)
The caller should ensure that the 'size' argument is non-negative. Returns
0 on success, -1 otherwise. */
static int
-resize_buffer(bytesio *self, size_t size)
+resize_buffer_lock_held(bytesio *self, size_t size)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
assert(self->buf != NULL);
- assert(self->exports == 0);
+ assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0);
/* Here, unsigned types are used to avoid dealing with signed integer
overflow, which is undefined in C. */
@@ -160,7 +169,7 @@ resize_buffer(bytesio *self, size_t size)
}
if (SHARED_BUF(self)) {
- if (unshare_buffer(self, alloc) < 0)
+ if (unshare_buffer_lock_held(self, alloc) < 0)
return -1;
}
else {
@@ -181,8 +190,10 @@ resize_buffer(bytesio *self, size_t size)
Inlining is disabled because it's significantly decreases performance
of writelines() in PGO build. */
Py_NO_INLINE static Py_ssize_t
-write_bytes(bytesio *self, PyObject *b)
+write_bytes_lock_held(bytesio *self, PyObject *b)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
if (check_closed(self)) {
return -1;
}
@@ -202,13 +213,13 @@ write_bytes(bytesio *self, PyObject *b)
assert(self->pos >= 0);
size_t endpos = (size_t)self->pos + len;
if (endpos > (size_t)PyBytes_GET_SIZE(self->buf)) {
- if (resize_buffer(self, endpos) < 0) {
+ if (resize_buffer_lock_held(self, endpos) < 0) {
len = -1;
goto done;
}
}
else if (SHARED_BUF(self)) {
- if (unshare_buffer(self, Py_MAX(endpos, (size_t)self->string_size)) < 0) {
+ if (unshare_buffer_lock_held(self, Py_MAX(endpos, (size_t)self->string_size)) < 0) {
len = -1;
goto done;
}
@@ -245,13 +256,17 @@ write_bytes(bytesio *self, PyObject *b)
static PyObject *
bytesio_get_closed(PyObject *op, void *Py_UNUSED(closure))
{
+ PyObject *ret;
bytesio *self = bytesio_CAST(op);
+ Py_BEGIN_CRITICAL_SECTION(self);
if (self->buf == NULL) {
- Py_RETURN_TRUE;
+ ret = Py_True;
}
else {
- Py_RETURN_FALSE;
+ ret = Py_False;
}
+ Py_END_CRITICAL_SECTION();
+ return ret;
}
/*[clinic input]
@@ -311,6 +326,7 @@ _io_BytesIO_flush_impl(bytesio *self)
}
/*[clinic input]
+@critical_section
_io.BytesIO.getbuffer
cls: defining_class
@@ -321,7 +337,7 @@ Get a read-write view over the contents of the BytesIO object.
static PyObject *
_io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject *cls)
-/*[clinic end generated code: output=045091d7ce87fe4e input=0668fbb48f95dffa]*/
+/*[clinic end generated code: output=045091d7ce87fe4e input=8295764061be77fd]*/
{
_PyIO_State *state = get_io_state_by_cls(cls);
PyTypeObject *type = state->PyBytesIOBuffer_Type;
@@ -340,6 +356,7 @@ _io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject *cls)
}
/*[clinic input]
+@critical_section
_io.BytesIO.getvalue
Retrieve the entire contents of the BytesIO object.
@@ -347,16 +364,16 @@ Retrieve the entire contents of the BytesIO object.
static PyObject *
_io_BytesIO_getvalue_impl(bytesio *self)
-/*[clinic end generated code: output=b3f6a3233c8fd628 input=4b403ac0af3973ed]*/
+/*[clinic end generated code: output=b3f6a3233c8fd628 input=c91bff398df0c352]*/
{
CHECK_CLOSED(self);
- if (self->string_size <= 1 || self->exports > 0)
+ if (self->string_size <= 1 || FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0)
return PyBytes_FromStringAndSize(PyBytes_AS_STRING(self->buf),
self->string_size);
if (self->string_size != PyBytes_GET_SIZE(self->buf)) {
if (SHARED_BUF(self)) {
- if (unshare_buffer(self, self->string_size) < 0)
+ if (unshare_buffer_lock_held(self, self->string_size) < 0)
return NULL;
}
else {
@@ -384,6 +401,7 @@ _io_BytesIO_isatty_impl(bytesio *self)
}
/*[clinic input]
+@critical_section
_io.BytesIO.tell
Current file position, an integer.
@@ -391,22 +409,24 @@ Current file position, an integer.
static PyObject *
_io_BytesIO_tell_impl(bytesio *self)
-/*[clinic end generated code: output=b54b0f93cd0e5e1d input=b106adf099cb3657]*/
+/*[clinic end generated code: output=b54b0f93cd0e5e1d input=2c7b0e8f82e05c4d]*/
{
CHECK_CLOSED(self);
return PyLong_FromSsize_t(self->pos);
}
static PyObject *
-read_bytes(bytesio *self, Py_ssize_t size)
+read_bytes_lock_held(bytesio *self, Py_ssize_t size)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
const char *output;
assert(self->buf != NULL);
assert(size <= self->string_size);
if (size > 1 &&
self->pos == 0 && size == PyBytes_GET_SIZE(self->buf) &&
- self->exports == 0) {
+ FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0) {
self->pos += size;
return Py_NewRef(self->buf);
}
@@ -417,6 +437,7 @@ read_bytes(bytesio *self, Py_ssize_t size)
}
/*[clinic input]
+@critical_section
_io.BytesIO.read
size: Py_ssize_t(accept={int, NoneType}) = -1
/
@@ -429,7 +450,7 @@ Return an empty bytes object at EOF.
static PyObject *
_io_BytesIO_read_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=9cc025f21c75bdd2 input=74344a39f431c3d7]*/
+/*[clinic end generated code: output=9cc025f21c75bdd2 input=9e2f7ff3075fdd39]*/
{
Py_ssize_t n;
@@ -443,11 +464,12 @@ _io_BytesIO_read_impl(bytesio *self, Py_ssize_t size)
size = 0;
}
- return read_bytes(self, size);
+ return read_bytes_lock_held(self, size);
}
/*[clinic input]
+@critical_section
_io.BytesIO.read1
size: Py_ssize_t(accept={int, NoneType}) = -1
/
@@ -460,12 +482,13 @@ Return an empty bytes object at EOF.
static PyObject *
_io_BytesIO_read1_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=d0f843285aa95f1c input=440a395bf9129ef5]*/
+/*[clinic end generated code: output=d0f843285aa95f1c input=a08fc9e507ab380c]*/
{
return _io_BytesIO_read_impl(self, size);
}
/*[clinic input]
+@critical_section
_io.BytesIO.readline
size: Py_ssize_t(accept={int, NoneType}) = -1
/
@@ -479,18 +502,19 @@ Return an empty bytes object at EOF.
static PyObject *
_io_BytesIO_readline_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=4bff3c251df8ffcd input=e7c3fbd1744e2783]*/
+/*[clinic end generated code: output=4bff3c251df8ffcd input=db09d47e23cf2c9e]*/
{
Py_ssize_t n;
CHECK_CLOSED(self);
- n = scan_eol(self, size);
+ n = scan_eol_lock_held(self, size);
- return read_bytes(self, n);
+ return read_bytes_lock_held(self, n);
}
/*[clinic input]
+@critical_section
_io.BytesIO.readlines
size as arg: object = None
/
@@ -504,7 +528,7 @@ total number of bytes in the lines returned.
static PyObject *
_io_BytesIO_readlines_impl(bytesio *self, PyObject *arg)
-/*[clinic end generated code: output=09b8e34c880808ff input=691aa1314f2c2a87]*/
+/*[clinic end generated code: output=09b8e34c880808ff input=5c57d7d78e409985]*/
{
Py_ssize_t maxsize, size, n;
PyObject *result, *line;
@@ -533,7 +557,7 @@ _io_BytesIO_readlines_impl(bytesio *self, PyObject *arg)
return NULL;
output = PyBytes_AS_STRING(self->buf) + self->pos;
- while ((n = scan_eol(self, -1)) != 0) {
+ while ((n = scan_eol_lock_held(self, -1)) != 0) {
self->pos += n;
line = PyBytes_FromStringAndSize(output, n);
if (!line)
@@ -556,6 +580,7 @@ _io_BytesIO_readlines_impl(bytesio *self, PyObject *arg)
}
/*[clinic input]
+@critical_section
_io.BytesIO.readinto
buffer: Py_buffer(accept={rwbuffer})
/
@@ -568,7 +593,7 @@ is set not to block and has no data to read.
static PyObject *
_io_BytesIO_readinto_impl(bytesio *self, Py_buffer *buffer)
-/*[clinic end generated code: output=a5d407217dcf0639 input=1424d0fdce857919]*/
+/*[clinic end generated code: output=a5d407217dcf0639 input=093a8d330de3fcd1]*/
{
Py_ssize_t len, n;
@@ -583,17 +608,18 @@ _io_BytesIO_readinto_impl(bytesio *self, Py_buffer *buffer)
len = 0;
}
- memcpy(buffer->buf, PyBytes_AS_STRING(self->buf) + self->pos, len);
assert(self->pos + len < PY_SSIZE_T_MAX);
assert(len >= 0);
+ memcpy(buffer->buf, PyBytes_AS_STRING(self->buf) + self->pos, len);
self->pos += len;
return PyLong_FromSsize_t(len);
}
/*[clinic input]
+@critical_section
_io.BytesIO.truncate
- size: Py_ssize_t(accept={int, NoneType}, c_default="((bytesio *)self)->pos") = None
+ size: object = None
/
Truncate the file to at most size bytes.
@@ -603,44 +629,68 @@ The current file position is unchanged. Returns the new size.
[clinic start generated code]*/
static PyObject *
-_io_BytesIO_truncate_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=9ad17650c15fa09b input=dae4295e11c1bbb4]*/
+_io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
+/*[clinic end generated code: output=ab42491b4824f384 input=b4acb5f80481c053]*/
{
CHECK_CLOSED(self);
CHECK_EXPORTS(self);
- if (size < 0) {
- PyErr_Format(PyExc_ValueError,
- "negative size value %zd", size);
- return NULL;
+ Py_ssize_t new_size;
+
+ if (size == Py_None) {
+ new_size = self->pos;
+ }
+ else {
+ new_size = PyLong_AsLong(size);
+ if (new_size == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ if (new_size < 0) {
+ PyErr_Format(PyExc_ValueError,
+ "negative size value %zd", new_size);
+ return NULL;
+ }
}
- if (size < self->string_size) {
- self->string_size = size;
- if (resize_buffer(self, size) < 0)
+ if (new_size < self->string_size) {
+ self->string_size = new_size;
+ if (resize_buffer_lock_held(self, new_size) < 0)
return NULL;
}
- return PyLong_FromSsize_t(size);
+ return PyLong_FromSsize_t(new_size);
}
static PyObject *
-bytesio_iternext(PyObject *op)
+bytesio_iternext_lock_held(PyObject *op)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
Py_ssize_t n;
bytesio *self = bytesio_CAST(op);
CHECK_CLOSED(self);
- n = scan_eol(self, -1);
+ n = scan_eol_lock_held(self, -1);
if (n == 0)
return NULL;
- return read_bytes(self, n);
+ return read_bytes_lock_held(self, n);
+}
+
+static PyObject *
+bytesio_iternext(PyObject *op)
+{
+ PyObject *ret;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ ret = bytesio_iternext_lock_held(op);
+ Py_END_CRITICAL_SECTION();
+ return ret;
}
/*[clinic input]
+@critical_section
_io.BytesIO.seek
pos: Py_ssize_t
whence: int = 0
@@ -657,7 +707,7 @@ Returns the new absolute position.
static PyObject *
_io_BytesIO_seek_impl(bytesio *self, Py_ssize_t pos, int whence)
-/*[clinic end generated code: output=c26204a68e9190e4 input=1e875e6ebc652948]*/
+/*[clinic end generated code: output=c26204a68e9190e4 input=20f05ddf659255df]*/
{
CHECK_CLOSED(self);
@@ -700,6 +750,7 @@ _io_BytesIO_seek_impl(bytesio *self, Py_ssize_t pos, int whence)
}
/*[clinic input]
+@critical_section
_io.BytesIO.write
b: object
/
@@ -711,13 +762,14 @@ Return the number of bytes written.
static PyObject *
_io_BytesIO_write_impl(bytesio *self, PyObject *b)
-/*[clinic end generated code: output=d3e46bcec8d9e21c input=f5ec7c8c64ed720a]*/
+/*[clinic end generated code: output=d3e46bcec8d9e21c input=46c0c17eac7474a4]*/
{
- Py_ssize_t n = write_bytes(self, b);
+ Py_ssize_t n = write_bytes_lock_held(self, b);
return n >= 0 ? PyLong_FromSsize_t(n) : NULL;
}
/*[clinic input]
+@critical_section
_io.BytesIO.writelines
lines: object
/
@@ -731,7 +783,7 @@ each element.
static PyObject *
_io_BytesIO_writelines_impl(bytesio *self, PyObject *lines)
-/*[clinic end generated code: output=03a43a75773bc397 input=e972539176fc8fc1]*/
+/*[clinic end generated code: output=03a43a75773bc397 input=5d6a616ae39dc9ca]*/
{
PyObject *it, *item;
@@ -742,7 +794,7 @@ _io_BytesIO_writelines_impl(bytesio *self, PyObject *lines)
return NULL;
while ((item = PyIter_Next(it)) != NULL) {
- Py_ssize_t ret = write_bytes(self, item);
+ Py_ssize_t ret = write_bytes_lock_held(self, item);
Py_DECREF(item);
if (ret < 0) {
Py_DECREF(it);
@@ -759,6 +811,7 @@ _io_BytesIO_writelines_impl(bytesio *self, PyObject *lines)
}
/*[clinic input]
+@critical_section
_io.BytesIO.close
Disable all I/O operations.
@@ -766,7 +819,7 @@ Disable all I/O operations.
static PyObject *
_io_BytesIO_close_impl(bytesio *self)
-/*[clinic end generated code: output=1471bb9411af84a0 input=37e1f55556e61f60]*/
+/*[clinic end generated code: output=1471bb9411af84a0 input=34ce76d8bd17a23b]*/
{
CHECK_EXPORTS(self);
Py_CLEAR(self->buf);
@@ -788,35 +841,49 @@ _io_BytesIO_close_impl(bytesio *self)
function to use the efficient instance representation of PEP 307.
*/
+ static PyObject *
+ bytesio_getstate_lock_held(PyObject *op)
+ {
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
+ bytesio *self = bytesio_CAST(op);
+ PyObject *initvalue = _io_BytesIO_getvalue_impl(self);
+ PyObject *dict;
+ PyObject *state;
+
+ if (initvalue == NULL)
+ return NULL;
+ if (self->dict == NULL) {
+ dict = Py_NewRef(Py_None);
+ }
+ else {
+ dict = PyDict_Copy(self->dict);
+ if (dict == NULL) {
+ Py_DECREF(initvalue);
+ return NULL;
+ }
+ }
+
+ state = Py_BuildValue("(OnN)", initvalue, self->pos, dict);
+ Py_DECREF(initvalue);
+ return state;
+}
+
static PyObject *
bytesio_getstate(PyObject *op, PyObject *Py_UNUSED(dummy))
{
- bytesio *self = bytesio_CAST(op);
- PyObject *initvalue = _io_BytesIO_getvalue_impl(self);
- PyObject *dict;
- PyObject *state;
-
- if (initvalue == NULL)
- return NULL;
- if (self->dict == NULL) {
- dict = Py_NewRef(Py_None);
- }
- else {
- dict = PyDict_Copy(self->dict);
- if (dict == NULL) {
- Py_DECREF(initvalue);
- return NULL;
- }
- }
-
- state = Py_BuildValue("(OnN)", initvalue, self->pos, dict);
- Py_DECREF(initvalue);
- return state;
+ PyObject *ret;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ ret = bytesio_getstate_lock_held(op);
+ Py_END_CRITICAL_SECTION();
+ return ret;
}
static PyObject *
-bytesio_setstate(PyObject *op, PyObject *state)
+bytesio_setstate_lock_held(PyObject *op, PyObject *state)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
PyObject *result;
PyObject *position_obj;
PyObject *dict;
@@ -890,21 +957,30 @@ bytesio_setstate(PyObject *op, PyObject *state)
Py_RETURN_NONE;
}
+static PyObject *
+bytesio_setstate(PyObject *op, PyObject *state)
+{
+ PyObject *ret;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ ret = bytesio_setstate_lock_held(op, state);
+ Py_END_CRITICAL_SECTION();
+ return ret;
+}
+
static void
bytesio_dealloc(PyObject *op)
{
bytesio *self = bytesio_CAST(op);
PyTypeObject *tp = Py_TYPE(self);
_PyObject_GC_UNTRACK(self);
- if (self->exports > 0) {
+ if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) {
PyErr_SetString(PyExc_SystemError,
"deallocated BytesIO object has exported buffers");
PyErr_Print();
}
Py_CLEAR(self->buf);
Py_CLEAR(self->dict);
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
tp->tp_free(self);
Py_DECREF(tp);
}
@@ -932,6 +1008,7 @@ bytesio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
}
/*[clinic input]
+@critical_section
_io.BytesIO.__init__
initial_bytes as initvalue: object(c_default="NULL") = b''
@@ -940,13 +1017,13 @@ Buffered I/O implementation using an in-memory bytes buffer.
static int
_io_BytesIO___init___impl(bytesio *self, PyObject *initvalue)
-/*[clinic end generated code: output=65c0c51e24c5b621 input=aac7f31b67bf0fb6]*/
+/*[clinic end generated code: output=65c0c51e24c5b621 input=3da5a74ee4c4f1ac]*/
{
/* In case, __init__ is called multiple times. */
self->string_size = 0;
self->pos = 0;
- if (self->exports > 0) {
+ if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) {
PyErr_SetString(PyExc_BufferError,
"Existing exports of data: object cannot be re-sized");
return -1;
@@ -970,8 +1047,10 @@ _io_BytesIO___init___impl(bytesio *self, PyObject *initvalue)
}
static PyObject *
-bytesio_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy))
+bytesio_sizeof_lock_held(PyObject *op)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
bytesio *self = bytesio_CAST(op);
size_t res = _PyObject_SIZE(Py_TYPE(self));
if (self->buf && !SHARED_BUF(self)) {
@@ -984,6 +1063,16 @@ bytesio_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy))
return PyLong_FromSize_t(res);
}
+static PyObject *
+bytesio_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy))
+{
+ PyObject *ret;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ ret = bytesio_sizeof_lock_held(op);
+ Py_END_CRITICAL_SECTION();
+ return ret;
+}
+
static int
bytesio_traverse(PyObject *op, visitproc visit, void *arg)
{
@@ -999,7 +1088,7 @@ bytesio_clear(PyObject *op)
{
bytesio *self = bytesio_CAST(op);
Py_CLEAR(self->dict);
- if (self->exports == 0) {
+ if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0) {
Py_CLEAR(self->buf);
}
return 0;
@@ -1077,18 +1166,15 @@ PyType_Spec bytesio_spec = {
*/
static int
-bytesiobuf_getbuffer(PyObject *op, Py_buffer *view, int flags)
+bytesiobuf_getbuffer_lock_held(PyObject *op, Py_buffer *view, int flags)
{
bytesiobuf *obj = bytesiobuf_CAST(op);
bytesio *b = bytesio_CAST(obj->source);
- if (view == NULL) {
- PyErr_SetString(PyExc_BufferError,
- "bytesiobuf_getbuffer: view==NULL argument is obsolete");
- return -1;
- }
- if (b->exports == 0 && SHARED_BUF(b)) {
- if (unshare_buffer(b, b->string_size) < 0)
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(b);
+
+ if (FT_ATOMIC_LOAD_SSIZE_RELAXED(b->exports) == 0 && SHARED_BUF(b)) {
+ if (unshare_buffer_lock_held(b, b->string_size) < 0)
return -1;
}
@@ -1096,16 +1182,32 @@ bytesiobuf_getbuffer(PyObject *op, Py_buffer *view, int flags)
(void)PyBuffer_FillInfo(view, op,
PyBytes_AS_STRING(b->buf), b->string_size,
0, flags);
- b->exports++;
+ FT_ATOMIC_ADD_SSIZE(b->exports, 1);
return 0;
}
+static int
+bytesiobuf_getbuffer(PyObject *op, Py_buffer *view, int flags)
+{
+ if (view == NULL) {
+ PyErr_SetString(PyExc_BufferError,
+ "bytesiobuf_getbuffer: view==NULL argument is obsolete");
+ return -1;
+ }
+
+ int ret;
+ Py_BEGIN_CRITICAL_SECTION(bytesiobuf_CAST(op)->source);
+ ret = bytesiobuf_getbuffer_lock_held(op, view, flags);
+ Py_END_CRITICAL_SECTION();
+ return ret;
+}
+
static void
bytesiobuf_releasebuffer(PyObject *op, Py_buffer *Py_UNUSED(view))
{
bytesiobuf *obj = bytesiobuf_CAST(op);
bytesio *b = bytesio_CAST(obj->source);
- b->exports--;
+ FT_ATOMIC_ADD_SSIZE(b->exports, -1);
}
static int
diff --git a/Modules/_io/clinic/bytesio.c.h b/Modules/_io/clinic/bytesio.c.h
index aaf4884d173..8553ed05f70 100644
--- a/Modules/_io/clinic/bytesio.c.h
+++ b/Modules/_io/clinic/bytesio.c.h
@@ -7,6 +7,7 @@ preserve
# include "pycore_runtime.h" // _Py_ID()
#endif
#include "pycore_abstract.h" // _Py_convert_optional_to_ssize_t()
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_io_BytesIO_readable__doc__,
@@ -96,11 +97,18 @@ _io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject *cls);
static PyObject *
_io_BytesIO_getbuffer(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "getbuffer() takes no arguments");
- return NULL;
+ goto exit;
}
- return _io_BytesIO_getbuffer_impl((bytesio *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _io_BytesIO_getbuffer_impl((bytesio *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(_io_BytesIO_getvalue__doc__,
@@ -118,7 +126,13 @@ _io_BytesIO_getvalue_impl(bytesio *self);
static PyObject *
_io_BytesIO_getvalue(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return _io_BytesIO_getvalue_impl((bytesio *)self);
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _io_BytesIO_getvalue_impl((bytesio *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
}
PyDoc_STRVAR(_io_BytesIO_isatty__doc__,
@@ -156,7 +170,13 @@ _io_BytesIO_tell_impl(bytesio *self);
static PyObject *
_io_BytesIO_tell(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return _io_BytesIO_tell_impl((bytesio *)self);
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _io_BytesIO_tell_impl((bytesio *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
}
PyDoc_STRVAR(_io_BytesIO_read__doc__,
@@ -190,7 +210,9 @@ _io_BytesIO_read(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
goto exit;
}
skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_read_impl((bytesio *)self, size);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -227,7 +249,9 @@ _io_BytesIO_read1(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
goto exit;
}
skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_read1_impl((bytesio *)self, size);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -265,7 +289,9 @@ _io_BytesIO_readline(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
goto exit;
}
skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_readline_impl((bytesio *)self, size);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -301,7 +327,9 @@ _io_BytesIO_readlines(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
}
arg = args[0];
skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_readlines_impl((bytesio *)self, arg);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -332,7 +360,9 @@ _io_BytesIO_readinto(PyObject *self, PyObject *arg)
_PyArg_BadArgument("readinto", "argument", "read-write bytes-like object", arg);
goto exit;
}
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_readinto_impl((bytesio *)self, &buffer);
+ Py_END_CRITICAL_SECTION();
exit:
/* Cleanup for buffer */
@@ -356,13 +386,13 @@ PyDoc_STRVAR(_io_BytesIO_truncate__doc__,
{"truncate", _PyCFunction_CAST(_io_BytesIO_truncate), METH_FASTCALL, _io_BytesIO_truncate__doc__},
static PyObject *
-_io_BytesIO_truncate_impl(bytesio *self, Py_ssize_t size);
+_io_BytesIO_truncate_impl(bytesio *self, PyObject *size);
static PyObject *
_io_BytesIO_truncate(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
- Py_ssize_t size = ((bytesio *)self)->pos;
+ PyObject *size = Py_None;
if (!_PyArg_CheckPositional("truncate", nargs, 0, 1)) {
goto exit;
@@ -370,11 +400,11 @@ _io_BytesIO_truncate(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
if (nargs < 1) {
goto skip_optional;
}
- if (!_Py_convert_optional_to_ssize_t(args[0], &size)) {
- goto exit;
- }
+ size = args[0];
skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_truncate_impl((bytesio *)self, size);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -428,7 +458,9 @@ _io_BytesIO_seek(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
goto exit;
}
skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_seek_impl((bytesio *)self, pos, whence);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -453,7 +485,9 @@ _io_BytesIO_write(PyObject *self, PyObject *b)
{
PyObject *return_value = NULL;
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_write_impl((bytesio *)self, b);
+ Py_END_CRITICAL_SECTION();
return return_value;
}
@@ -479,7 +513,9 @@ _io_BytesIO_writelines(PyObject *self, PyObject *lines)
{
PyObject *return_value = NULL;
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO_writelines_impl((bytesio *)self, lines);
+ Py_END_CRITICAL_SECTION();
return return_value;
}
@@ -499,7 +535,13 @@ _io_BytesIO_close_impl(bytesio *self);
static PyObject *
_io_BytesIO_close(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return _io_BytesIO_close_impl((bytesio *)self);
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _io_BytesIO_close_impl((bytesio *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
}
PyDoc_STRVAR(_io_BytesIO___init____doc__,
@@ -558,9 +600,11 @@ _io_BytesIO___init__(PyObject *self, PyObject *args, PyObject *kwargs)
}
initvalue = fastargs[0];
skip_optional_pos:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_BytesIO___init___impl((bytesio *)self, initvalue);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
}
-/*[clinic end generated code: output=6dbfd82f4e9d4ef3 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=580205daa01def2e input=a9049054013a1b77]*/
diff --git a/Modules/_io/clinic/stringio.c.h b/Modules/_io/clinic/stringio.c.h
index 8e8cd8df9ab..83165e5f7ad 100644
--- a/Modules/_io/clinic/stringio.c.h
+++ b/Modules/_io/clinic/stringio.c.h
@@ -149,13 +149,13 @@ PyDoc_STRVAR(_io_StringIO_truncate__doc__,
{"truncate", _PyCFunction_CAST(_io_StringIO_truncate), METH_FASTCALL, _io_StringIO_truncate__doc__},
static PyObject *
-_io_StringIO_truncate_impl(stringio *self, Py_ssize_t size);
+_io_StringIO_truncate_impl(stringio *self, PyObject *pos);
static PyObject *
_io_StringIO_truncate(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
- Py_ssize_t size = ((stringio *)self)->pos;
+ PyObject *pos = Py_None;
if (!_PyArg_CheckPositional("truncate", nargs, 0, 1)) {
goto exit;
@@ -163,12 +163,10 @@ _io_StringIO_truncate(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
if (nargs < 1) {
goto skip_optional;
}
- if (!_Py_convert_optional_to_ssize_t(args[0], &size)) {
- goto exit;
- }
+ pos = args[0];
skip_optional:
Py_BEGIN_CRITICAL_SECTION(self);
- return_value = _io_StringIO_truncate_impl((stringio *)self, size);
+ return_value = _io_StringIO_truncate_impl((stringio *)self, pos);
Py_END_CRITICAL_SECTION();
exit:
@@ -552,4 +550,4 @@ _io_StringIO_newlines_get(PyObject *self, void *Py_UNUSED(context))
return return_value;
}
-/*[clinic end generated code: output=5bfaaab7f41ee6b5 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=bccc25ef8e6ce9ef input=a9049054013a1b77]*/
diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c
index 0c5424954be..26537fc6395 100644
--- a/Modules/_io/fileio.c
+++ b/Modules/_io/fileio.c
@@ -4,6 +4,7 @@
#include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stdbool.h> // bool
#ifdef HAVE_UNISTD_H
@@ -570,9 +571,7 @@ fileio_dealloc(PyObject *op)
PyMem_Free(self->stat_atopen);
self->stat_atopen = NULL;
}
- if (self->weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
(void)fileio_clear(op);
PyTypeObject *tp = Py_TYPE(op);
@@ -1262,8 +1261,7 @@ static PyMethodDef fileio_methods[] = {
_IO_FILEIO_ISATTY_METHODDEF
{"_isatty_open_only", _io_FileIO_isatty_open_only, METH_NOARGS},
{"_dealloc_warn", fileio_dealloc_warn, METH_O, NULL},
- {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
- {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
+ {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
{NULL, NULL} /* sentinel */
};
diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c
index cd4c7e7cead..044f6b7803c 100644
--- a/Modules/_io/iobase.c
+++ b/Modules/_io/iobase.c
@@ -14,6 +14,7 @@
#include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_object.h" // _PyType_HasFeature()
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stddef.h> // offsetof()
#include "_iomodule.h"
@@ -383,8 +384,7 @@ iobase_dealloc(PyObject *op)
}
PyTypeObject *tp = Py_TYPE(self);
_PyObject_GC_UNTRACK(self);
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
Py_CLEAR(self->dict);
tp->tp_free(self);
Py_DECREF(tp);
diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c
index 9d1bfa3ea05..20b7cfc0088 100644
--- a/Modules/_io/stringio.c
+++ b/Modules/_io/stringio.c
@@ -1,6 +1,7 @@
#include "Python.h"
#include <stddef.h> // offsetof()
#include "pycore_object.h"
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "_iomodule.h"
/* Implementation note: the buffer is always at least one character longer
@@ -444,7 +445,7 @@ stringio_iternext(PyObject *op)
/*[clinic input]
@critical_section
_io.StringIO.truncate
- pos as size: Py_ssize_t(accept={int, NoneType}, c_default="((stringio *)self)->pos") = None
+ pos: object = None
/
Truncate size to pos.
@@ -455,16 +456,26 @@ Returns the new absolute position.
[clinic start generated code]*/
static PyObject *
-_io_StringIO_truncate_impl(stringio *self, Py_ssize_t size)
-/*[clinic end generated code: output=eb3aef8e06701365 input=fa8a6c98bb2ba780]*/
+_io_StringIO_truncate_impl(stringio *self, PyObject *pos)
+/*[clinic end generated code: output=c76c43b5ecfaf4e2 input=d59fd2ee49757ae6]*/
{
CHECK_INITIALIZED(self);
CHECK_CLOSED(self);
- if (size < 0) {
- PyErr_Format(PyExc_ValueError,
- "Negative size value %zd", size);
- return NULL;
+ Py_ssize_t size;
+ if (pos == Py_None) {
+ size = self->pos;
+ }
+ else {
+ size = PyLong_AsLong(pos);
+ if (size == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ if (size < 0) {
+ PyErr_Format(PyExc_ValueError,
+ "negative pos value %zd", size);
+ return NULL;
+ }
}
if (size < self->string_size) {
@@ -628,9 +639,7 @@ stringio_dealloc(PyObject *op)
}
PyUnicodeWriter_Discard(self->writer);
(void)stringio_clear(op);
- if (self->weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
tp->tp_free(self);
Py_DECREF(tp);
}
diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index a5b2ca7240a..5354cf63442 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -16,6 +16,7 @@
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_unicodeobject.h" // _PyUnicode_AsASCIIString()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "_iomodule.h"
@@ -1469,8 +1470,7 @@ textiowrapper_dealloc(PyObject *op)
return;
self->ok = 0;
_PyObject_GC_UNTRACK(self);
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
(void)textiowrapper_clear(op);
tp->tp_free(self);
Py_DECREF(tp);
@@ -1578,6 +1578,8 @@ _io_TextIOWrapper_detach_impl(textio *self)
static int
_textiowrapper_writeflush(textio *self)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
if (self->pending_bytes == NULL)
return 0;
@@ -3173,8 +3175,9 @@ _io_TextIOWrapper_close_impl(textio *self)
}
static PyObject *
-textiowrapper_iternext(PyObject *op)
+textiowrapper_iternext_lock_held(PyObject *op)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
PyObject *line;
textio *self = textio_CAST(op);
@@ -3210,6 +3213,16 @@ textiowrapper_iternext(PyObject *op)
return line;
}
+static PyObject *
+textiowrapper_iternext(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = textiowrapper_iternext_lock_held(op);
+ Py_END_CRITICAL_SECTION();
+ return result;
+}
+
/*[clinic input]
@critical_section
@getter
@@ -3366,8 +3379,7 @@ static PyMethodDef textiowrapper_methods[] = {
_IO_TEXTIOWRAPPER_TELL_METHODDEF
_IO_TEXTIOWRAPPER_TRUNCATE_METHODDEF
- {"__reduce__", _PyIOBase_cannot_pickle, METH_NOARGS},
- {"__reduce_ex__", _PyIOBase_cannot_pickle, METH_O},
+ {"__getstate__", _PyIOBase_cannot_pickle, METH_NOARGS},
{NULL, NULL}
};
diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c
index 3e783b9da45..950b7fe241c 100644
--- a/Modules/_io/winconsoleio.c
+++ b/Modules/_io/winconsoleio.c
@@ -10,6 +10,7 @@
#include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#ifdef HAVE_WINDOWS_CONSOLE_IO
@@ -518,8 +519,7 @@ winconsoleio_dealloc(PyObject *op)
if (_PyIOBase_finalize(op) < 0)
return;
_PyObject_GC_UNTRACK(self);
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
Py_CLEAR(self->dict);
tp->tp_free(self);
Py_DECREF(tp);
diff --git a/Modules/_json.c b/Modules/_json.c
index 89b0a41dd10..6b5f6ea42df 100644
--- a/Modules/_json.c
+++ b/Modules/_json.c
@@ -360,13 +360,6 @@ _build_rval_index_tuple(PyObject *rval, Py_ssize_t idx) {
return tpl;
}
-static inline int
-_PyUnicodeWriter_IsEmpty(PyUnicodeWriter *writer_pub)
-{
- _PyUnicodeWriter *writer = (_PyUnicodeWriter*)writer_pub;
- return (writer->pos == 0);
-}
-
static PyObject *
scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr)
{
@@ -385,10 +378,7 @@ scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next
const void *buf;
int kind;
- PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
- if (writer == NULL) {
- goto bail;
- }
+ PyUnicodeWriter *writer = NULL;
len = PyUnicode_GET_LENGTH(pystr);
buf = PyUnicode_DATA(pystr);
@@ -419,12 +409,11 @@ scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next
if (c == '"') {
// Fast path for simple case.
- if (_PyUnicodeWriter_IsEmpty(writer)) {
+ if (writer == NULL) {
PyObject *ret = PyUnicode_Substring(pystr, end, next);
if (ret == NULL) {
goto bail;
}
- PyUnicodeWriter_Discard(writer);
*next_end_ptr = next + 1;;
return ret;
}
@@ -432,6 +421,11 @@ scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next
else if (c != '\\') {
raise_errmsg("Unterminated string starting at", pystr, begin);
goto bail;
+ } else if (writer == NULL) {
+ writer = PyUnicodeWriter_Create(0);
+ if (writer == NULL) {
+ goto bail;
+ }
}
/* Pick up this chunk if it's not zero length */
@@ -1476,13 +1470,13 @@ encoder_listencode_obj(PyEncoderObject *s, PyUnicodeWriter *writer,
int rv;
if (obj == Py_None) {
- return PyUnicodeWriter_WriteUTF8(writer, "null", 4);
+ return PyUnicodeWriter_WriteASCII(writer, "null", 4);
}
else if (obj == Py_True) {
- return PyUnicodeWriter_WriteUTF8(writer, "true", 4);
+ return PyUnicodeWriter_WriteASCII(writer, "true", 4);
}
else if (obj == Py_False) {
- return PyUnicodeWriter_WriteUTF8(writer, "false", 5);
+ return PyUnicodeWriter_WriteASCII(writer, "false", 5);
}
else if (PyUnicode_Check(obj)) {
PyObject *encoded = encoder_encode_string(s, obj);
@@ -1609,6 +1603,12 @@ encoder_encode_key_value(PyEncoderObject *s, PyUnicodeWriter *writer, bool *firs
if (*first) {
*first = false;
+ if (s->indent != Py_None) {
+ if (write_newline_indent(writer, indent_level, indent_cache) < 0) {
+ Py_DECREF(keystr);
+ return -1;
+ }
+ }
}
else {
if (PyUnicodeWriter_WriteStr(writer, item_separator) < 0) {
@@ -1649,7 +1649,7 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer,
if (PyDict_GET_SIZE(dct) == 0) {
/* Fast path */
- return PyUnicodeWriter_WriteUTF8(writer, "{}", 2);
+ return PyUnicodeWriter_WriteASCII(writer, "{}", 2);
}
if (s->markers != Py_None) {
@@ -1676,11 +1676,8 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer,
if (s->indent != Py_None) {
indent_level++;
separator = get_item_separator(s, indent_level, indent_cache);
- if (separator == NULL ||
- write_newline_indent(writer, indent_level, indent_cache) < 0)
- {
+ if (separator == NULL)
goto bail;
- }
}
if (s->sort_keys || !PyDict_CheckExact(dct)) {
@@ -1720,7 +1717,7 @@ encoder_listencode_dict(PyEncoderObject *s, PyUnicodeWriter *writer,
goto bail;
Py_CLEAR(ident);
}
- if (s->indent != Py_None) {
+ if (s->indent != Py_None && !first) {
indent_level--;
if (write_newline_indent(writer, indent_level, indent_cache) < 0) {
goto bail;
@@ -1753,7 +1750,7 @@ encoder_listencode_list(PyEncoderObject *s, PyUnicodeWriter *writer,
return -1;
if (PySequence_Fast_GET_SIZE(s_fast) == 0) {
Py_DECREF(s_fast);
- return PyUnicodeWriter_WriteUTF8(writer, "[]", 2);
+ return PyUnicodeWriter_WriteASCII(writer, "[]", 2);
}
if (s->markers != Py_None) {
diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c
index ad618398d5b..41e6d48b1db 100644
--- a/Modules/_localemodule.c
+++ b/Modules/_localemodule.c
@@ -692,7 +692,17 @@ _locale_nl_langinfo_impl(PyObject *module, int item)
result = result != NULL ? result : "";
char *oldloc = NULL;
if (langinfo_constants[i].category != LC_CTYPE
- && !is_all_ascii(result)
+ && *result && (
+#ifdef __GLIBC__
+ // gh-133740: Always change the locale for ALT_DIGITS and ERA
+# ifdef ALT_DIGITS
+ item == ALT_DIGITS ||
+# endif
+# ifdef ERA
+ item == ERA ||
+# endif
+#endif
+ !is_all_ascii(result))
&& change_locale(langinfo_constants[i].category, &oldloc) < 0)
{
return NULL;
diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c
index 626c176715b..d0074b2a0d1 100644
--- a/Modules/_lsprof.c
+++ b/Modules/_lsprof.c
@@ -632,6 +632,27 @@ _lsprof_Profiler__pystart_callback_impl(ProfilerObject *self, PyObject *code,
}
/*[clinic input]
+_lsprof.Profiler._pythrow_callback
+
+ code: object
+ instruction_offset: object
+ exception: object
+ /
+
+[clinic start generated code]*/
+
+static PyObject *
+_lsprof_Profiler__pythrow_callback_impl(ProfilerObject *self, PyObject *code,
+ PyObject *instruction_offset,
+ PyObject *exception)
+/*[clinic end generated code: output=0a32988919dfb94c input=fd728fc2c074f5e6]*/
+{
+ ptrace_enter_call((PyObject*)self, (void *)code, code);
+
+ Py_RETURN_NONE;
+}
+
+/*[clinic input]
_lsprof.Profiler._pyreturn_callback
code: object
@@ -747,7 +768,7 @@ static const struct {
} callback_table[] = {
{PY_MONITORING_EVENT_PY_START, "_pystart_callback"},
{PY_MONITORING_EVENT_PY_RESUME, "_pystart_callback"},
- {PY_MONITORING_EVENT_PY_THROW, "_pystart_callback"},
+ {PY_MONITORING_EVENT_PY_THROW, "_pythrow_callback"},
{PY_MONITORING_EVENT_PY_RETURN, "_pyreturn_callback"},
{PY_MONITORING_EVENT_PY_YIELD, "_pyreturn_callback"},
{PY_MONITORING_EVENT_PY_UNWIND, "_pyreturn_callback"},
@@ -782,7 +803,7 @@ _lsprof_Profiler_enable_impl(ProfilerObject *self, int subcalls,
return NULL;
}
- PyObject* monitoring = PyImport_ImportModuleAttrString("sys", "monitoring");
+ PyObject* monitoring = PySys_GetAttrString("monitoring");
if (!monitoring) {
return NULL;
}
@@ -864,7 +885,7 @@ _lsprof_Profiler_disable_impl(ProfilerObject *self)
}
if (self->flags & POF_ENABLED) {
PyObject* result = NULL;
- PyObject* monitoring = PyImport_ImportModuleAttrString("sys", "monitoring");
+ PyObject* monitoring = PySys_GetAttrString("monitoring");
if (!monitoring) {
return NULL;
@@ -983,7 +1004,7 @@ profiler_init_impl(ProfilerObject *self, PyObject *timer, double timeunit,
Py_XSETREF(self->externalTimer, Py_XNewRef(timer));
self->tool_id = PY_MONITORING_PROFILER_ID;
- PyObject* monitoring = PyImport_ImportModuleAttrString("sys", "monitoring");
+ PyObject* monitoring = PySys_GetAttrString("monitoring");
if (!monitoring) {
return -1;
}
@@ -1002,6 +1023,7 @@ static PyMethodDef profiler_methods[] = {
_LSPROF_PROFILER_DISABLE_METHODDEF
_LSPROF_PROFILER_CLEAR_METHODDEF
_LSPROF_PROFILER__PYSTART_CALLBACK_METHODDEF
+ _LSPROF_PROFILER__PYTHROW_CALLBACK_METHODDEF
_LSPROF_PROFILER__PYRETURN_CALLBACK_METHODDEF
_LSPROF_PROFILER__CCALL_CALLBACK_METHODDEF
_LSPROF_PROFILER__CRETURN_CALLBACK_METHODDEF
diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c
index f9b4c2a170e..462c2181fa6 100644
--- a/Modules/_lzmamodule.c
+++ b/Modules/_lzmamodule.c
@@ -17,6 +17,7 @@
#include <lzma.h>
+#include "pycore_long.h" // _PyLong_UInt32_Converter()
// Blocks output buffer wrappers
#include "pycore_blocks_output_buffer.h"
@@ -223,8 +224,6 @@ FUNCNAME(PyObject *obj, void *ptr) \
return 1; \
}
-INT_TYPE_CONVERTER_FUNC(uint32_t, uint32_converter)
-INT_TYPE_CONVERTER_FUNC(uint64_t, uint64_converter)
INT_TYPE_CONVERTER_FUNC(lzma_vli, lzma_vli_converter)
INT_TYPE_CONVERTER_FUNC(lzma_mode, lzma_mode_converter)
INT_TYPE_CONVERTER_FUNC(lzma_match_finder, lzma_mf_converter)
@@ -254,7 +253,7 @@ parse_filter_spec_lzma(_lzma_state *state, PyObject *spec)
return NULL;
}
if (preset_obj != NULL) {
- int ok = uint32_converter(preset_obj, &preset);
+ int ok = _PyLong_UInt32_Converter(preset_obj, &preset);
Py_DECREF(preset_obj);
if (!ok) {
return NULL;
@@ -275,14 +274,14 @@ parse_filter_spec_lzma(_lzma_state *state, PyObject *spec)
if (!PyArg_ParseTupleAndKeywords(state->empty_tuple, spec,
"|OOO&O&O&O&O&O&O&O&", optnames,
&id, &preset_obj,
- uint32_converter, &options->dict_size,
- uint32_converter, &options->lc,
- uint32_converter, &options->lp,
- uint32_converter, &options->pb,
+ _PyLong_UInt32_Converter, &options->dict_size,
+ _PyLong_UInt32_Converter, &options->lc,
+ _PyLong_UInt32_Converter, &options->lp,
+ _PyLong_UInt32_Converter, &options->pb,
lzma_mode_converter, &options->mode,
- uint32_converter, &options->nice_len,
+ _PyLong_UInt32_Converter, &options->nice_len,
lzma_mf_converter, &options->mf,
- uint32_converter, &options->depth)) {
+ _PyLong_UInt32_Converter, &options->depth)) {
PyErr_SetString(PyExc_ValueError,
"Invalid filter specifier for LZMA filter");
PyMem_Free(options);
@@ -301,7 +300,7 @@ parse_filter_spec_delta(_lzma_state *state, PyObject *spec)
lzma_options_delta *options;
if (!PyArg_ParseTupleAndKeywords(state->empty_tuple, spec, "|OO&", optnames,
- &id, uint32_converter, &dist)) {
+ &id, _PyLong_UInt32_Converter, &dist)) {
PyErr_SetString(PyExc_ValueError,
"Invalid filter specifier for delta filter");
return NULL;
@@ -325,7 +324,7 @@ parse_filter_spec_bcj(_lzma_state *state, PyObject *spec)
lzma_options_bcj *options;
if (!PyArg_ParseTupleAndKeywords(state->empty_tuple, spec, "|OO&", optnames,
- &id, uint32_converter, &start_offset)) {
+ &id, _PyLong_UInt32_Converter, &start_offset)) {
PyErr_SetString(PyExc_ValueError,
"Invalid filter specifier for BCJ filter");
return NULL;
@@ -806,7 +805,7 @@ Compressor_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
return NULL;
}
- if (preset_obj != Py_None && !uint32_converter(preset_obj, &preset)) {
+ if (preset_obj != Py_None && !_PyLong_UInt32_Converter(preset_obj, &preset)) {
return NULL;
}
@@ -1226,7 +1225,7 @@ _lzma_LZMADecompressor_impl(PyTypeObject *type, int format,
"Cannot specify memory limit with FORMAT_RAW");
return NULL;
}
- if (!uint64_converter(memlimit, &memlimit_)) {
+ if (!_PyLong_UInt64_Converter(memlimit, &memlimit_)) {
return NULL;
}
}
diff --git a/Modules/_opcode.c b/Modules/_opcode.c
index c295f7b3152..ef271b6ef56 100644
--- a/Modules/_opcode.c
+++ b/Modules/_opcode.c
@@ -5,7 +5,7 @@
#include "Python.h"
#include "compile.h"
#include "opcode.h"
-#include "pycore_ceval.h"
+#include "pycore_ceval.h" // SPECIAL_MAX
#include "pycore_code.h"
#include "pycore_compile.h"
#include "pycore_intrinsics.h"
diff --git a/Modules/_pickle.c b/Modules/_pickle.c
index d260f1a68f8..cf3ceb43fb3 100644
--- a/Modules/_pickle.c
+++ b/Modules/_pickle.c
@@ -1879,6 +1879,10 @@ _checkmodule(PyObject *module_name, PyObject *module,
_PyUnicode_EqualToASCIIString(module_name, "__main__")) {
return -1;
}
+ if (PyUnicode_Check(module_name) &&
+ _PyUnicode_EqualToASCIIString(module_name, "__mp_main__")) {
+ return -1;
+ }
PyObject *candidate = getattribute(module, dotted_path, 0);
if (candidate == NULL) {
@@ -1911,7 +1915,7 @@ whichmodule(PickleState *st, PyObject *global, PyObject *global_name, PyObject *
__module__ can be None. If it is so, then search sys.modules for
the module of global. */
Py_CLEAR(module_name);
- modules = _PySys_GetRequiredAttr(&_Py_ID(modules));
+ modules = PySys_GetAttr(&_Py_ID(modules));
if (modules == NULL) {
return NULL;
}
@@ -5539,17 +5543,16 @@ static int
load_counted_binstring(PickleState *st, UnpicklerObject *self, int nbytes)
{
PyObject *obj;
- Py_ssize_t size;
+ long size;
char *s;
if (_Unpickler_Read(self, st, &s, nbytes) < 0)
return -1;
- size = calc_binsize(s, nbytes);
+ size = calc_binint(s, nbytes);
if (size < 0) {
- PyErr_Format(st->UnpicklingError,
- "BINSTRING exceeds system's maximum size of %zd bytes",
- PY_SSIZE_T_MAX);
+ PyErr_SetString(st->UnpicklingError,
+ "BINSTRING pickle has negative byte count");
return -1;
}
diff --git a/Modules/_queuemodule.c b/Modules/_queuemodule.c
index 3ee14b61b82..01235c77bd7 100644
--- a/Modules/_queuemodule.c
+++ b/Modules/_queuemodule.c
@@ -7,6 +7,7 @@
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_parking_lot.h"
#include "pycore_time.h" // _PyTime_FromSecondsObject()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stdbool.h>
#include <stddef.h> // offsetof()
@@ -221,9 +222,7 @@ simplequeue_dealloc(PyObject *op)
PyObject_GC_UnTrack(self);
(void)simplequeue_clear(op);
- if (self->weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
tp->tp_free(self);
Py_DECREF(tp);
}
diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c
index d5bac2f5b78..2f4f388ce11 100644
--- a/Modules/_randommodule.c
+++ b/Modules/_randommodule.c
@@ -497,34 +497,32 @@ _random_Random_setstate_impl(RandomObject *self, PyObject *state)
_random.Random.getrandbits
self: self(type="RandomObject *")
- k: int
+ k: uint64
/
getrandbits(k) -> x. Generates an int with k random bits.
[clinic start generated code]*/
static PyObject *
-_random_Random_getrandbits_impl(RandomObject *self, int k)
-/*[clinic end generated code: output=b402f82a2158887f input=87603cd60f79f730]*/
+_random_Random_getrandbits_impl(RandomObject *self, uint64_t k)
+/*[clinic end generated code: output=c30ef8435f3433cf input=64226ac13bb4d2a3]*/
{
- int i, words;
+ Py_ssize_t i, words;
uint32_t r;
uint32_t *wordarray;
PyObject *result;
- if (k < 0) {
- PyErr_SetString(PyExc_ValueError,
- "number of bits must be non-negative");
- return NULL;
- }
-
if (k == 0)
return PyLong_FromLong(0);
if (k <= 32) /* Fast path */
return PyLong_FromUnsignedLong(genrand_uint32(self) >> (32 - k));
- words = (k - 1) / 32 + 1;
+ if ((k - 1u) / 32u + 1u > PY_SSIZE_T_MAX / 4u) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ words = (k - 1u) / 32u + 1u;
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
if (wordarray == NULL) {
PyErr_NoMemory();
diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c
new file mode 100644
index 00000000000..ce7189637c2
--- /dev/null
+++ b/Modules/_remote_debugging_module.c
@@ -0,0 +1,3139 @@
+/******************************************************************************
+ * Python Remote Debugging Module
+ *
+ * This module provides functionality to debug Python processes remotely by
+ * reading their memory and reconstructing stack traces and asyncio task states.
+ ******************************************************************************/
+
+#define _GNU_SOURCE
+
+/* ============================================================================
+ * HEADERS AND INCLUDES
+ * ============================================================================ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
+#endif
+#include "Python.h"
+#include <internal/pycore_debug_offsets.h> // _Py_DebugOffsets
+#include <internal/pycore_frame.h> // FRAME_SUSPENDED_YIELD_FROM
+#include <internal/pycore_interpframe.h> // FRAME_OWNED_BY_CSTACK
+#include <internal/pycore_llist.h> // struct llist_node
+#include <internal/pycore_stackref.h> // Py_TAG_BITS
+#include "../Python/remote_debug.h"
+
+#ifndef HAVE_PROCESS_VM_READV
+# define HAVE_PROCESS_VM_READV 0
+#endif
+
+/* ============================================================================
+ * TYPE DEFINITIONS AND STRUCTURES
+ * ============================================================================ */
+
+#define GET_MEMBER(type, obj, offset) (*(type*)((char*)(obj) + (offset)))
+#define CLEAR_PTR_TAG(ptr) (((uintptr_t)(ptr) & ~Py_TAG_BITS))
+#define GET_MEMBER_NO_TAG(type, obj, offset) (type)(CLEAR_PTR_TAG(*(type*)((char*)(obj) + (offset))))
+
+/* Size macros for opaque buffers */
+#define SIZEOF_BYTES_OBJ sizeof(PyBytesObject)
+#define SIZEOF_CODE_OBJ sizeof(PyCodeObject)
+#define SIZEOF_GEN_OBJ sizeof(PyGenObject)
+#define SIZEOF_INTERP_FRAME sizeof(_PyInterpreterFrame)
+#define SIZEOF_LLIST_NODE sizeof(struct llist_node)
+#define SIZEOF_PAGE_CACHE_ENTRY sizeof(page_cache_entry_t)
+#define SIZEOF_PYOBJECT sizeof(PyObject)
+#define SIZEOF_SET_OBJ sizeof(PySetObject)
+#define SIZEOF_TASK_OBJ 4096
+#define SIZEOF_THREAD_STATE sizeof(PyThreadState)
+#define SIZEOF_TYPE_OBJ sizeof(PyTypeObject)
+#define SIZEOF_UNICODE_OBJ sizeof(PyUnicodeObject)
+#define SIZEOF_LONG_OBJ sizeof(PyLongObject)
+
+// Calculate the minimum buffer size needed to read interpreter state fields
+// We need to read code_object_generation and potentially tlbc_generation
+#ifndef MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifdef Py_GIL_DISABLED
+#define INTERP_STATE_MIN_SIZE MAX(MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
+ offsetof(PyInterpreterState, tlbc_indices.tlbc_generation) + sizeof(uint32_t)), \
+ offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \
+ offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*))
+#else
+#define INTERP_STATE_MIN_SIZE MAX(MAX(offsetof(PyInterpreterState, _code_object_generation) + sizeof(uint64_t), \
+ offsetof(PyInterpreterState, threads.head) + sizeof(void*)), \
+ offsetof(PyInterpreterState, _gil.last_holder) + sizeof(PyThreadState*))
+#endif
+#define INTERP_STATE_BUFFER_SIZE MAX(INTERP_STATE_MIN_SIZE, 256)
+
+
+
+// Copied from Modules/_asynciomodule.c because it's not exported
+
+struct _Py_AsyncioModuleDebugOffsets {
+ struct _asyncio_task_object {
+ uint64_t size;
+ uint64_t task_name;
+ uint64_t task_awaited_by;
+ uint64_t task_is_task;
+ uint64_t task_awaited_by_is_set;
+ uint64_t task_coro;
+ uint64_t task_node;
+ } asyncio_task_object;
+ struct _asyncio_interpreter_state {
+ uint64_t size;
+ uint64_t asyncio_tasks_head;
+ } asyncio_interpreter_state;
+ struct _asyncio_thread_state {
+ uint64_t size;
+ uint64_t asyncio_running_loop;
+ uint64_t asyncio_running_task;
+ uint64_t asyncio_tasks_head;
+ } asyncio_thread_state;
+};
+
+/* ============================================================================
+ * STRUCTSEQ TYPE DEFINITIONS
+ * ============================================================================ */
+
+// TaskInfo structseq type - replaces 4-tuple (task_id, task_name, coroutine_stack, awaited_by)
+static PyStructSequence_Field TaskInfo_fields[] = {
+ {"task_id", "Task ID (memory address)"},
+ {"task_name", "Task name"},
+ {"coroutine_stack", "Coroutine call stack"},
+ {"awaited_by", "Tasks awaiting this task"},
+ {NULL}
+};
+
+static PyStructSequence_Desc TaskInfo_desc = {
+ "_remote_debugging.TaskInfo",
+ "Information about an asyncio task",
+ TaskInfo_fields,
+ 4
+};
+
+// FrameInfo structseq type - replaces 3-tuple (filename, lineno, funcname)
+static PyStructSequence_Field FrameInfo_fields[] = {
+ {"filename", "Source code filename"},
+ {"lineno", "Line number"},
+ {"funcname", "Function name"},
+ {NULL}
+};
+
+static PyStructSequence_Desc FrameInfo_desc = {
+ "_remote_debugging.FrameInfo",
+ "Information about a frame",
+ FrameInfo_fields,
+ 3
+};
+
+// CoroInfo structseq type - replaces 2-tuple (call_stack, task_name)
+static PyStructSequence_Field CoroInfo_fields[] = {
+ {"call_stack", "Coroutine call stack"},
+ {"task_name", "Task name"},
+ {NULL}
+};
+
+static PyStructSequence_Desc CoroInfo_desc = {
+ "_remote_debugging.CoroInfo",
+ "Information about a coroutine",
+ CoroInfo_fields,
+ 2
+};
+
+// ThreadInfo structseq type - replaces 2-tuple (thread_id, frame_info)
+static PyStructSequence_Field ThreadInfo_fields[] = {
+ {"thread_id", "Thread ID"},
+ {"frame_info", "Frame information"},
+ {NULL}
+};
+
+static PyStructSequence_Desc ThreadInfo_desc = {
+ "_remote_debugging.ThreadInfo",
+ "Information about a thread",
+ ThreadInfo_fields,
+ 2
+};
+
+// AwaitedInfo structseq type - replaces 2-tuple (tid, awaited_by_list)
+static PyStructSequence_Field AwaitedInfo_fields[] = {
+ {"thread_id", "Thread ID"},
+ {"awaited_by", "List of tasks awaited by this thread"},
+ {NULL}
+};
+
+static PyStructSequence_Desc AwaitedInfo_desc = {
+ "_remote_debugging.AwaitedInfo",
+ "Information about what a thread is awaiting",
+ AwaitedInfo_fields,
+ 2
+};
+
+typedef struct {
+ PyObject *func_name;
+ PyObject *file_name;
+ int first_lineno;
+ PyObject *linetable; // bytes
+ uintptr_t addr_code_adaptive;
+} CachedCodeMetadata;
+
+typedef struct {
+ /* Types */
+ PyTypeObject *RemoteDebugging_Type;
+ PyTypeObject *TaskInfo_Type;
+ PyTypeObject *FrameInfo_Type;
+ PyTypeObject *CoroInfo_Type;
+ PyTypeObject *ThreadInfo_Type;
+ PyTypeObject *AwaitedInfo_Type;
+} RemoteDebuggingState;
+
+typedef struct {
+ PyObject_HEAD
+ proc_handle_t handle;
+ uintptr_t runtime_start_address;
+ struct _Py_DebugOffsets debug_offsets;
+ int async_debug_offsets_available;
+ struct _Py_AsyncioModuleDebugOffsets async_debug_offsets;
+ uintptr_t interpreter_addr;
+ uintptr_t tstate_addr;
+ uint64_t code_object_generation;
+ _Py_hashtable_t *code_object_cache;
+ int debug;
+ int only_active_thread;
+ RemoteDebuggingState *cached_state; // Cached module state
+#ifdef Py_GIL_DISABLED
+ // TLBC cache invalidation tracking
+ uint32_t tlbc_generation; // Track TLBC index pool changes
+ _Py_hashtable_t *tlbc_cache; // Cache of TLBC arrays by code object address
+#endif
+} RemoteUnwinderObject;
+
+#define RemoteUnwinder_CAST(op) ((RemoteUnwinderObject *)(op))
+
+typedef struct
+{
+ int lineno;
+ int end_lineno;
+ int column;
+ int end_column;
+} LocationInfo;
+
+typedef struct {
+ uintptr_t remote_addr;
+ size_t size;
+ void *local_copy;
+} StackChunkInfo;
+
+typedef struct {
+ StackChunkInfo *chunks;
+ size_t count;
+} StackChunkList;
+
+#include "clinic/_remote_debugging_module.c.h"
+
+/*[clinic input]
+module _remote_debugging
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=5f507d5b2e76a7f7]*/
+
+
+/* ============================================================================
+ * FORWARD DECLARATIONS
+ * ============================================================================ */
+
+static inline int
+is_frame_valid(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t frame_addr,
+ uintptr_t code_object_addr
+);
+
+static int
+parse_tasks_in_set(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t set_addr,
+ PyObject *awaited_by,
+ int recurse_task
+);
+
+static int
+parse_task(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_address,
+ PyObject *render_to,
+ int recurse_task
+);
+
+static int
+parse_coro_chain(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t coro_address,
+ PyObject *render_to
+);
+
+/* Forward declarations for task parsing functions */
+static int parse_frame_object(
+ RemoteUnwinderObject *unwinder,
+ PyObject** result,
+ uintptr_t address,
+ uintptr_t* previous_frame
+);
+
+/* ============================================================================
+ * UTILITY FUNCTIONS AND HELPERS
+ * ============================================================================ */
+
+#define set_exception_cause(unwinder, exc_type, message) \
+ if (unwinder->debug) { \
+ _set_debug_exception_cause(exc_type, message); \
+ }
+
+static void
+cached_code_metadata_destroy(void *ptr)
+{
+ CachedCodeMetadata *meta = (CachedCodeMetadata *)ptr;
+ Py_DECREF(meta->func_name);
+ Py_DECREF(meta->file_name);
+ Py_DECREF(meta->linetable);
+ PyMem_RawFree(meta);
+}
+
+static inline RemoteDebuggingState *
+RemoteDebugging_GetState(PyObject *module)
+{
+ void *state = _PyModule_GetState(module);
+ assert(state != NULL);
+ return (RemoteDebuggingState *)state;
+}
+
+static inline RemoteDebuggingState *
+RemoteDebugging_GetStateFromType(PyTypeObject *type)
+{
+ PyObject *module = PyType_GetModule(type);
+ assert(module != NULL);
+ return RemoteDebugging_GetState(module);
+}
+
+static inline RemoteDebuggingState *
+RemoteDebugging_GetStateFromObject(PyObject *obj)
+{
+ RemoteUnwinderObject *unwinder = (RemoteUnwinderObject *)obj;
+ if (unwinder->cached_state == NULL) {
+ unwinder->cached_state = RemoteDebugging_GetStateFromType(Py_TYPE(obj));
+ }
+ return unwinder->cached_state;
+}
+
+static inline int
+RemoteDebugging_InitState(RemoteDebuggingState *st)
+{
+ return 0;
+}
+
+static int
+is_prerelease_version(uint64_t version)
+{
+ return (version & 0xF0) != 0xF0;
+}
+
+static inline int
+validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets)
+{
+ if (memcmp(debug_offsets->cookie, _Py_Debug_Cookie, sizeof(debug_offsets->cookie)) != 0) {
+ // The remote is probably running a Python version predating debug offsets.
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Can't determine the Python version of the remote process");
+ return -1;
+ }
+
+ // Assume debug offsets could change from one pre-release version to another,
+ // or one minor version to another, but are stable across patch versions.
+ if (is_prerelease_version(Py_Version) && Py_Version != debug_offsets->version) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Can't attach from a pre-release Python interpreter"
+ " to a process running a different Python version");
+ return -1;
+ }
+
+ if (is_prerelease_version(debug_offsets->version) && Py_Version != debug_offsets->version) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Can't attach to a pre-release Python interpreter"
+ " from a process running a different Python version");
+ return -1;
+ }
+
+ unsigned int remote_major = (debug_offsets->version >> 24) & 0xFF;
+ unsigned int remote_minor = (debug_offsets->version >> 16) & 0xFF;
+
+ if (PY_MAJOR_VERSION != remote_major || PY_MINOR_VERSION != remote_minor) {
+ PyErr_Format(
+ PyExc_RuntimeError,
+ "Can't attach from a Python %d.%d process to a Python %d.%d process",
+ PY_MAJOR_VERSION, PY_MINOR_VERSION, remote_major, remote_minor);
+ return -1;
+ }
+
+ // The debug offsets differ between free threaded and non-free threaded builds.
+ if (_Py_Debug_Free_Threaded && !debug_offsets->free_threaded) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Cannot attach from a free-threaded Python process"
+ " to a process running a non-free-threaded version");
+ return -1;
+ }
+
+ if (!_Py_Debug_Free_Threaded && debug_offsets->free_threaded) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "Cannot attach to a free-threaded Python process"
+ " from a process running a non-free-threaded version");
+ return -1;
+ }
+
+ return 0;
+}
+
+/* ============================================================================
+ * MEMORY READING FUNCTIONS
+ * ============================================================================ */
+
+static inline int
+read_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr)
+{
+ int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(void*), ptr_addr);
+ if (result < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read pointer from remote memory");
+ return -1;
+ }
+ return 0;
+}
+
+static inline int
+read_Py_ssize_t(RemoteUnwinderObject *unwinder, uintptr_t address, Py_ssize_t *size)
+{
+ int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(Py_ssize_t), size);
+ if (result < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read Py_ssize_t from remote memory");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+read_py_ptr(RemoteUnwinderObject *unwinder, uintptr_t address, uintptr_t *ptr_addr)
+{
+ if (read_ptr(unwinder, address, ptr_addr)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read Python pointer");
+ return -1;
+ }
+ *ptr_addr &= ~Py_TAG_BITS;
+ return 0;
+}
+
+static int
+read_char(RemoteUnwinderObject *unwinder, uintptr_t address, char *result)
+{
+ int res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address, sizeof(char), result);
+ if (res < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read char from remote memory");
+ return -1;
+ }
+ return 0;
+}
+
+/* ============================================================================
+ * PYTHON OBJECT READING FUNCTIONS
+ * ============================================================================ */
+
+static PyObject *
+read_py_str(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t address,
+ Py_ssize_t max_len
+) {
+ PyObject *result = NULL;
+ char *buf = NULL;
+
+ // Read the entire PyUnicodeObject at once
+ char unicode_obj[SIZEOF_UNICODE_OBJ];
+ int res = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address,
+ SIZEOF_UNICODE_OBJ,
+ unicode_obj
+ );
+ if (res < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyUnicodeObject");
+ goto err;
+ }
+
+ Py_ssize_t len = GET_MEMBER(Py_ssize_t, unicode_obj, unwinder->debug_offsets.unicode_object.length);
+ if (len < 0 || len > max_len) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Invalid string length (%zd) at 0x%lx", len, address);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid string length in remote Unicode object");
+ return NULL;
+ }
+
+ buf = (char *)PyMem_RawMalloc(len+1);
+ if (buf == NULL) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate buffer for string reading");
+ return NULL;
+ }
+
+ size_t offset = unwinder->debug_offsets.unicode_object.asciiobject_size;
+ res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address + offset, len, buf);
+ if (res < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read string data from remote memory");
+ goto err;
+ }
+ buf[len] = '\0';
+
+ result = PyUnicode_FromStringAndSize(buf, len);
+ if (result == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create PyUnicode from remote string data");
+ goto err;
+ }
+
+ PyMem_RawFree(buf);
+ assert(result != NULL);
+ return result;
+
+err:
+ if (buf != NULL) {
+ PyMem_RawFree(buf);
+ }
+ return NULL;
+}
+
+static PyObject *
+read_py_bytes(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t address,
+ Py_ssize_t max_len
+) {
+ PyObject *result = NULL;
+ char *buf = NULL;
+
+ // Read the entire PyBytesObject at once
+ char bytes_obj[SIZEOF_BYTES_OBJ];
+ int res = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address,
+ SIZEOF_BYTES_OBJ,
+ bytes_obj
+ );
+ if (res < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyBytesObject");
+ goto err;
+ }
+
+ Py_ssize_t len = GET_MEMBER(Py_ssize_t, bytes_obj, unwinder->debug_offsets.bytes_object.ob_size);
+ if (len < 0 || len > max_len) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Invalid bytes length (%zd) at 0x%lx", len, address);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid bytes length in remote bytes object");
+ return NULL;
+ }
+
+ buf = (char *)PyMem_RawMalloc(len+1);
+ if (buf == NULL) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate buffer for bytes reading");
+ return NULL;
+ }
+
+ size_t offset = unwinder->debug_offsets.bytes_object.ob_sval;
+ res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address + offset, len, buf);
+ if (res < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read bytes data from remote memory");
+ goto err;
+ }
+ buf[len] = '\0';
+
+ result = PyBytes_FromStringAndSize(buf, len);
+ if (result == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create PyBytes from remote bytes data");
+ goto err;
+ }
+
+ PyMem_RawFree(buf);
+ assert(result != NULL);
+ return result;
+
+err:
+ if (buf != NULL) {
+ PyMem_RawFree(buf);
+ }
+ return NULL;
+}
+
+static long
+read_py_long(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t address
+)
+{
+ unsigned int shift = PYLONG_BITS_IN_DIGIT;
+
+ // Read the entire PyLongObject at once
+ char long_obj[SIZEOF_LONG_OBJ];
+ int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address,
+ unwinder->debug_offsets.long_object.size,
+ long_obj);
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyLongObject");
+ return -1;
+ }
+
+ uintptr_t lv_tag = GET_MEMBER(uintptr_t, long_obj, unwinder->debug_offsets.long_object.lv_tag);
+ int negative = (lv_tag & 3) == 2;
+ Py_ssize_t size = lv_tag >> 3;
+
+ if (size == 0) {
+ return 0;
+ }
+
+ // If the long object has inline digits, use them directly
+ digit *digits;
+ if (size <= _PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS) {
+ // For small integers, digits are inline in the long_value.ob_digit array
+ digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
+ if (!digits) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate digits for small PyLong");
+ return -1;
+ }
+ memcpy(digits, long_obj + unwinder->debug_offsets.long_object.ob_digit, size * sizeof(digit));
+ } else {
+ // For larger integers, we need to read the digits separately
+ digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
+ if (!digits) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate digits for large PyLong");
+ return -1;
+ }
+
+ bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address + unwinder->debug_offsets.long_object.ob_digit,
+ sizeof(digit) * size,
+ digits
+ );
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyLong digits from remote memory");
+ goto error;
+ }
+ }
+
+ long long value = 0;
+
+ // In theory this can overflow, but because of llvm/llvm-project#16778
+ // we can't use __builtin_mul_overflow because it fails to link with
+ // __muloti4 on aarch64. In practice this is fine because all we're
+ // testing here are task numbers that would fit in a single byte.
+ for (Py_ssize_t i = 0; i < size; ++i) {
+ long long factor = digits[i] * (1UL << (Py_ssize_t)(shift * i));
+ value += factor;
+ }
+ PyMem_RawFree(digits);
+ if (negative) {
+ value *= -1;
+ }
+ return (long)value;
+error:
+ PyMem_RawFree(digits);
+ return -1;
+}
+
+/* ============================================================================
+ * ASYNCIO DEBUG FUNCTIONS
+ * ============================================================================ */
+
+// Get the PyAsyncioDebug section address for any platform
+static uintptr_t
+_Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
+{
+ uintptr_t address;
+
+#ifdef MS_WINDOWS
+ // On Windows, search for asyncio debug in executable or DLL
+ address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio");
+ if (address == 0) {
+ // Error out: 'python' substring covers both executable and DLL
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
+ _PyErr_ChainExceptions1(exc);
+ }
+#elif defined(__linux__)
+ // On Linux, search for asyncio debug in executable or DLL
+ address = search_linux_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
+ if (address == 0) {
+ // Error out: 'python' substring covers both executable and DLL
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
+ _PyErr_ChainExceptions1(exc);
+ }
+#elif defined(__APPLE__) && TARGET_OS_OSX
+ // On macOS, try libpython first, then fall back to python
+ address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
+ if (address == 0) {
+ PyErr_Clear();
+ address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
+ }
+ if (address == 0) {
+ // Error out: 'python' substring covers both executable and DLL
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
+ _PyErr_ChainExceptions1(exc);
+ }
+#else
+ Py_UNREACHABLE();
+#endif
+
+ return address;
+}
+
+static int
+read_async_debug(
+ RemoteUnwinderObject *unwinder
+) {
+ uintptr_t async_debug_addr = _Py_RemoteDebug_GetAsyncioDebugAddress(&unwinder->handle);
+ if (!async_debug_addr) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to get AsyncioDebug address");
+ return -1;
+ }
+
+ size_t size = sizeof(struct _Py_AsyncioModuleDebugOffsets);
+ int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, async_debug_addr, size, &unwinder->async_debug_offsets);
+ if (result < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read AsyncioDebug offsets");
+ }
+ return result;
+}
+
+/* ============================================================================
+ * ASYNCIO TASK PARSING FUNCTIONS
+ * ============================================================================ */
+
+static PyObject *
+parse_task_name(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_address
+) {
+ // Read the entire TaskObj at once
+ char task_obj[SIZEOF_TASK_OBJ];
+ int err = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ task_address,
+ unwinder->async_debug_offsets.asyncio_task_object.size,
+ task_obj);
+ if (err < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object");
+ return NULL;
+ }
+
+ uintptr_t task_name_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_name);
+
+ // The task name can be a long or a string so we need to check the type
+ char task_name_obj[SIZEOF_PYOBJECT];
+ err = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ task_name_addr,
+ SIZEOF_PYOBJECT,
+ task_name_obj);
+ if (err < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task name object");
+ return NULL;
+ }
+
+ // Now read the type object to get the flags
+ char type_obj[SIZEOF_TYPE_OBJ];
+ err = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ GET_MEMBER(uintptr_t, task_name_obj, unwinder->debug_offsets.pyobject.ob_type),
+ SIZEOF_TYPE_OBJ,
+ type_obj);
+ if (err < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task name type object");
+ return NULL;
+ }
+
+ if ((GET_MEMBER(unsigned long, type_obj, unwinder->debug_offsets.type_object.tp_flags) & Py_TPFLAGS_LONG_SUBCLASS)) {
+ long res = read_py_long(unwinder, task_name_addr);
+ if (res == -1) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Task name PyLong parsing failed");
+ return NULL;
+ }
+ return PyUnicode_FromFormat("Task-%d", res);
+ }
+
+ if(!(GET_MEMBER(unsigned long, type_obj, unwinder->debug_offsets.type_object.tp_flags) & Py_TPFLAGS_UNICODE_SUBCLASS)) {
+ PyErr_SetString(PyExc_RuntimeError, "Invalid task name object");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Task name object is neither long nor unicode");
+ return NULL;
+ }
+
+ return read_py_str(
+ unwinder,
+ task_name_addr,
+ 255
+ );
+}
+
+static int parse_task_awaited_by(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_address,
+ PyObject *awaited_by,
+ int recurse_task
+) {
+ // Read the entire TaskObj at once
+ char task_obj[SIZEOF_TASK_OBJ];
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
+ unwinder->async_debug_offsets.asyncio_task_object.size,
+ task_obj) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object in awaited_by parsing");
+ return -1;
+ }
+
+ uintptr_t task_ab_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by);
+
+ if ((void*)task_ab_addr == NULL) {
+ return 0;
+ }
+
+ char awaited_by_is_a_set = GET_MEMBER(char, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by_is_set);
+
+ if (awaited_by_is_a_set) {
+ if (parse_tasks_in_set(unwinder, task_ab_addr, awaited_by, recurse_task)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse tasks in awaited_by set");
+ return -1;
+ }
+ } else {
+ if (parse_task(unwinder, task_ab_addr, awaited_by, recurse_task)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse single awaited_by task");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+handle_yield_from_frame(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t gi_iframe_addr,
+ uintptr_t gen_type_addr,
+ PyObject *render_to
+) {
+ // Read the entire interpreter frame at once
+ char iframe[SIZEOF_INTERP_FRAME];
+ int err = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ gi_iframe_addr,
+ SIZEOF_INTERP_FRAME,
+ iframe);
+ if (err < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter frame in yield_from handler");
+ return -1;
+ }
+
+ if (GET_MEMBER(char, iframe, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_GENERATOR) {
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "generator doesn't own its frame \\_o_/");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Frame ownership mismatch in yield_from");
+ return -1;
+ }
+
+ uintptr_t stackpointer_addr = GET_MEMBER_NO_TAG(uintptr_t, iframe, unwinder->debug_offsets.interpreter_frame.stackpointer);
+
+ if ((void*)stackpointer_addr != NULL) {
+ uintptr_t gi_await_addr;
+ err = read_py_ptr(
+ unwinder,
+ stackpointer_addr - sizeof(void*),
+ &gi_await_addr);
+ if (err) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read gi_await address");
+ return -1;
+ }
+
+ if ((void*)gi_await_addr != NULL) {
+ uintptr_t gi_await_addr_type_addr;
+ err = read_ptr(
+ unwinder,
+ gi_await_addr + unwinder->debug_offsets.pyobject.ob_type,
+ &gi_await_addr_type_addr);
+ if (err) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read gi_await type address");
+ return -1;
+ }
+
+ if (gen_type_addr == gi_await_addr_type_addr) {
+ /* This needs an explanation. We always start with parsing
+ native coroutine / generator frames. Ultimately they
+ are awaiting on something. That something can be
+ a native coroutine frame or... an iterator.
+ If it's the latter -- we can't continue building
+ our chain. So the condition to bail out of this is
+ to do that when the type of the current coroutine
+ doesn't match the type of whatever it points to
+ in its cr_await.
+ */
+ err = parse_coro_chain(unwinder, gi_await_addr, render_to);
+ if (err) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse coroutine chain in yield_from");
+ return -1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+parse_coro_chain(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t coro_address,
+ PyObject *render_to
+) {
+ assert((void*)coro_address != NULL);
+
+ // Read the entire generator object at once
+ char gen_object[SIZEOF_GEN_OBJ];
+ int err = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ coro_address,
+ SIZEOF_GEN_OBJ,
+ gen_object);
+ if (err < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read generator object in coro chain");
+ return -1;
+ }
+
+ int8_t frame_state = GET_MEMBER(int8_t, gen_object, unwinder->debug_offsets.gen_object.gi_frame_state);
+ if (frame_state == FRAME_CLEARED) {
+ return 0;
+ }
+
+ uintptr_t gen_type_addr = GET_MEMBER(uintptr_t, gen_object, unwinder->debug_offsets.pyobject.ob_type);
+
+ PyObject* name = NULL;
+
+ // Parse the previous frame using the gi_iframe from local copy
+ uintptr_t prev_frame;
+ uintptr_t gi_iframe_addr = coro_address + unwinder->debug_offsets.gen_object.gi_iframe;
+ if (parse_frame_object(unwinder, &name, gi_iframe_addr, &prev_frame) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in coro chain");
+ return -1;
+ }
+
+ if (PyList_Append(render_to, name)) {
+ Py_DECREF(name);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain");
+ return -1;
+ }
+ Py_DECREF(name);
+
+ if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
+ return handle_yield_from_frame(unwinder, gi_iframe_addr, gen_type_addr, render_to);
+ }
+
+ return 0;
+}
+
+static PyObject*
+create_task_result(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_address,
+ int recurse_task
+) {
+ PyObject* result = NULL;
+ PyObject *call_stack = NULL;
+ PyObject *tn = NULL;
+ char task_obj[SIZEOF_TASK_OBJ];
+ uintptr_t coro_addr;
+
+ // Create call_stack first since it's the first tuple element
+ call_stack = PyList_New(0);
+ if (call_stack == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create call stack list");
+ goto error;
+ }
+
+ // Create task name/address for second tuple element
+ if (recurse_task) {
+ tn = parse_task_name(unwinder, task_address);
+ } else {
+ tn = PyLong_FromUnsignedLongLong(task_address);
+ }
+ if (tn == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task name/address");
+ goto error;
+ }
+
+ // Parse coroutine chain
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, task_address,
+ unwinder->async_debug_offsets.asyncio_task_object.size,
+ task_obj) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task object for coro chain");
+ goto error;
+ }
+
+ coro_addr = GET_MEMBER_NO_TAG(uintptr_t, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_coro);
+
+ if ((void*)coro_addr != NULL) {
+ if (parse_coro_chain(unwinder, coro_addr, call_stack) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse coroutine chain");
+ goto error;
+ }
+
+ if (PyList_Reverse(call_stack)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to reverse call stack");
+ goto error;
+ }
+ }
+
+ // Create final CoroInfo result
+ RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
+ result = PyStructSequence_New(state->CoroInfo_Type);
+ if (result == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create CoroInfo");
+ goto error;
+ }
+
+ // PyStructSequence_SetItem steals references, so we don't need to DECREF on success
+ PyStructSequence_SetItem(result, 0, call_stack); // This steals the reference
+ PyStructSequence_SetItem(result, 1, tn); // This steals the reference
+
+ return result;
+
+error:
+ Py_XDECREF(result);
+ Py_XDECREF(call_stack);
+ Py_XDECREF(tn);
+ return NULL;
+}
+
+static int
+parse_task(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_address,
+ PyObject *render_to,
+ int recurse_task
+) {
+ char is_task;
+ PyObject* result = NULL;
+ int err;
+
+ err = read_char(
+ unwinder,
+ task_address + unwinder->async_debug_offsets.asyncio_task_object.task_is_task,
+ &is_task);
+ if (err) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read is_task flag");
+ goto error;
+ }
+
+ if (is_task) {
+ result = create_task_result(unwinder, task_address, recurse_task);
+ if (!result) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task result");
+ goto error;
+ }
+ } else {
+ // Create an empty CoroInfo for non-task objects
+ RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
+ result = PyStructSequence_New(state->CoroInfo_Type);
+ if (result == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create empty CoroInfo");
+ goto error;
+ }
+ PyObject *empty_list = PyList_New(0);
+ if (empty_list == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create empty list");
+ goto error;
+ }
+ PyObject *task_name = PyLong_FromUnsignedLongLong(task_address);
+ if (task_name == NULL) {
+ Py_DECREF(empty_list);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task name");
+ goto error;
+ }
+ PyStructSequence_SetItem(result, 0, empty_list); // This steals the reference
+ PyStructSequence_SetItem(result, 1, task_name); // This steals the reference
+ }
+
+ if (PyList_Append(render_to, result)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append task result to render list");
+ goto error;
+ }
+ Py_DECREF(result);
+ return 0;
+
+error:
+ Py_XDECREF(result);
+ return -1;
+}
+
+static int
+process_set_entry(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t table_ptr,
+ PyObject *awaited_by,
+ int recurse_task
+) {
+ uintptr_t key_addr;
+ if (read_py_ptr(unwinder, table_ptr, &key_addr)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry key");
+ return -1;
+ }
+
+ if ((void*)key_addr != NULL) {
+ Py_ssize_t ref_cnt;
+ if (read_Py_ssize_t(unwinder, table_ptr, &ref_cnt)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry reference count");
+ return -1;
+ }
+
+ if (ref_cnt) {
+ // if 'ref_cnt=0' it's a set dummy marker
+ if (parse_task(unwinder, key_addr, awaited_by, recurse_task)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task in set entry");
+ return -1;
+ }
+ return 1; // Successfully processed a valid entry
+ }
+ }
+ return 0; // Entry was NULL or dummy marker
+}
+
+static int
+parse_tasks_in_set(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t set_addr,
+ PyObject *awaited_by,
+ int recurse_task
+) {
+ char set_object[SIZEOF_SET_OBJ];
+ int err = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ set_addr,
+ SIZEOF_SET_OBJ,
+ set_object);
+ if (err < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set object");
+ return -1;
+ }
+
+ Py_ssize_t num_els = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.used);
+ Py_ssize_t set_len = GET_MEMBER(Py_ssize_t, set_object, unwinder->debug_offsets.set_object.mask) + 1; // The set contains the `mask+1` element slots.
+ uintptr_t table_ptr = GET_MEMBER(uintptr_t, set_object, unwinder->debug_offsets.set_object.table);
+
+ Py_ssize_t i = 0;
+ Py_ssize_t els = 0;
+ while (i < set_len && els < num_els) {
+ int result = process_set_entry(unwinder, table_ptr, awaited_by, recurse_task);
+
+ if (result < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process set entry");
+ return -1;
+ }
+ if (result > 0) {
+ els++;
+ }
+
+ table_ptr += sizeof(void*) * 2;
+ i++;
+ }
+ return 0;
+}
+
+
+static int
+setup_async_result_structure(RemoteUnwinderObject *unwinder, PyObject **result, PyObject **calls)
+{
+ *result = PyList_New(1);
+ if (*result == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create async result structure");
+ return -1;
+ }
+
+ *calls = PyList_New(0);
+ if (*calls == NULL) {
+ Py_DECREF(*result);
+ *result = NULL;
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create calls list in async result");
+ return -1;
+ }
+
+ if (PyList_SetItem(*result, 0, *calls)) { /* steals ref to 'calls' */
+ Py_DECREF(*calls);
+ Py_DECREF(*result);
+ *result = NULL;
+ *calls = NULL;
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to set calls list in async result");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+add_task_info_to_result(
+ RemoteUnwinderObject *unwinder,
+ PyObject *result,
+ uintptr_t running_task_addr
+) {
+ PyObject *tn = parse_task_name(unwinder, running_task_addr);
+ if (tn == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task name for result");
+ return -1;
+ }
+
+ if (PyList_Append(result, tn)) {
+ Py_DECREF(tn);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append task name to result");
+ return -1;
+ }
+ Py_DECREF(tn);
+
+ PyObject* awaited_by = PyList_New(0);
+ if (awaited_by == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create awaited_by list for result");
+ return -1;
+ }
+
+ if (PyList_Append(result, awaited_by)) {
+ Py_DECREF(awaited_by);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append awaited_by to result");
+ return -1;
+ }
+ Py_DECREF(awaited_by);
+
+ if (parse_task_awaited_by(
+ unwinder, running_task_addr, awaited_by, 1) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse awaited_by for result");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+process_single_task_node(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t task_addr,
+ PyObject *result
+) {
+ PyObject *tn = NULL;
+ PyObject *current_awaited_by = NULL;
+ PyObject *task_id = NULL;
+ PyObject *result_item = NULL;
+ PyObject *coroutine_stack = NULL;
+
+ tn = parse_task_name(unwinder, task_addr);
+ if (tn == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task name in single task node");
+ goto error;
+ }
+
+ current_awaited_by = PyList_New(0);
+ if (current_awaited_by == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create awaited_by list in single task node");
+ goto error;
+ }
+
+ // Extract the coroutine stack for this task
+ coroutine_stack = PyList_New(0);
+ if (coroutine_stack == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create coroutine stack list in single task node");
+ goto error;
+ }
+
+ if (parse_task(unwinder, task_addr, coroutine_stack, 0) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse task coroutine stack in single task node");
+ goto error;
+ }
+
+ task_id = PyLong_FromUnsignedLongLong(task_addr);
+ if (task_id == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create task ID in single task node");
+ goto error;
+ }
+
+ RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
+ result_item = PyStructSequence_New(state->TaskInfo_Type);
+ if (result_item == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create TaskInfo in single task node");
+ goto error;
+ }
+
+ PyStructSequence_SetItem(result_item, 0, task_id); // steals ref
+ PyStructSequence_SetItem(result_item, 1, tn); // steals ref
+ PyStructSequence_SetItem(result_item, 2, coroutine_stack); // steals ref
+ PyStructSequence_SetItem(result_item, 3, current_awaited_by); // steals ref
+
+ // References transferred to tuple
+ task_id = NULL;
+ tn = NULL;
+ coroutine_stack = NULL;
+ current_awaited_by = NULL;
+
+ if (PyList_Append(result, result_item)) {
+ Py_DECREF(result_item);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append result item in single task node");
+ return -1;
+ }
+ Py_DECREF(result_item);
+
+ // Get back current_awaited_by reference for parse_task_awaited_by
+ current_awaited_by = PyStructSequence_GetItem(result_item, 3);
+ if (parse_task_awaited_by(unwinder, task_addr, current_awaited_by, 0) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse awaited_by in single task node");
+ // No cleanup needed here since all references were transferred to result_item
+ // and result_item was already added to result list and decreffed
+ return -1;
+ }
+
+ return 0;
+
+error:
+ Py_XDECREF(tn);
+ Py_XDECREF(current_awaited_by);
+ Py_XDECREF(task_id);
+ Py_XDECREF(result_item);
+ Py_XDECREF(coroutine_stack);
+ return -1;
+}
+
+/* ============================================================================
+ * TLBC CACHING FUNCTIONS
+ * ============================================================================ */
+
+#ifdef Py_GIL_DISABLED
+
+typedef struct {
+ void *tlbc_array; // Local copy of the TLBC array
+ Py_ssize_t tlbc_array_size; // Size of the TLBC array
+ uint32_t generation; // Generation when this was cached
+} TLBCCacheEntry;
+
+static void
+tlbc_cache_entry_destroy(void *ptr)
+{
+ TLBCCacheEntry *entry = (TLBCCacheEntry *)ptr;
+ if (entry->tlbc_array) {
+ PyMem_RawFree(entry->tlbc_array);
+ }
+ PyMem_RawFree(entry);
+}
+
+static TLBCCacheEntry *
+get_tlbc_cache_entry(RemoteUnwinderObject *self, uintptr_t code_addr, uint32_t current_generation)
+{
+ void *key = (void *)code_addr;
+ TLBCCacheEntry *entry = _Py_hashtable_get(self->tlbc_cache, key);
+
+ if (entry && entry->generation != current_generation) {
+ // Entry is stale, remove it by setting to NULL
+ _Py_hashtable_set(self->tlbc_cache, key, NULL);
+ entry = NULL;
+ }
+
+ return entry;
+}
+
+static int
+cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t code_addr, uintptr_t tlbc_array_addr, uint32_t generation)
+{
+ uintptr_t tlbc_array_ptr;
+ void *tlbc_array = NULL;
+ TLBCCacheEntry *entry = NULL;
+
+ // Read the TLBC array pointer
+ if (read_ptr(unwinder, tlbc_array_addr, &tlbc_array_ptr) != 0 || tlbc_array_ptr == 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC array pointer");
+ return 0; // No TLBC array
+ }
+
+ // Read the TLBC array size
+ Py_ssize_t tlbc_size;
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, tlbc_array_ptr, sizeof(tlbc_size), &tlbc_size) != 0 || tlbc_size <= 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC array size");
+ return 0; // Invalid size
+ }
+
+ // Allocate and read the entire TLBC array
+ size_t array_data_size = tlbc_size * sizeof(void*);
+ tlbc_array = PyMem_RawMalloc(sizeof(Py_ssize_t) + array_data_size);
+ if (!tlbc_array) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate TLBC array");
+ return 0; // Memory error
+ }
+
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, tlbc_array_ptr, sizeof(Py_ssize_t) + array_data_size, tlbc_array) != 0) {
+ PyMem_RawFree(tlbc_array);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC array data");
+ return 0; // Read error
+ }
+
+ // Create cache entry
+ entry = PyMem_RawMalloc(sizeof(TLBCCacheEntry));
+ if (!entry) {
+ PyMem_RawFree(tlbc_array);
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate TLBC cache entry");
+ return 0; // Memory error
+ }
+
+ entry->tlbc_array = tlbc_array;
+ entry->tlbc_array_size = tlbc_size;
+ entry->generation = generation;
+
+ // Store in cache
+ void *key = (void *)code_addr;
+ if (_Py_hashtable_set(unwinder->tlbc_cache, key, entry) < 0) {
+ tlbc_cache_entry_destroy(entry);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to store TLBC entry in cache");
+ return 0; // Cache error
+ }
+
+ return 1; // Success
+}
+
+
+
+#endif
+
+/* ============================================================================
+ * LINE TABLE PARSING FUNCTIONS
+ * ============================================================================ */
+
+static int
+scan_varint(const uint8_t **ptr)
+{
+ unsigned int read = **ptr;
+ *ptr = *ptr + 1;
+ unsigned int val = read & 63;
+ unsigned int shift = 0;
+ while (read & 64) {
+ read = **ptr;
+ *ptr = *ptr + 1;
+ shift += 6;
+ val |= (read & 63) << shift;
+ }
+ return val;
+}
+
+static int
+scan_signed_varint(const uint8_t **ptr)
+{
+ unsigned int uval = scan_varint(ptr);
+ if (uval & 1) {
+ return -(int)(uval >> 1);
+ }
+ else {
+ return uval >> 1;
+ }
+}
+
+static bool
+parse_linetable(const uintptr_t addrq, const char* linetable, int firstlineno, LocationInfo* info)
+{
+ const uint8_t* ptr = (const uint8_t*)(linetable);
+ uint64_t addr = 0;
+ info->lineno = firstlineno;
+
+ while (*ptr != '\0') {
+ // See InternalDocs/code_objects.md for where these magic numbers are from
+ // and for the decoding algorithm.
+ uint8_t first_byte = *(ptr++);
+ uint8_t code = (first_byte >> 3) & 15;
+ size_t length = (first_byte & 7) + 1;
+ uintptr_t end_addr = addr + length;
+ switch (code) {
+ case PY_CODE_LOCATION_INFO_NONE: {
+ break;
+ }
+ case PY_CODE_LOCATION_INFO_LONG: {
+ int line_delta = scan_signed_varint(&ptr);
+ info->lineno += line_delta;
+ info->end_lineno = info->lineno + scan_varint(&ptr);
+ info->column = scan_varint(&ptr) - 1;
+ info->end_column = scan_varint(&ptr) - 1;
+ break;
+ }
+ case PY_CODE_LOCATION_INFO_NO_COLUMNS: {
+ int line_delta = scan_signed_varint(&ptr);
+ info->lineno += line_delta;
+ info->column = info->end_column = -1;
+ break;
+ }
+ case PY_CODE_LOCATION_INFO_ONE_LINE0:
+ case PY_CODE_LOCATION_INFO_ONE_LINE1:
+ case PY_CODE_LOCATION_INFO_ONE_LINE2: {
+ int line_delta = code - 10;
+ info->lineno += line_delta;
+ info->end_lineno = info->lineno;
+ info->column = *(ptr++);
+ info->end_column = *(ptr++);
+ break;
+ }
+ default: {
+ uint8_t second_byte = *(ptr++);
+ if ((second_byte & 128) != 0) {
+ return false;
+ }
+ info->column = code << 3 | (second_byte >> 4);
+ info->end_column = info->column + (second_byte & 15);
+ break;
+ }
+ }
+ if (addr <= addrq && end_addr > addrq) {
+ return true;
+ }
+ addr = end_addr;
+ }
+ return false;
+}
+
+/* ============================================================================
+ * CODE OBJECT AND FRAME PARSING FUNCTIONS
+ * ============================================================================ */
+
+static int
+parse_code_object(RemoteUnwinderObject *unwinder,
+ PyObject **result,
+ uintptr_t address,
+ uintptr_t instruction_pointer,
+ uintptr_t *previous_frame,
+ int32_t tlbc_index)
+{
+ void *key = (void *)address;
+ CachedCodeMetadata *meta = NULL;
+ PyObject *func = NULL;
+ PyObject *file = NULL;
+ PyObject *linetable = NULL;
+ PyObject *lineno = NULL;
+ PyObject *tuple = NULL;
+
+#ifdef Py_GIL_DISABLED
+ // In free threading builds, code object addresses might have the low bit set
+ // as a flag, so we need to mask it off to get the real address
+ uintptr_t real_address = address & (~1);
+#else
+ uintptr_t real_address = address;
+#endif
+
+ if (unwinder && unwinder->code_object_cache != NULL) {
+ meta = _Py_hashtable_get(unwinder->code_object_cache, key);
+ }
+
+ if (meta == NULL) {
+ char code_object[SIZEOF_CODE_OBJ];
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle, real_address, SIZEOF_CODE_OBJ, code_object) < 0)
+ {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read code object");
+ goto error;
+ }
+
+ func = read_py_str(unwinder,
+ GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.qualname), 1024);
+ if (!func) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read function name from code object");
+ goto error;
+ }
+
+ file = read_py_str(unwinder,
+ GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.filename), 1024);
+ if (!file) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read filename from code object");
+ goto error;
+ }
+
+ linetable = read_py_bytes(unwinder,
+ GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.linetable), 4096);
+ if (!linetable) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read linetable from code object");
+ goto error;
+ }
+
+ meta = PyMem_RawMalloc(sizeof(CachedCodeMetadata));
+ if (!meta) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate cached code metadata");
+ goto error;
+ }
+
+ meta->func_name = func;
+ meta->file_name = file;
+ meta->linetable = linetable;
+ meta->first_lineno = GET_MEMBER(int, code_object, unwinder->debug_offsets.code_object.firstlineno);
+ meta->addr_code_adaptive = real_address + unwinder->debug_offsets.code_object.co_code_adaptive;
+
+ if (unwinder && unwinder->code_object_cache && _Py_hashtable_set(unwinder->code_object_cache, key, meta) < 0) {
+ cached_code_metadata_destroy(meta);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to cache code metadata");
+ goto error;
+ }
+
+ // Ownership transferred to meta
+ func = NULL;
+ file = NULL;
+ linetable = NULL;
+ }
+
+ uintptr_t ip = instruction_pointer;
+ ptrdiff_t addrq;
+
+#ifdef Py_GIL_DISABLED
+ // Handle thread-local bytecode (TLBC) in free threading builds
+ if (tlbc_index == 0 || unwinder->debug_offsets.code_object.co_tlbc == 0 || unwinder == NULL) {
+ // No TLBC or no unwinder - use main bytecode directly
+ addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
+ goto done_tlbc;
+ }
+
+ // Try to get TLBC data from cache (we'll get generation from the caller)
+ TLBCCacheEntry *tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
+
+ if (!tlbc_entry) {
+ // Cache miss - try to read and cache TLBC array
+ if (!cache_tlbc_array(unwinder, real_address, real_address + unwinder->debug_offsets.code_object.co_tlbc, unwinder->tlbc_generation)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to cache TLBC array");
+ goto error;
+ }
+ tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
+ }
+
+ if (tlbc_entry && tlbc_index < tlbc_entry->tlbc_array_size) {
+ // Use cached TLBC data
+ uintptr_t *entries = (uintptr_t *)((char *)tlbc_entry->tlbc_array + sizeof(Py_ssize_t));
+ uintptr_t tlbc_bytecode_addr = entries[tlbc_index];
+
+ if (tlbc_bytecode_addr != 0) {
+ // Calculate offset from TLBC bytecode
+ addrq = (uint16_t *)ip - (uint16_t *)tlbc_bytecode_addr;
+ goto done_tlbc;
+ }
+ }
+
+ // Fall back to main bytecode
+ addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
+
+done_tlbc:
+#else
+ // Non-free-threaded build, always use the main bytecode
+ (void)tlbc_index; // Suppress unused parameter warning
+ (void)unwinder; // Suppress unused parameter warning
+ addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
+#endif
+ ; // Empty statement to avoid C23 extension warning
+ LocationInfo info = {0};
+ bool ok = parse_linetable(addrq, PyBytes_AS_STRING(meta->linetable),
+ meta->first_lineno, &info);
+ if (!ok) {
+ info.lineno = -1;
+ }
+
+ lineno = PyLong_FromLong(info.lineno);
+ if (!lineno) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create line number object");
+ goto error;
+ }
+
+ RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
+ tuple = PyStructSequence_New(state->FrameInfo_Type);
+ if (!tuple) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create FrameInfo for code object");
+ goto error;
+ }
+
+ Py_INCREF(meta->func_name);
+ Py_INCREF(meta->file_name);
+ PyStructSequence_SetItem(tuple, 0, meta->file_name);
+ PyStructSequence_SetItem(tuple, 1, lineno);
+ PyStructSequence_SetItem(tuple, 2, meta->func_name);
+
+ *result = tuple;
+ return 0;
+
+error:
+ Py_XDECREF(func);
+ Py_XDECREF(file);
+ Py_XDECREF(linetable);
+ Py_XDECREF(lineno);
+ Py_XDECREF(tuple);
+ return -1;
+}
+
+/* ============================================================================
+ * STACK CHUNK MANAGEMENT FUNCTIONS
+ * ============================================================================ */
+
+static void
+cleanup_stack_chunks(StackChunkList *chunks)
+{
+ for (size_t i = 0; i < chunks->count; ++i) {
+ PyMem_RawFree(chunks->chunks[i].local_copy);
+ }
+ PyMem_RawFree(chunks->chunks);
+}
+
+static int
+process_single_stack_chunk(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t chunk_addr,
+ StackChunkInfo *chunk_info
+) {
+ // Start with default size assumption
+ size_t current_size = _PY_DATA_STACK_CHUNK_SIZE;
+
+ char *this_chunk = PyMem_RawMalloc(current_size);
+ if (!this_chunk) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate stack chunk buffer");
+ return -1;
+ }
+
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, chunk_addr, current_size, this_chunk) < 0) {
+ PyMem_RawFree(this_chunk);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read stack chunk");
+ return -1;
+ }
+
+ // Check actual size and reread if necessary
+ size_t actual_size = GET_MEMBER(size_t, this_chunk, offsetof(_PyStackChunk, size));
+ if (actual_size != current_size) {
+ this_chunk = PyMem_RawRealloc(this_chunk, actual_size);
+ if (!this_chunk) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to reallocate stack chunk buffer");
+ return -1;
+ }
+
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, chunk_addr, actual_size, this_chunk) < 0) {
+ PyMem_RawFree(this_chunk);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to reread stack chunk with correct size");
+ return -1;
+ }
+ current_size = actual_size;
+ }
+
+ chunk_info->remote_addr = chunk_addr;
+ chunk_info->size = current_size;
+ chunk_info->local_copy = this_chunk;
+ return 0;
+}
+
+static int
+copy_stack_chunks(RemoteUnwinderObject *unwinder,
+ uintptr_t tstate_addr,
+ StackChunkList *out_chunks)
+{
+ uintptr_t chunk_addr;
+ StackChunkInfo *chunks = NULL;
+ size_t count = 0;
+ size_t max_chunks = 16;
+
+ if (read_ptr(unwinder, tstate_addr + unwinder->debug_offsets.thread_state.datastack_chunk, &chunk_addr)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read initial stack chunk address");
+ return -1;
+ }
+
+ chunks = PyMem_RawMalloc(max_chunks * sizeof(StackChunkInfo));
+ if (!chunks) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate stack chunks array");
+ return -1;
+ }
+
+ while (chunk_addr != 0) {
+ // Grow array if needed
+ if (count >= max_chunks) {
+ max_chunks *= 2;
+ StackChunkInfo *new_chunks = PyMem_RawRealloc(chunks, max_chunks * sizeof(StackChunkInfo));
+ if (!new_chunks) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to grow stack chunks array");
+ goto error;
+ }
+ chunks = new_chunks;
+ }
+
+ // Process this chunk
+ if (process_single_stack_chunk(unwinder, chunk_addr, &chunks[count]) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process stack chunk");
+ goto error;
+ }
+
+ // Get next chunk address and increment count
+ chunk_addr = GET_MEMBER(uintptr_t, chunks[count].local_copy, offsetof(_PyStackChunk, previous));
+ count++;
+ }
+
+ out_chunks->chunks = chunks;
+ out_chunks->count = count;
+ return 0;
+
+error:
+ for (size_t i = 0; i < count; ++i) {
+ PyMem_RawFree(chunks[i].local_copy);
+ }
+ PyMem_RawFree(chunks);
+ return -1;
+}
+
+static void *
+find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr)
+{
+ for (size_t i = 0; i < chunks->count; ++i) {
+ uintptr_t base = chunks->chunks[i].remote_addr + offsetof(_PyStackChunk, data);
+ size_t payload = chunks->chunks[i].size - offsetof(_PyStackChunk, data);
+
+ if (remote_ptr >= base && remote_ptr < base + payload) {
+ return (char *)chunks->chunks[i].local_copy + (remote_ptr - chunks->chunks[i].remote_addr);
+ }
+ }
+ return NULL;
+}
+
+static int
+parse_frame_from_chunks(
+ RemoteUnwinderObject *unwinder,
+ PyObject **result,
+ uintptr_t address,
+ uintptr_t *previous_frame,
+ StackChunkList *chunks
+) {
+ void *frame_ptr = find_frame_in_chunks(chunks, address);
+ if (!frame_ptr) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Frame not found in stack chunks");
+ return -1;
+ }
+
+ char *frame = (char *)frame_ptr;
+ *previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
+ uintptr_t code_object = GET_MEMBER_NO_TAG(uintptr_t, frame_ptr, unwinder->debug_offsets.interpreter_frame.executable);
+ int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, code_object);
+ if (frame_valid != 1) {
+ return frame_valid;
+ }
+
+ uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
+
+ // Get tlbc_index for free threading builds
+ int32_t tlbc_index = 0;
+#ifdef Py_GIL_DISABLED
+ if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
+ tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
+ }
+#endif
+
+ return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
+}
+
+/* ============================================================================
+ * INTERPRETER STATE AND THREAD DISCOVERY FUNCTIONS
+ * ============================================================================ */
+
+static int
+populate_initial_state_data(
+ int all_threads,
+ RemoteUnwinderObject *unwinder,
+ uintptr_t runtime_start_address,
+ uintptr_t *interpreter_state,
+ uintptr_t *tstate
+) {
+ uint64_t interpreter_state_list_head =
+ unwinder->debug_offsets.runtime_state.interpreters_head;
+
+ uintptr_t address_of_interpreter_state;
+ int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ runtime_start_address + interpreter_state_list_head,
+ sizeof(void*),
+ &address_of_interpreter_state);
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state address");
+ return -1;
+ }
+
+ if (address_of_interpreter_state == 0) {
+ PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL");
+ return -1;
+ }
+
+ *interpreter_state = address_of_interpreter_state;
+
+ if (all_threads) {
+ *tstate = 0;
+ return 0;
+ }
+
+ uintptr_t address_of_thread = address_of_interpreter_state +
+ unwinder->debug_offsets.interpreter_state.threads_main;
+
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address_of_thread,
+ sizeof(void*),
+ tstate) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main thread state address");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+find_running_frame(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t runtime_start_address,
+ uintptr_t *frame
+) {
+ uint64_t interpreter_state_list_head =
+ unwinder->debug_offsets.runtime_state.interpreters_head;
+
+ uintptr_t address_of_interpreter_state;
+ int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ runtime_start_address + interpreter_state_list_head,
+ sizeof(void*),
+ &address_of_interpreter_state);
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state for running frame");
+ return -1;
+ }
+
+ if (address_of_interpreter_state == 0) {
+ PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL in running frame search");
+ return -1;
+ }
+
+ uintptr_t address_of_thread;
+ bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address_of_interpreter_state +
+ unwinder->debug_offsets.interpreter_state.threads_main,
+ sizeof(void*),
+ &address_of_thread);
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread address for running frame");
+ return -1;
+ }
+
+ // No Python frames are available for us (can happen at tear-down).
+ if ((void*)address_of_thread != NULL) {
+ int err = read_ptr(
+ unwinder,
+ address_of_thread + unwinder->debug_offsets.thread_state.current_frame,
+ frame);
+ if (err) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read current frame pointer");
+ return -1;
+ }
+ return 0;
+ }
+
+ *frame = (uintptr_t)NULL;
+ return 0;
+}
+
+static int
+find_running_task(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t *running_task_addr
+) {
+ *running_task_addr = (uintptr_t)NULL;
+
+ uint64_t interpreter_state_list_head =
+ unwinder->debug_offsets.runtime_state.interpreters_head;
+
+ uintptr_t address_of_interpreter_state;
+ int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ unwinder->runtime_start_address + interpreter_state_list_head,
+ sizeof(void*),
+ &address_of_interpreter_state);
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state for running task");
+ return -1;
+ }
+
+ if (address_of_interpreter_state == 0) {
+ PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL in running task search");
+ return -1;
+ }
+
+ uintptr_t address_of_thread;
+ bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address_of_interpreter_state +
+ unwinder->debug_offsets.interpreter_state.threads_head,
+ sizeof(void*),
+ &address_of_thread);
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread head for running task");
+ return -1;
+ }
+
+ uintptr_t address_of_running_loop;
+ // No Python frames are available for us (can happen at tear-down).
+ if ((void*)address_of_thread == NULL) {
+ return 0;
+ }
+
+ bytes_read = read_py_ptr(
+ unwinder,
+ address_of_thread
+ + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_loop,
+ &address_of_running_loop);
+ if (bytes_read == -1) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running loop address");
+ return -1;
+ }
+
+ // no asyncio loop is now running
+ if ((void*)address_of_running_loop == NULL) {
+ return 0;
+ }
+
+ int err = read_ptr(
+ unwinder,
+ address_of_thread
+ + unwinder->async_debug_offsets.asyncio_thread_state.asyncio_running_task,
+ running_task_addr);
+ if (err) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task address");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+find_running_task_and_coro(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t *running_task_addr,
+ uintptr_t *running_coro_addr,
+ uintptr_t *running_task_code_obj
+) {
+ *running_task_addr = (uintptr_t)NULL;
+ if (find_running_task(
+ unwinder, running_task_addr) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task search failed");
+ return -1;
+ }
+
+ if ((void*)*running_task_addr == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "No running task found");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task address is NULL");
+ return -1;
+ }
+
+ if (read_py_ptr(
+ unwinder,
+ *running_task_addr + unwinder->async_debug_offsets.asyncio_task_object.task_coro,
+ running_coro_addr) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro read failed");
+ return -1;
+ }
+
+ if ((void*)*running_coro_addr == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro address is NULL");
+ return -1;
+ }
+
+ // note: genobject's gi_iframe is an embedded struct so the address to
+ // the offset leads directly to its first field: f_executable
+ if (read_py_ptr(
+ unwinder,
+ *running_coro_addr + unwinder->debug_offsets.gen_object.gi_iframe,
+ running_task_code_obj) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object");
+ return -1;
+ }
+
+ if ((void*)*running_task_code_obj == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* ============================================================================
+ * FRAME PARSING FUNCTIONS
+ * ============================================================================ */
+
+static inline int
+is_frame_valid(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t frame_addr,
+ uintptr_t code_object_addr
+) {
+ if ((void*)code_object_addr == NULL) {
+ return 0;
+ }
+
+ void* frame = (void*)frame_addr;
+
+ if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) == FRAME_OWNED_BY_CSTACK ||
+ GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) == FRAME_OWNED_BY_INTERPRETER) {
+ return 0; // C frame
+ }
+
+ if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_GENERATOR
+ && GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_THREAD) {
+ PyErr_Format(PyExc_RuntimeError, "Unhandled frame owner %d.\n",
+ GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner));
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Unhandled frame owner type in async frame");
+ return -1;
+ }
+ return 1;
+}
+
+static int
+parse_frame_object(
+ RemoteUnwinderObject *unwinder,
+ PyObject** result,
+ uintptr_t address,
+ uintptr_t* previous_frame
+) {
+ char frame[SIZEOF_INTERP_FRAME];
+
+ Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address,
+ SIZEOF_INTERP_FRAME,
+ frame
+ );
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter frame");
+ return -1;
+ }
+
+ *previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
+ uintptr_t code_object = GET_MEMBER_NO_TAG(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable);
+ int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, code_object);
+ if (frame_valid != 1) {
+ return frame_valid;
+ }
+
+ uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
+
+ // Get tlbc_index for free threading builds
+ int32_t tlbc_index = 0;
+#ifdef Py_GIL_DISABLED
+ if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
+ tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
+ }
+#endif
+
+ return parse_code_object(unwinder, result, code_object,instruction_pointer, previous_frame, tlbc_index);
+}
+
+static int
+parse_async_frame_object(
+ RemoteUnwinderObject *unwinder,
+ PyObject** result,
+ uintptr_t address,
+ uintptr_t* previous_frame,
+ uintptr_t* code_object
+) {
+ char frame[SIZEOF_INTERP_FRAME];
+
+ Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ address,
+ SIZEOF_INTERP_FRAME,
+ frame
+ );
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read async frame");
+ return -1;
+ }
+
+ *previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
+ *code_object = GET_MEMBER_NO_TAG(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable);
+ int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, *code_object);
+ if (frame_valid != 1) {
+ return frame_valid;
+ }
+
+ uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
+
+ // Get tlbc_index for free threading builds
+ int32_t tlbc_index = 0;
+#ifdef Py_GIL_DISABLED
+ if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
+ tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
+ }
+#endif
+
+ if (parse_code_object(
+ unwinder, result, *code_object, instruction_pointer, previous_frame, tlbc_index)) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse code object in async frame");
+ return -1;
+ }
+
+ return 1;
+}
+
+static int
+parse_async_frame_chain(
+ RemoteUnwinderObject *unwinder,
+ PyObject *calls,
+ uintptr_t running_task_code_obj
+) {
+ uintptr_t address_of_current_frame;
+ if (find_running_frame(unwinder, unwinder->runtime_start_address, &address_of_current_frame) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Running frame search failed in async chain");
+ return -1;
+ }
+
+ uintptr_t address_of_code_object;
+ while ((void*)address_of_current_frame != NULL) {
+ PyObject* frame_info = NULL;
+ int res = parse_async_frame_object(
+ unwinder,
+ &frame_info,
+ address_of_current_frame,
+ &address_of_current_frame,
+ &address_of_code_object
+ );
+
+ if (res < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Async frame object parsing failed in chain");
+ return -1;
+ }
+
+ if (!frame_info) {
+ continue;
+ }
+
+ if (PyList_Append(calls, frame_info) == -1) {
+ Py_DECREF(frame_info);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame info to async chain");
+ return -1;
+ }
+
+ Py_DECREF(frame_info);
+
+ if (address_of_code_object == running_task_code_obj) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* ============================================================================
+ * AWAITED BY PARSING FUNCTIONS
+ * ============================================================================ */
+
+static int
+append_awaited_by_for_thread(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t head_addr,
+ PyObject *result
+) {
+ char task_node[SIZEOF_LLIST_NODE];
+
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, head_addr,
+ sizeof(task_node), task_node) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read task node head");
+ return -1;
+ }
+
+ size_t iteration_count = 0;
+ const size_t MAX_ITERATIONS = 2 << 15; // A reasonable upper bound
+
+ while (GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next) != head_addr) {
+ if (++iteration_count > MAX_ITERATIONS) {
+ PyErr_SetString(PyExc_RuntimeError, "Task list appears corrupted");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Task list iteration limit exceeded");
+ return -1;
+ }
+
+ if (GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next) == 0) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Invalid linked list structure reading remote memory");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "NULL pointer in task linked list");
+ return -1;
+ }
+
+ uintptr_t task_addr = (uintptr_t)GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next)
+ - unwinder->async_debug_offsets.asyncio_task_object.task_node;
+
+ if (process_single_task_node(unwinder, task_addr, result) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process task node in awaited_by");
+ return -1;
+ }
+
+ // Read next node
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle,
+ (uintptr_t)GET_MEMBER(uintptr_t, task_node, unwinder->debug_offsets.llist_node.next),
+ sizeof(task_node),
+ task_node) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read next task node in awaited_by");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+append_awaited_by(
+ RemoteUnwinderObject *unwinder,
+ unsigned long tid,
+ uintptr_t head_addr,
+ PyObject *result)
+{
+ PyObject *tid_py = PyLong_FromUnsignedLong(tid);
+ if (tid_py == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID object");
+ return -1;
+ }
+
+ PyObject* awaited_by_for_thread = PyList_New(0);
+ if (awaited_by_for_thread == NULL) {
+ Py_DECREF(tid_py);
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create awaited_by thread list");
+ return -1;
+ }
+
+ RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
+ PyObject *result_item = PyStructSequence_New(state->AwaitedInfo_Type);
+ if (result_item == NULL) {
+ Py_DECREF(tid_py);
+ Py_DECREF(awaited_by_for_thread);
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create AwaitedInfo");
+ return -1;
+ }
+
+ PyStructSequence_SetItem(result_item, 0, tid_py); // steals ref
+ PyStructSequence_SetItem(result_item, 1, awaited_by_for_thread); // steals ref
+ if (PyList_Append(result, result_item)) {
+ Py_DECREF(result_item);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append awaited_by result item");
+ return -1;
+ }
+ Py_DECREF(result_item);
+
+ if (append_awaited_by_for_thread(unwinder, head_addr, awaited_by_for_thread))
+ {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append awaited_by for thread");
+ return -1;
+ }
+
+ return 0;
+}
+
+/* ============================================================================
+ * STACK UNWINDING FUNCTIONS
+ * ============================================================================ */
+
+static int
+process_frame_chain(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t initial_frame_addr,
+ StackChunkList *chunks,
+ PyObject *frame_info
+) {
+ uintptr_t frame_addr = initial_frame_addr;
+ uintptr_t prev_frame_addr = 0;
+ const size_t MAX_FRAMES = 1024;
+ size_t frame_count = 0;
+
+ while ((void*)frame_addr != NULL) {
+ PyObject *frame = NULL;
+ uintptr_t next_frame_addr = 0;
+
+ if (++frame_count > MAX_FRAMES) {
+ PyErr_SetString(PyExc_RuntimeError, "Too many stack frames (possible infinite loop)");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Frame chain iteration limit exceeded");
+ return -1;
+ }
+
+ // Try chunks first, fallback to direct memory read
+ if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, chunks) < 0) {
+ PyErr_Clear();
+ if (parse_frame_object(unwinder, &frame, frame_addr, &next_frame_addr) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in chain");
+ return -1;
+ }
+ }
+
+ if (!frame) {
+ break;
+ }
+
+ if (prev_frame_addr && frame_addr != prev_frame_addr) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Broken frame chain: expected frame at 0x%lx, got 0x%lx",
+ prev_frame_addr, frame_addr);
+ Py_DECREF(frame);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Frame chain consistency check failed");
+ return -1;
+ }
+
+ if (PyList_Append(frame_info, frame) == -1) {
+ Py_DECREF(frame);
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to frame info list");
+ return -1;
+ }
+ Py_DECREF(frame);
+
+ prev_frame_addr = next_frame_addr;
+ frame_addr = next_frame_addr;
+ }
+
+ return 0;
+}
+
+static PyObject*
+unwind_stack_for_thread(
+ RemoteUnwinderObject *unwinder,
+ uintptr_t *current_tstate
+) {
+ PyObject *frame_info = NULL;
+ PyObject *thread_id = NULL;
+ PyObject *result = NULL;
+ StackChunkList chunks = {0};
+
+ char ts[SIZEOF_THREAD_STATE];
+ int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
+ &unwinder->handle, *current_tstate, unwinder->debug_offsets.thread_state.size, ts);
+ if (bytes_read < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread state");
+ goto error;
+ }
+
+ uintptr_t frame_addr = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.current_frame);
+
+ frame_info = PyList_New(0);
+ if (!frame_info) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create frame info list");
+ goto error;
+ }
+
+ if (copy_stack_chunks(unwinder, *current_tstate, &chunks) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to copy stack chunks");
+ goto error;
+ }
+
+ if (process_frame_chain(unwinder, frame_addr, &chunks, frame_info) < 0) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process frame chain");
+ goto error;
+ }
+
+ *current_tstate = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.next);
+
+ thread_id = PyLong_FromLongLong(
+ GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.native_thread_id));
+ if (thread_id == NULL) {
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
+ goto error;
+ }
+
+ RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
+ result = PyStructSequence_New(state->ThreadInfo_Type);
+ if (result == NULL) {
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create ThreadInfo");
+ goto error;
+ }
+
+ PyStructSequence_SetItem(result, 0, thread_id); // Steals reference
+ PyStructSequence_SetItem(result, 1, frame_info); // Steals reference
+
+ cleanup_stack_chunks(&chunks);
+ return result;
+
+error:
+ Py_XDECREF(frame_info);
+ Py_XDECREF(thread_id);
+ Py_XDECREF(result);
+ cleanup_stack_chunks(&chunks);
+ return NULL;
+}
+
+
+/* ============================================================================
+ * REMOTEUNWINDER CLASS IMPLEMENTATION
+ * ============================================================================ */
+
+/*[clinic input]
+class _remote_debugging.RemoteUnwinder "RemoteUnwinderObject *" "&RemoteUnwinder_Type"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=55f164d8803318be]*/
+
+/*[clinic input]
+_remote_debugging.RemoteUnwinder.__init__
+ pid: int
+ *
+ all_threads: bool = False
+ only_active_thread: bool = False
+ debug: bool = False
+
+Initialize a new RemoteUnwinder object for debugging a remote Python process.
+
+Args:
+ pid: Process ID of the target Python process to debug
+ all_threads: If True, initialize state for all threads in the process.
+ If False, only initialize for the main thread.
+ only_active_thread: If True, only sample the thread holding the GIL.
+ Cannot be used together with all_threads=True.
+ debug: If True, chain exceptions to explain the sequence of events that
+ lead to the exception.
+
+The RemoteUnwinder provides functionality to inspect and debug a running Python
+process, including examining thread states, stack frames and other runtime data.
+
+Raises:
+ PermissionError: If access to the target process is denied
+ OSError: If unable to attach to the target process or access its memory
+ RuntimeError: If unable to read debug information from the target process
+ ValueError: If both all_threads and only_active_thread are True
+[clinic start generated code]*/
+
+static int
+_remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
+ int pid, int all_threads,
+ int only_active_thread,
+ int debug)
+/*[clinic end generated code: output=13ba77598ecdcbe1 input=8f8f12504e17da04]*/
+{
+ // Validate that all_threads and only_active_thread are not both True
+ if (all_threads && only_active_thread) {
+ PyErr_SetString(PyExc_ValueError,
+ "all_threads and only_active_thread cannot both be True");
+ return -1;
+ }
+
+#ifdef Py_GIL_DISABLED
+ if (only_active_thread) {
+ PyErr_SetString(PyExc_ValueError,
+ "only_active_thread is not supported when Py_GIL_DISABLED is not defined");
+ return -1;
+ }
+#endif
+
+ self->debug = debug;
+ self->only_active_thread = only_active_thread;
+ self->cached_state = NULL;
+ if (_Py_RemoteDebug_InitProcHandle(&self->handle, pid) < 0) {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to initialize process handle");
+ return -1;
+ }
+
+ self->runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&self->handle);
+ if (self->runtime_start_address == 0) {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to get Python runtime address");
+ return -1;
+ }
+
+ if (_Py_RemoteDebug_ReadDebugOffsets(&self->handle,
+ &self->runtime_start_address,
+ &self->debug_offsets) < 0)
+ {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to read debug offsets");
+ return -1;
+ }
+
+ // Validate that the debug offsets are valid
+ if(validate_debug_offsets(&self->debug_offsets) == -1) {
+ set_exception_cause(self, PyExc_RuntimeError, "Invalid debug offsets found");
+ return -1;
+ }
+
+ // Try to read async debug offsets, but don't fail if they're not available
+ self->async_debug_offsets_available = 1;
+ if (read_async_debug(self) < 0) {
+ PyErr_Clear();
+ memset(&self->async_debug_offsets, 0, sizeof(self->async_debug_offsets));
+ self->async_debug_offsets_available = 0;
+ }
+
+ if (populate_initial_state_data(all_threads, self, self->runtime_start_address,
+ &self->interpreter_addr ,&self->tstate_addr) < 0)
+ {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to populate initial state data");
+ return -1;
+ }
+
+ self->code_object_cache = _Py_hashtable_new_full(
+ _Py_hashtable_hash_ptr,
+ _Py_hashtable_compare_direct,
+ NULL, // keys are stable pointers, don't destroy
+ cached_code_metadata_destroy,
+ NULL
+ );
+ if (self->code_object_cache == NULL) {
+ PyErr_NoMemory();
+ set_exception_cause(self, PyExc_MemoryError, "Failed to create code object cache");
+ return -1;
+ }
+
+#ifdef Py_GIL_DISABLED
+ // Initialize TLBC cache
+ self->tlbc_generation = 0;
+ self->tlbc_cache = _Py_hashtable_new_full(
+ _Py_hashtable_hash_ptr,
+ _Py_hashtable_compare_direct,
+ NULL, // keys are stable pointers, don't destroy
+ tlbc_cache_entry_destroy,
+ NULL
+ );
+ if (self->tlbc_cache == NULL) {
+ _Py_hashtable_destroy(self->code_object_cache);
+ PyErr_NoMemory();
+ set_exception_cause(self, PyExc_MemoryError, "Failed to create TLBC cache");
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+/*[clinic input]
+@critical_section
+_remote_debugging.RemoteUnwinder.get_stack_trace
+
+Returns a list of stack traces for threads in the target process.
+
+Each element in the returned list is a tuple of (thread_id, frame_list), where:
+- thread_id is the OS thread identifier
+- frame_list is a list of tuples (function_name, filename, line_number) representing
+ the Python stack frames for that thread, ordered from most recent to oldest
+
+The threads returned depend on the initialization parameters:
+- If only_active_thread was True: returns only the thread holding the GIL
+- If all_threads was True: returns all threads
+- Otherwise: returns only the main thread
+
+Example:
+ [
+ (1234, [
+ ('process_data', 'worker.py', 127),
+ ('run_worker', 'worker.py', 45),
+ ('main', 'app.py', 23)
+ ]),
+ (1235, [
+ ('handle_request', 'server.py', 89),
+ ('serve_forever', 'server.py', 52)
+ ])
+ ]
+
+Raises:
+ RuntimeError: If there is an error copying memory from the target process
+ OSError: If there is an error accessing the target process
+ PermissionError: If access to the target process is denied
+ UnicodeDecodeError: If there is an error decoding strings from the target process
+
+[clinic start generated code]*/
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self)
+/*[clinic end generated code: output=666192b90c69d567 input=f756f341206f9116]*/
+{
+ PyObject* result = NULL;
+ // Read interpreter state into opaque buffer
+ char interp_state_buffer[INTERP_STATE_BUFFER_SIZE];
+ if (_Py_RemoteDebug_PagedReadRemoteMemory(
+ &self->handle,
+ self->interpreter_addr,
+ INTERP_STATE_BUFFER_SIZE,
+ interp_state_buffer) < 0) {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to read interpreter state buffer");
+ goto exit;
+ }
+
+ // Get code object generation from buffer
+ uint64_t code_object_generation = GET_MEMBER(uint64_t, interp_state_buffer,
+ self->debug_offsets.interpreter_state.code_object_generation);
+
+ if (code_object_generation != self->code_object_generation) {
+ self->code_object_generation = code_object_generation;
+ _Py_hashtable_clear(self->code_object_cache);
+ }
+
+ // If only_active_thread is true, we need to determine which thread holds the GIL
+ PyThreadState* gil_holder = NULL;
+ if (self->only_active_thread) {
+ // The GIL state is already in interp_state_buffer, just read from there
+ // Check if GIL is locked
+ int gil_locked = GET_MEMBER(int, interp_state_buffer,
+ self->debug_offsets.interpreter_state.gil_runtime_state_locked);
+
+ if (gil_locked) {
+ // Get the last holder (current holder when GIL is locked)
+ gil_holder = GET_MEMBER(PyThreadState*, interp_state_buffer,
+ self->debug_offsets.interpreter_state.gil_runtime_state_holder);
+ } else {
+ // GIL is not locked, return empty list
+ result = PyList_New(0);
+ if (!result) {
+ set_exception_cause(self, PyExc_MemoryError, "Failed to create empty result list");
+ }
+ goto exit;
+ }
+ }
+
+#ifdef Py_GIL_DISABLED
+ // Check TLBC generation and invalidate cache if needed
+ uint32_t current_tlbc_generation = GET_MEMBER(uint32_t, interp_state_buffer,
+ self->debug_offsets.interpreter_state.tlbc_generation);
+ if (current_tlbc_generation != self->tlbc_generation) {
+ self->tlbc_generation = current_tlbc_generation;
+ _Py_hashtable_clear(self->tlbc_cache);
+ }
+#endif
+
+ uintptr_t current_tstate;
+ if (self->only_active_thread && gil_holder != NULL) {
+ // We have the GIL holder, process only that thread
+ current_tstate = (uintptr_t)gil_holder;
+ } else if (self->tstate_addr == 0) {
+ // Get threads head from buffer
+ current_tstate = GET_MEMBER(uintptr_t, interp_state_buffer,
+ self->debug_offsets.interpreter_state.threads_head);
+ } else {
+ current_tstate = self->tstate_addr;
+ }
+
+ result = PyList_New(0);
+ if (!result) {
+ set_exception_cause(self, PyExc_MemoryError, "Failed to create stack trace result list");
+ goto exit;
+ }
+
+ while (current_tstate != 0) {
+ PyObject* frame_info = unwind_stack_for_thread(self, &current_tstate);
+ if (!frame_info) {
+ Py_CLEAR(result);
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to unwind stack for thread");
+ goto exit;
+ }
+
+ if (PyList_Append(result, frame_info) == -1) {
+ Py_DECREF(frame_info);
+ Py_CLEAR(result);
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to append thread frame info");
+ goto exit;
+ }
+ Py_DECREF(frame_info);
+
+ // We are targeting a single tstate, break here
+ if (self->tstate_addr) {
+ break;
+ }
+
+ // If we're only processing the GIL holder, we're done after one iteration
+ if (self->only_active_thread && gil_holder != NULL) {
+ break;
+ }
+ }
+
+exit:
+ return result;
+}
+
+/*[clinic input]
+@critical_section
+_remote_debugging.RemoteUnwinder.get_all_awaited_by
+
+Get all tasks and their awaited_by relationships from the remote process.
+
+This provides a tree structure showing which tasks are waiting for other tasks.
+
+For each task, returns:
+1. The call stack frames leading to where the task is currently executing
+2. The name of the task
+3. A list of tasks that this task is waiting for, with their own frames/names/etc
+
+Returns a list of [frames, task_name, subtasks] where:
+- frames: List of (func_name, filename, lineno) showing the call stack
+- task_name: String identifier for the task
+- subtasks: List of tasks being awaited by this task, in same format
+
+Raises:
+ RuntimeError: If AsyncioDebug section is not available in the remote process
+ MemoryError: If memory allocation fails
+ OSError: If reading from the remote process fails
+
+Example output:
+[
+ # Task c2_root waiting for two subtasks
+ [
+ # Call stack of c2_root
+ [("c5", "script.py", 10), ("c4", "script.py", 14)],
+ "c2_root",
+ [
+ # First subtask (sub_main_2) and what it's waiting for
+ [
+ [("c1", "script.py", 23)],
+ "sub_main_2",
+ [...]
+ ],
+ # Second subtask and its waiters
+ [...]
+ ]
+ ]
+]
+[clinic start generated code]*/
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *self)
+/*[clinic end generated code: output=6a49cd345e8aec53 input=a452c652bb00701a]*/
+{
+ if (!self->async_debug_offsets_available) {
+ PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
+ set_exception_cause(self, PyExc_RuntimeError, "AsyncioDebug section unavailable in get_all_awaited_by");
+ return NULL;
+ }
+
+ PyObject *result = PyList_New(0);
+ if (result == NULL) {
+ set_exception_cause(self, PyExc_MemoryError, "Failed to create awaited_by result list");
+ goto result_err;
+ }
+
+ uintptr_t thread_state_addr;
+ unsigned long tid = 0;
+ if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
+ &self->handle,
+ self->interpreter_addr
+ + self->debug_offsets.interpreter_state.threads_main,
+ sizeof(void*),
+ &thread_state_addr))
+ {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to read main thread state in get_all_awaited_by");
+ goto result_err;
+ }
+
+ uintptr_t head_addr;
+ while (thread_state_addr != 0) {
+ if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
+ &self->handle,
+ thread_state_addr
+ + self->debug_offsets.thread_state.native_thread_id,
+ sizeof(tid),
+ &tid))
+ {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to read thread ID in get_all_awaited_by");
+ goto result_err;
+ }
+
+ head_addr = thread_state_addr
+ + self->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head;
+
+ if (append_awaited_by(self, tid, head_addr, result))
+ {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to append awaited_by for thread in get_all_awaited_by");
+ goto result_err;
+ }
+
+ if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
+ &self->handle,
+ thread_state_addr + self->debug_offsets.thread_state.next,
+ sizeof(void*),
+ &thread_state_addr))
+ {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to read next thread state in get_all_awaited_by");
+ goto result_err;
+ }
+ }
+
+ head_addr = self->interpreter_addr
+ + self->async_debug_offsets.asyncio_interpreter_state.asyncio_tasks_head;
+
+ // On top of a per-thread task lists used by default by asyncio to avoid
+ // contention, there is also a fallback per-interpreter list of tasks;
+ // any tasks still pending when a thread is destroyed will be moved to the
+ // per-interpreter task list. It's unlikely we'll find anything here, but
+ // interesting for debugging.
+ if (append_awaited_by(self, 0, head_addr, result))
+ {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to append interpreter awaited_by in get_all_awaited_by");
+ goto result_err;
+ }
+
+ return result;
+
+result_err:
+ Py_XDECREF(result);
+ return NULL;
+}
+
+/*[clinic input]
+@critical_section
+_remote_debugging.RemoteUnwinder.get_async_stack_trace
+
+Returns information about the currently running async task and its stack trace.
+
+Returns a tuple of (task_info, stack_frames) where:
+- task_info is a tuple of (task_id, task_name) identifying the task
+- stack_frames is a list of tuples (function_name, filename, line_number) representing
+ the Python stack frames for the task, ordered from most recent to oldest
+
+Example:
+ ((4345585712, 'Task-1'), [
+ ('run_echo_server', 'server.py', 127),
+ ('serve_forever', 'server.py', 45),
+ ('main', 'app.py', 23)
+ ])
+
+Raises:
+ RuntimeError: If AsyncioDebug section is not available in the target process
+ RuntimeError: If there is an error copying memory from the target process
+ OSError: If there is an error accessing the target process
+ PermissionError: If access to the target process is denied
+ UnicodeDecodeError: If there is an error decoding strings from the target process
+
+[clinic start generated code]*/
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject *self)
+/*[clinic end generated code: output=6433d52b55e87bbe input=11b7150c59d4c60f]*/
+{
+ if (!self->async_debug_offsets_available) {
+ PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
+ set_exception_cause(self, PyExc_RuntimeError, "AsyncioDebug section unavailable in get_async_stack_trace");
+ return NULL;
+ }
+
+ PyObject *result = NULL;
+ PyObject *calls = NULL;
+
+ if (setup_async_result_structure(self, &result, &calls) < 0) {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to setup async result structure");
+ goto cleanup;
+ }
+
+ uintptr_t running_task_addr, running_coro_addr, running_task_code_obj;
+ if (find_running_task_and_coro(self, &running_task_addr,
+ &running_coro_addr, &running_task_code_obj) < 0) {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to find running task and coro");
+ goto cleanup;
+ }
+
+ if (parse_async_frame_chain(self, calls, running_task_code_obj) < 0) {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to parse async frame chain");
+ goto cleanup;
+ }
+
+ if (add_task_info_to_result(self, result, running_task_addr) < 0) {
+ set_exception_cause(self, PyExc_RuntimeError, "Failed to add task info to result");
+ goto cleanup;
+ }
+
+ return result;
+
+cleanup:
+ Py_XDECREF(result);
+ return NULL;
+}
+
+static PyMethodDef RemoteUnwinder_methods[] = {
+ _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_STACK_TRACE_METHODDEF
+ _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ALL_AWAITED_BY_METHODDEF
+ _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ASYNC_STACK_TRACE_METHODDEF
+ {NULL, NULL}
+};
+
+static void
+RemoteUnwinder_dealloc(PyObject *op)
+{
+ RemoteUnwinderObject *self = RemoteUnwinder_CAST(op);
+ PyTypeObject *tp = Py_TYPE(self);
+ if (self->code_object_cache) {
+ _Py_hashtable_destroy(self->code_object_cache);
+ }
+#ifdef Py_GIL_DISABLED
+ if (self->tlbc_cache) {
+ _Py_hashtable_destroy(self->tlbc_cache);
+ }
+#endif
+ if (self->handle.pid != 0) {
+ _Py_RemoteDebug_CleanupProcHandle(&self->handle);
+ }
+ PyObject_Del(self);
+ Py_DECREF(tp);
+}
+
+static PyType_Slot RemoteUnwinder_slots[] = {
+ {Py_tp_doc, (void *)"RemoteUnwinder(pid): Inspect stack of a remote Python process."},
+ {Py_tp_methods, RemoteUnwinder_methods},
+ {Py_tp_init, _remote_debugging_RemoteUnwinder___init__},
+ {Py_tp_dealloc, RemoteUnwinder_dealloc},
+ {0, NULL}
+};
+
+static PyType_Spec RemoteUnwinder_spec = {
+ .name = "_remote_debugging.RemoteUnwinder",
+ .basicsize = sizeof(RemoteUnwinderObject),
+ .flags = Py_TPFLAGS_DEFAULT,
+ .slots = RemoteUnwinder_slots,
+};
+
+/* ============================================================================
+ * MODULE INITIALIZATION
+ * ============================================================================ */
+
+static int
+_remote_debugging_exec(PyObject *m)
+{
+ RemoteDebuggingState *st = RemoteDebugging_GetState(m);
+#define CREATE_TYPE(mod, type, spec) \
+ do { \
+ type = (PyTypeObject *)PyType_FromMetaclass(NULL, mod, spec, NULL); \
+ if (type == NULL) { \
+ return -1; \
+ } \
+ } while (0)
+
+ CREATE_TYPE(m, st->RemoteDebugging_Type, &RemoteUnwinder_spec);
+
+ if (PyModule_AddType(m, st->RemoteDebugging_Type) < 0) {
+ return -1;
+ }
+
+ // Initialize structseq types
+ st->TaskInfo_Type = PyStructSequence_NewType(&TaskInfo_desc);
+ if (st->TaskInfo_Type == NULL) {
+ return -1;
+ }
+ if (PyModule_AddType(m, st->TaskInfo_Type) < 0) {
+ return -1;
+ }
+
+ st->FrameInfo_Type = PyStructSequence_NewType(&FrameInfo_desc);
+ if (st->FrameInfo_Type == NULL) {
+ return -1;
+ }
+ if (PyModule_AddType(m, st->FrameInfo_Type) < 0) {
+ return -1;
+ }
+
+ st->CoroInfo_Type = PyStructSequence_NewType(&CoroInfo_desc);
+ if (st->CoroInfo_Type == NULL) {
+ return -1;
+ }
+ if (PyModule_AddType(m, st->CoroInfo_Type) < 0) {
+ return -1;
+ }
+
+ st->ThreadInfo_Type = PyStructSequence_NewType(&ThreadInfo_desc);
+ if (st->ThreadInfo_Type == NULL) {
+ return -1;
+ }
+ if (PyModule_AddType(m, st->ThreadInfo_Type) < 0) {
+ return -1;
+ }
+
+ st->AwaitedInfo_Type = PyStructSequence_NewType(&AwaitedInfo_desc);
+ if (st->AwaitedInfo_Type == NULL) {
+ return -1;
+ }
+ if (PyModule_AddType(m, st->AwaitedInfo_Type) < 0) {
+ return -1;
+ }
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+ int rc = PyModule_AddIntConstant(m, "PROCESS_VM_READV_SUPPORTED", HAVE_PROCESS_VM_READV);
+ if (rc < 0) {
+ return -1;
+ }
+ if (RemoteDebugging_InitState(st) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+remote_debugging_traverse(PyObject *mod, visitproc visit, void *arg)
+{
+ RemoteDebuggingState *state = RemoteDebugging_GetState(mod);
+ Py_VISIT(state->RemoteDebugging_Type);
+ Py_VISIT(state->TaskInfo_Type);
+ Py_VISIT(state->FrameInfo_Type);
+ Py_VISIT(state->CoroInfo_Type);
+ Py_VISIT(state->ThreadInfo_Type);
+ Py_VISIT(state->AwaitedInfo_Type);
+ return 0;
+}
+
+static int
+remote_debugging_clear(PyObject *mod)
+{
+ RemoteDebuggingState *state = RemoteDebugging_GetState(mod);
+ Py_CLEAR(state->RemoteDebugging_Type);
+ Py_CLEAR(state->TaskInfo_Type);
+ Py_CLEAR(state->FrameInfo_Type);
+ Py_CLEAR(state->CoroInfo_Type);
+ Py_CLEAR(state->ThreadInfo_Type);
+ Py_CLEAR(state->AwaitedInfo_Type);
+ return 0;
+}
+
+static void
+remote_debugging_free(void *mod)
+{
+ (void)remote_debugging_clear((PyObject *)mod);
+}
+
+static PyModuleDef_Slot remote_debugging_slots[] = {
+ {Py_mod_exec, _remote_debugging_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+ {0, NULL},
+};
+
+static PyMethodDef remote_debugging_methods[] = {
+ {NULL, NULL, 0, NULL},
+};
+
+static struct PyModuleDef remote_debugging_module = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "_remote_debugging",
+ .m_size = sizeof(RemoteDebuggingState),
+ .m_methods = remote_debugging_methods,
+ .m_slots = remote_debugging_slots,
+ .m_traverse = remote_debugging_traverse,
+ .m_clear = remote_debugging_clear,
+ .m_free = remote_debugging_free,
+};
+
+PyMODINIT_FUNC
+PyInit__remote_debugging(void)
+{
+ return PyModuleDef_Init(&remote_debugging_module);
+}
diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c
index 35d090e3ca2..aafefbf316e 100644
--- a/Modules/_sqlite/blob.c
+++ b/Modules/_sqlite/blob.c
@@ -4,6 +4,7 @@
#include "blob.h"
#include "util.h"
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#define clinic_state() (pysqlite_get_state_by_type(Py_TYPE(self)))
#include "clinic/blob.c.h"
@@ -56,9 +57,7 @@ blob_dealloc(PyObject *op)
close_blob(self);
- if (self->in_weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, self->in_weakreflist);
(void)tp->tp_clear(op);
tp->tp_free(self);
Py_DECREF(tp);
diff --git a/Modules/_sqlite/clinic/_sqlite3.connect.c.h b/Modules/_sqlite/clinic/_sqlite3.connect.c.h
index 1bcda7702c2..e9d560666c1 100644
--- a/Modules/_sqlite/clinic/_sqlite3.connect.c.h
+++ b/Modules/_sqlite/clinic/_sqlite3.connect.c.h
@@ -9,23 +9,17 @@ preserve
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(pysqlite_connect__doc__,
-"connect($module, /, database, timeout=5.0, detect_types=0,\n"
+"connect($module, /, database, *, timeout=5.0, detect_types=0,\n"
" isolation_level=\'\', check_same_thread=True,\n"
-" factory=ConnectionType, cached_statements=128, uri=False, *,\n"
+" factory=ConnectionType, cached_statements=128, uri=False,\n"
" autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL)\n"
"--\n"
"\n"
"Open a connection to the SQLite database file \'database\'.\n"
"\n"
"You can use \":memory:\" to open a database connection to a database that\n"
-"resides in RAM instead of on disk.\n"
-"\n"
-"Note: Passing more than 1 positional argument to _sqlite3.connect() is\n"
-"deprecated. Parameters \'timeout\', \'detect_types\', \'isolation_level\',\n"
-"\'check_same_thread\', \'factory\', \'cached_statements\' and \'uri\' will\n"
-"become keyword-only parameters in Python 3.15.\n"
-"");
+"resides in RAM instead of on disk.");
#define PYSQLITE_CONNECT_METHODDEF \
{"connect", _PyCFunction_CAST(pysqlite_connect), METH_FASTCALL|METH_KEYWORDS, pysqlite_connect__doc__},
-/*[clinic end generated code: output=69b9b00da71c3c0a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=3d83139ba65e0bb5 input=a9049054013a1b77]*/
diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h
index c8e1d0b7a73..f0e9fdb8894 100644
--- a/Modules/_sqlite/clinic/connection.c.h
+++ b/Modules/_sqlite/clinic/connection.c.h
@@ -16,17 +16,6 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database,
int cache_size, int uri,
enum autocommit_mode autocommit);
-// Emit compiler warnings when we get to Python 3.15.
-#if PY_VERSION_HEX >= 0x030f00C0
-# error "Update the clinic input of '_sqlite3.Connection.__init__'."
-#elif PY_VERSION_HEX >= 0x030f00A0
-# ifdef _MSC_VER
-# pragma message ("Update the clinic input of '_sqlite3.Connection.__init__'.")
-# else
-# warning "Update the clinic input of '_sqlite3.Connection.__init__'."
-# endif
-#endif
-
static int
pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
{
@@ -72,25 +61,14 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
int uri = 0;
enum autocommit_mode autocommit = LEGACY_TRANSACTION_CONTROL;
- if (nargs > 1 && nargs <= 8) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "Passing more than 1 positional argument to _sqlite3.Connection()"
- " is deprecated. Parameters 'timeout', 'detect_types', "
- "'isolation_level', 'check_same_thread', 'factory', "
- "'cached_statements' and 'uri' will become keyword-only "
- "parameters in Python 3.15.", 1))
- {
- goto exit;
- }
- }
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser,
- /*minpos*/ 1, /*maxpos*/ 8, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
if (!fastargs) {
goto exit;
}
database = fastargs[0];
if (!noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
if (fastargs[1]) {
if (PyFloat_CheckExact(fastargs[1])) {
@@ -104,7 +82,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
}
}
if (!--noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
}
if (fastargs[2]) {
@@ -113,7 +91,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
goto exit;
}
if (!--noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
}
if (fastargs[3]) {
@@ -121,7 +99,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
goto exit;
}
if (!--noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
}
if (fastargs[4]) {
@@ -130,13 +108,13 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
goto exit;
}
if (!--noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
}
if (fastargs[5]) {
factory = fastargs[5];
if (!--noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
}
if (fastargs[6]) {
@@ -145,7 +123,7 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
goto exit;
}
if (!--noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
}
if (fastargs[7]) {
@@ -154,13 +132,9 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs)
goto exit;
}
if (!--noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
}
}
-skip_optional_pos:
- if (!noptargs) {
- goto skip_optional_kwonly;
- }
if (!autocommit_converter(fastargs[8], &autocommit)) {
goto exit;
}
@@ -424,15 +398,10 @@ pysqlite_connection_rollback(PyObject *self, PyObject *Py_UNUSED(ignored))
}
PyDoc_STRVAR(pysqlite_connection_create_function__doc__,
-"create_function($self, /, name, narg, func, *, deterministic=False)\n"
+"create_function($self, name, narg, func, /, *, deterministic=False)\n"
"--\n"
"\n"
-"Creates a new function.\n"
-"\n"
-"Note: Passing keyword arguments \'name\', \'narg\' and \'func\' to\n"
-"_sqlite3.Connection.create_function() is deprecated. Parameters\n"
-"\'name\', \'narg\' and \'func\' will become positional-only in Python 3.15.\n"
-"");
+"Creates a new function.");
#define PYSQLITE_CONNECTION_CREATE_FUNCTION_METHODDEF \
{"create_function", _PyCFunction_CAST(pysqlite_connection_create_function), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_create_function__doc__},
@@ -443,24 +412,13 @@ pysqlite_connection_create_function_impl(pysqlite_Connection *self,
int narg, PyObject *func,
int deterministic);
-// Emit compiler warnings when we get to Python 3.15.
-#if PY_VERSION_HEX >= 0x030f00C0
-# error "Update the clinic input of '_sqlite3.Connection.create_function'."
-#elif PY_VERSION_HEX >= 0x030f00A0
-# ifdef _MSC_VER
-# pragma message ("Update the clinic input of '_sqlite3.Connection.create_function'.")
-# else
-# warning "Update the clinic input of '_sqlite3.Connection.create_function'."
-# endif
-#endif
-
static PyObject *
pysqlite_connection_create_function(PyObject *self, PyTypeObject *cls, 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 4
+ #define NUM_KEYWORDS 1
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -469,7 +427,7 @@ pysqlite_connection_create_function(PyObject *self, PyTypeObject *cls, PyObject
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(name), &_Py_ID(narg), &_Py_ID(func), &_Py_ID(deterministic), },
+ .ob_item = { &_Py_ID(deterministic), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -478,7 +436,7 @@ pysqlite_connection_create_function(PyObject *self, PyTypeObject *cls, PyObject
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"name", "narg", "func", "deterministic", NULL};
+ static const char * const _keywords[] = {"", "", "", "deterministic", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "create_function",
@@ -497,18 +455,8 @@ pysqlite_connection_create_function(PyObject *self, PyTypeObject *cls, PyObject
if (!args) {
goto exit;
}
- if (nargs < 3) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "Passing keyword arguments 'name', 'narg' and 'func' to "
- "_sqlite3.Connection.create_function() is deprecated. Parameters "
- "'name', 'narg' and 'func' will become positional-only in Python "
- "3.15.", 1))
- {
- goto exit;
- }
- }
if (!PyUnicode_Check(args[0])) {
- _PyArg_BadArgument("create_function", "argument 'name'", "str", args[0]);
+ _PyArg_BadArgument("create_function", "argument 1", "str", args[0]);
goto exit;
}
Py_ssize_t name_length;
@@ -618,16 +566,10 @@ exit:
#endif /* defined(HAVE_WINDOW_FUNCTIONS) */
PyDoc_STRVAR(pysqlite_connection_create_aggregate__doc__,
-"create_aggregate($self, /, name, n_arg, aggregate_class)\n"
+"create_aggregate($self, name, n_arg, aggregate_class, /)\n"
"--\n"
"\n"
-"Creates a new aggregate.\n"
-"\n"
-"Note: Passing keyword arguments \'name\', \'n_arg\' and \'aggregate_class\'\n"
-"to _sqlite3.Connection.create_aggregate() is deprecated. Parameters\n"
-"\'name\', \'n_arg\' and \'aggregate_class\' will become positional-only in\n"
-"Python 3.15.\n"
-"");
+"Creates a new aggregate.");
#define PYSQLITE_CONNECTION_CREATE_AGGREGATE_METHODDEF \
{"create_aggregate", _PyCFunction_CAST(pysqlite_connection_create_aggregate), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_create_aggregate__doc__},
@@ -638,42 +580,17 @@ pysqlite_connection_create_aggregate_impl(pysqlite_Connection *self,
const char *name, int n_arg,
PyObject *aggregate_class);
-// Emit compiler warnings when we get to Python 3.15.
-#if PY_VERSION_HEX >= 0x030f00C0
-# error "Update the clinic input of '_sqlite3.Connection.create_aggregate'."
-#elif PY_VERSION_HEX >= 0x030f00A0
-# ifdef _MSC_VER
-# pragma message ("Update the clinic input of '_sqlite3.Connection.create_aggregate'.")
-# else
-# warning "Update the clinic input of '_sqlite3.Connection.create_aggregate'."
-# endif
-#endif
-
static PyObject *
pysqlite_connection_create_aggregate(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
-
- #define NUM_KEYWORDS 3
- static struct {
- PyGC_Head _this_is_not_used;
- PyObject_VAR_HEAD
- 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(name), &_Py_ID(n_arg), &_Py_ID(aggregate_class), },
- };
- #undef NUM_KEYWORDS
- #define KWTUPLE (&_kwtuple.ob_base.ob_base)
-
- #else // !Py_BUILD_CORE
+ # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
+ #else
# define KWTUPLE NULL
- #endif // !Py_BUILD_CORE
+ #endif
- static const char * const _keywords[] = {"name", "n_arg", "aggregate_class", NULL};
+ static const char * const _keywords[] = {"", "", "", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "create_aggregate",
@@ -690,18 +607,8 @@ pysqlite_connection_create_aggregate(PyObject *self, PyTypeObject *cls, PyObject
if (!args) {
goto exit;
}
- if (nargs < 3) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "Passing keyword arguments 'name', 'n_arg' and 'aggregate_class' "
- "to _sqlite3.Connection.create_aggregate() is deprecated. "
- "Parameters 'name', 'n_arg' and 'aggregate_class' will become "
- "positional-only in Python 3.15.", 1))
- {
- goto exit;
- }
- }
if (!PyUnicode_Check(args[0])) {
- _PyArg_BadArgument("create_aggregate", "argument 'name'", "str", args[0]);
+ _PyArg_BadArgument("create_aggregate", "argument 1", "str", args[0]);
goto exit;
}
Py_ssize_t name_length;
@@ -725,15 +632,10 @@ exit:
}
PyDoc_STRVAR(pysqlite_connection_set_authorizer__doc__,
-"set_authorizer($self, /, authorizer_callback)\n"
+"set_authorizer($self, authorizer_callback, /)\n"
"--\n"
"\n"
-"Set authorizer callback.\n"
-"\n"
-"Note: Passing keyword argument \'authorizer_callback\' to\n"
-"_sqlite3.Connection.set_authorizer() is deprecated. Parameter\n"
-"\'authorizer_callback\' will become positional-only in Python 3.15.\n"
-"");
+"Set authorizer callback.");
#define PYSQLITE_CONNECTION_SET_AUTHORIZER_METHODDEF \
{"set_authorizer", _PyCFunction_CAST(pysqlite_connection_set_authorizer), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_authorizer__doc__},
@@ -743,42 +645,17 @@ pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self,
PyTypeObject *cls,
PyObject *callable);
-// Emit compiler warnings when we get to Python 3.15.
-#if PY_VERSION_HEX >= 0x030f00C0
-# error "Update the clinic input of '_sqlite3.Connection.set_authorizer'."
-#elif PY_VERSION_HEX >= 0x030f00A0
-# ifdef _MSC_VER
-# pragma message ("Update the clinic input of '_sqlite3.Connection.set_authorizer'.")
-# else
-# warning "Update the clinic input of '_sqlite3.Connection.set_authorizer'."
-# endif
-#endif
-
static PyObject *
pysqlite_connection_set_authorizer(PyObject *self, PyTypeObject *cls, 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(authorizer_callback), },
- };
- #undef NUM_KEYWORDS
- #define KWTUPLE (&_kwtuple.ob_base.ob_base)
-
- #else // !Py_BUILD_CORE
+ # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
+ #else
# define KWTUPLE NULL
- #endif // !Py_BUILD_CORE
+ #endif
- static const char * const _keywords[] = {"authorizer_callback", NULL};
+ static const char * const _keywords[] = {"", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "set_authorizer",
@@ -793,16 +670,6 @@ pysqlite_connection_set_authorizer(PyObject *self, PyTypeObject *cls, PyObject *
if (!args) {
goto exit;
}
- if (nargs < 1) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "Passing keyword argument 'authorizer_callback' to "
- "_sqlite3.Connection.set_authorizer() is deprecated. Parameter "
- "'authorizer_callback' will become positional-only in Python "
- "3.15.", 1))
- {
- goto exit;
- }
- }
callable = args[0];
return_value = pysqlite_connection_set_authorizer_impl((pysqlite_Connection *)self, cls, callable);
@@ -811,7 +678,7 @@ exit:
}
PyDoc_STRVAR(pysqlite_connection_set_progress_handler__doc__,
-"set_progress_handler($self, /, progress_handler, n)\n"
+"set_progress_handler($self, progress_handler, /, n)\n"
"--\n"
"\n"
"Set progress handler callback.\n"
@@ -824,12 +691,7 @@ PyDoc_STRVAR(pysqlite_connection_set_progress_handler__doc__,
" The number of SQLite virtual machine instructions that are\n"
" executed between invocations of \'progress_handler\'.\n"
"\n"
-"If \'progress_handler\' is None or \'n\' is 0, the progress handler is disabled.\n"
-"\n"
-"Note: Passing keyword argument \'progress_handler\' to\n"
-"_sqlite3.Connection.set_progress_handler() is deprecated. Parameter\n"
-"\'progress_handler\' will become positional-only in Python 3.15.\n"
-"");
+"If \'progress_handler\' is None or \'n\' is 0, the progress handler is disabled.");
#define PYSQLITE_CONNECTION_SET_PROGRESS_HANDLER_METHODDEF \
{"set_progress_handler", _PyCFunction_CAST(pysqlite_connection_set_progress_handler), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_progress_handler__doc__},
@@ -839,24 +701,13 @@ pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self,
PyTypeObject *cls,
PyObject *callable, int n);
-// Emit compiler warnings when we get to Python 3.15.
-#if PY_VERSION_HEX >= 0x030f00C0
-# error "Update the clinic input of '_sqlite3.Connection.set_progress_handler'."
-#elif PY_VERSION_HEX >= 0x030f00A0
-# ifdef _MSC_VER
-# pragma message ("Update the clinic input of '_sqlite3.Connection.set_progress_handler'.")
-# else
-# warning "Update the clinic input of '_sqlite3.Connection.set_progress_handler'."
-# endif
-#endif
-
static PyObject *
pysqlite_connection_set_progress_handler(PyObject *self, PyTypeObject *cls, 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
+ #define NUM_KEYWORDS 1
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -865,7 +716,7 @@ pysqlite_connection_set_progress_handler(PyObject *self, PyTypeObject *cls, PyOb
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(progress_handler), _Py_LATIN1_CHR('n'), },
+ .ob_item = { _Py_LATIN1_CHR('n'), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -874,7 +725,7 @@ pysqlite_connection_set_progress_handler(PyObject *self, PyTypeObject *cls, PyOb
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"progress_handler", "n", NULL};
+ static const char * const _keywords[] = {"", "n", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "set_progress_handler",
@@ -890,16 +741,6 @@ pysqlite_connection_set_progress_handler(PyObject *self, PyTypeObject *cls, PyOb
if (!args) {
goto exit;
}
- if (nargs < 1) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "Passing keyword argument 'progress_handler' to "
- "_sqlite3.Connection.set_progress_handler() is deprecated. "
- "Parameter 'progress_handler' will become positional-only in "
- "Python 3.15.", 1))
- {
- goto exit;
- }
- }
callable = args[0];
n = PyLong_AsInt(args[1]);
if (n == -1 && PyErr_Occurred()) {
@@ -912,15 +753,10 @@ exit:
}
PyDoc_STRVAR(pysqlite_connection_set_trace_callback__doc__,
-"set_trace_callback($self, /, trace_callback)\n"
+"set_trace_callback($self, trace_callback, /)\n"
"--\n"
"\n"
-"Set a trace callback called for each SQL statement (passed as unicode).\n"
-"\n"
-"Note: Passing keyword argument \'trace_callback\' to\n"
-"_sqlite3.Connection.set_trace_callback() is deprecated. Parameter\n"
-"\'trace_callback\' will become positional-only in Python 3.15.\n"
-"");
+"Set a trace callback called for each SQL statement (passed as unicode).");
#define PYSQLITE_CONNECTION_SET_TRACE_CALLBACK_METHODDEF \
{"set_trace_callback", _PyCFunction_CAST(pysqlite_connection_set_trace_callback), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_trace_callback__doc__},
@@ -930,42 +766,17 @@ pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self,
PyTypeObject *cls,
PyObject *callable);
-// Emit compiler warnings when we get to Python 3.15.
-#if PY_VERSION_HEX >= 0x030f00C0
-# error "Update the clinic input of '_sqlite3.Connection.set_trace_callback'."
-#elif PY_VERSION_HEX >= 0x030f00A0
-# ifdef _MSC_VER
-# pragma message ("Update the clinic input of '_sqlite3.Connection.set_trace_callback'.")
-# else
-# warning "Update the clinic input of '_sqlite3.Connection.set_trace_callback'."
-# endif
-#endif
-
static PyObject *
pysqlite_connection_set_trace_callback(PyObject *self, PyTypeObject *cls, 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(trace_callback), },
- };
- #undef NUM_KEYWORDS
- #define KWTUPLE (&_kwtuple.ob_base.ob_base)
-
- #else // !Py_BUILD_CORE
+ # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
+ #else
# define KWTUPLE NULL
- #endif // !Py_BUILD_CORE
+ #endif
- static const char * const _keywords[] = {"trace_callback", NULL};
+ static const char * const _keywords[] = {"", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "set_trace_callback",
@@ -980,16 +791,6 @@ pysqlite_connection_set_trace_callback(PyObject *self, PyTypeObject *cls, PyObje
if (!args) {
goto exit;
}
- if (nargs < 1) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "Passing keyword argument 'trace_callback' to "
- "_sqlite3.Connection.set_trace_callback() is deprecated. "
- "Parameter 'trace_callback' will become positional-only in Python"
- " 3.15.", 1))
- {
- goto exit;
- }
- }
callable = args[0];
return_value = pysqlite_connection_set_trace_callback_impl((pysqlite_Connection *)self, cls, callable);
@@ -1921,4 +1722,4 @@ exit:
#ifndef DESERIALIZE_METHODDEF
#define DESERIALIZE_METHODDEF
#endif /* !defined(DESERIALIZE_METHODDEF) */
-/*[clinic end generated code: output=2f325c2444b4bb47 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=6cb96e557133d553 input=a9049054013a1b77]*/
diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c
index 2a184f78754..16ec6efc850 100644
--- a/Modules/_sqlite/connection.c
+++ b/Modules/_sqlite/connection.c
@@ -215,7 +215,7 @@ class sqlite3_int64_converter(CConverter):
_sqlite3.Connection.__init__ as pysqlite_connection_init
database: object
- * [from 3.15]
+ *
timeout: double = 5.0
detect_types: int = 0
isolation_level: IsolationLevel = ""
@@ -223,7 +223,6 @@ _sqlite3.Connection.__init__ as pysqlite_connection_init
factory: object(c_default='(PyObject*)clinic_state()->ConnectionType') = ConnectionType
cached_statements as cache_size: int = 128
uri: bool = False
- *
autocommit: Autocommit(c_default='LEGACY_TRANSACTION_CONTROL') = sqlite3.LEGACY_TRANSACTION_CONTROL
[clinic start generated code]*/
@@ -234,7 +233,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database,
int check_same_thread, PyObject *factory,
int cache_size, int uri,
enum autocommit_mode autocommit)
-/*[clinic end generated code: output=cba057313ea7712f input=219c3dbecbae7d99]*/
+/*[clinic end generated code: output=cba057313ea7712f input=5ca4883d8747a49b]*/
{
if (PySys_Audit("sqlite3.connect", "O", database) < 0) {
return -1;
@@ -1158,11 +1157,10 @@ check_num_params(pysqlite_Connection *self, const int n, const char *name)
_sqlite3.Connection.create_function as pysqlite_connection_create_function
cls: defining_class
- /
name: str
narg: int
func: object
- / [from 3.15]
+ /
*
deterministic: bool = False
@@ -1174,7 +1172,7 @@ pysqlite_connection_create_function_impl(pysqlite_Connection *self,
PyTypeObject *cls, const char *name,
int narg, PyObject *func,
int deterministic)
-/*[clinic end generated code: output=8a811529287ad240 input=c7c313b0ca8b519e]*/
+/*[clinic end generated code: output=8a811529287ad240 input=a896096ed5390ae1]*/
{
int rc;
int flags = SQLITE_UTF8;
@@ -1366,11 +1364,10 @@ create_window_function_impl(pysqlite_Connection *self, PyTypeObject *cls,
_sqlite3.Connection.create_aggregate as pysqlite_connection_create_aggregate
cls: defining_class
- /
name: str
n_arg: int
aggregate_class: object
- / [from 3.15]
+ /
Creates a new aggregate.
[clinic start generated code]*/
@@ -1380,7 +1377,7 @@ pysqlite_connection_create_aggregate_impl(pysqlite_Connection *self,
PyTypeObject *cls,
const char *name, int n_arg,
PyObject *aggregate_class)
-/*[clinic end generated code: output=1b02d0f0aec7ff96 input=8087056db6eae1cf]*/
+/*[clinic end generated code: output=1b02d0f0aec7ff96 input=aa2773f6a42f7e17]*/
{
int rc;
@@ -1531,7 +1528,7 @@ _sqlite3.Connection.set_authorizer as pysqlite_connection_set_authorizer
cls: defining_class
authorizer_callback as callable: object
- / [from 3.15]
+ /
Set authorizer callback.
[clinic start generated code]*/
@@ -1540,7 +1537,7 @@ static PyObject *
pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self,
PyTypeObject *cls,
PyObject *callable)
-/*[clinic end generated code: output=75fa60114fc971c3 input=a52bd4937c588752]*/
+/*[clinic end generated code: output=75fa60114fc971c3 input=e76469ab0bb1bbcd]*/
{
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
return NULL;
@@ -1576,7 +1573,7 @@ _sqlite3.Connection.set_progress_handler as pysqlite_connection_set_progress_han
A callable that takes no arguments.
If the callable returns non-zero, the current query is terminated,
and an exception is raised.
- / [from 3.15]
+ /
n: int
The number of SQLite virtual machine instructions that are
executed between invocations of 'progress_handler'.
@@ -1590,7 +1587,7 @@ static PyObject *
pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self,
PyTypeObject *cls,
PyObject *callable, int n)
-/*[clinic end generated code: output=0739957fd8034a50 input=b4d6e2ef8b4d32f9]*/
+/*[clinic end generated code: output=0739957fd8034a50 input=74c943f1ae7d8880]*/
{
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
return NULL;
@@ -1617,7 +1614,7 @@ _sqlite3.Connection.set_trace_callback as pysqlite_connection_set_trace_callback
cls: defining_class
trace_callback as callable: object
- / [from 3.15]
+ /
Set a trace callback called for each SQL statement (passed as unicode).
[clinic start generated code]*/
@@ -1626,7 +1623,7 @@ static PyObject *
pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self,
PyTypeObject *cls,
PyObject *callable)
-/*[clinic end generated code: output=d91048c03bfcee05 input=d705d592ec03cf28]*/
+/*[clinic end generated code: output=d91048c03bfcee05 input=f4f59bf2f87f2026]*/
{
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
return NULL;
diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c
index 7943bfcca36..0c3f43d0e50 100644
--- a/Modules/_sqlite/cursor.c
+++ b/Modules/_sqlite/cursor.c
@@ -31,6 +31,7 @@
#include "util.h"
#include "pycore_pyerrors.h" // _PyErr_FormatFromCause()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
typedef enum {
TYPE_LONG,
@@ -185,9 +186,7 @@ cursor_dealloc(PyObject *op)
pysqlite_Cursor *self = _pysqlite_Cursor_CAST(op);
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
- if (self->in_weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, self->in_weakreflist);
(void)tp->tp_clear(op);
tp->tp_free(self);
Py_DECREF(tp);
diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c
index 27e8dab92e0..5464fd1227a 100644
--- a/Modules/_sqlite/module.c
+++ b/Modules/_sqlite/module.c
@@ -32,6 +32,7 @@
#include "microprotocols.h"
#include "row.h"
#include "blob.h"
+#include "util.h"
#if SQLITE_VERSION_NUMBER < 3015002
#error "SQLite 3.15.2 or higher required"
@@ -60,26 +61,16 @@ pysqlite_connect(PyObject *module, PyObject *const *args, Py_ssize_t nargsf,
pysqlite_state *state = pysqlite_get_state(module);
PyObject *factory = (PyObject *)state->ConnectionType;
- static const int FACTORY_POS = 5;
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
- if (nargs > 1 && nargs <= 8) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "Passing more than 1 positional argument to sqlite3.connect()"
- " is deprecated. Parameters 'timeout', 'detect_types', "
- "'isolation_level', 'check_same_thread', 'factory', "
- "'cached_statements' and 'uri' will become keyword-only "
- "parameters in Python 3.15.", 1))
- {
- return NULL;
- }
- }
- if (nargs > FACTORY_POS) {
- factory = args[FACTORY_POS];
+ if (nargs > 1) {
+ PyErr_Format(PyExc_TypeError,
+ "connect() takes at most 1 positional arguments (%zd given)", nargs);
+ return NULL;
}
- else if (kwnames != NULL) {
+ if (kwnames != NULL) {
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwnames); i++) {
PyObject *item = PyTuple_GET_ITEM(kwnames, i); // borrowed ref.
- if (PyUnicode_CompareWithASCIIString(item, "factory") == 0) {
+ if (PyUnicode_EqualToUTF8(item, "factory")) {
factory = args[nargs + i];
break;
}
@@ -415,6 +406,40 @@ pysqlite_error_name(int rc)
}
static int
+add_keyword_tuple(PyObject *module)
+{
+#if SQLITE_VERSION_NUMBER >= 3024000
+ int count = sqlite3_keyword_count();
+ PyObject *keywords = PyTuple_New(count);
+ if (keywords == NULL) {
+ return -1;
+ }
+ for (int i = 0; i < count; i++) {
+ const char *keyword;
+ int size;
+ int result = sqlite3_keyword_name(i, &keyword, &size);
+ if (result != SQLITE_OK) {
+ pysqlite_state *state = pysqlite_get_state(module);
+ set_error_from_code(state, result);
+ goto error;
+ }
+ PyObject *kwd = PyUnicode_FromStringAndSize(keyword, size);
+ if (!kwd) {
+ goto error;
+ }
+ PyTuple_SET_ITEM(keywords, i, kwd);
+ }
+ return PyModule_Add(module, "SQLITE_KEYWORDS", keywords);
+
+error:
+ Py_DECREF(keywords);
+ return -1;
+#else
+ return 0;
+#endif
+}
+
+static int
add_integer_constants(PyObject *module) {
#define ADD_INT(ival) \
do { \
@@ -712,6 +737,10 @@ module_exec(PyObject *module)
goto error;
}
+ if (add_keyword_tuple(module) < 0) {
+ goto error;
+ }
+
if (PyModule_AddStringConstant(module, "sqlite_version", sqlite3_libversion())) {
goto error;
}
diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c
index 602d0ab8588..e8943920043 100644
--- a/Modules/_sre/sre.c
+++ b/Modules/_sre/sre.c
@@ -44,6 +44,7 @@ static const char copyright[] =
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_unicodeobject.h" // _PyUnicode_Copy
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "sre.h" // SRE_CODE
@@ -736,10 +737,7 @@ pattern_dealloc(PyObject *self)
{
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
- PatternObject *obj = _PatternObject_CAST(self);
- if (obj->weakreflist != NULL) {
- PyObject_ClearWeakRefs(self);
- }
+ FT_CLEAR_WEAKREFS(self, _PatternObject_CAST(self)->weakreflist);
(void)pattern_clear(self);
tp->tp_free(self);
Py_DECREF(tp);
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 97a29f4d0e1..014e624f6c2 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -563,7 +563,7 @@ fill_and_set_sslerror(_sslmodulestate *state,
goto fail;
}
}
- if (PyUnicodeWriter_WriteUTF8(writer, "] ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, "] ", 2) < 0) {
goto fail;
}
}
@@ -4427,7 +4427,7 @@ _ssl__SSLContext_load_dh_params_impl(PySSLContext *self, PyObject *filepath)
FILE *f;
DH *dh;
-#if defined(MS_WINDOWS) && defined(_DEBUG)
+#if defined(MS_WINDOWS) && defined(Py_DEBUG)
PyErr_SetString(PyExc_NotImplementedError,
"load_dh_params: unavailable on Windows debug build");
return NULL;
@@ -6626,6 +6626,12 @@ sslmodule_init_constants(PyObject *m)
addbool(m, "HAS_PSK", 1);
#endif
+#ifdef OPENSSL_NO_EXTERNAL_PSK_TLS13
+ addbool(m, "HAS_PSK_TLS13", 0);
+#else
+ addbool(m, "HAS_PSK_TLS13", 1);
+#endif
+
#ifdef SSL_VERIFY_POST_HANDSHAKE
addbool(m, "HAS_PHA", 1);
#else
diff --git a/Modules/_ssl/debughelpers.c b/Modules/_ssl/debughelpers.c
index 7c0b4876f43..f0a0a1674f3 100644
--- a/Modules/_ssl/debughelpers.c
+++ b/Modules/_ssl/debughelpers.c
@@ -175,7 +175,7 @@ _PySSLContext_set_keylog_filename(PyObject *op, PyObject *arg,
PySSLContext *self = PySSLContext_CAST(op);
FILE *fp;
-#if defined(MS_WINDOWS) && defined(_DEBUG)
+#if defined(MS_WINDOWS) && defined(Py_DEBUG)
PyErr_SetString(PyExc_NotImplementedError,
"set_keylog_filename: unavailable on Windows debug build");
return -1;
diff --git a/Modules/_stat.c b/Modules/_stat.c
index f11ca7d23b4..1dabf2f6d5b 100644
--- a/Modules/_stat.c
+++ b/Modules/_stat.c
@@ -57,7 +57,7 @@ typedef unsigned short mode_t;
* Only the names are defined by POSIX but not their value. All common file
* types seems to have the same numeric value on all platforms, though.
*
- * pyport.h guarantees S_IFMT, S_IFDIR, S_IFCHR, S_IFREG and S_IFLNK
+ * fileutils.h guarantees S_IFMT, S_IFDIR, S_IFCHR, S_IFREG and S_IFLNK
*/
#ifndef S_IFBLK
@@ -86,7 +86,7 @@ typedef unsigned short mode_t;
/* S_ISXXX()
- * pyport.h defines S_ISDIR(), S_ISREG() and S_ISCHR()
+ * fileutils.h defines S_ISDIR(), S_ISREG() and S_ISCHR()
*/
#ifndef S_ISBLK
diff --git a/Modules/_struct.c b/Modules/_struct.c
index e400f607b55..3fad35a8c94 100644
--- a/Modules/_struct.c
+++ b/Modules/_struct.c
@@ -11,6 +11,7 @@
#include "pycore_bytesobject.h" // _PyBytesWriter
#include "pycore_long.h" // _PyLong_AsByteArray()
#include "pycore_moduleobject.h" // _PyModule_GetState()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stddef.h> // offsetof()
@@ -853,8 +854,8 @@ static const formatdef native_table[] = {
{'e', sizeof(short), _Alignof(short), nu_halffloat, np_halffloat},
{'f', sizeof(float), _Alignof(float), nu_float, np_float},
{'d', sizeof(double), _Alignof(double), nu_double, np_double},
- {'F', 2*sizeof(float), _Alignof(float[2]), nu_float_complex, np_float_complex},
- {'D', 2*sizeof(double), _Alignof(double[2]), nu_double_complex, np_double_complex},
+ {'F', 2*sizeof(float), _Alignof(float), nu_float_complex, np_float_complex},
+ {'D', 2*sizeof(double), _Alignof(double), nu_double_complex, np_double_complex},
{'P', sizeof(void *), _Alignof(void *), nu_void_p, np_void_p},
{0}
};
@@ -1794,9 +1795,7 @@ s_dealloc(PyObject *op)
PyStructObject *s = PyStructObject_CAST(op);
PyTypeObject *tp = Py_TYPE(s);
PyObject_GC_UnTrack(s);
- if (s->weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, s->weakreflist);
if (s->s_codes != NULL) {
PyMem_Free(s->s_codes);
}
diff --git a/Modules/_testcapi/abstract.c b/Modules/_testcapi/abstract.c
index d4045afd515..c1f769456ac 100644
--- a/Modules/_testcapi/abstract.c
+++ b/Modules/_testcapi/abstract.c
@@ -178,6 +178,42 @@ sequence_fast_get_item(PyObject *self, PyObject *args)
}
+static PyObject *
+object_setattr_null_exc(PyObject *self, PyObject *args)
+{
+ PyObject *obj, *name, *exc;
+ if (!PyArg_ParseTuple(args, "OOO", &obj, &name, &exc)) {
+ return NULL;
+ }
+
+ PyErr_SetObject((PyObject*)Py_TYPE(exc), exc);
+ if (PyObject_SetAttr(obj, name, NULL) < 0) {
+ return NULL;
+ }
+ assert(PyErr_Occurred());
+ return NULL;
+}
+
+
+static PyObject *
+object_setattrstring_null_exc(PyObject *self, PyObject *args)
+{
+ PyObject *obj, *exc;
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "Oz#O", &obj, &name, &size, &exc)) {
+ return NULL;
+ }
+
+ PyErr_SetObject((PyObject*)Py_TYPE(exc), exc);
+ if (PyObject_SetAttrString(obj, name, NULL) < 0) {
+ return NULL;
+ }
+ assert(PyErr_Occurred());
+ return NULL;
+}
+
+
static PyMethodDef test_methods[] = {
{"object_getoptionalattr", object_getoptionalattr, METH_VARARGS},
{"object_getoptionalattrstring", object_getoptionalattrstring, METH_VARARGS},
@@ -191,6 +227,8 @@ static PyMethodDef test_methods[] = {
{"sequence_fast_get_size", sequence_fast_get_size, METH_O},
{"sequence_fast_get_item", sequence_fast_get_item, METH_VARARGS},
+ {"object_setattr_null_exc", object_setattr_null_exc, METH_VARARGS},
+ {"object_setattrstring_null_exc", object_setattrstring_null_exc, METH_VARARGS},
{NULL},
};
diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c
index 42243023a45..6313abf5485 100644
--- a/Modules/_testcapi/long.c
+++ b/Modules/_testcapi/long.c
@@ -228,7 +228,7 @@ pylongwriter_create(PyObject *module, PyObject *args)
goto error;
}
- if (num < 0 || num >= PyLong_BASE) {
+ if (num < 0 || num >= (long)PyLong_BASE) {
PyErr_SetString(PyExc_ValueError, "digit doesn't fit into digit");
goto error;
}
diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c
index 5c67adfee29..798ef97c495 100644
--- a/Modules/_testcapi/object.c
+++ b/Modules/_testcapi/object.c
@@ -478,6 +478,13 @@ clear_managed_dict(PyObject *self, PyObject *obj)
}
+static PyObject *
+is_uniquely_referenced(PyObject *self, PyObject *op)
+{
+ return PyBool_FromLong(PyUnstable_Object_IsUniquelyReferenced(op));
+}
+
+
static PyMethodDef test_methods[] = {
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
@@ -503,6 +510,7 @@ static PyMethodDef test_methods[] = {
{"test_py_is_macros", test_py_is_macros, METH_NOARGS},
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
+ {"is_uniquely_referenced", is_uniquely_referenced, METH_O},
{NULL},
};
diff --git a/Modules/_testcapi/unicode.c b/Modules/_testcapi/unicode.c
index b8ecf53f4f8..203282dd53d 100644
--- a/Modules/_testcapi/unicode.c
+++ b/Modules/_testcapi/unicode.c
@@ -220,6 +220,12 @@ unicode_copycharacters(PyObject *self, PyObject *args)
return Py_BuildValue("(Nn)", to_copy, copied);
}
+static PyObject*
+unicode_GET_CACHED_HASH(PyObject *self, PyObject *arg)
+{
+ return PyLong_FromSsize_t(PyUnstable_Unicode_GET_CACHED_HASH(arg));
+}
+
// --- PyUnicodeWriter type -------------------------------------------------
@@ -333,6 +339,27 @@ writer_write_utf8(PyObject *self_raw, PyObject *args)
static PyObject*
+writer_write_ascii(PyObject *self_raw, PyObject *args)
+{
+ WriterObject *self = (WriterObject *)self_raw;
+ if (writer_check(self) < 0) {
+ return NULL;
+ }
+
+ char *str;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "yn", &str, &size)) {
+ return NULL;
+ }
+
+ if (PyUnicodeWriter_WriteASCII(self->writer, str, size) < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+
+static PyObject*
writer_write_widechar(PyObject *self_raw, PyObject *args)
{
WriterObject *self = (WriterObject *)self_raw;
@@ -513,6 +540,7 @@ writer_finish(PyObject *self_raw, PyObject *Py_UNUSED(args))
static PyMethodDef writer_methods[] = {
{"write_char", _PyCFunction_CAST(writer_write_char), METH_VARARGS},
{"write_utf8", _PyCFunction_CAST(writer_write_utf8), METH_VARARGS},
+ {"write_ascii", _PyCFunction_CAST(writer_write_ascii), METH_VARARGS},
{"write_widechar", _PyCFunction_CAST(writer_write_widechar), METH_VARARGS},
{"write_ucs4", _PyCFunction_CAST(writer_write_ucs4), METH_VARARGS},
{"write_str", _PyCFunction_CAST(writer_write_str), METH_VARARGS},
@@ -548,6 +576,7 @@ static PyMethodDef TestMethods[] = {
{"unicode_asucs4copy", unicode_asucs4copy, METH_VARARGS},
{"unicode_asutf8", unicode_asutf8, METH_VARARGS},
{"unicode_copycharacters", unicode_copycharacters, METH_VARARGS},
+ {"unicode_GET_CACHED_HASH", unicode_GET_CACHED_HASH, METH_O},
{NULL},
};
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 3aa6e4c9e43..71fffedee14 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2424,7 +2424,7 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args))
// Used by `finalize_thread_hang`.
-#ifdef _POSIX_THREADS
+#if defined(_POSIX_THREADS) && !defined(__wasi__)
static void finalize_thread_hang_cleanup_callback(void *Py_UNUSED(arg)) {
// Should not reach here.
Py_FatalError("pthread thread termination was triggered unexpectedly");
@@ -3175,6 +3175,48 @@ create_manual_heap_type(void)
return (PyObject *)type;
}
+typedef struct {
+ PyObject_VAR_HEAD
+} ManagedDictObject;
+
+int ManagedDict_traverse(PyObject *self, visitproc visit, void *arg) {
+ PyObject_VisitManagedDict(self, visit, arg);
+ Py_VISIT(Py_TYPE(self));
+ return 0;
+}
+
+int ManagedDict_clear(PyObject *self) {
+ PyObject_ClearManagedDict(self);
+ return 0;
+}
+
+static PyGetSetDef ManagedDict_getset[] = {
+ {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, NULL, NULL},
+ {NULL, NULL, NULL, NULL, NULL},
+};
+
+static PyType_Slot ManagedDict_slots[] = {
+ {Py_tp_new, (void *)PyType_GenericNew},
+ {Py_tp_getset, (void *)ManagedDict_getset},
+ {Py_tp_traverse, (void *)ManagedDict_traverse},
+ {Py_tp_clear, (void *)ManagedDict_clear},
+ {0}
+};
+
+static PyType_Spec ManagedDict_spec = {
+ "_testcapi.ManagedDictType",
+ sizeof(ManagedDictObject),
+ 0, // itemsize
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_MANAGED_DICT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC,
+ ManagedDict_slots
+};
+
+static PyObject *
+create_managed_dict_type(void)
+{
+ return PyType_FromSpec(&ManagedDict_spec);
+}
+
static struct PyModuleDef _testcapimodule = {
PyModuleDef_HEAD_INIT,
.m_name = "_testcapi",
@@ -3315,6 +3357,13 @@ PyInit__testcapi(void)
return NULL;
}
+ PyObject *managed_dict_type = create_managed_dict_type();
+ if (managed_dict_type == NULL) {
+ return NULL;
+ }
+ if (PyModule_Add(m, "ManagedDictType", managed_dict_type) < 0) {
+ return NULL;
+ }
/* Include tests from the _testcapi/ directory */
if (_PyTestCapi_Init_Vectorcall(m) < 0) {
diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c
index 3e903b6d87d..7e4ea0901fe 100644
--- a/Modules/_testclinic.c
+++ b/Modules/_testclinic.c
@@ -1734,14 +1734,14 @@ output impl_definition block
class _testclinic.DeprStarNew "PyObject *" "PyObject"
@classmethod
_testclinic.DeprStarNew.__new__ as depr_star_new
- * [from 3.14]
+ * [from 3.37]
a: object = None
The deprecation message should use the class name instead of __new__.
[clinic start generated code]*/
static PyObject *
depr_star_new_impl(PyTypeObject *type, PyObject *a)
-/*[clinic end generated code: output=bdbb36244f90cf46 input=fdd640db964b4dc1]*/
+/*[clinic end generated code: output=bdbb36244f90cf46 input=df8930826b302c3a]*/
{
return type->tp_alloc(type, 0);
}
@@ -1775,14 +1775,14 @@ static PyTypeObject DeprStarNew = {
/*[clinic input]
class _testclinic.DeprStarInit "PyObject *" "PyObject"
_testclinic.DeprStarInit.__init__ as depr_star_init
- * [from 3.14]
+ * [from 3.37]
a: object = None
The deprecation message should use the class name instead of __init__.
[clinic start generated code]*/
static int
depr_star_init_impl(PyObject *self, PyObject *a)
-/*[clinic end generated code: output=8d27b43c286d3ecc input=5575b77229d5e2be]*/
+/*[clinic end generated code: output=8d27b43c286d3ecc input=07a5c35e04f526c5]*/
{
return 0;
}
@@ -1818,7 +1818,7 @@ static PyTypeObject DeprStarInit = {
class _testclinic.DeprStarInitNoInline "PyObject *" "PyObject"
_testclinic.DeprStarInitNoInline.__init__ as depr_star_init_noinline
a: object
- * [from 3.14]
+ * [from 3.37]
b: object
c: object = None
*
@@ -1829,7 +1829,7 @@ _testclinic.DeprStarInitNoInline.__init__ as depr_star_init_noinline
static int
depr_star_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length)
-/*[clinic end generated code: output=9b31fc167f1bf9f7 input=5a887543122bca48]*/
+/*[clinic end generated code: output=9b31fc167f1bf9f7 input=45171504f009a391]*/
{
return 0;
}
@@ -1849,13 +1849,13 @@ class _testclinic.DeprKwdNew "PyObject *" "PyObject"
@classmethod
_testclinic.DeprKwdNew.__new__ as depr_kwd_new
a: object = None
- / [from 3.14]
+ / [from 3.37]
The deprecation message should use the class name instead of __new__.
[clinic start generated code]*/
static PyObject *
depr_kwd_new_impl(PyTypeObject *type, PyObject *a)
-/*[clinic end generated code: output=618d07afc5616149 input=6c7d13c471013c10]*/
+/*[clinic end generated code: output=618d07afc5616149 input=1bfb1b86f56ad2e6]*/
{
return type->tp_alloc(type, 0);
}
@@ -1873,13 +1873,13 @@ static PyTypeObject DeprKwdNew = {
class _testclinic.DeprKwdInit "PyObject *" "PyObject"
_testclinic.DeprKwdInit.__init__ as depr_kwd_init
a: object = None
- / [from 3.14]
+ / [from 3.37]
The deprecation message should use the class name instead of __init__.
[clinic start generated code]*/
static int
depr_kwd_init_impl(PyObject *self, PyObject *a)
-/*[clinic end generated code: output=6e02eb724a85d840 input=b9bf3c20f012d539]*/
+/*[clinic end generated code: output=6e02eb724a85d840 input=6f4daaa912ec24b2]*/
{
return 0;
}
@@ -1901,7 +1901,7 @@ _testclinic.DeprKwdInitNoInline.__init__ as depr_kwd_init_noinline
/
b: object
c: object = None
- / [from 3.14]
+ / [from 3.37]
# Force to use _PyArg_ParseTupleAndKeywordsFast.
d: str(accept={str, robuffer}, zeroes=True) = ''
[clinic start generated code]*/
@@ -1909,7 +1909,7 @@ _testclinic.DeprKwdInitNoInline.__init__ as depr_kwd_init_noinline
static int
depr_kwd_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length)
-/*[clinic end generated code: output=27759d70ddd25873 input=c19d982c8c70a930]*/
+/*[clinic end generated code: output=27759d70ddd25873 input=a022ad17f4b6008c]*/
{
return 0;
}
@@ -1926,13 +1926,13 @@ static PyTypeObject DeprKwdInitNoInline = {
/*[clinic input]
depr_star_pos0_len1
- * [from 3.14]
+ * [from 3.37]
a: object
[clinic start generated code]*/
static PyObject *
depr_star_pos0_len1_impl(PyObject *module, PyObject *a)
-/*[clinic end generated code: output=e1c6c2b423129499 input=089b9aee25381b69]*/
+/*[clinic end generated code: output=e1c6c2b423129499 input=c8f49d8c6165ab6c]*/
{
Py_RETURN_NONE;
}
@@ -1940,14 +1940,14 @@ depr_star_pos0_len1_impl(PyObject *module, PyObject *a)
/*[clinic input]
depr_star_pos0_len2
- * [from 3.14]
+ * [from 3.37]
a: object
b: object
[clinic start generated code]*/
static PyObject *
depr_star_pos0_len2_impl(PyObject *module, PyObject *a, PyObject *b)
-/*[clinic end generated code: output=96df9be39859c7e4 input=65c83a32e01495c6]*/
+/*[clinic end generated code: output=96df9be39859c7e4 input=aca96f36892eda25]*/
{
Py_RETURN_NONE;
}
@@ -1955,7 +1955,7 @@ depr_star_pos0_len2_impl(PyObject *module, PyObject *a, PyObject *b)
/*[clinic input]
depr_star_pos0_len3_with_kwd
- * [from 3.14]
+ * [from 3.37]
a: object
b: object
c: object
@@ -1966,7 +1966,7 @@ depr_star_pos0_len3_with_kwd
static PyObject *
depr_star_pos0_len3_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d)
-/*[clinic end generated code: output=7f2531eda837052f input=b33f620f57d9270f]*/
+/*[clinic end generated code: output=7f2531eda837052f input=5602f0bced3d5094]*/
{
Py_RETURN_NONE;
}
@@ -1975,13 +1975,13 @@ depr_star_pos0_len3_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
/*[clinic input]
depr_star_pos1_len1_opt
a: object
- * [from 3.14]
+ * [from 3.37]
b: object = None
[clinic start generated code]*/
static PyObject *
depr_star_pos1_len1_opt_impl(PyObject *module, PyObject *a, PyObject *b)
-/*[clinic end generated code: output=b5b4e326ee3b216f input=4a4b8ff72eae9ff7]*/
+/*[clinic end generated code: output=b5b4e326ee3b216f input=070817da1d6ccf49]*/
{
Py_RETURN_NONE;
}
@@ -1990,13 +1990,13 @@ depr_star_pos1_len1_opt_impl(PyObject *module, PyObject *a, PyObject *b)
/*[clinic input]
depr_star_pos1_len1
a: object
- * [from 3.14]
+ * [from 3.37]
b: object
[clinic start generated code]*/
static PyObject *
depr_star_pos1_len1_impl(PyObject *module, PyObject *a, PyObject *b)
-/*[clinic end generated code: output=eab92e37d5b0a480 input=1e7787a9fe5f62a0]*/
+/*[clinic end generated code: output=eab92e37d5b0a480 input=2e3a30c71edd0f30]*/
{
Py_RETURN_NONE;
}
@@ -2005,7 +2005,7 @@ depr_star_pos1_len1_impl(PyObject *module, PyObject *a, PyObject *b)
/*[clinic input]
depr_star_pos1_len2_with_kwd
a: object
- * [from 3.14]
+ * [from 3.37]
b: object
c: object
*
@@ -2015,7 +2015,7 @@ depr_star_pos1_len2_with_kwd
static PyObject *
depr_star_pos1_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d)
-/*[clinic end generated code: output=3bccab672b7cfbb8 input=6bc7bd742fa8be15]*/
+/*[clinic end generated code: output=3bccab672b7cfbb8 input=48492b028a4f281c]*/
{
Py_RETURN_NONE;
}
@@ -2025,14 +2025,14 @@ depr_star_pos1_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
depr_star_pos2_len1
a: object
b: object
- * [from 3.14]
+ * [from 3.37]
c: object
[clinic start generated code]*/
static PyObject *
depr_star_pos2_len1_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c)
-/*[clinic end generated code: output=20f5b230e9beeb70 input=5fc3e1790dec00d5]*/
+/*[clinic end generated code: output=20f5b230e9beeb70 input=80ee46e15cd14cf3]*/
{
Py_RETURN_NONE;
}
@@ -2042,7 +2042,7 @@ depr_star_pos2_len1_impl(PyObject *module, PyObject *a, PyObject *b,
depr_star_pos2_len2
a: object
b: object
- * [from 3.14]
+ * [from 3.37]
c: object
d: object
[clinic start generated code]*/
@@ -2050,7 +2050,7 @@ depr_star_pos2_len2
static PyObject *
depr_star_pos2_len2_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d)
-/*[clinic end generated code: output=9f90ed8fbce27d7a input=9cc8003b89d38779]*/
+/*[clinic end generated code: output=9f90ed8fbce27d7a input=ac57914cf40a011b]*/
{
Py_RETURN_NONE;
}
@@ -2060,7 +2060,7 @@ depr_star_pos2_len2_impl(PyObject *module, PyObject *a, PyObject *b,
depr_star_pos2_len2_with_kwd
a: object
b: object
- * [from 3.14]
+ * [from 3.37]
c: object
d: object
*
@@ -2070,7 +2070,7 @@ depr_star_pos2_len2_with_kwd
static PyObject *
depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d, PyObject *e)
-/*[clinic end generated code: output=05432c4f20527215 input=831832d90534da91]*/
+/*[clinic end generated code: output=05432c4f20527215 input=98f25e33c01285a3]*/
{
Py_RETURN_NONE;
}
@@ -2079,7 +2079,7 @@ depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
/*[clinic input]
depr_star_noinline
a: object
- * [from 3.14]
+ * [from 3.37]
b: object
c: object = None
*
@@ -2090,7 +2090,7 @@ depr_star_noinline
static PyObject *
depr_star_noinline_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length)
-/*[clinic end generated code: output=cc27dacf5c2754af input=d36cc862a2daef98]*/
+/*[clinic end generated code: output=cc27dacf5c2754af input=a829784557a42349]*/
{
Py_RETURN_NONE;
}
@@ -2099,12 +2099,12 @@ depr_star_noinline_impl(PyObject *module, PyObject *a, PyObject *b,
/*[clinic input]
depr_star_multi
a: object
- * [from 3.16]
+ * [from 3.39]
b: object
- * [from 3.15]
+ * [from 3.38]
c: object
d: object
- * [from 3.14]
+ * [from 3.37]
e: object
f: object
g: object
@@ -2116,7 +2116,7 @@ static PyObject *
depr_star_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g,
PyObject *h)
-/*[clinic end generated code: output=77681653f4202068 input=3ebd05d888a957ea]*/
+/*[clinic end generated code: output=77681653f4202068 input=6798940a18b2e62b]*/
{
Py_RETURN_NONE;
}
@@ -2127,12 +2127,12 @@ depr_kwd_required_1
a: object
/
b: object
- / [from 3.14]
+ / [from 3.37]
[clinic start generated code]*/
static PyObject *
depr_kwd_required_1_impl(PyObject *module, PyObject *a, PyObject *b)
-/*[clinic end generated code: output=1d8ab19ea78418af input=53f2c398b828462d]*/
+/*[clinic end generated code: output=1d8ab19ea78418af input=37853d8548d42df5]*/
{
Py_RETURN_NONE;
}
@@ -2144,13 +2144,13 @@ depr_kwd_required_2
/
b: object
c: object
- / [from 3.14]
+ / [from 3.37]
[clinic start generated code]*/
static PyObject *
depr_kwd_required_2_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c)
-/*[clinic end generated code: output=44a89cb82509ddde input=a2b0ef37de8a01a7]*/
+/*[clinic end generated code: output=44a89cb82509ddde input=0e1faedd8ec248b1]*/
{
Py_RETURN_NONE;
}
@@ -2161,12 +2161,12 @@ depr_kwd_optional_1
a: object
/
b: object = None
- / [from 3.14]
+ / [from 3.37]
[clinic start generated code]*/
static PyObject *
depr_kwd_optional_1_impl(PyObject *module, PyObject *a, PyObject *b)
-/*[clinic end generated code: output=a8a3d67efcc7b058 input=e416981eb78c3053]*/
+/*[clinic end generated code: output=a8a3d67efcc7b058 input=adb240416511b30a]*/
{
Py_RETURN_NONE;
}
@@ -2178,13 +2178,13 @@ depr_kwd_optional_2
/
b: object = None
c: object = None
- / [from 3.14]
+ / [from 3.37]
[clinic start generated code]*/
static PyObject *
depr_kwd_optional_2_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c)
-/*[clinic end generated code: output=aa2d967f26fdb9f6 input=cae3afb783bfc855]*/
+/*[clinic end generated code: output=aa2d967f26fdb9f6 input=fb8a82d0087f8732]*/
{
Py_RETURN_NONE;
}
@@ -2195,13 +2195,13 @@ depr_kwd_optional_3
a: object = None
b: object = None
c: object = None
- / [from 3.14]
+ / [from 3.37]
[clinic start generated code]*/
static PyObject *
depr_kwd_optional_3_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c)
-/*[clinic end generated code: output=a26025bf6118fd07 input=c9183b2f9ccaf992]*/
+/*[clinic end generated code: output=a26025bf6118fd07 input=e54bbbacd8624fa7]*/
{
Py_RETURN_NONE;
}
@@ -2213,13 +2213,13 @@ depr_kwd_required_optional
/
b: object
c: object = None
- / [from 3.14]
+ / [from 3.37]
[clinic start generated code]*/
static PyObject *
depr_kwd_required_optional_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c)
-/*[clinic end generated code: output=e53a8b7a250d8ffc input=23237a046f8388f5]*/
+/*[clinic end generated code: output=e53a8b7a250d8ffc input=285105a1ffb784b5]*/
{
Py_RETURN_NONE;
}
@@ -2231,7 +2231,7 @@ depr_kwd_noinline
/
b: object
c: object = None
- / [from 3.14]
+ / [from 3.37]
# Force to use _PyArg_ParseStackAndKeywords.
d: str(accept={str, robuffer}, zeroes=True) = ''
[clinic start generated code]*/
@@ -2239,7 +2239,7 @@ depr_kwd_noinline
static PyObject *
depr_kwd_noinline_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length)
-/*[clinic end generated code: output=f59da8113f2bad7c input=1d6db65bebb069d7]*/
+/*[clinic end generated code: output=f59da8113f2bad7c input=3773d1bdc1f82298]*/
{
Py_RETURN_NONE;
}
@@ -2250,14 +2250,14 @@ depr_kwd_multi
a: object
/
b: object
- / [from 3.14]
+ / [from 3.37]
c: object
d: object
- / [from 3.15]
+ / [from 3.38]
e: object
f: object
g: object
- / [from 3.16]
+ / [from 3.39]
h: object
[clinic start generated code]*/
@@ -2265,7 +2265,7 @@ static PyObject *
depr_kwd_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g,
PyObject *h)
-/*[clinic end generated code: output=ddfbde80fe1942e1 input=7a074e621c79efd7]*/
+/*[clinic end generated code: output=ddfbde80fe1942e1 input=a84c447a74284174]*/
{
Py_RETURN_NONE;
}
@@ -2276,13 +2276,13 @@ depr_multi
a: object
/
b: object
- / [from 3.14]
+ / [from 3.37]
c: object
- / [from 3.15]
+ / [from 3.38]
d: object
- * [from 3.15]
+ * [from 3.38]
e: object
- * [from 3.14]
+ * [from 3.37]
f: object
*
g: object
@@ -2291,7 +2291,7 @@ depr_multi
static PyObject *
depr_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g)
-/*[clinic end generated code: output=f81c92852ca2d4ee input=5b847c5e44bedd02]*/
+/*[clinic end generated code: output=f81c92852ca2d4ee input=0e859e8b8eda75d7]*/
{
Py_RETURN_NONE;
}
diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c
deleted file mode 100644
index b65c5821443..00000000000
--- a/Modules/_testexternalinspection.c
+++ /dev/null
@@ -1,1551 +0,0 @@
-#define _GNU_SOURCE
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifndef Py_BUILD_CORE_BUILTIN
-# define Py_BUILD_CORE_MODULE 1
-#endif
-#include "Python.h"
-#include <internal/pycore_debug_offsets.h> // _Py_DebugOffsets
-#include <internal/pycore_frame.h> // FRAME_SUSPENDED_YIELD_FROM
-#include <internal/pycore_interpframe.h> // FRAME_OWNED_BY_CSTACK
-#include <internal/pycore_llist.h> // struct llist_node
-#include <internal/pycore_stackref.h> // Py_TAG_BITS
-#include "../Python/remote_debug.h"
-
-#ifndef HAVE_PROCESS_VM_READV
-# define HAVE_PROCESS_VM_READV 0
-#endif
-
-struct _Py_AsyncioModuleDebugOffsets {
- struct _asyncio_task_object {
- uint64_t size;
- uint64_t task_name;
- uint64_t task_awaited_by;
- uint64_t task_is_task;
- uint64_t task_awaited_by_is_set;
- uint64_t task_coro;
- uint64_t task_node;
- } asyncio_task_object;
- struct _asyncio_interpreter_state {
- uint64_t size;
- uint64_t asyncio_tasks_head;
- } asyncio_interpreter_state;
- struct _asyncio_thread_state {
- uint64_t size;
- uint64_t asyncio_running_loop;
- uint64_t asyncio_running_task;
- uint64_t asyncio_tasks_head;
- } asyncio_thread_state;
-};
-
-// Helper to chain exceptions and avoid repetitions
-static void
-chain_exceptions(PyObject *exception, const char *string)
-{
- PyObject *exc = PyErr_GetRaisedException();
- PyErr_SetString(exception, string);
- _PyErr_ChainExceptions1(exc);
-}
-
-// Get the PyAsyncioDebug section address for any platform
-static uintptr_t
-_Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
-{
- uintptr_t address;
-
-#ifdef MS_WINDOWS
- // On Windows, search for asyncio debug in executable or DLL
- address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio");
-#elif defined(__linux__)
- // On Linux, search for asyncio debug in executable or DLL
- address = search_linux_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
-#elif defined(__APPLE__) && TARGET_OS_OSX
- // On macOS, try libpython first, then fall back to python
- address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
- if (address == 0) {
- PyErr_Clear();
- address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython");
- }
-#else
- Py_UNREACHABLE();
-#endif
-
- return address;
-}
-
-static int
-read_string(
- proc_handle_t *handle,
- _Py_DebugOffsets* debug_offsets,
- uintptr_t address,
- char* buffer,
- Py_ssize_t size
-) {
- Py_ssize_t len;
- int result = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- address + debug_offsets->unicode_object.length,
- sizeof(Py_ssize_t),
- &len
- );
- if (result < 0) {
- return -1;
- }
- if (len >= size) {
- PyErr_SetString(PyExc_RuntimeError, "Buffer too small");
- return -1;
- }
- size_t offset = debug_offsets->unicode_object.asciiobject_size;
- result = _Py_RemoteDebug_ReadRemoteMemory(handle, address + offset, len, buffer);
- if (result < 0) {
- return -1;
- }
- buffer[len] = '\0';
- return 0;
-}
-
-static inline int
-read_ptr(proc_handle_t *handle, uintptr_t address, uintptr_t *ptr_addr)
-{
- int result = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(void*), ptr_addr);
- if (result < 0) {
- return -1;
- }
- return 0;
-}
-
-static inline int
-read_Py_ssize_t(proc_handle_t *handle, uintptr_t address, Py_ssize_t *size)
-{
- int result = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(Py_ssize_t), size);
- if (result < 0) {
- return -1;
- }
- return 0;
-}
-
-static int
-read_py_ptr(proc_handle_t *handle, uintptr_t address, uintptr_t *ptr_addr)
-{
- if (read_ptr(handle, address, ptr_addr)) {
- return -1;
- }
- *ptr_addr &= ~Py_TAG_BITS;
- return 0;
-}
-
-static int
-read_char(proc_handle_t *handle, uintptr_t address, char *result)
-{
- int res = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(char), result);
- if (res < 0) {
- return -1;
- }
- return 0;
-}
-
-static int
-read_int(proc_handle_t *handle, uintptr_t address, int *result)
-{
- int res = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(int), result);
- if (res < 0) {
- return -1;
- }
- return 0;
-}
-
-static int
-read_unsigned_long(proc_handle_t *handle, uintptr_t address, unsigned long *result)
-{
- int res = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(unsigned long), result);
- if (res < 0) {
- return -1;
- }
- return 0;
-}
-
-static int
-read_pyobj(proc_handle_t *handle, uintptr_t address, PyObject *ptr_addr)
-{
- int res = _Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(PyObject), ptr_addr);
- if (res < 0) {
- return -1;
- }
- return 0;
-}
-
-static PyObject *
-read_py_str(
- proc_handle_t *handle,
- _Py_DebugOffsets* debug_offsets,
- uintptr_t address,
- Py_ssize_t max_len
-) {
- assert(max_len > 0);
-
- PyObject *result = NULL;
-
- char *buf = (char *)PyMem_RawMalloc(max_len);
- if (buf == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- if (read_string(handle, debug_offsets, address, buf, max_len)) {
- goto err;
- }
-
- result = PyUnicode_FromString(buf);
- if (result == NULL) {
- goto err;
- }
-
- PyMem_RawFree(buf);
- assert(result != NULL);
- return result;
-
-err:
- PyMem_RawFree(buf);
- return NULL;
-}
-
-static long
-read_py_long(proc_handle_t *handle, _Py_DebugOffsets* offsets, uintptr_t address)
-{
- unsigned int shift = PYLONG_BITS_IN_DIGIT;
-
- Py_ssize_t size;
- uintptr_t lv_tag;
-
- int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle, address + offsets->long_object.lv_tag,
- sizeof(uintptr_t),
- &lv_tag);
- if (bytes_read < 0) {
- return -1;
- }
-
- int negative = (lv_tag & 3) == 2;
- size = lv_tag >> 3;
-
- if (size == 0) {
- return 0;
- }
-
- digit *digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
- if (!digits) {
- PyErr_NoMemory();
- return -1;
- }
-
- bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- address + offsets->long_object.ob_digit,
- sizeof(digit) * size,
- digits
- );
- if (bytes_read < 0) {
- goto error;
- }
-
- long long value = 0;
-
- // In theory this can overflow, but because of llvm/llvm-project#16778
- // we can't use __builtin_mul_overflow because it fails to link with
- // __muloti4 on aarch64. In practice this is fine because all we're
- // testing here are task numbers that would fit in a single byte.
- for (Py_ssize_t i = 0; i < size; ++i) {
- long long factor = digits[i] * (1UL << (Py_ssize_t)(shift * i));
- value += factor;
- }
- PyMem_RawFree(digits);
- if (negative) {
- value *= -1;
- }
- return (long)value;
-error:
- PyMem_RawFree(digits);
- return -1;
-}
-
-static PyObject *
-parse_task_name(
- proc_handle_t *handle,
- _Py_DebugOffsets* offsets,
- struct _Py_AsyncioModuleDebugOffsets* async_offsets,
- uintptr_t task_address
-) {
- uintptr_t task_name_addr;
- int err = read_py_ptr(
- handle,
- task_address + async_offsets->asyncio_task_object.task_name,
- &task_name_addr);
- if (err) {
- return NULL;
- }
-
- // The task name can be a long or a string so we need to check the type
-
- PyObject task_name_obj;
- err = read_pyobj(
- handle,
- task_name_addr,
- &task_name_obj);
- if (err) {
- return NULL;
- }
-
- unsigned long flags;
- err = read_unsigned_long(
- handle,
- (uintptr_t)task_name_obj.ob_type + offsets->type_object.tp_flags,
- &flags);
- if (err) {
- return NULL;
- }
-
- if ((flags & Py_TPFLAGS_LONG_SUBCLASS)) {
- long res = read_py_long(handle, offsets, task_name_addr);
- if (res == -1) {
- chain_exceptions(PyExc_RuntimeError, "Failed to get task name");
- return NULL;
- }
- return PyUnicode_FromFormat("Task-%d", res);
- }
-
- if(!(flags & Py_TPFLAGS_UNICODE_SUBCLASS)) {
- PyErr_SetString(PyExc_RuntimeError, "Invalid task name object");
- return NULL;
- }
-
- return read_py_str(
- handle,
- offsets,
- task_name_addr,
- 255
- );
-}
-
-static int
-parse_coro_chain(
- proc_handle_t *handle,
- struct _Py_DebugOffsets* offsets,
- struct _Py_AsyncioModuleDebugOffsets* async_offsets,
- uintptr_t coro_address,
- PyObject *render_to
-) {
- assert((void*)coro_address != NULL);
-
- uintptr_t gen_type_addr;
- int err = read_ptr(
- handle,
- coro_address + sizeof(void*),
- &gen_type_addr);
- if (err) {
- return -1;
- }
-
- uintptr_t gen_name_addr;
- err = read_py_ptr(
- handle,
- coro_address + offsets->gen_object.gi_name,
- &gen_name_addr);
- if (err) {
- return -1;
- }
-
- PyObject *name = read_py_str(
- handle,
- offsets,
- gen_name_addr,
- 255
- );
- if (name == NULL) {
- return -1;
- }
-
- if (PyList_Append(render_to, name)) {
- Py_DECREF(name);
- return -1;
- }
- Py_DECREF(name);
-
- int gi_frame_state;
- err = read_int(
- handle,
- coro_address + offsets->gen_object.gi_frame_state,
- &gi_frame_state);
- if (err) {
- return -1;
- }
-
- if (gi_frame_state == FRAME_SUSPENDED_YIELD_FROM) {
- char owner;
- err = read_char(
- handle,
- coro_address + offsets->gen_object.gi_iframe +
- offsets->interpreter_frame.owner,
- &owner
- );
- if (err) {
- return -1;
- }
- if (owner != FRAME_OWNED_BY_GENERATOR) {
- PyErr_SetString(
- PyExc_RuntimeError,
- "generator doesn't own its frame \\_o_/");
- return -1;
- }
-
- uintptr_t stackpointer_addr;
- err = read_py_ptr(
- handle,
- coro_address + offsets->gen_object.gi_iframe +
- offsets->interpreter_frame.stackpointer,
- &stackpointer_addr);
- if (err) {
- return -1;
- }
-
- if ((void*)stackpointer_addr != NULL) {
- uintptr_t gi_await_addr;
- err = read_py_ptr(
- handle,
- stackpointer_addr - sizeof(void*),
- &gi_await_addr);
- if (err) {
- return -1;
- }
-
- if ((void*)gi_await_addr != NULL) {
- uintptr_t gi_await_addr_type_addr;
- int err = read_ptr(
- handle,
- gi_await_addr + sizeof(void*),
- &gi_await_addr_type_addr);
- if (err) {
- return -1;
- }
-
- if (gen_type_addr == gi_await_addr_type_addr) {
- /* This needs an explanation. We always start with parsing
- native coroutine / generator frames. Ultimately they
- are awaiting on something. That something can be
- a native coroutine frame or... an iterator.
- If it's the latter -- we can't continue building
- our chain. So the condition to bail out of this is
- to do that when the type of the current coroutine
- doesn't match the type of whatever it points to
- in its cr_await.
- */
- err = parse_coro_chain(
- handle,
- offsets,
- async_offsets,
- gi_await_addr,
- render_to
- );
- if (err) {
- return -1;
- }
- }
- }
- }
-
- }
-
- return 0;
-}
-
-
-static int
-parse_task_awaited_by(
- proc_handle_t *handle,
- struct _Py_DebugOffsets* offsets,
- struct _Py_AsyncioModuleDebugOffsets* async_offsets,
- uintptr_t task_address,
- PyObject *awaited_by
-);
-
-
-static int
-parse_task(
- proc_handle_t *handle,
- struct _Py_DebugOffsets* offsets,
- struct _Py_AsyncioModuleDebugOffsets* async_offsets,
- uintptr_t task_address,
- PyObject *render_to
-) {
- char is_task;
- int err = read_char(
- handle,
- task_address + async_offsets->asyncio_task_object.task_is_task,
- &is_task);
- if (err) {
- return -1;
- }
-
- PyObject* result = PyList_New(0);
- if (result == NULL) {
- return -1;
- }
-
- PyObject *call_stack = PyList_New(0);
- if (call_stack == NULL) {
- goto err;
- }
- if (PyList_Append(result, call_stack)) {
- Py_DECREF(call_stack);
- goto err;
- }
- /* we can operate on a borrowed one to simplify cleanup */
- Py_DECREF(call_stack);
-
- if (is_task) {
- PyObject *tn = parse_task_name(
- handle, offsets, async_offsets, task_address);
- if (tn == NULL) {
- goto err;
- }
- if (PyList_Append(result, tn)) {
- Py_DECREF(tn);
- goto err;
- }
- Py_DECREF(tn);
-
- uintptr_t coro_addr;
- err = read_py_ptr(
- handle,
- task_address + async_offsets->asyncio_task_object.task_coro,
- &coro_addr);
- if (err) {
- goto err;
- }
-
- if ((void*)coro_addr != NULL) {
- err = parse_coro_chain(
- handle,
- offsets,
- async_offsets,
- coro_addr,
- call_stack
- );
- if (err) {
- goto err;
- }
-
- if (PyList_Reverse(call_stack)) {
- goto err;
- }
- }
- }
-
- if (PyList_Append(render_to, result)) {
- goto err;
- }
-
- PyObject *awaited_by = PyList_New(0);
- if (awaited_by == NULL) {
- goto err;
- }
- if (PyList_Append(result, awaited_by)) {
- Py_DECREF(awaited_by);
- goto err;
- }
- /* we can operate on a borrowed one to simplify cleanup */
- Py_DECREF(awaited_by);
-
- if (parse_task_awaited_by(handle, offsets, async_offsets,
- task_address, awaited_by)
- ) {
- goto err;
- }
- Py_DECREF(result);
-
- return 0;
-
-err:
- Py_DECREF(result);
- return -1;
-}
-
-static int
-parse_tasks_in_set(
- proc_handle_t *handle,
- struct _Py_DebugOffsets* offsets,
- struct _Py_AsyncioModuleDebugOffsets* async_offsets,
- uintptr_t set_addr,
- PyObject *awaited_by
-) {
- uintptr_t set_obj;
- if (read_py_ptr(
- handle,
- set_addr,
- &set_obj)
- ) {
- return -1;
- }
-
- Py_ssize_t num_els;
- if (read_Py_ssize_t(
- handle,
- set_obj + offsets->set_object.used,
- &num_els)
- ) {
- return -1;
- }
-
- Py_ssize_t set_len;
- if (read_Py_ssize_t(
- handle,
- set_obj + offsets->set_object.mask,
- &set_len)
- ) {
- return -1;
- }
- set_len++; // The set contains the `mask+1` element slots.
-
- uintptr_t table_ptr;
- if (read_ptr(
- handle,
- set_obj + offsets->set_object.table,
- &table_ptr)
- ) {
- return -1;
- }
-
- Py_ssize_t i = 0;
- Py_ssize_t els = 0;
- while (i < set_len) {
- uintptr_t key_addr;
- if (read_py_ptr(handle, table_ptr, &key_addr)) {
- return -1;
- }
-
- if ((void*)key_addr != NULL) {
- Py_ssize_t ref_cnt;
- if (read_Py_ssize_t(handle, table_ptr, &ref_cnt)) {
- return -1;
- }
-
- if (ref_cnt) {
- // if 'ref_cnt=0' it's a set dummy marker
-
- if (parse_task(
- handle,
- offsets,
- async_offsets,
- key_addr,
- awaited_by)
- ) {
- return -1;
- }
-
- if (++els == num_els) {
- break;
- }
- }
- }
-
- table_ptr += sizeof(void*) * 2;
- i++;
- }
- return 0;
-}
-
-
-static int
-parse_task_awaited_by(
- proc_handle_t *handle,
- struct _Py_DebugOffsets* offsets,
- struct _Py_AsyncioModuleDebugOffsets* async_offsets,
- uintptr_t task_address,
- PyObject *awaited_by
-) {
- uintptr_t task_ab_addr;
- int err = read_py_ptr(
- handle,
- task_address + async_offsets->asyncio_task_object.task_awaited_by,
- &task_ab_addr);
- if (err) {
- return -1;
- }
-
- if ((void*)task_ab_addr == NULL) {
- return 0;
- }
-
- char awaited_by_is_a_set;
- err = read_char(
- handle,
- task_address + async_offsets->asyncio_task_object.task_awaited_by_is_set,
- &awaited_by_is_a_set);
- if (err) {
- return -1;
- }
-
- if (awaited_by_is_a_set) {
- if (parse_tasks_in_set(
- handle,
- offsets,
- async_offsets,
- task_address + async_offsets->asyncio_task_object.task_awaited_by,
- awaited_by)
- ) {
- return -1;
- }
- } else {
- uintptr_t sub_task;
- if (read_py_ptr(
- handle,
- task_address + async_offsets->asyncio_task_object.task_awaited_by,
- &sub_task)
- ) {
- return -1;
- }
-
- if (parse_task(
- handle,
- offsets,
- async_offsets,
- sub_task,
- awaited_by)
- ) {
- return -1;
- }
- }
-
- return 0;
-}
-
-static int
-parse_code_object(
- proc_handle_t *handle,
- PyObject* result,
- struct _Py_DebugOffsets* offsets,
- uintptr_t address,
- uintptr_t* previous_frame
-) {
- uintptr_t address_of_function_name;
- int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- address + offsets->code_object.name,
- sizeof(void*),
- &address_of_function_name
- );
- if (bytes_read < 0) {
- return -1;
- }
-
- if ((void*)address_of_function_name == NULL) {
- PyErr_SetString(PyExc_RuntimeError, "No function name found");
- return -1;
- }
-
- PyObject* py_function_name = read_py_str(
- handle, offsets, address_of_function_name, 256);
- if (py_function_name == NULL) {
- return -1;
- }
-
- if (PyList_Append(result, py_function_name) == -1) {
- Py_DECREF(py_function_name);
- return -1;
- }
- Py_DECREF(py_function_name);
-
- return 0;
-}
-
-static int
-parse_frame_object(
- proc_handle_t *handle,
- PyObject* result,
- struct _Py_DebugOffsets* offsets,
- uintptr_t address,
- uintptr_t* previous_frame
-) {
- int err;
-
- Py_ssize_t bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- address + offsets->interpreter_frame.previous,
- sizeof(void*),
- previous_frame
- );
- if (bytes_read < 0) {
- return -1;
- }
-
- char owner;
- if (read_char(handle, address + offsets->interpreter_frame.owner, &owner)) {
- return -1;
- }
-
- if (owner >= FRAME_OWNED_BY_INTERPRETER) {
- return 0;
- }
-
- uintptr_t address_of_code_object;
- err = read_py_ptr(
- handle,
- address + offsets->interpreter_frame.executable,
- &address_of_code_object
- );
- if (err) {
- return -1;
- }
-
- if ((void*)address_of_code_object == NULL) {
- return 0;
- }
-
- return parse_code_object(
- handle, result, offsets, address_of_code_object, previous_frame);
-}
-
-static int
-parse_async_frame_object(
- proc_handle_t *handle,
- PyObject* result,
- struct _Py_DebugOffsets* offsets,
- uintptr_t address,
- uintptr_t* previous_frame,
- uintptr_t* code_object
-) {
- int err;
-
- Py_ssize_t bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- address + offsets->interpreter_frame.previous,
- sizeof(void*),
- previous_frame
- );
- if (bytes_read < 0) {
- return -1;
- }
-
- char owner;
- bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle, address + offsets->interpreter_frame.owner, sizeof(char), &owner);
- if (bytes_read < 0) {
- return -1;
- }
-
- if (owner == FRAME_OWNED_BY_CSTACK || owner == FRAME_OWNED_BY_INTERPRETER) {
- return 0; // C frame
- }
-
- if (owner != FRAME_OWNED_BY_GENERATOR
- && owner != FRAME_OWNED_BY_THREAD) {
- PyErr_Format(PyExc_RuntimeError, "Unhandled frame owner %d.\n", owner);
- return -1;
- }
-
- err = read_py_ptr(
- handle,
- address + offsets->interpreter_frame.executable,
- code_object
- );
- if (err) {
- return -1;
- }
-
- assert(code_object != NULL);
- if ((void*)*code_object == NULL) {
- return 0;
- }
-
- if (parse_code_object(
- handle, result, offsets, *code_object, previous_frame)) {
- return -1;
- }
-
- return 1;
-}
-
-static int
-read_async_debug(
- proc_handle_t *handle,
- struct _Py_AsyncioModuleDebugOffsets* async_debug
-) {
- uintptr_t async_debug_addr = _Py_RemoteDebug_GetAsyncioDebugAddress(handle);
- if (!async_debug_addr) {
- return -1;
- }
-
- size_t size = sizeof(struct _Py_AsyncioModuleDebugOffsets);
- int result = _Py_RemoteDebug_ReadRemoteMemory(handle, async_debug_addr, size, async_debug);
- return result;
-}
-
-static int
-find_running_frame(
- proc_handle_t *handle,
- uintptr_t runtime_start_address,
- _Py_DebugOffsets* local_debug_offsets,
- uintptr_t *frame
-) {
- uint64_t interpreter_state_list_head =
- local_debug_offsets->runtime_state.interpreters_head;
-
- uintptr_t address_of_interpreter_state;
- int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- runtime_start_address + interpreter_state_list_head,
- sizeof(void*),
- &address_of_interpreter_state);
- if (bytes_read < 0) {
- return -1;
- }
-
- if (address_of_interpreter_state == 0) {
- PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
- return -1;
- }
-
- uintptr_t address_of_thread;
- bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- address_of_interpreter_state +
- local_debug_offsets->interpreter_state.threads_main,
- sizeof(void*),
- &address_of_thread);
- if (bytes_read < 0) {
- return -1;
- }
-
- // No Python frames are available for us (can happen at tear-down).
- if ((void*)address_of_thread != NULL) {
- int err = read_ptr(
- handle,
- address_of_thread + local_debug_offsets->thread_state.current_frame,
- frame);
- if (err) {
- return -1;
- }
- return 0;
- }
-
- *frame = (uintptr_t)NULL;
- return 0;
-}
-
-static int
-find_running_task(
- proc_handle_t *handle,
- uintptr_t runtime_start_address,
- _Py_DebugOffsets *local_debug_offsets,
- struct _Py_AsyncioModuleDebugOffsets *async_offsets,
- uintptr_t *running_task_addr
-) {
- *running_task_addr = (uintptr_t)NULL;
-
- uint64_t interpreter_state_list_head =
- local_debug_offsets->runtime_state.interpreters_head;
-
- uintptr_t address_of_interpreter_state;
- int bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- runtime_start_address + interpreter_state_list_head,
- sizeof(void*),
- &address_of_interpreter_state);
- if (bytes_read < 0) {
- return -1;
- }
-
- if (address_of_interpreter_state == 0) {
- PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
- return -1;
- }
-
- uintptr_t address_of_thread;
- bytes_read = _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- address_of_interpreter_state +
- local_debug_offsets->interpreter_state.threads_head,
- sizeof(void*),
- &address_of_thread);
- if (bytes_read < 0) {
- return -1;
- }
-
- uintptr_t address_of_running_loop;
- // No Python frames are available for us (can happen at tear-down).
- if ((void*)address_of_thread == NULL) {
- return 0;
- }
-
- bytes_read = read_py_ptr(
- handle,
- address_of_thread
- + async_offsets->asyncio_thread_state.asyncio_running_loop,
- &address_of_running_loop);
- if (bytes_read == -1) {
- return -1;
- }
-
- // no asyncio loop is now running
- if ((void*)address_of_running_loop == NULL) {
- return 0;
- }
-
- int err = read_ptr(
- handle,
- address_of_thread
- + async_offsets->asyncio_thread_state.asyncio_running_task,
- running_task_addr);
- if (err) {
- return -1;
- }
-
- return 0;
-}
-
-static int
-append_awaited_by_for_thread(
- proc_handle_t *handle,
- uintptr_t head_addr,
- struct _Py_DebugOffsets *debug_offsets,
- struct _Py_AsyncioModuleDebugOffsets *async_offsets,
- PyObject *result
-) {
- struct llist_node task_node;
-
- if (0 > _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- head_addr,
- sizeof(task_node),
- &task_node))
- {
- return -1;
- }
-
- size_t iteration_count = 0;
- const size_t MAX_ITERATIONS = 2 << 15; // A reasonable upper bound
- while ((uintptr_t)task_node.next != head_addr) {
- if (++iteration_count > MAX_ITERATIONS) {
- PyErr_SetString(PyExc_RuntimeError, "Task list appears corrupted");
- return -1;
- }
-
- if (task_node.next == NULL) {
- PyErr_SetString(
- PyExc_RuntimeError,
- "Invalid linked list structure reading remote memory");
- return -1;
- }
-
- uintptr_t task_addr = (uintptr_t)task_node.next
- - async_offsets->asyncio_task_object.task_node;
-
- PyObject *tn = parse_task_name(
- handle,
- debug_offsets,
- async_offsets,
- task_addr);
- if (tn == NULL) {
- return -1;
- }
-
- PyObject *current_awaited_by = PyList_New(0);
- if (current_awaited_by == NULL) {
- Py_DECREF(tn);
- return -1;
- }
-
- PyObject *result_item = PyTuple_New(2);
- if (result_item == NULL) {
- Py_DECREF(tn);
- Py_DECREF(current_awaited_by);
- return -1;
- }
-
- PyTuple_SET_ITEM(result_item, 0, tn); // steals ref
- PyTuple_SET_ITEM(result_item, 1, current_awaited_by); // steals ref
- if (PyList_Append(result, result_item)) {
- Py_DECREF(result_item);
- return -1;
- }
- Py_DECREF(result_item);
-
- if (parse_task_awaited_by(handle, debug_offsets, async_offsets,
- task_addr, current_awaited_by))
- {
- return -1;
- }
-
- // onto the next one...
- if (0 > _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- (uintptr_t)task_node.next,
- sizeof(task_node),
- &task_node))
- {
- return -1;
- }
- }
-
- return 0;
-}
-
-static int
-append_awaited_by(
- proc_handle_t *handle,
- unsigned long tid,
- uintptr_t head_addr,
- struct _Py_DebugOffsets *debug_offsets,
- struct _Py_AsyncioModuleDebugOffsets *async_offsets,
- PyObject *result)
-{
- PyObject *tid_py = PyLong_FromUnsignedLong(tid);
- if (tid_py == NULL) {
- return -1;
- }
-
- PyObject *result_item = PyTuple_New(2);
- if (result_item == NULL) {
- Py_DECREF(tid_py);
- return -1;
- }
-
- PyObject* awaited_by_for_thread = PyList_New(0);
- if (awaited_by_for_thread == NULL) {
- Py_DECREF(tid_py);
- Py_DECREF(result_item);
- return -1;
- }
-
- PyTuple_SET_ITEM(result_item, 0, tid_py); // steals ref
- PyTuple_SET_ITEM(result_item, 1, awaited_by_for_thread); // steals ref
- if (PyList_Append(result, result_item)) {
- Py_DECREF(result_item);
- return -1;
- }
- Py_DECREF(result_item);
-
- if (append_awaited_by_for_thread(
- handle,
- head_addr,
- debug_offsets,
- async_offsets,
- awaited_by_for_thread))
- {
- return -1;
- }
-
- return 0;
-}
-
-static PyObject*
-get_all_awaited_by(PyObject* self, PyObject* args)
-{
-#if (!defined(__linux__) && !defined(__APPLE__)) && !defined(MS_WINDOWS) || \
- (defined(__linux__) && !HAVE_PROCESS_VM_READV)
- PyErr_SetString(
- PyExc_RuntimeError,
- "get_all_awaited_by is not implemented on this platform");
- return NULL;
-#endif
-
- int pid;
- if (!PyArg_ParseTuple(args, "i", &pid)) {
- return NULL;
- }
-
- proc_handle_t the_handle;
- proc_handle_t *handle = &the_handle;
- if (_Py_RemoteDebug_InitProcHandle(handle, pid) < 0) {
- return 0;
- }
-
- PyObject *result = NULL;
-
- uintptr_t runtime_start_addr = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
- if (runtime_start_addr == 0) {
- if (!PyErr_Occurred()) {
- PyErr_SetString(
- PyExc_RuntimeError, "Failed to get .PyRuntime address");
- }
- goto result_err;
- }
- struct _Py_DebugOffsets local_debug_offsets;
-
- if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_addr, &local_debug_offsets)) {
- chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets");
- goto result_err;
- }
-
- struct _Py_AsyncioModuleDebugOffsets local_async_debug;
- if (read_async_debug(handle, &local_async_debug)) {
- chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets");
- goto result_err;
- }
-
- result = PyList_New(0);
- if (result == NULL) {
- goto result_err;
- }
-
- uint64_t interpreter_state_list_head =
- local_debug_offsets.runtime_state.interpreters_head;
-
- uintptr_t interpreter_state_addr;
- if (0 > _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- runtime_start_addr + interpreter_state_list_head,
- sizeof(void*),
- &interpreter_state_addr))
- {
- goto result_err;
- }
-
- uintptr_t thread_state_addr;
- unsigned long tid = 0;
- if (0 > _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- interpreter_state_addr
- + local_debug_offsets.interpreter_state.threads_head,
- sizeof(void*),
- &thread_state_addr))
- {
- goto result_err;
- }
-
- uintptr_t head_addr;
- while (thread_state_addr != 0) {
- if (0 > _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- thread_state_addr
- + local_debug_offsets.thread_state.native_thread_id,
- sizeof(tid),
- &tid))
- {
- goto result_err;
- }
-
- head_addr = thread_state_addr
- + local_async_debug.asyncio_thread_state.asyncio_tasks_head;
-
- if (append_awaited_by(handle, tid, head_addr, &local_debug_offsets,
- &local_async_debug, result))
- {
- goto result_err;
- }
-
- if (0 > _Py_RemoteDebug_ReadRemoteMemory(
- handle,
- thread_state_addr + local_debug_offsets.thread_state.next,
- sizeof(void*),
- &thread_state_addr))
- {
- goto result_err;
- }
- }
-
- head_addr = interpreter_state_addr
- + local_async_debug.asyncio_interpreter_state.asyncio_tasks_head;
-
- // On top of a per-thread task lists used by default by asyncio to avoid
- // contention, there is also a fallback per-interpreter list of tasks;
- // any tasks still pending when a thread is destroyed will be moved to the
- // per-interpreter task list. It's unlikely we'll find anything here, but
- // interesting for debugging.
- if (append_awaited_by(handle, 0, head_addr, &local_debug_offsets,
- &local_async_debug, result))
- {
- goto result_err;
- }
-
- _Py_RemoteDebug_CleanupProcHandle(handle);
- return result;
-
-result_err:
- Py_XDECREF(result);
- _Py_RemoteDebug_CleanupProcHandle(handle);
- return NULL;
-}
-
-static PyObject*
-get_stack_trace(PyObject* self, PyObject* args)
-{
-#if (!defined(__linux__) && !defined(__APPLE__)) && !defined(MS_WINDOWS) || \
- (defined(__linux__) && !HAVE_PROCESS_VM_READV)
- PyErr_SetString(
- PyExc_RuntimeError,
- "get_stack_trace is not supported on this platform");
- return NULL;
-#endif
-
- int pid;
- if (!PyArg_ParseTuple(args, "i", &pid)) {
- return NULL;
- }
-
- proc_handle_t the_handle;
- proc_handle_t *handle = &the_handle;
- if (_Py_RemoteDebug_InitProcHandle(handle, pid) < 0) {
- return 0;
- }
-
- PyObject* result = NULL;
-
- uintptr_t runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
- if (runtime_start_address == 0) {
- if (!PyErr_Occurred()) {
- PyErr_SetString(
- PyExc_RuntimeError, "Failed to get .PyRuntime address");
- }
- goto result_err;
- }
- struct _Py_DebugOffsets local_debug_offsets;
-
- if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) {
- chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets");
- goto result_err;
- }
-
- uintptr_t address_of_current_frame;
- if (find_running_frame(
- handle, runtime_start_address, &local_debug_offsets,
- &address_of_current_frame)
- ) {
- goto result_err;
- }
-
- result = PyList_New(0);
- if (result == NULL) {
- goto result_err;
- }
-
- while ((void*)address_of_current_frame != NULL) {
- if (parse_frame_object(
- handle,
- result,
- &local_debug_offsets,
- address_of_current_frame,
- &address_of_current_frame)
- < 0)
- {
- Py_DECREF(result);
- goto result_err;
- }
- }
-
-result_err:
- _Py_RemoteDebug_CleanupProcHandle(handle);
- return result;
-}
-
-static PyObject*
-get_async_stack_trace(PyObject* self, PyObject* args)
-{
-#if (!defined(__linux__) && !defined(__APPLE__)) && !defined(MS_WINDOWS) || \
- (defined(__linux__) && !HAVE_PROCESS_VM_READV)
- PyErr_SetString(
- PyExc_RuntimeError,
- "get_stack_trace is not supported on this platform");
- return NULL;
-#endif
- int pid;
-
- if (!PyArg_ParseTuple(args, "i", &pid)) {
- return NULL;
- }
-
- proc_handle_t the_handle;
- proc_handle_t *handle = &the_handle;
- if (_Py_RemoteDebug_InitProcHandle(handle, pid) < 0) {
- return 0;
- }
-
- PyObject *result = NULL;
-
- uintptr_t runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
- if (runtime_start_address == 0) {
- if (!PyErr_Occurred()) {
- PyErr_SetString(
- PyExc_RuntimeError, "Failed to get .PyRuntime address");
- }
- goto result_err;
- }
- struct _Py_DebugOffsets local_debug_offsets;
-
- if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) {
- chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets");
- goto result_err;
- }
-
- struct _Py_AsyncioModuleDebugOffsets local_async_debug;
- if (read_async_debug(handle, &local_async_debug)) {
- chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets");
- goto result_err;
- }
-
- result = PyList_New(1);
- if (result == NULL) {
- goto result_err;
- }
- PyObject* calls = PyList_New(0);
- if (calls == NULL) {
- goto result_err;
- }
- if (PyList_SetItem(result, 0, calls)) { /* steals ref to 'calls' */
- Py_DECREF(calls);
- goto result_err;
- }
-
- uintptr_t running_task_addr = (uintptr_t)NULL;
- if (find_running_task(
- handle, runtime_start_address, &local_debug_offsets, &local_async_debug,
- &running_task_addr)
- ) {
- chain_exceptions(PyExc_RuntimeError, "Failed to find running task");
- goto result_err;
- }
-
- if ((void*)running_task_addr == NULL) {
- PyErr_SetString(PyExc_RuntimeError, "No running task found");
- goto result_err;
- }
-
- uintptr_t running_coro_addr;
- if (read_py_ptr(
- handle,
- running_task_addr + local_async_debug.asyncio_task_object.task_coro,
- &running_coro_addr
- )) {
- chain_exceptions(PyExc_RuntimeError, "Failed to read running task coro");
- goto result_err;
- }
-
- if ((void*)running_coro_addr == NULL) {
- PyErr_SetString(PyExc_RuntimeError, "Running task coro is NULL");
- goto result_err;
- }
-
- // note: genobject's gi_iframe is an embedded struct so the address to
- // the offset leads directly to its first field: f_executable
- uintptr_t address_of_running_task_code_obj;
- if (read_py_ptr(
- handle,
- running_coro_addr + local_debug_offsets.gen_object.gi_iframe,
- &address_of_running_task_code_obj
- )) {
- goto result_err;
- }
-
- if ((void*)address_of_running_task_code_obj == NULL) {
- PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
- goto result_err;
- }
-
- uintptr_t address_of_current_frame;
- if (find_running_frame(
- handle, runtime_start_address, &local_debug_offsets,
- &address_of_current_frame)
- ) {
- chain_exceptions(PyExc_RuntimeError, "Failed to find running frame");
- goto result_err;
- }
-
- uintptr_t address_of_code_object;
- while ((void*)address_of_current_frame != NULL) {
- int res = parse_async_frame_object(
- handle,
- calls,
- &local_debug_offsets,
- address_of_current_frame,
- &address_of_current_frame,
- &address_of_code_object
- );
-
- if (res < 0) {
- chain_exceptions(PyExc_RuntimeError, "Failed to parse async frame object");
- goto result_err;
- }
-
- if (address_of_code_object == address_of_running_task_code_obj) {
- break;
- }
- }
-
- PyObject *tn = parse_task_name(
- handle, &local_debug_offsets, &local_async_debug, running_task_addr);
- if (tn == NULL) {
- goto result_err;
- }
- if (PyList_Append(result, tn)) {
- Py_DECREF(tn);
- goto result_err;
- }
- Py_DECREF(tn);
-
- PyObject* awaited_by = PyList_New(0);
- if (awaited_by == NULL) {
- goto result_err;
- }
- if (PyList_Append(result, awaited_by)) {
- Py_DECREF(awaited_by);
- goto result_err;
- }
- Py_DECREF(awaited_by);
-
- if (parse_task_awaited_by(
- handle, &local_debug_offsets, &local_async_debug,
- running_task_addr, awaited_by)
- ) {
- goto result_err;
- }
-
- _Py_RemoteDebug_CleanupProcHandle(handle);
- return result;
-
-result_err:
- _Py_RemoteDebug_CleanupProcHandle(handle);
- Py_XDECREF(result);
- return NULL;
-}
-
-
-static PyMethodDef methods[] = {
- {"get_stack_trace", get_stack_trace, METH_VARARGS,
- "Get the Python stack from a given pod"},
- {"get_async_stack_trace", get_async_stack_trace, METH_VARARGS,
- "Get the asyncio stack from a given pid"},
- {"get_all_awaited_by", get_all_awaited_by, METH_VARARGS,
- "Get all tasks and their awaited_by from a given pid"},
- {NULL, NULL, 0, NULL},
-};
-
-static struct PyModuleDef module = {
- .m_base = PyModuleDef_HEAD_INIT,
- .m_name = "_testexternalinspection",
- .m_size = -1,
- .m_methods = methods,
-};
-
-PyMODINIT_FUNC
-PyInit__testexternalinspection(void)
-{
- PyObject* mod = PyModule_Create(&module);
- if (mod == NULL) {
- return NULL;
- }
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED);
-#endif
- int rc = PyModule_AddIntConstant(
- mod, "PROCESS_VM_READV_SUPPORTED", HAVE_PROCESS_VM_READV);
- if (rc < 0) {
- Py_DECREF(mod);
- return NULL;
- }
- return mod;
-}
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index 065f4135b75..8027f0015c7 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -21,6 +21,7 @@
#include "pycore_fileutils.h" // _Py_normpath()
#include "pycore_flowgraph.h" // _PyCompile_OptimizeCfg()
#include "pycore_frame.h" // _PyInterpreterFrame
+#include "pycore_function.h" // _PyFunction_GET_BUILTINS
#include "pycore_gc.h" // PyGC_Head
#include "pycore_hashtable.h" // _Py_hashtable_new()
#include "pycore_import.h" // _PyImport_ClearExtension()
@@ -120,7 +121,7 @@ get_c_recursion_remaining(PyObject *self, PyObject *Py_UNUSED(args))
PyThreadState *tstate = _PyThreadState_GET();
uintptr_t here_addr = _Py_get_machine_stack_pointer();
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
- int remaining = (int)((here_addr - _tstate->c_stack_soft_limit)/PYOS_STACK_MARGIN_BYTES * 50);
+ int remaining = (int)((here_addr - _tstate->c_stack_soft_limit) / _PyOS_STACK_MARGIN_BYTES * 50);
return PyLong_FromLong(remaining);
}
@@ -1000,9 +1001,213 @@ get_co_localskinds(PyObject *self, PyObject *arg)
}
static PyObject *
-jit_enabled(PyObject *self, PyObject *arg)
+get_code_var_counts(PyObject *self, PyObject *_args, PyObject *_kwargs)
{
- return PyBool_FromLong(_PyInterpreterState_GET()->jit);
+ PyThreadState *tstate = _PyThreadState_GET();
+ PyObject *codearg;
+ PyObject *globalnames = NULL;
+ PyObject *attrnames = NULL;
+ PyObject *globalsns = NULL;
+ PyObject *builtinsns = NULL;
+ static char *kwlist[] = {"code", "globalnames", "attrnames", "globalsns",
+ "builtinsns", NULL};
+ if (!PyArg_ParseTupleAndKeywords(_args, _kwargs,
+ "O|OOO!O!:get_code_var_counts", kwlist,
+ &codearg, &globalnames, &attrnames,
+ &PyDict_Type, &globalsns, &PyDict_Type, &builtinsns))
+ {
+ return NULL;
+ }
+ if (PyFunction_Check(codearg)) {
+ if (globalsns == NULL) {
+ globalsns = PyFunction_GET_GLOBALS(codearg);
+ }
+ if (builtinsns == NULL) {
+ builtinsns = _PyFunction_GET_BUILTINS(codearg);
+ }
+ codearg = PyFunction_GET_CODE(codearg);
+ }
+ else if (!PyCode_Check(codearg)) {
+ PyErr_SetString(PyExc_TypeError,
+ "argument must be a code object or a function");
+ return NULL;
+ }
+ PyCodeObject *code = (PyCodeObject *)codearg;
+
+ _PyCode_var_counts_t counts = {0};
+ _PyCode_GetVarCounts(code, &counts);
+ if (_PyCode_SetUnboundVarCounts(
+ tstate, code, &counts, globalnames, attrnames,
+ globalsns, builtinsns) < 0)
+ {
+ return NULL;
+ }
+
+#define SET_COUNT(DICT, STRUCT, NAME) \
+ do { \
+ PyObject *count = PyLong_FromLong(STRUCT.NAME); \
+ if (count == NULL) { \
+ goto error; \
+ } \
+ int res = PyDict_SetItemString(DICT, #NAME, count); \
+ Py_DECREF(count); \
+ if (res < 0) { \
+ goto error; \
+ } \
+ } while (0)
+
+ PyObject *locals = NULL;
+ PyObject *args = NULL;
+ PyObject *cells = NULL;
+ PyObject *hidden = NULL;
+ PyObject *unbound = NULL;
+ PyObject *globals = NULL;
+ PyObject *countsobj = PyDict_New();
+ if (countsobj == NULL) {
+ return NULL;
+ }
+ SET_COUNT(countsobj, counts, total);
+
+ // locals
+ locals = PyDict_New();
+ if (locals == NULL) {
+ goto error;
+ }
+ if (PyDict_SetItemString(countsobj, "locals", locals) < 0) {
+ goto error;
+ }
+ SET_COUNT(locals, counts.locals, total);
+
+ // locals.args
+ args = PyDict_New();
+ if (args == NULL) {
+ goto error;
+ }
+ if (PyDict_SetItemString(locals, "args", args) < 0) {
+ goto error;
+ }
+ SET_COUNT(args, counts.locals.args, total);
+ SET_COUNT(args, counts.locals.args, numposonly);
+ SET_COUNT(args, counts.locals.args, numposorkw);
+ SET_COUNT(args, counts.locals.args, numkwonly);
+ SET_COUNT(args, counts.locals.args, varargs);
+ SET_COUNT(args, counts.locals.args, varkwargs);
+
+ // locals.numpure
+ SET_COUNT(locals, counts.locals, numpure);
+
+ // locals.cells
+ cells = PyDict_New();
+ if (cells == NULL) {
+ goto error;
+ }
+ if (PyDict_SetItemString(locals, "cells", cells) < 0) {
+ goto error;
+ }
+ SET_COUNT(cells, counts.locals.cells, total);
+ SET_COUNT(cells, counts.locals.cells, numargs);
+ SET_COUNT(cells, counts.locals.cells, numothers);
+
+ // locals.hidden
+ hidden = PyDict_New();
+ if (hidden == NULL) {
+ goto error;
+ }
+ if (PyDict_SetItemString(locals, "hidden", hidden) < 0) {
+ goto error;
+ }
+ SET_COUNT(hidden, counts.locals.hidden, total);
+ SET_COUNT(hidden, counts.locals.hidden, numpure);
+ SET_COUNT(hidden, counts.locals.hidden, numcells);
+
+ // numfree
+ SET_COUNT(countsobj, counts, numfree);
+
+ // unbound
+ unbound = PyDict_New();
+ if (unbound == NULL) {
+ goto error;
+ }
+ if (PyDict_SetItemString(countsobj, "unbound", unbound) < 0) {
+ goto error;
+ }
+ SET_COUNT(unbound, counts.unbound, total);
+ SET_COUNT(unbound, counts.unbound, numattrs);
+ SET_COUNT(unbound, counts.unbound, numunknown);
+
+ // unbound.globals
+ globals = PyDict_New();
+ if (globals == NULL) {
+ goto error;
+ }
+ if (PyDict_SetItemString(unbound, "globals", globals) < 0) {
+ goto error;
+ }
+ SET_COUNT(globals, counts.unbound.globals, total);
+ SET_COUNT(globals, counts.unbound.globals, numglobal);
+ SET_COUNT(globals, counts.unbound.globals, numbuiltin);
+ SET_COUNT(globals, counts.unbound.globals, numunknown);
+
+#undef SET_COUNT
+
+ Py_DECREF(locals);
+ Py_DECREF(args);
+ Py_DECREF(cells);
+ Py_DECREF(hidden);
+ Py_DECREF(unbound);
+ Py_DECREF(globals);
+ return countsobj;
+
+error:
+ Py_DECREF(countsobj);
+ Py_XDECREF(locals);
+ Py_XDECREF(args);
+ Py_XDECREF(cells);
+ Py_XDECREF(hidden);
+ Py_XDECREF(unbound);
+ Py_XDECREF(globals);
+ return NULL;
+}
+
+static PyObject *
+verify_stateless_code(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ PyObject *codearg;
+ PyObject *globalnames = NULL;
+ PyObject *globalsns = NULL;
+ PyObject *builtinsns = NULL;
+ static char *kwlist[] = {"code", "globalnames",
+ "globalsns", "builtinsns", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "O|O!O!O!:get_code_var_counts", kwlist,
+ &codearg, &PySet_Type, &globalnames,
+ &PyDict_Type, &globalsns, &PyDict_Type, &builtinsns))
+ {
+ return NULL;
+ }
+ if (PyFunction_Check(codearg)) {
+ if (globalsns == NULL) {
+ globalsns = PyFunction_GET_GLOBALS(codearg);
+ }
+ if (builtinsns == NULL) {
+ builtinsns = _PyFunction_GET_BUILTINS(codearg);
+ }
+ codearg = PyFunction_GET_CODE(codearg);
+ }
+ else if (!PyCode_Check(codearg)) {
+ PyErr_SetString(PyExc_TypeError,
+ "argument must be a code object or a function");
+ return NULL;
+ }
+ PyCodeObject *code = (PyCodeObject *)codearg;
+
+ if (_PyCode_VerifyStateless(
+ tstate, code, globalnames, globalsns, builtinsns) < 0)
+ {
+ return NULL;
+ }
+ Py_RETURN_NONE;
}
#ifdef _Py_TIER2
@@ -1489,11 +1694,12 @@ create_interpreter(PyObject *self, PyObject *args, PyObject *kwargs)
static PyObject *
destroy_interpreter(PyObject *self, PyObject *args, PyObject *kwargs)
{
- static char *kwlist[] = {"id", NULL};
+ static char *kwlist[] = {"id", "basic", NULL};
PyObject *idobj = NULL;
+ int basic = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
- "O:destroy_interpreter", kwlist,
- &idobj))
+ "O|p:destroy_interpreter", kwlist,
+ &idobj, &basic))
{
return NULL;
}
@@ -1503,7 +1709,27 @@ destroy_interpreter(PyObject *self, PyObject *args, PyObject *kwargs)
return NULL;
}
- _PyXI_EndInterpreter(interp, NULL, NULL);
+ if (basic)
+ {
+ // Test the basic Py_EndInterpreter with weird out of order thread states
+ PyThreadState *t1, *t2;
+ PyThreadState *prev;
+ t1 = interp->threads.head;
+ if (t1 == NULL) {
+ t1 = PyThreadState_New(interp);
+ }
+ t2 = PyThreadState_New(interp);
+ prev = PyThreadState_Swap(t2);
+ PyThreadState_Clear(t1);
+ PyThreadState_Delete(t1);
+ Py_EndInterpreter(t2);
+ PyThreadState_Swap(prev);
+ }
+ else
+ {
+ // use the cross interpreter _PyXI_EndInterpreter normally
+ _PyXI_EndInterpreter(interp, NULL, NULL);
+ }
Py_RETURN_NONE;
}
@@ -1563,9 +1789,9 @@ finally:
/* To run some code in a sub-interpreter.
-Generally you can use test.support.interpreters,
+Generally you can use the interpreters module,
but we keep this helper as a distinct implementation.
-That's especially important for testing test.support.interpreters.
+That's especially important for testing the interpreters module.
*/
static PyObject *
run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
@@ -1769,7 +1995,14 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
return NULL;
}
if (strcmp(mode, "xidata") == 0) {
- if (_PyObject_GetXIData(tstate, obj, xidata) != 0) {
+ if (_PyObject_GetXIDataNoFallback(tstate, obj, xidata) != 0) {
+ goto error;
+ }
+ }
+ else if (strcmp(mode, "fallback") == 0) {
+ xidata_fallback_t fallback = _PyXIDATA_FULL_FALLBACK;
+ if (_PyObject_GetXIData(tstate, obj, fallback, xidata) != 0)
+ {
goto error;
}
}
@@ -1783,6 +2016,26 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
goto error;
}
}
+ else if (strcmp(mode, "code") == 0) {
+ if (_PyCode_GetXIData(tstate, obj, xidata) != 0) {
+ goto error;
+ }
+ }
+ else if (strcmp(mode, "func") == 0) {
+ if (_PyFunction_GetXIData(tstate, obj, xidata) != 0) {
+ goto error;
+ }
+ }
+ else if (strcmp(mode, "script") == 0) {
+ if (_PyCode_GetScriptXIData(tstate, obj, xidata) != 0) {
+ goto error;
+ }
+ }
+ else if (strcmp(mode, "script-pure") == 0) {
+ if (_PyCode_GetPureScriptXIData(tstate, obj, xidata) != 0) {
+ goto error;
+ }
+ }
else {
PyErr_Format(PyExc_ValueError, "unsupported mode %R", modeobj);
goto error;
@@ -2125,7 +2378,10 @@ static PyMethodDef module_functions[] = {
{"code_returns_only_none", code_returns_only_none, METH_O, NULL},
{"get_co_framesize", get_co_framesize, METH_O, NULL},
{"get_co_localskinds", get_co_localskinds, METH_O, NULL},
- {"jit_enabled", jit_enabled, METH_NOARGS, NULL},
+ {"get_code_var_counts", _PyCFunction_CAST(get_code_var_counts),
+ METH_VARARGS | METH_KEYWORDS, NULL},
+ {"verify_stateless_code", _PyCFunction_CAST(verify_stateless_code),
+ METH_VARARGS | METH_KEYWORDS, NULL},
#ifdef _Py_TIER2
{"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL},
{"invalidate_executors", invalidate_executors, METH_O, NULL},
diff --git a/Modules/_testinternalcapi/test_lock.c b/Modules/_testinternalcapi/test_lock.c
index 8d678412fe7..8d8cb992b0e 100644
--- a/Modules/_testinternalcapi/test_lock.c
+++ b/Modules/_testinternalcapi/test_lock.c
@@ -57,7 +57,10 @@ lock_thread(void *arg)
_Py_atomic_store_int(&test_data->started, 1);
PyMutex_Lock(m);
- assert(m->_bits == 1);
+ // gh-135641: in rare cases the lock may still have `_Py_HAS_PARKED` set
+ // (m->_bits == 3) due to bucket collisions in the parking lot hash table
+ // between this mutex and the `test_data.done` event.
+ assert(m->_bits == 1 || m->_bits == 3);
PyMutex_Unlock(m);
assert(m->_bits == 0);
diff --git a/Modules/_testlimitedcapi/import.c b/Modules/_testlimitedcapi/import.c
index 3707dbedeea..f85daee57d7 100644
--- a/Modules/_testlimitedcapi/import.c
+++ b/Modules/_testlimitedcapi/import.c
@@ -108,20 +108,19 @@ pyimport_importmodule(PyObject *Py_UNUSED(module), PyObject *args)
}
-/* Test PyImport_ImportModuleNoBlock() */
+/* Test PyImport_ImportModuleNoBlock() (removed in 3.15) */
static PyObject *
pyimport_importmodulenoblock(PyObject *Py_UNUSED(module), PyObject *args)
{
+ // Get the function from the stable ABI.
+ PyAPI_FUNC(PyObject *) PyImport_ImportModuleNoBlock(const char *name);
+
const char *name;
Py_ssize_t size;
if (!PyArg_ParseTuple(args, "z#", &name, &size)) {
return NULL;
}
-
- _Py_COMP_DIAG_PUSH
- _Py_COMP_DIAG_IGNORE_DEPR_DECLS
return PyImport_ImportModuleNoBlock(name);
- _Py_COMP_DIAG_POP
}
diff --git a/Modules/_testlimitedcapi/sys.c b/Modules/_testlimitedcapi/sys.c
index 7d8b7a8569e..cec7f8ab612 100644
--- a/Modules/_testlimitedcapi/sys.c
+++ b/Modules/_testlimitedcapi/sys.c
@@ -1,8 +1,77 @@
+#include "pyconfig.h" // Py_GIL_DISABLED
+// Need limited C API version 3.15 for PySys_GetAttr() etc
+#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
+# define Py_LIMITED_API 0x030f0000
+#endif
#include "parts.h"
#include "util.h"
static PyObject *
+sys_getattr(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ NULLABLE(name);
+ return PySys_GetAttr(name);
+}
+
+static PyObject *
+sys_getattrstring(PyObject *Py_UNUSED(module), PyObject *arg)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_Parse(arg, "z#", &name, &size)) {
+ return NULL;
+ }
+ return PySys_GetAttrString(name);
+}
+
+static PyObject *
+sys_getoptionalattr(PyObject *Py_UNUSED(module), PyObject *name)
+{
+ PyObject *value = UNINITIALIZED_PTR;
+ NULLABLE(name);
+
+ switch (PySys_GetOptionalAttr(name, &value)) {
+ case -1:
+ assert(value == NULL);
+ assert(PyErr_Occurred());
+ return NULL;
+ case 0:
+ assert(value == NULL);
+ return Py_NewRef(PyExc_AttributeError);
+ case 1:
+ return value;
+ default:
+ Py_FatalError("PySys_GetOptionalAttr() returned invalid code");
+ }
+}
+
+static PyObject *
+sys_getoptionalattrstring(PyObject *Py_UNUSED(module), PyObject *arg)
+{
+ PyObject *value = UNINITIALIZED_PTR;
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_Parse(arg, "z#", &name, &size)) {
+ return NULL;
+ }
+
+ switch (PySys_GetOptionalAttrString(name, &value)) {
+ case -1:
+ assert(value == NULL);
+ assert(PyErr_Occurred());
+ return NULL;
+ case 0:
+ assert(value == NULL);
+ return Py_NewRef(PyExc_AttributeError);
+ case 1:
+ return value;
+ default:
+ Py_FatalError("PySys_GetOptionalAttrString() returned invalid code");
+ }
+}
+
+static PyObject *
sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg)
{
const char *name;
@@ -39,6 +108,10 @@ sys_getxoptions(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(ignored))
static PyMethodDef test_methods[] = {
+ {"sys_getattr", sys_getattr, METH_O},
+ {"sys_getattrstring", sys_getattrstring, METH_O},
+ {"sys_getoptionalattr", sys_getoptionalattr, METH_O},
+ {"sys_getoptionalattrstring", sys_getoptionalattrstring, METH_O},
{"sys_getobject", sys_getobject, METH_O},
{"sys_setobject", sys_setobject, METH_VARARGS},
{"sys_getxoptions", sys_getxoptions, METH_NOARGS},
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index 9776a32755d..8886a9d6bd0 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -10,7 +10,6 @@
#include "pycore_object_deferred.h" // _PyObject_SetDeferredRefcount()
#include "pycore_pylifecycle.h"
#include "pycore_pystate.h" // _PyThreadState_SetCurrent()
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
#include "pycore_time.h" // _PyTime_FromSeconds()
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
@@ -19,8 +18,6 @@
# include <signal.h> // SIGINT
#endif
-#include "clinic/_threadmodule.c.h"
-
// ThreadError is just an alias to PyExc_RuntimeError
#define ThreadError PyExc_RuntimeError
@@ -31,6 +28,7 @@ static struct PyModuleDef thread_module;
typedef struct {
PyTypeObject *excepthook_type;
PyTypeObject *lock_type;
+ PyTypeObject *rlock_type;
PyTypeObject *local_type;
PyTypeObject *local_dummy_type;
PyTypeObject *thread_handle_type;
@@ -48,6 +46,17 @@ get_thread_state(PyObject *module)
return (thread_module_state *)state;
}
+static inline thread_module_state*
+get_thread_state_by_cls(PyTypeObject *cls)
+{
+ // Use PyType_GetModuleByDef() to handle (R)Lock subclasses.
+ PyObject *module = PyType_GetModuleByDef(cls, &thread_module);
+ if (module == NULL) {
+ return NULL;
+ }
+ return get_thread_state(module);
+}
+
#ifdef MS_WINDOWS
typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*);
@@ -59,9 +68,14 @@ static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL;
/*[clinic input]
module _thread
+class _thread.lock "lockobject *" "clinic_state()->lock_type"
+class _thread.RLock "rlockobject *" "clinic_state()->rlock_type"
[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=be8dbe5cc4b16df7]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=c5a0f8c492a0c263]*/
+#define clinic_state() get_thread_state_by_cls(type)
+#include "clinic/_threadmodule.c.h"
+#undef clinic_state
// _ThreadHandle type
@@ -281,6 +295,12 @@ _PyThread_AfterFork(struct _pythread_runtime_state *state)
continue;
}
+ // Keep handles for threads that have not been started yet. They are
+ // safe to start in the child process.
+ if (handle->state == THREAD_HANDLE_NOT_STARTED) {
+ continue;
+ }
+
// Mark all threads as done. Any attempts to join or detach the
// underlying OS thread (if any) could crash. We are the only thread;
// it's safe to set this non-atomically.
@@ -814,9 +834,14 @@ lock_PyThread_acquire_lock(PyObject *op, PyObject *args, PyObject *kwds)
return NULL;
}
- PyLockStatus r = _PyMutex_LockTimed(&self->lock, timeout,
- _PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
+ PyLockStatus r = _PyMutex_LockTimed(
+ &self->lock, timeout,
+ _PY_LOCK_PYTHONLOCK | _PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
if (r == PY_LOCK_INTR) {
+ assert(PyErr_Occurred());
+ return NULL;
+ }
+ if (r == PY_LOCK_FAILURE && PyErr_Occurred()) {
return NULL;
}
@@ -916,25 +941,21 @@ lock__at_fork_reinit(PyObject *op, PyObject *Py_UNUSED(dummy))
}
#endif /* HAVE_FORK */
-static lockobject *newlockobject(PyObject *module);
+/*[clinic input]
+@classmethod
+_thread.lock.__new__ as lock_new
+[clinic start generated code]*/
static PyObject *
-lock_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+lock_new_impl(PyTypeObject *type)
+/*[clinic end generated code: output=eab660d5a4c05c8a input=260208a4e277d250]*/
{
- // convert to AC?
- if (!_PyArg_NoKeywords("lock", kwargs)) {
- goto error;
- }
- if (!_PyArg_CheckPositional("lock", PyTuple_GET_SIZE(args), 0, 0)) {
- goto error;
+ lockobject *self = (lockobject *)type->tp_alloc(type, 0);
+ if (self == NULL) {
+ return NULL;
}
-
- PyObject *module = PyType_GetModuleByDef(type, &thread_module);
- assert(module != NULL);
- return (PyObject *)newlockobject(module);
-
-error:
- return NULL;
+ self->lock = (PyMutex){0};
+ return (PyObject *)self;
}
@@ -1011,6 +1032,11 @@ rlock_traverse(PyObject *self, visitproc visit, void *arg)
return 0;
}
+static int
+rlock_locked_impl(rlockobject *self)
+{
+ return PyMutex_IsLocked(&self->lock.mutex);
+}
static void
rlock_dealloc(PyObject *self)
@@ -1033,9 +1059,14 @@ rlock_acquire(PyObject *op, PyObject *args, PyObject *kwds)
return NULL;
}
- PyLockStatus r = _PyRecursiveMutex_LockTimed(&self->lock, timeout,
- _PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
+ PyLockStatus r = _PyRecursiveMutex_LockTimed(
+ &self->lock, timeout,
+ _PY_LOCK_PYTHONLOCK | _PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
if (r == PY_LOCK_INTR) {
+ assert(PyErr_Occurred());
+ return NULL;
+ }
+ if (r == PY_LOCK_FAILURE && PyErr_Occurred()) {
return NULL;
}
@@ -1100,7 +1131,7 @@ static PyObject *
rlock_locked(PyObject *op, PyObject *Py_UNUSED(ignored))
{
rlockobject *self = rlockobject_CAST(op);
- int is_locked = _PyRecursiveMutex_IsLockedByCurrentThread(&self->lock);
+ int is_locked = rlock_locked_impl(self);
return PyBool_FromLong(is_locked);
}
@@ -1186,8 +1217,14 @@ PyDoc_STRVAR(rlock_is_owned_doc,
\n\
For internal use by `threading.Condition`.");
+/*[clinic input]
+@classmethod
+_thread.RLock.__new__ as rlock_new
+[clinic start generated code]*/
+
static PyObject *
-rlock_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+rlock_new_impl(PyTypeObject *type)
+/*[clinic end generated code: output=bb4fb1edf6818df5 input=013591361bf1ac6e]*/
{
rlockobject *self = (rlockobject *) type->tp_alloc(type, 0);
if (self == NULL) {
@@ -1202,10 +1239,17 @@ rlock_repr(PyObject *op)
{
rlockobject *self = rlockobject_CAST(op);
PyThread_ident_t owner = self->lock.thread;
- size_t count = self->lock.level + 1;
+ int locked = rlock_locked_impl(self);
+ size_t count;
+ if (locked) {
+ count = self->lock.level + 1;
+ }
+ else {
+ count = 0;
+ }
return PyUnicode_FromFormat(
"<%s %s object owner=%" PY_FORMAT_THREAD_IDENT_T " count=%zu at %p>",
- owner ? "locked" : "unlocked",
+ locked ? "locked" : "unlocked",
Py_TYPE(self)->tp_name, owner,
count, self);
}
@@ -1267,20 +1311,6 @@ static PyType_Spec rlock_type_spec = {
.slots = rlock_type_slots,
};
-static lockobject *
-newlockobject(PyObject *module)
-{
- thread_module_state *state = get_thread_state(module);
-
- PyTypeObject *type = state->lock_type;
- lockobject *self = (lockobject *)type->tp_alloc(type, 0);
- if (self == NULL) {
- return NULL;
- }
- self->lock = (PyMutex){0};
- return self;
-}
-
/* Thread-local objects */
/* Quick overview:
@@ -1345,9 +1375,7 @@ static void
localdummy_dealloc(PyObject *op)
{
localdummyobject *self = localdummyobject_CAST(op);
- if (self->weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
PyTypeObject *tp = Py_TYPE(self);
tp->tp_free(self);
Py_DECREF(tp);
@@ -2035,7 +2063,8 @@ Note: the default signal handler for SIGINT raises ``KeyboardInterrupt``."
static PyObject *
thread_PyThread_allocate_lock(PyObject *module, PyObject *Py_UNUSED(ignored))
{
- return (PyObject *) newlockobject(module);
+ thread_module_state *state = get_thread_state(module);
+ return lock_new_impl(state->lock_type);
}
PyDoc_STRVAR(allocate_lock_doc,
@@ -2268,7 +2297,7 @@ thread_excepthook(PyObject *module, PyObject *args)
PyObject *thread = PyStructSequence_GET_ITEM(args, 3);
PyObject *file;
- if (_PySys_GetOptionalAttr( &_Py_ID(stderr), &file) < 0) {
+ if (PySys_GetOptionalAttr( &_Py_ID(stderr), &file) < 0) {
return NULL;
}
if (file == NULL || file == Py_None) {
@@ -2645,15 +2674,13 @@ thread_module_exec(PyObject *module)
}
// RLock
- PyTypeObject *rlock_type = (PyTypeObject *)PyType_FromSpec(&rlock_type_spec);
- if (rlock_type == NULL) {
+ state->rlock_type = (PyTypeObject *)PyType_FromModuleAndSpec(module, &rlock_type_spec, NULL);
+ if (state->rlock_type == NULL) {
return -1;
}
- if (PyModule_AddType(module, rlock_type) < 0) {
- Py_DECREF(rlock_type);
+ if (PyModule_AddType(module, state->rlock_type) < 0) {
return -1;
}
- Py_DECREF(rlock_type);
// Local dummy
state->local_dummy_type = (PyTypeObject *)PyType_FromSpec(&local_dummy_type_spec);
@@ -2740,6 +2767,7 @@ thread_module_traverse(PyObject *module, visitproc visit, void *arg)
thread_module_state *state = get_thread_state(module);
Py_VISIT(state->excepthook_type);
Py_VISIT(state->lock_type);
+ Py_VISIT(state->rlock_type);
Py_VISIT(state->local_type);
Py_VISIT(state->local_dummy_type);
Py_VISIT(state->thread_handle_type);
@@ -2752,6 +2780,7 @@ thread_module_clear(PyObject *module)
thread_module_state *state = get_thread_state(module);
Py_CLEAR(state->excepthook_type);
Py_CLEAR(state->lock_type);
+ Py_CLEAR(state->rlock_type);
Py_CLEAR(state->local_type);
Py_CLEAR(state->local_dummy_type);
Py_CLEAR(state->thread_handle_type);
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 77695401919..875840bd6a6 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -31,7 +31,6 @@ Copyright (C) 1994 Steen Lumholt.
#endif
#include "pycore_long.h" // _PyLong_IsNegative()
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
#include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8String
#ifdef MS_WINDOWS
@@ -146,7 +145,7 @@ _get_tcl_lib_path(void)
int stat_return_value;
PyObject *prefix;
- (void) _PySys_GetOptionalAttrString("base_prefix", &prefix);
+ (void) PySys_GetOptionalAttrString("base_prefix", &prefix);
if (prefix == NULL) {
return NULL;
}
@@ -3547,7 +3546,7 @@ PyInit__tkinter(void)
/* This helps the dynamic loader; in Unicode aware Tcl versions
it also helps Tcl find its encodings. */
- (void) _PySys_GetOptionalAttrString("executable", &uexe);
+ (void) PySys_GetOptionalAttrString("executable", &uexe);
if (uexe && PyUnicode_Check(uexe)) { // sys.executable can be None
cexe = PyUnicode_EncodeFSDefault(uexe);
Py_DECREF(uexe);
diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c
index c5e78b1510b..c31a7e8fea5 100644
--- a/Modules/_uuidmodule.c
+++ b/Modules/_uuidmodule.c
@@ -78,23 +78,47 @@ py_UuidCreate(PyObject *Py_UNUSED(context),
return NULL;
}
+static int
+py_windows_has_stable_node(void)
+{
+ UUID uuid;
+ RPC_STATUS res;
+ Py_BEGIN_ALLOW_THREADS
+ res = UuidCreateSequential(&uuid);
+ Py_END_ALLOW_THREADS
+ return res == RPC_S_OK;
+}
#endif /* MS_WINDOWS */
static int
-uuid_exec(PyObject *module) {
+uuid_exec(PyObject *module)
+{
+#define ADD_INT(NAME, VALUE) \
+ do { \
+ if (PyModule_AddIntConstant(module, (NAME), (VALUE)) < 0) { \
+ return -1; \
+ } \
+ } while (0)
+
assert(sizeof(uuid_t) == 16);
#if defined(MS_WINDOWS)
- int has_uuid_generate_time_safe = 0;
+ ADD_INT("has_uuid_generate_time_safe", 0);
#elif defined(HAVE_UUID_GENERATE_TIME_SAFE)
- int has_uuid_generate_time_safe = 1;
+ ADD_INT("has_uuid_generate_time_safe", 1);
#else
- int has_uuid_generate_time_safe = 0;
+ ADD_INT("has_uuid_generate_time_safe", 0);
#endif
- if (PyModule_AddIntConstant(module, "has_uuid_generate_time_safe",
- has_uuid_generate_time_safe) < 0) {
- return -1;
- }
+
+#if defined(MS_WINDOWS)
+ ADD_INT("has_stable_extractable_node", py_windows_has_stable_node());
+#elif defined(HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC)
+ ADD_INT("has_stable_extractable_node", 1);
+#else
+ ADD_INT("has_stable_extractable_node", 0);
+#endif
+
+#undef ADD_INT
return 0;
}
diff --git a/Modules/_winapi.c b/Modules/_winapi.c
index 02817e09b93..b4cfbebcb1b 100644
--- a/Modules/_winapi.c
+++ b/Modules/_winapi.c
@@ -1573,6 +1573,7 @@ static PyObject *
_winapi_GetLongPathName_impl(PyObject *module, LPCWSTR path)
/*[clinic end generated code: output=c4774b080275a2d0 input=9872e211e3a4a88f]*/
{
+#if defined(MS_WINDOWS_APP) || defined(MS_WINDOWS_SYSTEM)
DWORD cchBuffer;
PyObject *result = NULL;
@@ -1596,6 +1597,9 @@ _winapi_GetLongPathName_impl(PyObject *module, LPCWSTR path)
PyErr_SetFromWindowsErr(0);
}
return result;
+#else
+ return PyUnicode_FromWideChar(path, -1);
+#endif
}
/*[clinic input]
@@ -1629,9 +1633,11 @@ _winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle)
if (! result)
return PyErr_SetFromWindowsErr(GetLastError());
- return PyUnicode_FromWideChar(filename, wcslen(filename));
+ return PyUnicode_FromWideChar(filename, -1);
}
+#if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)
+
/*[clinic input]
_winapi.GetShortPathName
@@ -1674,6 +1680,8 @@ _winapi_GetShortPathName_impl(PyObject *module, LPCWSTR path)
return result;
}
+#endif /* MS_WINDOWS_DESKTOP || MS_WINDOWS_SYSTEM */
+
/*[clinic input]
_winapi.GetStdHandle -> HANDLE
@@ -2740,6 +2748,19 @@ _winapi_GetACP_impl(PyObject *module)
}
/*[clinic input]
+_winapi.GetOEMCP
+
+Get the current Windows ANSI code page identifier.
+[clinic start generated code]*/
+
+static PyObject *
+_winapi_GetOEMCP_impl(PyObject *module)
+/*[clinic end generated code: output=4def5b07a8be1b3b input=e8caf4353a28e28e]*/
+{
+ return PyLong_FromUnsignedLong(GetOEMCP());
+}
+
+/*[clinic input]
_winapi.GetFileType -> DWORD
handle: HANDLE
@@ -2883,6 +2904,7 @@ _winapi_NeedCurrentDirectoryForExePath_impl(PyObject *module,
LPCWSTR exe_name)
/*[clinic end generated code: output=a65ec879502b58fc input=972aac88a1ec2f00]*/
{
+#if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)
BOOL result;
Py_BEGIN_ALLOW_THREADS
@@ -2890,6 +2912,9 @@ _winapi_NeedCurrentDirectoryForExePath_impl(PyObject *module,
Py_END_ALLOW_THREADS
return result;
+#else
+ return TRUE;
+#endif
}
@@ -2995,6 +3020,7 @@ static PyMethodDef winapi_functions[] = {
_WINAPI_WAITFORSINGLEOBJECT_METHODDEF
_WINAPI_WRITEFILE_METHODDEF
_WINAPI_GETACP_METHODDEF
+ _WINAPI_GETOEMCP_METHODDEF
_WINAPI_GETFILETYPE_METHODDEF
_WINAPI__MIMETYPES_READ_WINDOWS_REGISTRY_METHODDEF
_WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF
diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c
index abd53436b21..5c5383d260a 100644
--- a/Modules/_zoneinfo.c
+++ b/Modules/_zoneinfo.c
@@ -7,6 +7,7 @@
#include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
#include "pycore_typeobject.h" // _PyType_GetModuleState()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "datetime.h" // PyDateTime_TZInfo
@@ -375,9 +376,7 @@ zoneinfo_dealloc(PyObject *obj_self)
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
- if (self->weakreflist != NULL) {
- PyObject_ClearWeakRefs(obj_self);
- }
+ FT_CLEAR_WEAKREFS(obj_self, self->weakreflist);
if (self->trans_list_utc != NULL) {
PyMem_Free(self->trans_list_utc);
diff --git a/Modules/_zstd/_zstdmodule.c b/Modules/_zstd/_zstdmodule.c
new file mode 100644
index 00000000000..d75c0779474
--- /dev/null
+++ b/Modules/_zstd/_zstdmodule.c
@@ -0,0 +1,767 @@
+/* Low level interface to the Zstandard algorthm & the zstd library. */
+
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
+#endif
+
+#include "Python.h"
+
+#include "_zstdmodule.h"
+
+#include <zstd.h> // ZSTD_*()
+#include <zdict.h> // ZDICT_*()
+
+/*[clinic input]
+module _zstd
+
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=4b5f5587aac15c14]*/
+#include "clinic/_zstdmodule.c.h"
+
+
+ZstdDict *
+_Py_parse_zstd_dict(const _zstd_state *state, PyObject *dict, int *ptype)
+{
+ if (state == NULL) {
+ return NULL;
+ }
+
+ /* Check ZstdDict */
+ if (PyObject_TypeCheck(dict, state->ZstdDict_type)) {
+ return (ZstdDict*)dict;
+ }
+
+ /* Check (ZstdDict, type) */
+ if (PyTuple_CheckExact(dict) && PyTuple_GET_SIZE(dict) == 2
+ && PyObject_TypeCheck(PyTuple_GET_ITEM(dict, 0), state->ZstdDict_type)
+ && PyLong_Check(PyTuple_GET_ITEM(dict, 1)))
+ {
+ int type = PyLong_AsInt(PyTuple_GET_ITEM(dict, 1));
+ if (type == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ if (type == DICT_TYPE_DIGESTED
+ || type == DICT_TYPE_UNDIGESTED
+ || type == DICT_TYPE_PREFIX)
+ {
+ *ptype = type;
+ return (ZstdDict*)PyTuple_GET_ITEM(dict, 0);
+ }
+ }
+
+ /* Wrong type */
+ PyErr_SetString(PyExc_TypeError,
+ "zstd_dict argument should be a ZstdDict object.");
+ return NULL;
+}
+
+/* Format error message and set ZstdError. */
+void
+set_zstd_error(const _zstd_state *state, error_type type, size_t zstd_ret)
+{
+ const char *msg;
+ assert(ZSTD_isError(zstd_ret));
+
+ if (state == NULL) {
+ return;
+ }
+ switch (type) {
+ case ERR_DECOMPRESS:
+ msg = "Unable to decompress Zstandard data: %s";
+ break;
+ case ERR_COMPRESS:
+ msg = "Unable to compress Zstandard data: %s";
+ break;
+ case ERR_SET_PLEDGED_INPUT_SIZE:
+ msg = "Unable to set pledged uncompressed content size: %s";
+ break;
+
+ case ERR_LOAD_D_DICT:
+ msg = "Unable to load Zstandard dictionary or prefix for "
+ "decompression: %s";
+ break;
+ case ERR_LOAD_C_DICT:
+ msg = "Unable to load Zstandard dictionary or prefix for "
+ "compression: %s";
+ break;
+
+ case ERR_GET_C_BOUNDS:
+ msg = "Unable to get zstd compression parameter bounds: %s";
+ break;
+ case ERR_GET_D_BOUNDS:
+ msg = "Unable to get zstd decompression parameter bounds: %s";
+ break;
+ case ERR_SET_C_LEVEL:
+ msg = "Unable to set zstd compression level: %s";
+ break;
+
+ case ERR_TRAIN_DICT:
+ msg = "Unable to train the Zstandard dictionary: %s";
+ break;
+ case ERR_FINALIZE_DICT:
+ msg = "Unable to finalize the Zstandard dictionary: %s";
+ break;
+
+ default:
+ Py_UNREACHABLE();
+ }
+ PyErr_Format(state->ZstdError, msg, ZSTD_getErrorName(zstd_ret));
+}
+
+typedef struct {
+ int parameter;
+ char parameter_name[32];
+} ParameterInfo;
+
+static const ParameterInfo cp_list[] = {
+ {ZSTD_c_compressionLevel, "compression_level"},
+ {ZSTD_c_windowLog, "window_log"},
+ {ZSTD_c_hashLog, "hash_log"},
+ {ZSTD_c_chainLog, "chain_log"},
+ {ZSTD_c_searchLog, "search_log"},
+ {ZSTD_c_minMatch, "min_match"},
+ {ZSTD_c_targetLength, "target_length"},
+ {ZSTD_c_strategy, "strategy"},
+
+ {ZSTD_c_enableLongDistanceMatching, "enable_long_distance_matching"},
+ {ZSTD_c_ldmHashLog, "ldm_hash_log"},
+ {ZSTD_c_ldmMinMatch, "ldm_min_match"},
+ {ZSTD_c_ldmBucketSizeLog, "ldm_bucket_size_log"},
+ {ZSTD_c_ldmHashRateLog, "ldm_hash_rate_log"},
+
+ {ZSTD_c_contentSizeFlag, "content_size_flag"},
+ {ZSTD_c_checksumFlag, "checksum_flag"},
+ {ZSTD_c_dictIDFlag, "dict_id_flag"},
+
+ {ZSTD_c_nbWorkers, "nb_workers"},
+ {ZSTD_c_jobSize, "job_size"},
+ {ZSTD_c_overlapLog, "overlap_log"}
+};
+
+static const ParameterInfo dp_list[] = {
+ {ZSTD_d_windowLogMax, "window_log_max"}
+};
+
+void
+set_parameter_error(int is_compress, int key_v, int value_v)
+{
+ ParameterInfo const *list;
+ int list_size;
+ char *type;
+ ZSTD_bounds bounds;
+ char pos_msg[64];
+
+ if (is_compress) {
+ list = cp_list;
+ list_size = Py_ARRAY_LENGTH(cp_list);
+ type = "compression";
+ }
+ else {
+ list = dp_list;
+ list_size = Py_ARRAY_LENGTH(dp_list);
+ type = "decompression";
+ }
+
+ /* Find parameter's name */
+ char const *name = NULL;
+ for (int i = 0; i < list_size; i++) {
+ if (key_v == (list+i)->parameter) {
+ name = (list+i)->parameter_name;
+ break;
+ }
+ }
+
+ /* Unknown parameter */
+ if (name == NULL) {
+ PyOS_snprintf(pos_msg, sizeof(pos_msg),
+ "unknown parameter (key %d)", key_v);
+ name = pos_msg;
+ }
+
+ /* Get parameter bounds */
+ if (is_compress) {
+ bounds = ZSTD_cParam_getBounds(key_v);
+ }
+ else {
+ bounds = ZSTD_dParam_getBounds(key_v);
+ }
+ if (ZSTD_isError(bounds.error)) {
+ PyErr_Format(PyExc_ValueError, "invalid %s parameter '%s'",
+ type, name);
+ return;
+ }
+
+ /* Error message */
+ PyErr_Format(PyExc_ValueError,
+ "%s parameter '%s' received an illegal value %d; "
+ "the valid range is [%d, %d]",
+ type, name, value_v, bounds.lowerBound, bounds.upperBound);
+}
+
+static inline _zstd_state*
+get_zstd_state(PyObject *module)
+{
+ void *state = PyModule_GetState(module);
+ assert(state != NULL);
+ return (_zstd_state *)state;
+}
+
+static Py_ssize_t
+calculate_samples_stats(PyBytesObject *samples_bytes, PyObject *samples_sizes,
+ size_t **chunk_sizes)
+{
+ Py_ssize_t chunks_number;
+ Py_ssize_t sizes_sum;
+ Py_ssize_t i;
+
+ chunks_number = PyTuple_GET_SIZE(samples_sizes);
+ if ((size_t) chunks_number > UINT32_MAX) {
+ PyErr_Format(PyExc_ValueError,
+ "The number of samples should be <= %u.", UINT32_MAX);
+ return -1;
+ }
+
+ /* Prepare chunk_sizes */
+ *chunk_sizes = PyMem_New(size_t, chunks_number);
+ if (*chunk_sizes == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ sizes_sum = PyBytes_GET_SIZE(samples_bytes);
+ for (i = 0; i < chunks_number; i++) {
+ size_t size = PyLong_AsSize_t(PyTuple_GET_ITEM(samples_sizes, i));
+ (*chunk_sizes)[i] = size;
+ if (size == (size_t)-1 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ goto sum_error;
+ }
+ return -1;
+ }
+ if ((size_t)sizes_sum < size) {
+ goto sum_error;
+ }
+ sizes_sum -= size;
+ }
+
+ if (sizes_sum != 0) {
+sum_error:
+ PyErr_SetString(PyExc_ValueError,
+ "The samples size tuple doesn't match the "
+ "concatenation's size.");
+ return -1;
+ }
+ return chunks_number;
+}
+
+
+/*[clinic input]
+_zstd.train_dict
+
+ samples_bytes: PyBytesObject
+ Concatenation of samples.
+ samples_sizes: object(subclass_of='&PyTuple_Type')
+ Tuple of samples' sizes.
+ dict_size: Py_ssize_t
+ The size of the dictionary.
+ /
+
+Train a Zstandard dictionary on sample data.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_train_dict_impl(PyObject *module, PyBytesObject *samples_bytes,
+ PyObject *samples_sizes, Py_ssize_t dict_size)
+/*[clinic end generated code: output=8e87fe43935e8f77 input=d20dedb21c72cb62]*/
+{
+ PyObject *dst_dict_bytes = NULL;
+ size_t *chunk_sizes = NULL;
+ Py_ssize_t chunks_number;
+ size_t zstd_ret;
+
+ /* Check arguments */
+ if (dict_size <= 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "dict_size argument should be positive number.");
+ return NULL;
+ }
+
+ /* Check that the samples are valid and get their sizes */
+ chunks_number = calculate_samples_stats(samples_bytes, samples_sizes,
+ &chunk_sizes);
+ if (chunks_number < 0) {
+ goto error;
+ }
+
+ /* Allocate dict buffer */
+ dst_dict_bytes = PyBytes_FromStringAndSize(NULL, dict_size);
+ if (dst_dict_bytes == NULL) {
+ goto error;
+ }
+
+ /* Train the dictionary */
+ char *dst_dict_buffer = PyBytes_AS_STRING(dst_dict_bytes);
+ const char *samples_buffer = PyBytes_AS_STRING(samples_bytes);
+ Py_BEGIN_ALLOW_THREADS
+ zstd_ret = ZDICT_trainFromBuffer(dst_dict_buffer, dict_size,
+ samples_buffer,
+ chunk_sizes, (uint32_t)chunks_number);
+ Py_END_ALLOW_THREADS
+
+ /* Check Zstandard dict error */
+ if (ZDICT_isError(zstd_ret)) {
+ _zstd_state* mod_state = get_zstd_state(module);
+ set_zstd_error(mod_state, ERR_TRAIN_DICT, zstd_ret);
+ goto error;
+ }
+
+ /* Resize dict_buffer */
+ if (_PyBytes_Resize(&dst_dict_bytes, zstd_ret) < 0) {
+ goto error;
+ }
+
+ goto success;
+
+error:
+ Py_CLEAR(dst_dict_bytes);
+
+success:
+ PyMem_Free(chunk_sizes);
+ return dst_dict_bytes;
+}
+
+/*[clinic input]
+_zstd.finalize_dict
+
+ custom_dict_bytes: PyBytesObject
+ Custom dictionary content.
+ samples_bytes: PyBytesObject
+ Concatenation of samples.
+ samples_sizes: object(subclass_of='&PyTuple_Type')
+ Tuple of samples' sizes.
+ dict_size: Py_ssize_t
+ The size of the dictionary.
+ compression_level: int
+ Optimize for a specific Zstandard compression level, 0 means default.
+ /
+
+Finalize a Zstandard dictionary.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_finalize_dict_impl(PyObject *module, PyBytesObject *custom_dict_bytes,
+ PyBytesObject *samples_bytes,
+ PyObject *samples_sizes, Py_ssize_t dict_size,
+ int compression_level)
+/*[clinic end generated code: output=f91821ba5ae85bda input=3c7e2480aa08fb56]*/
+{
+ Py_ssize_t chunks_number;
+ size_t *chunk_sizes = NULL;
+ PyObject *dst_dict_bytes = NULL;
+ size_t zstd_ret;
+ ZDICT_params_t params;
+
+ /* Check arguments */
+ if (dict_size <= 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "dict_size argument should be positive number.");
+ return NULL;
+ }
+
+ /* Check that the samples are valid and get their sizes */
+ chunks_number = calculate_samples_stats(samples_bytes, samples_sizes,
+ &chunk_sizes);
+ if (chunks_number < 0) {
+ goto error;
+ }
+
+ /* Allocate dict buffer */
+ dst_dict_bytes = PyBytes_FromStringAndSize(NULL, dict_size);
+ if (dst_dict_bytes == NULL) {
+ goto error;
+ }
+
+ /* Parameters */
+
+ /* Optimize for a specific Zstandard compression level, 0 means default. */
+ params.compressionLevel = compression_level;
+ /* Write log to stderr, 0 = none. */
+ params.notificationLevel = 0;
+ /* Force dictID value, 0 means auto mode (32-bits random value). */
+ params.dictID = 0;
+
+ /* Finalize the dictionary */
+ Py_BEGIN_ALLOW_THREADS
+ zstd_ret = ZDICT_finalizeDictionary(
+ PyBytes_AS_STRING(dst_dict_bytes), dict_size,
+ PyBytes_AS_STRING(custom_dict_bytes),
+ Py_SIZE(custom_dict_bytes),
+ PyBytes_AS_STRING(samples_bytes), chunk_sizes,
+ (uint32_t)chunks_number, params);
+ Py_END_ALLOW_THREADS
+
+ /* Check Zstandard dict error */
+ if (ZDICT_isError(zstd_ret)) {
+ _zstd_state* mod_state = get_zstd_state(module);
+ set_zstd_error(mod_state, ERR_FINALIZE_DICT, zstd_ret);
+ goto error;
+ }
+
+ /* Resize dict_buffer */
+ if (_PyBytes_Resize(&dst_dict_bytes, zstd_ret) < 0) {
+ goto error;
+ }
+
+ goto success;
+
+error:
+ Py_CLEAR(dst_dict_bytes);
+
+success:
+ PyMem_Free(chunk_sizes);
+ return dst_dict_bytes;
+}
+
+
+/*[clinic input]
+_zstd.get_param_bounds
+
+ parameter: int
+ The parameter to get bounds.
+ is_compress: bool
+ True for CompressionParameter, False for DecompressionParameter.
+
+Get CompressionParameter/DecompressionParameter bounds.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_get_param_bounds_impl(PyObject *module, int parameter, int is_compress)
+/*[clinic end generated code: output=4acf5a876f0620ca input=45742ef0a3531b65]*/
+{
+ ZSTD_bounds bound;
+ if (is_compress) {
+ bound = ZSTD_cParam_getBounds(parameter);
+ if (ZSTD_isError(bound.error)) {
+ _zstd_state* mod_state = get_zstd_state(module);
+ set_zstd_error(mod_state, ERR_GET_C_BOUNDS, bound.error);
+ return NULL;
+ }
+ }
+ else {
+ bound = ZSTD_dParam_getBounds(parameter);
+ if (ZSTD_isError(bound.error)) {
+ _zstd_state* mod_state = get_zstd_state(module);
+ set_zstd_error(mod_state, ERR_GET_D_BOUNDS, bound.error);
+ return NULL;
+ }
+ }
+
+ return Py_BuildValue("ii", bound.lowerBound, bound.upperBound);
+}
+
+/*[clinic input]
+_zstd.get_frame_size
+
+ frame_buffer: Py_buffer
+ A bytes-like object, it should start from the beginning of a frame,
+ and contains at least one complete frame.
+
+Get the size of a Zstandard frame, including the header and optional checksum.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_get_frame_size_impl(PyObject *module, Py_buffer *frame_buffer)
+/*[clinic end generated code: output=a7384c2f8780f442 input=3b9f73f8c8129d38]*/
+{
+ size_t frame_size;
+
+ frame_size = ZSTD_findFrameCompressedSize(frame_buffer->buf,
+ frame_buffer->len);
+ if (ZSTD_isError(frame_size)) {
+ _zstd_state* mod_state = get_zstd_state(module);
+ PyErr_Format(mod_state->ZstdError,
+ "Error when finding the compressed size of a Zstandard frame. "
+ "Ensure the frame_buffer argument starts from the "
+ "beginning of a frame, and its length is not less than this "
+ "complete frame. Zstd error message: %s.",
+ ZSTD_getErrorName(frame_size));
+ return NULL;
+ }
+
+ return PyLong_FromSize_t(frame_size);
+}
+
+/*[clinic input]
+_zstd.get_frame_info
+
+ frame_buffer: Py_buffer
+ A bytes-like object, containing the header of a Zstandard frame.
+
+Get Zstandard frame infomation from a frame header.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_get_frame_info_impl(PyObject *module, Py_buffer *frame_buffer)
+/*[clinic end generated code: output=56e033cf48001929 input=94b240583ae22ca5]*/
+{
+ uint64_t decompressed_size;
+ uint32_t dict_id;
+
+ /* ZSTD_getFrameContentSize */
+ decompressed_size = ZSTD_getFrameContentSize(frame_buffer->buf,
+ frame_buffer->len);
+
+ /* #define ZSTD_CONTENTSIZE_UNKNOWN (0ULL - 1)
+ #define ZSTD_CONTENTSIZE_ERROR (0ULL - 2) */
+ if (decompressed_size == ZSTD_CONTENTSIZE_ERROR) {
+ _zstd_state* mod_state = get_zstd_state(module);
+ PyErr_SetString(mod_state->ZstdError,
+ "Error when getting information from the header of "
+ "a Zstandard frame. Ensure the frame_buffer argument "
+ "starts from the beginning of a frame, and its length "
+ "is not less than the frame header (6~18 bytes).");
+ return NULL;
+ }
+
+ /* ZSTD_getDictID_fromFrame */
+ dict_id = ZSTD_getDictID_fromFrame(frame_buffer->buf, frame_buffer->len);
+
+ /* Build tuple */
+ if (decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN) {
+ return Py_BuildValue("OI", Py_None, dict_id);
+ }
+ return Py_BuildValue("KI", decompressed_size, dict_id);
+}
+
+/*[clinic input]
+_zstd.set_parameter_types
+
+ c_parameter_type: object(subclass_of='&PyType_Type')
+ CompressionParameter IntEnum type object
+ d_parameter_type: object(subclass_of='&PyType_Type')
+ DecompressionParameter IntEnum type object
+
+Set CompressionParameter and DecompressionParameter types for validity check.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_set_parameter_types_impl(PyObject *module, PyObject *c_parameter_type,
+ PyObject *d_parameter_type)
+/*[clinic end generated code: output=f3313b1294f19502 input=75d7a953580fae5f]*/
+{
+ _zstd_state* mod_state = get_zstd_state(module);
+
+ Py_INCREF(c_parameter_type);
+ Py_XSETREF(mod_state->CParameter_type, (PyTypeObject*)c_parameter_type);
+ Py_INCREF(d_parameter_type);
+ Py_XSETREF(mod_state->DParameter_type, (PyTypeObject*)d_parameter_type);
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef _zstd_methods[] = {
+ _ZSTD_TRAIN_DICT_METHODDEF
+ _ZSTD_FINALIZE_DICT_METHODDEF
+ _ZSTD_GET_PARAM_BOUNDS_METHODDEF
+ _ZSTD_GET_FRAME_SIZE_METHODDEF
+ _ZSTD_GET_FRAME_INFO_METHODDEF
+ _ZSTD_SET_PARAMETER_TYPES_METHODDEF
+ {NULL, NULL}
+};
+
+static int
+_zstd_exec(PyObject *m)
+{
+#define ADD_TYPE(TYPE, SPEC) \
+do { \
+ TYPE = (PyTypeObject *)PyType_FromModuleAndSpec(m, &(SPEC), NULL); \
+ if (TYPE == NULL) { \
+ return -1; \
+ } \
+ if (PyModule_AddType(m, TYPE) < 0) { \
+ return -1; \
+ } \
+} while (0)
+
+#define ADD_INT_MACRO(MACRO) \
+ if (PyModule_AddIntConstant((m), #MACRO, (MACRO)) < 0) { \
+ return -1; \
+ }
+
+#define ADD_INT_CONST_TO_TYPE(TYPE, NAME, VALUE) \
+do { \
+ PyObject *v = PyLong_FromLong((VALUE)); \
+ if (v == NULL || PyObject_SetAttrString((PyObject *)(TYPE), \
+ (NAME), v) < 0) { \
+ Py_XDECREF(v); \
+ return -1; \
+ } \
+ Py_DECREF(v); \
+} while (0)
+
+ _zstd_state* mod_state = get_zstd_state(m);
+
+ /* Reusable objects & variables */
+ mod_state->CParameter_type = NULL;
+ mod_state->DParameter_type = NULL;
+
+ /* Create and add heap types */
+ ADD_TYPE(mod_state->ZstdDict_type, zstd_dict_type_spec);
+ ADD_TYPE(mod_state->ZstdCompressor_type, zstd_compressor_type_spec);
+ ADD_TYPE(mod_state->ZstdDecompressor_type, zstd_decompressor_type_spec);
+ mod_state->ZstdError = PyErr_NewExceptionWithDoc(
+ "compression.zstd.ZstdError",
+ "An error occurred in the zstd library.",
+ NULL, NULL);
+ if (mod_state->ZstdError == NULL) {
+ return -1;
+ }
+ if (PyModule_AddType(m, (PyTypeObject *)mod_state->ZstdError) < 0) {
+ return -1;
+ }
+
+ /* Add constants */
+ if (PyModule_AddIntConstant(m, "zstd_version_number",
+ ZSTD_versionNumber()) < 0) {
+ return -1;
+ }
+
+ if (PyModule_AddStringConstant(m, "zstd_version",
+ ZSTD_versionString()) < 0) {
+ return -1;
+ }
+
+#if ZSTD_VERSION_NUMBER >= 10500
+ if (PyModule_AddIntConstant(m, "ZSTD_CLEVEL_DEFAULT",
+ ZSTD_defaultCLevel()) < 0) {
+ return -1;
+ }
+#else
+ ADD_INT_MACRO(ZSTD_CLEVEL_DEFAULT);
+#endif
+
+ if (PyModule_Add(m, "ZSTD_DStreamOutSize",
+ PyLong_FromSize_t(ZSTD_DStreamOutSize())) < 0) {
+ return -1;
+ }
+
+ /* Add zstd compression parameters. All should also be in cp_list. */
+ ADD_INT_MACRO(ZSTD_c_compressionLevel);
+ ADD_INT_MACRO(ZSTD_c_windowLog);
+ ADD_INT_MACRO(ZSTD_c_hashLog);
+ ADD_INT_MACRO(ZSTD_c_chainLog);
+ ADD_INT_MACRO(ZSTD_c_searchLog);
+ ADD_INT_MACRO(ZSTD_c_minMatch);
+ ADD_INT_MACRO(ZSTD_c_targetLength);
+ ADD_INT_MACRO(ZSTD_c_strategy);
+
+ ADD_INT_MACRO(ZSTD_c_enableLongDistanceMatching);
+ ADD_INT_MACRO(ZSTD_c_ldmHashLog);
+ ADD_INT_MACRO(ZSTD_c_ldmMinMatch);
+ ADD_INT_MACRO(ZSTD_c_ldmBucketSizeLog);
+ ADD_INT_MACRO(ZSTD_c_ldmHashRateLog);
+
+ ADD_INT_MACRO(ZSTD_c_contentSizeFlag);
+ ADD_INT_MACRO(ZSTD_c_checksumFlag);
+ ADD_INT_MACRO(ZSTD_c_dictIDFlag);
+
+ ADD_INT_MACRO(ZSTD_c_nbWorkers);
+ ADD_INT_MACRO(ZSTD_c_jobSize);
+ ADD_INT_MACRO(ZSTD_c_overlapLog);
+
+ /* Add zstd decompression parameters. All should also be in dp_list. */
+ ADD_INT_MACRO(ZSTD_d_windowLogMax);
+
+ /* Add ZSTD_strategy enum members */
+ ADD_INT_MACRO(ZSTD_fast);
+ ADD_INT_MACRO(ZSTD_dfast);
+ ADD_INT_MACRO(ZSTD_greedy);
+ ADD_INT_MACRO(ZSTD_lazy);
+ ADD_INT_MACRO(ZSTD_lazy2);
+ ADD_INT_MACRO(ZSTD_btlazy2);
+ ADD_INT_MACRO(ZSTD_btopt);
+ ADD_INT_MACRO(ZSTD_btultra);
+ ADD_INT_MACRO(ZSTD_btultra2);
+
+ /* Add ZSTD_EndDirective enum members to ZstdCompressor */
+ ADD_INT_CONST_TO_TYPE(mod_state->ZstdCompressor_type,
+ "CONTINUE", ZSTD_e_continue);
+ ADD_INT_CONST_TO_TYPE(mod_state->ZstdCompressor_type,
+ "FLUSH_BLOCK", ZSTD_e_flush);
+ ADD_INT_CONST_TO_TYPE(mod_state->ZstdCompressor_type,
+ "FLUSH_FRAME", ZSTD_e_end);
+
+ /* Make ZstdCompressor immutable (set Py_TPFLAGS_IMMUTABLETYPE) */
+ PyType_Freeze(mod_state->ZstdCompressor_type);
+
+#undef ADD_TYPE
+#undef ADD_INT_MACRO
+#undef ADD_ZSTD_COMPRESSOR_INT_CONST
+
+ return 0;
+}
+
+static int
+_zstd_traverse(PyObject *module, visitproc visit, void *arg)
+{
+ _zstd_state* mod_state = get_zstd_state(module);
+
+ Py_VISIT(mod_state->ZstdDict_type);
+ Py_VISIT(mod_state->ZstdCompressor_type);
+
+ Py_VISIT(mod_state->ZstdDecompressor_type);
+
+ Py_VISIT(mod_state->ZstdError);
+
+ Py_VISIT(mod_state->CParameter_type);
+ Py_VISIT(mod_state->DParameter_type);
+ return 0;
+}
+
+static int
+_zstd_clear(PyObject *module)
+{
+ _zstd_state* mod_state = get_zstd_state(module);
+
+ Py_CLEAR(mod_state->ZstdDict_type);
+ Py_CLEAR(mod_state->ZstdCompressor_type);
+
+ Py_CLEAR(mod_state->ZstdDecompressor_type);
+
+ Py_CLEAR(mod_state->ZstdError);
+
+ Py_CLEAR(mod_state->CParameter_type);
+ Py_CLEAR(mod_state->DParameter_type);
+ return 0;
+}
+
+static void
+_zstd_free(void *module)
+{
+ (void)_zstd_clear((PyObject *)module);
+}
+
+static struct PyModuleDef_Slot _zstd_slots[] = {
+ {Py_mod_exec, _zstd_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+ {0, NULL},
+};
+
+static struct PyModuleDef _zstdmodule = {
+ .m_base = PyModuleDef_HEAD_INIT,
+ .m_name = "_zstd",
+ .m_doc = "Implementation module for Zstandard compression.",
+ .m_size = sizeof(_zstd_state),
+ .m_slots = _zstd_slots,
+ .m_methods = _zstd_methods,
+ .m_traverse = _zstd_traverse,
+ .m_clear = _zstd_clear,
+ .m_free = _zstd_free,
+};
+
+PyMODINIT_FUNC
+PyInit__zstd(void)
+{
+ return PyModuleDef_Init(&_zstdmodule);
+}
diff --git a/Modules/_zstd/_zstdmodule.h b/Modules/_zstd/_zstdmodule.h
new file mode 100644
index 00000000000..4e8f708f223
--- /dev/null
+++ b/Modules/_zstd/_zstdmodule.h
@@ -0,0 +1,61 @@
+/* Low level interface to the Zstandard algorthm & the zstd library. */
+
+/* Declarations shared between different parts of the _zstd module*/
+
+#ifndef ZSTD_MODULE_H
+#define ZSTD_MODULE_H
+
+#include "zstddict.h"
+
+/* Type specs */
+extern PyType_Spec zstd_dict_type_spec;
+extern PyType_Spec zstd_compressor_type_spec;
+extern PyType_Spec zstd_decompressor_type_spec;
+
+typedef struct {
+ /* Module heap types. */
+ PyTypeObject *ZstdDict_type;
+ PyTypeObject *ZstdCompressor_type;
+ PyTypeObject *ZstdDecompressor_type;
+ PyObject *ZstdError;
+
+ /* enum types set by set_parameter_types. */
+ PyTypeObject *CParameter_type;
+ PyTypeObject *DParameter_type;
+} _zstd_state;
+
+typedef enum {
+ ERR_DECOMPRESS,
+ ERR_COMPRESS,
+ ERR_SET_PLEDGED_INPUT_SIZE,
+
+ ERR_LOAD_D_DICT,
+ ERR_LOAD_C_DICT,
+
+ ERR_GET_C_BOUNDS,
+ ERR_GET_D_BOUNDS,
+ ERR_SET_C_LEVEL,
+
+ ERR_TRAIN_DICT,
+ ERR_FINALIZE_DICT,
+} error_type;
+
+typedef enum {
+ DICT_TYPE_DIGESTED = 0,
+ DICT_TYPE_UNDIGESTED = 1,
+ DICT_TYPE_PREFIX = 2
+} dictionary_type;
+
+extern ZstdDict *
+_Py_parse_zstd_dict(const _zstd_state *state,
+ PyObject *dict, int *type);
+
+/* Format error message and set ZstdError. */
+extern void
+set_zstd_error(const _zstd_state *state,
+ error_type type, size_t zstd_ret);
+
+extern void
+set_parameter_error(int is_compress, int key_v, int value_v);
+
+#endif // !ZSTD_MODULE_H
diff --git a/Modules/_zstd/buffer.h b/Modules/_zstd/buffer.h
new file mode 100644
index 00000000000..4c885fa0d72
--- /dev/null
+++ b/Modules/_zstd/buffer.h
@@ -0,0 +1,108 @@
+/* Low level interface to the Zstandard algorthm & the zstd library. */
+
+#ifndef ZSTD_BUFFER_H
+#define ZSTD_BUFFER_H
+
+#include "pycore_blocks_output_buffer.h"
+
+#include <zstd.h> // ZSTD_outBuffer
+
+/* Blocks output buffer wrapper code */
+
+/* Initialize the buffer, and grow the buffer.
+ Return 0 on success
+ Return -1 on failure */
+static inline int
+_OutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer, ZSTD_outBuffer *ob,
+ Py_ssize_t max_length)
+{
+ /* Ensure .list was set to NULL */
+ assert(buffer->list == NULL);
+
+ Py_ssize_t res = _BlocksOutputBuffer_InitAndGrow(buffer, max_length,
+ &ob->dst);
+ if (res < 0) {
+ return -1;
+ }
+ ob->size = (size_t) res;
+ ob->pos = 0;
+ return 0;
+}
+
+/* Initialize the buffer, with an initial size.
+ init_size: the initial size.
+ Return 0 on success
+ Return -1 on failure */
+static inline int
+_OutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer, ZSTD_outBuffer *ob,
+ Py_ssize_t max_length, Py_ssize_t init_size)
+{
+ Py_ssize_t block_size;
+
+ /* Ensure .list was set to NULL */
+ assert(buffer->list == NULL);
+
+ /* Get block size */
+ if (0 <= max_length && max_length < init_size) {
+ block_size = max_length;
+ }
+ else {
+ block_size = init_size;
+ }
+
+ Py_ssize_t res = _BlocksOutputBuffer_InitWithSize(buffer, block_size,
+ &ob->dst);
+ if (res < 0) {
+ return -1;
+ }
+ // Set max_length, InitWithSize doesn't do this
+ buffer->max_length = max_length;
+ ob->size = (size_t) res;
+ ob->pos = 0;
+ return 0;
+}
+
+/* Grow the buffer.
+ Return 0 on success
+ Return -1 on failure */
+static inline int
+_OutputBuffer_Grow(_BlocksOutputBuffer *buffer, ZSTD_outBuffer *ob)
+{
+ assert(ob->pos == ob->size);
+ Py_ssize_t res = _BlocksOutputBuffer_Grow(buffer, &ob->dst, 0);
+ if (res < 0) {
+ return -1;
+ }
+ ob->size = (size_t) res;
+ ob->pos = 0;
+ return 0;
+}
+
+/* Finish the buffer.
+ Return a bytes object on success
+ Return NULL on failure */
+static inline PyObject *
+_OutputBuffer_Finish(_BlocksOutputBuffer *buffer, ZSTD_outBuffer *ob)
+{
+ return _BlocksOutputBuffer_Finish(buffer, ob->size - ob->pos);
+}
+
+/* Clean up the buffer */
+static inline void
+_OutputBuffer_OnError(_BlocksOutputBuffer *buffer)
+{
+ _BlocksOutputBuffer_OnError(buffer);
+}
+
+/* Whether the output data has reached max_length.
+The avail_out must be 0, please check it before calling. */
+static inline int
+_OutputBuffer_ReachedMaxLength(_BlocksOutputBuffer *buffer, ZSTD_outBuffer *ob)
+{
+ /* Ensure (data size == allocated size) */
+ assert(ob->pos == ob->size);
+
+ return buffer->allocated == buffer->max_length;
+}
+
+#endif // !ZSTD_BUFFER_H
diff --git a/Modules/_zstd/clinic/_zstdmodule.c.h b/Modules/_zstd/clinic/_zstdmodule.c.h
new file mode 100644
index 00000000000..766e1cfa776
--- /dev/null
+++ b/Modules/_zstd/clinic/_zstdmodule.c.h
@@ -0,0 +1,429 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_abstract.h" // _PyNumber_Index()
+#include "pycore_modsupport.h" // _PyArg_CheckPositional()
+
+PyDoc_STRVAR(_zstd_train_dict__doc__,
+"train_dict($module, samples_bytes, samples_sizes, dict_size, /)\n"
+"--\n"
+"\n"
+"Train a Zstandard dictionary on sample data.\n"
+"\n"
+" samples_bytes\n"
+" Concatenation of samples.\n"
+" samples_sizes\n"
+" Tuple of samples\' sizes.\n"
+" dict_size\n"
+" The size of the dictionary.");
+
+#define _ZSTD_TRAIN_DICT_METHODDEF \
+ {"train_dict", _PyCFunction_CAST(_zstd_train_dict), METH_FASTCALL, _zstd_train_dict__doc__},
+
+static PyObject *
+_zstd_train_dict_impl(PyObject *module, PyBytesObject *samples_bytes,
+ PyObject *samples_sizes, Py_ssize_t dict_size);
+
+static PyObject *
+_zstd_train_dict(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyBytesObject *samples_bytes;
+ PyObject *samples_sizes;
+ Py_ssize_t dict_size;
+
+ if (!_PyArg_CheckPositional("train_dict", nargs, 3, 3)) {
+ goto exit;
+ }
+ if (!PyBytes_Check(args[0])) {
+ _PyArg_BadArgument("train_dict", "argument 1", "bytes", args[0]);
+ goto exit;
+ }
+ samples_bytes = (PyBytesObject *)args[0];
+ if (!PyTuple_Check(args[1])) {
+ _PyArg_BadArgument("train_dict", "argument 2", "tuple", args[1]);
+ goto exit;
+ }
+ samples_sizes = args[1];
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[2]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ dict_size = ival;
+ }
+ return_value = _zstd_train_dict_impl(module, samples_bytes, samples_sizes, dict_size);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_finalize_dict__doc__,
+"finalize_dict($module, custom_dict_bytes, samples_bytes, samples_sizes,\n"
+" dict_size, compression_level, /)\n"
+"--\n"
+"\n"
+"Finalize a Zstandard dictionary.\n"
+"\n"
+" custom_dict_bytes\n"
+" Custom dictionary content.\n"
+" samples_bytes\n"
+" Concatenation of samples.\n"
+" samples_sizes\n"
+" Tuple of samples\' sizes.\n"
+" dict_size\n"
+" The size of the dictionary.\n"
+" compression_level\n"
+" Optimize for a specific Zstandard compression level, 0 means default.");
+
+#define _ZSTD_FINALIZE_DICT_METHODDEF \
+ {"finalize_dict", _PyCFunction_CAST(_zstd_finalize_dict), METH_FASTCALL, _zstd_finalize_dict__doc__},
+
+static PyObject *
+_zstd_finalize_dict_impl(PyObject *module, PyBytesObject *custom_dict_bytes,
+ PyBytesObject *samples_bytes,
+ PyObject *samples_sizes, Py_ssize_t dict_size,
+ int compression_level);
+
+static PyObject *
+_zstd_finalize_dict(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyBytesObject *custom_dict_bytes;
+ PyBytesObject *samples_bytes;
+ PyObject *samples_sizes;
+ Py_ssize_t dict_size;
+ int compression_level;
+
+ if (!_PyArg_CheckPositional("finalize_dict", nargs, 5, 5)) {
+ goto exit;
+ }
+ if (!PyBytes_Check(args[0])) {
+ _PyArg_BadArgument("finalize_dict", "argument 1", "bytes", args[0]);
+ goto exit;
+ }
+ custom_dict_bytes = (PyBytesObject *)args[0];
+ if (!PyBytes_Check(args[1])) {
+ _PyArg_BadArgument("finalize_dict", "argument 2", "bytes", args[1]);
+ goto exit;
+ }
+ samples_bytes = (PyBytesObject *)args[1];
+ if (!PyTuple_Check(args[2])) {
+ _PyArg_BadArgument("finalize_dict", "argument 3", "tuple", args[2]);
+ goto exit;
+ }
+ samples_sizes = args[2];
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[3]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ dict_size = ival;
+ }
+ compression_level = PyLong_AsInt(args[4]);
+ if (compression_level == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = _zstd_finalize_dict_impl(module, custom_dict_bytes, samples_bytes, samples_sizes, dict_size, compression_level);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_get_param_bounds__doc__,
+"get_param_bounds($module, /, parameter, is_compress)\n"
+"--\n"
+"\n"
+"Get CompressionParameter/DecompressionParameter bounds.\n"
+"\n"
+" parameter\n"
+" The parameter to get bounds.\n"
+" is_compress\n"
+" True for CompressionParameter, False for DecompressionParameter.");
+
+#define _ZSTD_GET_PARAM_BOUNDS_METHODDEF \
+ {"get_param_bounds", _PyCFunction_CAST(_zstd_get_param_bounds), METH_FASTCALL|METH_KEYWORDS, _zstd_get_param_bounds__doc__},
+
+static PyObject *
+_zstd_get_param_bounds_impl(PyObject *module, int parameter, int is_compress);
+
+static PyObject *
+_zstd_get_param_bounds(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(parameter), &_Py_ID(is_compress), },
+ };
+ #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[] = {"parameter", "is_compress", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "get_param_bounds",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ int parameter;
+ int is_compress;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ parameter = PyLong_AsInt(args[0]);
+ if (parameter == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ is_compress = PyObject_IsTrue(args[1]);
+ if (is_compress < 0) {
+ goto exit;
+ }
+ return_value = _zstd_get_param_bounds_impl(module, parameter, is_compress);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_get_frame_size__doc__,
+"get_frame_size($module, /, frame_buffer)\n"
+"--\n"
+"\n"
+"Get the size of a Zstandard frame, including the header and optional checksum.\n"
+"\n"
+" frame_buffer\n"
+" A bytes-like object, it should start from the beginning of a frame,\n"
+" and contains at least one complete frame.");
+
+#define _ZSTD_GET_FRAME_SIZE_METHODDEF \
+ {"get_frame_size", _PyCFunction_CAST(_zstd_get_frame_size), METH_FASTCALL|METH_KEYWORDS, _zstd_get_frame_size__doc__},
+
+static PyObject *
+_zstd_get_frame_size_impl(PyObject *module, Py_buffer *frame_buffer);
+
+static PyObject *
+_zstd_get_frame_size(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 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(frame_buffer), },
+ };
+ #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[] = {"frame_buffer", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "get_frame_size",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ Py_buffer frame_buffer = {NULL, NULL};
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (PyObject_GetBuffer(args[0], &frame_buffer, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ return_value = _zstd_get_frame_size_impl(module, &frame_buffer);
+
+exit:
+ /* Cleanup for frame_buffer */
+ if (frame_buffer.obj) {
+ PyBuffer_Release(&frame_buffer);
+ }
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_get_frame_info__doc__,
+"get_frame_info($module, /, frame_buffer)\n"
+"--\n"
+"\n"
+"Get Zstandard frame infomation from a frame header.\n"
+"\n"
+" frame_buffer\n"
+" A bytes-like object, containing the header of a Zstandard frame.");
+
+#define _ZSTD_GET_FRAME_INFO_METHODDEF \
+ {"get_frame_info", _PyCFunction_CAST(_zstd_get_frame_info), METH_FASTCALL|METH_KEYWORDS, _zstd_get_frame_info__doc__},
+
+static PyObject *
+_zstd_get_frame_info_impl(PyObject *module, Py_buffer *frame_buffer);
+
+static PyObject *
+_zstd_get_frame_info(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 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(frame_buffer), },
+ };
+ #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[] = {"frame_buffer", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "get_frame_info",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ Py_buffer frame_buffer = {NULL, NULL};
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (PyObject_GetBuffer(args[0], &frame_buffer, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ return_value = _zstd_get_frame_info_impl(module, &frame_buffer);
+
+exit:
+ /* Cleanup for frame_buffer */
+ if (frame_buffer.obj) {
+ PyBuffer_Release(&frame_buffer);
+ }
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_set_parameter_types__doc__,
+"set_parameter_types($module, /, c_parameter_type, d_parameter_type)\n"
+"--\n"
+"\n"
+"Set CompressionParameter and DecompressionParameter types for validity check.\n"
+"\n"
+" c_parameter_type\n"
+" CompressionParameter IntEnum type object\n"
+" d_parameter_type\n"
+" DecompressionParameter IntEnum type object");
+
+#define _ZSTD_SET_PARAMETER_TYPES_METHODDEF \
+ {"set_parameter_types", _PyCFunction_CAST(_zstd_set_parameter_types), METH_FASTCALL|METH_KEYWORDS, _zstd_set_parameter_types__doc__},
+
+static PyObject *
+_zstd_set_parameter_types_impl(PyObject *module, PyObject *c_parameter_type,
+ PyObject *d_parameter_type);
+
+static PyObject *
+_zstd_set_parameter_types(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(c_parameter_type), &_Py_ID(d_parameter_type), },
+ };
+ #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[] = {"c_parameter_type", "d_parameter_type", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "set_parameter_types",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ PyObject *c_parameter_type;
+ PyObject *d_parameter_type;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!PyObject_TypeCheck(args[0], &PyType_Type)) {
+ _PyArg_BadArgument("set_parameter_types", "argument 'c_parameter_type'", (&PyType_Type)->tp_name, args[0]);
+ goto exit;
+ }
+ c_parameter_type = args[0];
+ if (!PyObject_TypeCheck(args[1], &PyType_Type)) {
+ _PyArg_BadArgument("set_parameter_types", "argument 'd_parameter_type'", (&PyType_Type)->tp_name, args[1]);
+ goto exit;
+ }
+ d_parameter_type = args[1];
+ return_value = _zstd_set_parameter_types_impl(module, c_parameter_type, d_parameter_type);
+
+exit:
+ return return_value;
+}
+/*[clinic end generated code: output=437b084f149e68e5 input=a9049054013a1b77]*/
diff --git a/Modules/_zstd/clinic/compressor.c.h b/Modules/_zstd/clinic/compressor.c.h
new file mode 100644
index 00000000000..4f8d93fd9e8
--- /dev/null
+++ b/Modules/_zstd/clinic/compressor.c.h
@@ -0,0 +1,294 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+
+PyDoc_STRVAR(_zstd_ZstdCompressor_new__doc__,
+"ZstdCompressor(level=None, options=None, zstd_dict=None)\n"
+"--\n"
+"\n"
+"Create a compressor object for compressing data incrementally.\n"
+"\n"
+" level\n"
+" The compression level to use. Defaults to COMPRESSION_LEVEL_DEFAULT.\n"
+" options\n"
+" A dict object that contains advanced compression parameters.\n"
+" zstd_dict\n"
+" A ZstdDict object, a pre-trained Zstandard dictionary.\n"
+"\n"
+"Thread-safe at method level. For one-shot compression, use the compress()\n"
+"function instead.");
+
+static PyObject *
+_zstd_ZstdCompressor_new_impl(PyTypeObject *type, PyObject *level,
+ PyObject *options, PyObject *zstd_dict);
+
+static PyObject *
+_zstd_ZstdCompressor_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 3
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ 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(level), &_Py_ID(options), &_Py_ID(zstd_dict), },
+ };
+ #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[] = {"level", "options", "zstd_dict", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "ZstdCompressor",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[3];
+ PyObject * const *fastargs;
+ Py_ssize_t nargs = PyTuple_GET_SIZE(args);
+ Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
+ PyObject *level = Py_None;
+ PyObject *options = Py_None;
+ PyObject *zstd_dict = Py_None;
+
+ fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser,
+ /*minpos*/ 0, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!fastargs) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (fastargs[0]) {
+ level = fastargs[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (fastargs[1]) {
+ options = fastargs[1];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ zstd_dict = fastargs[2];
+skip_optional_pos:
+ return_value = _zstd_ZstdCompressor_new_impl(type, level, options, zstd_dict);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_ZstdCompressor_compress__doc__,
+"compress($self, /, data, mode=ZstdCompressor.CONTINUE)\n"
+"--\n"
+"\n"
+"Provide data to the compressor object.\n"
+"\n"
+" mode\n"
+" Can be these 3 values ZstdCompressor.CONTINUE,\n"
+" ZstdCompressor.FLUSH_BLOCK, ZstdCompressor.FLUSH_FRAME\n"
+"\n"
+"Return a chunk of compressed data if possible, or b\'\' otherwise. When you have\n"
+"finished providing data to the compressor, call the flush() method to finish\n"
+"the compression process.");
+
+#define _ZSTD_ZSTDCOMPRESSOR_COMPRESS_METHODDEF \
+ {"compress", _PyCFunction_CAST(_zstd_ZstdCompressor_compress), METH_FASTCALL|METH_KEYWORDS, _zstd_ZstdCompressor_compress__doc__},
+
+static PyObject *
+_zstd_ZstdCompressor_compress_impl(ZstdCompressor *self, Py_buffer *data,
+ int mode);
+
+static PyObject *
+_zstd_ZstdCompressor_compress(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 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(data), &_Py_ID(mode), },
+ };
+ #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[] = {"data", "mode", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "compress",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ Py_buffer data = {NULL, NULL};
+ int mode = ZSTD_e_continue;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ mode = PyLong_AsInt(args[1]);
+ if (mode == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_pos:
+ return_value = _zstd_ZstdCompressor_compress_impl((ZstdCompressor *)self, &data, mode);
+
+exit:
+ /* Cleanup for data */
+ if (data.obj) {
+ PyBuffer_Release(&data);
+ }
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_ZstdCompressor_flush__doc__,
+"flush($self, /, mode=ZstdCompressor.FLUSH_FRAME)\n"
+"--\n"
+"\n"
+"Finish the compression process.\n"
+"\n"
+" mode\n"
+" Can be these 2 values ZstdCompressor.FLUSH_FRAME,\n"
+" ZstdCompressor.FLUSH_BLOCK\n"
+"\n"
+"Flush any remaining data left in internal buffers. Since Zstandard data\n"
+"consists of one or more independent frames, the compressor object can still\n"
+"be used after this method is called.");
+
+#define _ZSTD_ZSTDCOMPRESSOR_FLUSH_METHODDEF \
+ {"flush", _PyCFunction_CAST(_zstd_ZstdCompressor_flush), METH_FASTCALL|METH_KEYWORDS, _zstd_ZstdCompressor_flush__doc__},
+
+static PyObject *
+_zstd_ZstdCompressor_flush_impl(ZstdCompressor *self, int mode);
+
+static PyObject *
+_zstd_ZstdCompressor_flush(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(mode), },
+ };
+ #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[] = {"mode", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "flush",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ int mode = ZSTD_e_end;
+
+ 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;
+ }
+ mode = PyLong_AsInt(args[0]);
+ if (mode == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_pos:
+ return_value = _zstd_ZstdCompressor_flush_impl((ZstdCompressor *)self, mode);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_ZstdCompressor_set_pledged_input_size__doc__,
+"set_pledged_input_size($self, size, /)\n"
+"--\n"
+"\n"
+"Set the uncompressed content size to be written into the frame header.\n"
+"\n"
+" size\n"
+" The size of the uncompressed data to be provided to the compressor.\n"
+"\n"
+"This method can be used to ensure the header of the frame about to be written\n"
+"includes the size of the data, unless the CompressionParameter.content_size_flag\n"
+"is set to False. If last_mode != FLUSH_FRAME, then a RuntimeError is raised.\n"
+"\n"
+"It is important to ensure that the pledged data size matches the actual data\n"
+"size. If they do not match the compressed output data may be corrupted and the\n"
+"final chunk written may be lost.");
+
+#define _ZSTD_ZSTDCOMPRESSOR_SET_PLEDGED_INPUT_SIZE_METHODDEF \
+ {"set_pledged_input_size", (PyCFunction)_zstd_ZstdCompressor_set_pledged_input_size, METH_O, _zstd_ZstdCompressor_set_pledged_input_size__doc__},
+
+static PyObject *
+_zstd_ZstdCompressor_set_pledged_input_size_impl(ZstdCompressor *self,
+ unsigned long long size);
+
+static PyObject *
+_zstd_ZstdCompressor_set_pledged_input_size(PyObject *self, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ unsigned long long size;
+
+ if (!zstd_contentsize_converter(arg, &size)) {
+ goto exit;
+ }
+ return_value = _zstd_ZstdCompressor_set_pledged_input_size_impl((ZstdCompressor *)self, size);
+
+exit:
+ return return_value;
+}
+/*[clinic end generated code: output=c1d5c2cf06a8becd input=a9049054013a1b77]*/
diff --git a/Modules/_zstd/clinic/decompressor.c.h b/Modules/_zstd/clinic/decompressor.c.h
new file mode 100644
index 00000000000..c6fdae74ab0
--- /dev/null
+++ b/Modules/_zstd/clinic/decompressor.c.h
@@ -0,0 +1,223 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_abstract.h" // _PyNumber_Index()
+#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+
+PyDoc_STRVAR(_zstd_ZstdDecompressor_new__doc__,
+"ZstdDecompressor(zstd_dict=None, options=None)\n"
+"--\n"
+"\n"
+"Create a decompressor object for decompressing data incrementally.\n"
+"\n"
+" zstd_dict\n"
+" A ZstdDict object, a pre-trained Zstandard dictionary.\n"
+" options\n"
+" A dict object that contains advanced decompression parameters.\n"
+"\n"
+"Thread-safe at method level. For one-shot decompression, use the decompress()\n"
+"function instead.");
+
+static PyObject *
+_zstd_ZstdDecompressor_new_impl(PyTypeObject *type, PyObject *zstd_dict,
+ PyObject *options);
+
+static PyObject *
+_zstd_ZstdDecompressor_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 2
+ 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(zstd_dict), &_Py_ID(options), },
+ };
+ #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[] = {"zstd_dict", "options", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "ZstdDecompressor",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ PyObject * const *fastargs;
+ Py_ssize_t nargs = PyTuple_GET_SIZE(args);
+ Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
+ PyObject *zstd_dict = Py_None;
+ PyObject *options = Py_None;
+
+ fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser,
+ /*minpos*/ 0, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!fastargs) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (fastargs[0]) {
+ zstd_dict = fastargs[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ options = fastargs[1];
+skip_optional_pos:
+ return_value = _zstd_ZstdDecompressor_new_impl(type, zstd_dict, options);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_ZstdDecompressor_unused_data__doc__,
+"A bytes object of un-consumed input data.\n"
+"\n"
+"When ZstdDecompressor object stops after a frame is\n"
+"decompressed, unused input data after the frame. Otherwise this will be b\'\'.");
+#if defined(_zstd_ZstdDecompressor_unused_data_DOCSTR)
+# undef _zstd_ZstdDecompressor_unused_data_DOCSTR
+#endif
+#define _zstd_ZstdDecompressor_unused_data_DOCSTR _zstd_ZstdDecompressor_unused_data__doc__
+
+#if !defined(_zstd_ZstdDecompressor_unused_data_DOCSTR)
+# define _zstd_ZstdDecompressor_unused_data_DOCSTR NULL
+#endif
+#if defined(_ZSTD_ZSTDDECOMPRESSOR_UNUSED_DATA_GETSETDEF)
+# undef _ZSTD_ZSTDDECOMPRESSOR_UNUSED_DATA_GETSETDEF
+# define _ZSTD_ZSTDDECOMPRESSOR_UNUSED_DATA_GETSETDEF {"unused_data", (getter)_zstd_ZstdDecompressor_unused_data_get, (setter)_zstd_ZstdDecompressor_unused_data_set, _zstd_ZstdDecompressor_unused_data_DOCSTR},
+#else
+# define _ZSTD_ZSTDDECOMPRESSOR_UNUSED_DATA_GETSETDEF {"unused_data", (getter)_zstd_ZstdDecompressor_unused_data_get, NULL, _zstd_ZstdDecompressor_unused_data_DOCSTR},
+#endif
+
+static PyObject *
+_zstd_ZstdDecompressor_unused_data_get_impl(ZstdDecompressor *self);
+
+static PyObject *
+_zstd_ZstdDecompressor_unused_data_get(PyObject *self, void *Py_UNUSED(context))
+{
+ return _zstd_ZstdDecompressor_unused_data_get_impl((ZstdDecompressor *)self);
+}
+
+PyDoc_STRVAR(_zstd_ZstdDecompressor_decompress__doc__,
+"decompress($self, /, data, max_length=-1)\n"
+"--\n"
+"\n"
+"Decompress *data*, returning uncompressed bytes if possible, or b\'\' otherwise.\n"
+"\n"
+" data\n"
+" A bytes-like object, Zstandard data to be decompressed.\n"
+" max_length\n"
+" Maximum size of returned data. When it is negative, the size of\n"
+" output buffer is unlimited. When it is nonnegative, returns at\n"
+" most max_length bytes of decompressed data.\n"
+"\n"
+"If *max_length* is nonnegative, returns at most *max_length* bytes of\n"
+"decompressed data. If this limit is reached and further output can be\n"
+"produced, *self.needs_input* will be set to ``False``. In this case, the next\n"
+"call to *decompress()* may provide *data* as b\'\' to obtain more of the output.\n"
+"\n"
+"If all of the input data was decompressed and returned (either because this\n"
+"was less than *max_length* bytes, or because *max_length* was negative),\n"
+"*self.needs_input* will be set to True.\n"
+"\n"
+"Attempting to decompress data after the end of a frame is reached raises an\n"
+"EOFError. Any data found after the end of the frame is ignored and saved in\n"
+"the self.unused_data attribute.");
+
+#define _ZSTD_ZSTDDECOMPRESSOR_DECOMPRESS_METHODDEF \
+ {"decompress", _PyCFunction_CAST(_zstd_ZstdDecompressor_decompress), METH_FASTCALL|METH_KEYWORDS, _zstd_ZstdDecompressor_decompress__doc__},
+
+static PyObject *
+_zstd_ZstdDecompressor_decompress_impl(ZstdDecompressor *self,
+ Py_buffer *data,
+ Py_ssize_t max_length);
+
+static PyObject *
+_zstd_ZstdDecompressor_decompress(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 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(data), &_Py_ID(max_length), },
+ };
+ #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[] = {"data", "max_length", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "decompress",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ Py_buffer data = {NULL, NULL};
+ Py_ssize_t max_length = -1;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[1]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ max_length = ival;
+ }
+skip_optional_pos:
+ return_value = _zstd_ZstdDecompressor_decompress_impl((ZstdDecompressor *)self, &data, max_length);
+
+exit:
+ /* Cleanup for data */
+ if (data.obj) {
+ PyBuffer_Release(&data);
+ }
+
+ return return_value;
+}
+/*[clinic end generated code: output=30c12ef047027ede input=a9049054013a1b77]*/
diff --git a/Modules/_zstd/clinic/zstddict.c.h b/Modules/_zstd/clinic/zstddict.c.h
new file mode 100644
index 00000000000..79db85405d6
--- /dev/null
+++ b/Modules/_zstd/clinic/zstddict.c.h
@@ -0,0 +1,225 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+
+PyDoc_STRVAR(_zstd_ZstdDict_new__doc__,
+"ZstdDict(dict_content, /, *, is_raw=False)\n"
+"--\n"
+"\n"
+"Represents a Zstandard dictionary.\n"
+"\n"
+" dict_content\n"
+" The content of a Zstandard dictionary as a bytes-like object.\n"
+" is_raw\n"
+" If true, perform no checks on *dict_content*, useful for some\n"
+" advanced cases. Otherwise, check that the content represents\n"
+" a Zstandard dictionary created by the zstd library or CLI.\n"
+"\n"
+"The dictionary can be used for compression or decompression, and can be shared\n"
+"by multiple ZstdCompressor or ZstdDecompressor objects.");
+
+static PyObject *
+_zstd_ZstdDict_new_impl(PyTypeObject *type, Py_buffer *dict_content,
+ int is_raw);
+
+static PyObject *
+_zstd_ZstdDict_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ 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(is_raw), },
+ };
+ #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[] = {"", "is_raw", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "ZstdDict",
+ .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;
+ Py_buffer dict_content = {NULL, NULL};
+ int is_raw = 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;
+ }
+ if (PyObject_GetBuffer(fastargs[0], &dict_content, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ is_raw = PyObject_IsTrue(fastargs[1]);
+ if (is_raw < 0) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = _zstd_ZstdDict_new_impl(type, &dict_content, is_raw);
+
+exit:
+ /* Cleanup for dict_content */
+ if (dict_content.obj) {
+ PyBuffer_Release(&dict_content);
+ }
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_zstd_ZstdDict_dict_content__doc__,
+"The content of a Zstandard dictionary, as a bytes object.");
+#if defined(_zstd_ZstdDict_dict_content_DOCSTR)
+# undef _zstd_ZstdDict_dict_content_DOCSTR
+#endif
+#define _zstd_ZstdDict_dict_content_DOCSTR _zstd_ZstdDict_dict_content__doc__
+
+#if !defined(_zstd_ZstdDict_dict_content_DOCSTR)
+# define _zstd_ZstdDict_dict_content_DOCSTR NULL
+#endif
+#if defined(_ZSTD_ZSTDDICT_DICT_CONTENT_GETSETDEF)
+# undef _ZSTD_ZSTDDICT_DICT_CONTENT_GETSETDEF
+# define _ZSTD_ZSTDDICT_DICT_CONTENT_GETSETDEF {"dict_content", (getter)_zstd_ZstdDict_dict_content_get, (setter)_zstd_ZstdDict_dict_content_set, _zstd_ZstdDict_dict_content_DOCSTR},
+#else
+# define _ZSTD_ZSTDDICT_DICT_CONTENT_GETSETDEF {"dict_content", (getter)_zstd_ZstdDict_dict_content_get, NULL, _zstd_ZstdDict_dict_content_DOCSTR},
+#endif
+
+static PyObject *
+_zstd_ZstdDict_dict_content_get_impl(ZstdDict *self);
+
+static PyObject *
+_zstd_ZstdDict_dict_content_get(PyObject *self, void *Py_UNUSED(context))
+{
+ return _zstd_ZstdDict_dict_content_get_impl((ZstdDict *)self);
+}
+
+PyDoc_STRVAR(_zstd_ZstdDict_as_digested_dict__doc__,
+"Load as a digested dictionary to compressor.\n"
+"\n"
+"Pass this attribute as zstd_dict argument:\n"
+"compress(dat, zstd_dict=zd.as_digested_dict)\n"
+"\n"
+"1. Some advanced compression parameters of compressor may be overridden\n"
+" by parameters of digested dictionary.\n"
+"2. ZstdDict has a digested dictionaries cache for each compression level.\n"
+" It\'s faster when loading again a digested dictionary with the same\n"
+" compression level.\n"
+"3. No need to use this for decompression.");
+#if defined(_zstd_ZstdDict_as_digested_dict_DOCSTR)
+# undef _zstd_ZstdDict_as_digested_dict_DOCSTR
+#endif
+#define _zstd_ZstdDict_as_digested_dict_DOCSTR _zstd_ZstdDict_as_digested_dict__doc__
+
+#if !defined(_zstd_ZstdDict_as_digested_dict_DOCSTR)
+# define _zstd_ZstdDict_as_digested_dict_DOCSTR NULL
+#endif
+#if defined(_ZSTD_ZSTDDICT_AS_DIGESTED_DICT_GETSETDEF)
+# undef _ZSTD_ZSTDDICT_AS_DIGESTED_DICT_GETSETDEF
+# define _ZSTD_ZSTDDICT_AS_DIGESTED_DICT_GETSETDEF {"as_digested_dict", (getter)_zstd_ZstdDict_as_digested_dict_get, (setter)_zstd_ZstdDict_as_digested_dict_set, _zstd_ZstdDict_as_digested_dict_DOCSTR},
+#else
+# define _ZSTD_ZSTDDICT_AS_DIGESTED_DICT_GETSETDEF {"as_digested_dict", (getter)_zstd_ZstdDict_as_digested_dict_get, NULL, _zstd_ZstdDict_as_digested_dict_DOCSTR},
+#endif
+
+static PyObject *
+_zstd_ZstdDict_as_digested_dict_get_impl(ZstdDict *self);
+
+static PyObject *
+_zstd_ZstdDict_as_digested_dict_get(PyObject *self, void *Py_UNUSED(context))
+{
+ return _zstd_ZstdDict_as_digested_dict_get_impl((ZstdDict *)self);
+}
+
+PyDoc_STRVAR(_zstd_ZstdDict_as_undigested_dict__doc__,
+"Load as an undigested dictionary to compressor.\n"
+"\n"
+"Pass this attribute as zstd_dict argument:\n"
+"compress(dat, zstd_dict=zd.as_undigested_dict)\n"
+"\n"
+"1. The advanced compression parameters of compressor will not be overridden.\n"
+"2. Loading an undigested dictionary is costly. If load an undigested dictionary\n"
+" multiple times, consider reusing a compressor object.\n"
+"3. No need to use this for decompression.");
+#if defined(_zstd_ZstdDict_as_undigested_dict_DOCSTR)
+# undef _zstd_ZstdDict_as_undigested_dict_DOCSTR
+#endif
+#define _zstd_ZstdDict_as_undigested_dict_DOCSTR _zstd_ZstdDict_as_undigested_dict__doc__
+
+#if !defined(_zstd_ZstdDict_as_undigested_dict_DOCSTR)
+# define _zstd_ZstdDict_as_undigested_dict_DOCSTR NULL
+#endif
+#if defined(_ZSTD_ZSTDDICT_AS_UNDIGESTED_DICT_GETSETDEF)
+# undef _ZSTD_ZSTDDICT_AS_UNDIGESTED_DICT_GETSETDEF
+# define _ZSTD_ZSTDDICT_AS_UNDIGESTED_DICT_GETSETDEF {"as_undigested_dict", (getter)_zstd_ZstdDict_as_undigested_dict_get, (setter)_zstd_ZstdDict_as_undigested_dict_set, _zstd_ZstdDict_as_undigested_dict_DOCSTR},
+#else
+# define _ZSTD_ZSTDDICT_AS_UNDIGESTED_DICT_GETSETDEF {"as_undigested_dict", (getter)_zstd_ZstdDict_as_undigested_dict_get, NULL, _zstd_ZstdDict_as_undigested_dict_DOCSTR},
+#endif
+
+static PyObject *
+_zstd_ZstdDict_as_undigested_dict_get_impl(ZstdDict *self);
+
+static PyObject *
+_zstd_ZstdDict_as_undigested_dict_get(PyObject *self, void *Py_UNUSED(context))
+{
+ return _zstd_ZstdDict_as_undigested_dict_get_impl((ZstdDict *)self);
+}
+
+PyDoc_STRVAR(_zstd_ZstdDict_as_prefix__doc__,
+"Load as a prefix to compressor/decompressor.\n"
+"\n"
+"Pass this attribute as zstd_dict argument:\n"
+"compress(dat, zstd_dict=zd.as_prefix)\n"
+"\n"
+"1. Prefix is compatible with long distance matching, while dictionary is not.\n"
+"2. It only works for the first frame, then the compressor/decompressor will\n"
+" return to no prefix state.\n"
+"3. When decompressing, must use the same prefix as when compressing.\"");
+#if defined(_zstd_ZstdDict_as_prefix_DOCSTR)
+# undef _zstd_ZstdDict_as_prefix_DOCSTR
+#endif
+#define _zstd_ZstdDict_as_prefix_DOCSTR _zstd_ZstdDict_as_prefix__doc__
+
+#if !defined(_zstd_ZstdDict_as_prefix_DOCSTR)
+# define _zstd_ZstdDict_as_prefix_DOCSTR NULL
+#endif
+#if defined(_ZSTD_ZSTDDICT_AS_PREFIX_GETSETDEF)
+# undef _ZSTD_ZSTDDICT_AS_PREFIX_GETSETDEF
+# define _ZSTD_ZSTDDICT_AS_PREFIX_GETSETDEF {"as_prefix", (getter)_zstd_ZstdDict_as_prefix_get, (setter)_zstd_ZstdDict_as_prefix_set, _zstd_ZstdDict_as_prefix_DOCSTR},
+#else
+# define _ZSTD_ZSTDDICT_AS_PREFIX_GETSETDEF {"as_prefix", (getter)_zstd_ZstdDict_as_prefix_get, NULL, _zstd_ZstdDict_as_prefix_DOCSTR},
+#endif
+
+static PyObject *
+_zstd_ZstdDict_as_prefix_get_impl(ZstdDict *self);
+
+static PyObject *
+_zstd_ZstdDict_as_prefix_get(PyObject *self, void *Py_UNUSED(context))
+{
+ return _zstd_ZstdDict_as_prefix_get_impl((ZstdDict *)self);
+}
+/*[clinic end generated code: output=4696cbc722e5fdfc input=a9049054013a1b77]*/
diff --git a/Modules/_zstd/compressor.c b/Modules/_zstd/compressor.c
new file mode 100644
index 00000000000..bc9e6eff89a
--- /dev/null
+++ b/Modules/_zstd/compressor.c
@@ -0,0 +1,797 @@
+/* Low level interface to the Zstandard algorthm & the zstd library. */
+
+/* ZstdCompressor class definitions */
+
+/*[clinic input]
+module _zstd
+class _zstd.ZstdCompressor "ZstdCompressor *" "&zstd_compressor_type_spec"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7166021db1ef7df8]*/
+
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
+#endif
+
+#include "Python.h"
+
+#include "_zstdmodule.h"
+#include "buffer.h"
+#include "internal/pycore_lock.h" // PyMutex_IsLocked
+
+#include <stddef.h> // offsetof()
+#include <zstd.h> // ZSTD_*()
+
+typedef struct {
+ PyObject_HEAD
+
+ /* Compression context */
+ ZSTD_CCtx *cctx;
+
+ /* ZstdDict object in use */
+ PyObject *dict;
+
+ /* Last mode, initialized to ZSTD_e_end */
+ int last_mode;
+
+ /* (nbWorker >= 1) ? 1 : 0 */
+ int use_multithread;
+
+ /* Compression level */
+ int compression_level;
+
+ /* Lock to protect the compression context */
+ PyMutex lock;
+} ZstdCompressor;
+
+#define ZstdCompressor_CAST(op) ((ZstdCompressor *)op)
+
+/*[python input]
+
+class zstd_contentsize_converter(CConverter):
+ type = 'unsigned long long'
+ converter = 'zstd_contentsize_converter'
+
+[python start generated code]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=0932c350d633c7de]*/
+
+
+static int
+zstd_contentsize_converter(PyObject *size, unsigned long long *p)
+{
+ // None means the user indicates the size is unknown.
+ if (size == Py_None) {
+ *p = ZSTD_CONTENTSIZE_UNKNOWN;
+ }
+ else {
+ /* ZSTD_CONTENTSIZE_UNKNOWN is 0ULL - 1
+ ZSTD_CONTENTSIZE_ERROR is 0ULL - 2
+ Users should only pass values < ZSTD_CONTENTSIZE_ERROR */
+ unsigned long long pledged_size = PyLong_AsUnsignedLongLong(size);
+ /* Here we check for (unsigned long long)-1 as a sign of an error in
+ PyLong_AsUnsignedLongLong */
+ if (pledged_size == (unsigned long long)-1 && PyErr_Occurred()) {
+ *p = ZSTD_CONTENTSIZE_ERROR;
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ PyErr_Format(PyExc_ValueError,
+ "size argument should be a positive int less "
+ "than %ull", ZSTD_CONTENTSIZE_ERROR);
+ return 0;
+ }
+ return 0;
+ }
+ if (pledged_size >= ZSTD_CONTENTSIZE_ERROR) {
+ *p = ZSTD_CONTENTSIZE_ERROR;
+ PyErr_Format(PyExc_ValueError,
+ "size argument should be a positive int less "
+ "than %ull", ZSTD_CONTENTSIZE_ERROR);
+ return 0;
+ }
+ *p = pledged_size;
+ }
+ return 1;
+}
+
+#include "clinic/compressor.c.h"
+
+static int
+_zstd_set_c_level(ZstdCompressor *self, int level)
+{
+ /* Set integer compression level */
+ int min_level = ZSTD_minCLevel();
+ int max_level = ZSTD_maxCLevel();
+ if (level < min_level || level > max_level) {
+ PyErr_Format(PyExc_ValueError,
+ "illegal compression level %d; the valid range is [%d, %d]",
+ level, min_level, max_level);
+ return -1;
+ }
+
+ /* Save for generating ZSTD_CDICT */
+ self->compression_level = level;
+
+ /* Set compressionLevel to compression context */
+ size_t zstd_ret = ZSTD_CCtx_setParameter(
+ self->cctx, ZSTD_c_compressionLevel, level);
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ set_zstd_error(mod_state, ERR_SET_C_LEVEL, zstd_ret);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+_zstd_set_c_parameters(ZstdCompressor *self, PyObject *options)
+{
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state == NULL) {
+ return -1;
+ }
+
+ if (!PyDict_Check(options)) {
+ PyErr_Format(PyExc_TypeError,
+ "ZstdCompressor() argument 'options' must be dict, not %T",
+ options);
+ return -1;
+ }
+
+ Py_ssize_t pos = 0;
+ PyObject *key, *value;
+ while (PyDict_Next(options, &pos, &key, &value)) {
+ /* Check key type */
+ if (Py_TYPE(key) == mod_state->DParameter_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "compression options dictionary key must not be a "
+ "DecompressionParameter attribute");
+ return -1;
+ }
+
+ Py_INCREF(key);
+ Py_INCREF(value);
+ int key_v = PyLong_AsInt(key);
+ Py_DECREF(key);
+ if (key_v == -1 && PyErr_Occurred()) {
+ Py_DECREF(value);
+ return -1;
+ }
+
+ int value_v = PyLong_AsInt(value);
+ Py_DECREF(value);
+ if (value_v == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ if (key_v == ZSTD_c_compressionLevel) {
+ if (_zstd_set_c_level(self, value_v) < 0) {
+ return -1;
+ }
+ continue;
+ }
+ if (key_v == ZSTD_c_nbWorkers) {
+ /* From the zstd library docs:
+ 1. When nbWorkers >= 1, triggers asynchronous mode when
+ used with ZSTD_compressStream2().
+ 2, Default value is `0`, aka "single-threaded mode" : no
+ worker is spawned, compression is performed inside
+ caller's thread, all invocations are blocking. */
+ if (value_v != 0) {
+ self->use_multithread = 1;
+ }
+ }
+
+ /* Set parameter to compression context */
+ size_t zstd_ret = ZSTD_CCtx_setParameter(self->cctx, key_v, value_v);
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ set_parameter_error(1, key_v, value_v);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void
+capsule_free_cdict(PyObject *capsule)
+{
+ ZSTD_CDict *cdict = PyCapsule_GetPointer(capsule, NULL);
+ ZSTD_freeCDict(cdict);
+}
+
+ZSTD_CDict *
+_get_CDict(ZstdDict *self, int compressionLevel)
+{
+ assert(PyMutex_IsLocked(&self->lock));
+ PyObject *level = NULL;
+ PyObject *capsule = NULL;
+ ZSTD_CDict *cdict;
+ int ret;
+
+
+ /* int level object */
+ level = PyLong_FromLong(compressionLevel);
+ if (level == NULL) {
+ goto error;
+ }
+
+ /* Get PyCapsule object from self->c_dicts */
+ ret = PyDict_GetItemRef(self->c_dicts, level, &capsule);
+ if (ret < 0) {
+ goto error;
+ }
+ if (capsule == NULL) {
+ /* Create ZSTD_CDict instance */
+ Py_BEGIN_ALLOW_THREADS
+ cdict = ZSTD_createCDict(self->dict_buffer, self->dict_len,
+ compressionLevel);
+ Py_END_ALLOW_THREADS
+
+ if (cdict == NULL) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state != NULL) {
+ PyErr_SetString(mod_state->ZstdError,
+ "Failed to create a ZSTD_CDict instance from "
+ "Zstandard dictionary content.");
+ }
+ goto error;
+ }
+
+ /* Put ZSTD_CDict instance into PyCapsule object */
+ capsule = PyCapsule_New(cdict, NULL, capsule_free_cdict);
+ if (capsule == NULL) {
+ ZSTD_freeCDict(cdict);
+ goto error;
+ }
+
+ /* Add PyCapsule object to self->c_dicts */
+ ret = PyDict_SetItem(self->c_dicts, level, capsule);
+ if (ret < 0) {
+ goto error;
+ }
+ }
+ else {
+ /* ZSTD_CDict instance already exists */
+ cdict = PyCapsule_GetPointer(capsule, NULL);
+ }
+ goto success;
+
+error:
+ cdict = NULL;
+success:
+ Py_XDECREF(level);
+ Py_XDECREF(capsule);
+ return cdict;
+}
+
+static int
+_zstd_load_impl(ZstdCompressor *self, ZstdDict *zd,
+ _zstd_state *mod_state, int type)
+{
+ size_t zstd_ret;
+ if (type == DICT_TYPE_DIGESTED) {
+ /* Get ZSTD_CDict */
+ ZSTD_CDict *c_dict = _get_CDict(zd, self->compression_level);
+ if (c_dict == NULL) {
+ return -1;
+ }
+ /* Reference a prepared dictionary.
+ It overrides some compression context's parameters. */
+ zstd_ret = ZSTD_CCtx_refCDict(self->cctx, c_dict);
+ }
+ else if (type == DICT_TYPE_UNDIGESTED) {
+ /* Load a dictionary.
+ It doesn't override compression context's parameters. */
+ zstd_ret = ZSTD_CCtx_loadDictionary(self->cctx, zd->dict_buffer,
+ zd->dict_len);
+ }
+ else if (type == DICT_TYPE_PREFIX) {
+ /* Load a prefix */
+ zstd_ret = ZSTD_CCtx_refPrefix(self->cctx, zd->dict_buffer,
+ zd->dict_len);
+ }
+ else {
+ Py_UNREACHABLE();
+ }
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ set_zstd_error(mod_state, ERR_LOAD_C_DICT, zstd_ret);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+_zstd_load_c_dict(ZstdCompressor *self, PyObject *dict)
+{
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ /* When compressing, use undigested dictionary by default. */
+ int type = DICT_TYPE_UNDIGESTED;
+ ZstdDict *zd = _Py_parse_zstd_dict(mod_state, dict, &type);
+ if (zd == NULL) {
+ return -1;
+ }
+ int ret;
+ PyMutex_Lock(&zd->lock);
+ ret = _zstd_load_impl(self, zd, mod_state, type);
+ PyMutex_Unlock(&zd->lock);
+ return ret;
+}
+
+/*[clinic input]
+@classmethod
+_zstd.ZstdCompressor.__new__ as _zstd_ZstdCompressor_new
+ level: object = None
+ The compression level to use. Defaults to COMPRESSION_LEVEL_DEFAULT.
+ options: object = None
+ A dict object that contains advanced compression parameters.
+ zstd_dict: object = None
+ A ZstdDict object, a pre-trained Zstandard dictionary.
+
+Create a compressor object for compressing data incrementally.
+
+Thread-safe at method level. For one-shot compression, use the compress()
+function instead.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdCompressor_new_impl(PyTypeObject *type, PyObject *level,
+ PyObject *options, PyObject *zstd_dict)
+/*[clinic end generated code: output=cdef61eafecac3d7 input=92de0211ae20ffdc]*/
+{
+ ZstdCompressor* self = PyObject_GC_New(ZstdCompressor, type);
+ if (self == NULL) {
+ goto error;
+ }
+
+ self->use_multithread = 0;
+ self->dict = NULL;
+ self->lock = (PyMutex){0};
+
+ /* Compression context */
+ self->cctx = ZSTD_createCCtx();
+ if (self->cctx == NULL) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state != NULL) {
+ PyErr_SetString(mod_state->ZstdError,
+ "Unable to create ZSTD_CCtx instance.");
+ }
+ goto error;
+ }
+
+ /* Last mode */
+ self->last_mode = ZSTD_e_end;
+
+ if (level != Py_None && options != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "Only one of level or options should be used.");
+ goto error;
+ }
+
+ /* Set compression level */
+ if (level != Py_None) {
+ if (!PyLong_Check(level)) {
+ PyErr_SetString(PyExc_TypeError,
+ "invalid type for level, expected int");
+ goto error;
+ }
+ int level_v = PyLong_AsInt(level);
+ if (level_v == -1 && PyErr_Occurred()) {
+ if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+ PyErr_Format(PyExc_ValueError,
+ "illegal compression level; the valid range is [%d, %d]",
+ ZSTD_minCLevel(), ZSTD_maxCLevel());
+ }
+ goto error;
+ }
+ if (_zstd_set_c_level(self, level_v) < 0) {
+ goto error;
+ }
+ }
+
+ /* Set options dictionary */
+ if (options != Py_None) {
+ if (_zstd_set_c_parameters(self, options) < 0) {
+ goto error;
+ }
+ }
+
+ /* Load Zstandard dictionary to compression context */
+ if (zstd_dict != Py_None) {
+ if (_zstd_load_c_dict(self, zstd_dict) < 0) {
+ goto error;
+ }
+ Py_INCREF(zstd_dict);
+ self->dict = zstd_dict;
+ }
+
+ // We can only start GC tracking once self->dict is set.
+ PyObject_GC_Track(self);
+
+ return (PyObject*)self;
+
+error:
+ Py_XDECREF(self);
+ return NULL;
+}
+
+static void
+ZstdCompressor_dealloc(PyObject *ob)
+{
+ ZstdCompressor *self = ZstdCompressor_CAST(ob);
+
+ PyObject_GC_UnTrack(self);
+
+ /* Free compression context */
+ if (self->cctx) {
+ ZSTD_freeCCtx(self->cctx);
+ }
+
+ assert(!PyMutex_IsLocked(&self->lock));
+
+ /* Py_XDECREF the dict after free the compression context */
+ Py_CLEAR(self->dict);
+
+ PyTypeObject *tp = Py_TYPE(self);
+ PyObject_GC_Del(ob);
+ Py_DECREF(tp);
+}
+
+static PyObject *
+compress_lock_held(ZstdCompressor *self, Py_buffer *data,
+ ZSTD_EndDirective end_directive)
+{
+ assert(PyMutex_IsLocked(&self->lock));
+ ZSTD_inBuffer in;
+ ZSTD_outBuffer out;
+ _BlocksOutputBuffer buffer = {.list = NULL};
+ size_t zstd_ret;
+ PyObject *ret;
+
+ /* Prepare input & output buffers */
+ if (data != NULL) {
+ in.src = data->buf;
+ in.size = data->len;
+ in.pos = 0;
+ }
+ else {
+ in.src = &in;
+ in.size = 0;
+ in.pos = 0;
+ }
+
+ /* Calculate output buffer's size */
+ size_t output_buffer_size = ZSTD_compressBound(in.size);
+ if (output_buffer_size > (size_t) PY_SSIZE_T_MAX) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ if (_OutputBuffer_InitWithSize(&buffer, &out, -1,
+ (Py_ssize_t) output_buffer_size) < 0) {
+ goto error;
+ }
+
+
+ /* Zstandard stream compress */
+ while (1) {
+ Py_BEGIN_ALLOW_THREADS
+ zstd_ret = ZSTD_compressStream2(self->cctx, &out, &in, end_directive);
+ Py_END_ALLOW_THREADS
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ set_zstd_error(mod_state, ERR_COMPRESS, zstd_ret);
+ goto error;
+ }
+
+ /* Finished */
+ if (zstd_ret == 0) {
+ break;
+ }
+
+ /* Output buffer should be exhausted, grow the buffer. */
+ assert(out.pos == out.size);
+ if (out.pos == out.size) {
+ if (_OutputBuffer_Grow(&buffer, &out) < 0) {
+ goto error;
+ }
+ }
+ }
+
+ /* Return a bytes object */
+ ret = _OutputBuffer_Finish(&buffer, &out);
+ if (ret != NULL) {
+ return ret;
+ }
+
+error:
+ _OutputBuffer_OnError(&buffer);
+ return NULL;
+}
+
+#ifndef NDEBUG
+static inline int
+mt_continue_should_break(ZSTD_inBuffer *in, ZSTD_outBuffer *out)
+{
+ return in->size == in->pos && out->size != out->pos;
+}
+#endif
+
+static PyObject *
+compress_mt_continue_lock_held(ZstdCompressor *self, Py_buffer *data)
+{
+ assert(PyMutex_IsLocked(&self->lock));
+ ZSTD_inBuffer in;
+ ZSTD_outBuffer out;
+ _BlocksOutputBuffer buffer = {.list = NULL};
+ size_t zstd_ret;
+ PyObject *ret;
+
+ /* Prepare input & output buffers */
+ in.src = data->buf;
+ in.size = data->len;
+ in.pos = 0;
+
+ if (_OutputBuffer_InitAndGrow(&buffer, &out, -1) < 0) {
+ goto error;
+ }
+
+ /* Zstandard stream compress */
+ while (1) {
+ Py_BEGIN_ALLOW_THREADS
+ do {
+ zstd_ret = ZSTD_compressStream2(self->cctx, &out, &in,
+ ZSTD_e_continue);
+ } while (out.pos != out.size
+ && in.pos != in.size
+ && !ZSTD_isError(zstd_ret));
+ Py_END_ALLOW_THREADS
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ set_zstd_error(mod_state, ERR_COMPRESS, zstd_ret);
+ goto error;
+ }
+
+ /* Like compress_lock_held(), output as much as possible. */
+ if (out.pos == out.size) {
+ if (_OutputBuffer_Grow(&buffer, &out) < 0) {
+ goto error;
+ }
+ }
+ else if (in.pos == in.size) {
+ /* Finished */
+ assert(mt_continue_should_break(&in, &out));
+ break;
+ }
+ }
+
+ /* Return a bytes object */
+ ret = _OutputBuffer_Finish(&buffer, &out);
+ if (ret != NULL) {
+ return ret;
+ }
+
+error:
+ _OutputBuffer_OnError(&buffer);
+ return NULL;
+}
+
+/*[clinic input]
+_zstd.ZstdCompressor.compress
+
+ data: Py_buffer
+ mode: int(c_default="ZSTD_e_continue") = ZstdCompressor.CONTINUE
+ Can be these 3 values ZstdCompressor.CONTINUE,
+ ZstdCompressor.FLUSH_BLOCK, ZstdCompressor.FLUSH_FRAME
+
+Provide data to the compressor object.
+
+Return a chunk of compressed data if possible, or b'' otherwise. When you have
+finished providing data to the compressor, call the flush() method to finish
+the compression process.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdCompressor_compress_impl(ZstdCompressor *self, Py_buffer *data,
+ int mode)
+/*[clinic end generated code: output=ed7982d1cf7b4f98 input=ac2c21d180f579ea]*/
+{
+ PyObject *ret;
+
+ /* Check mode value */
+ if (mode != ZSTD_e_continue &&
+ mode != ZSTD_e_flush &&
+ mode != ZSTD_e_end)
+ {
+ PyErr_SetString(PyExc_ValueError,
+ "mode argument wrong value, it should be one of "
+ "ZstdCompressor.CONTINUE, ZstdCompressor.FLUSH_BLOCK, "
+ "ZstdCompressor.FLUSH_FRAME.");
+ return NULL;
+ }
+
+ /* Thread-safe code */
+ PyMutex_Lock(&self->lock);
+
+ /* Compress */
+ if (self->use_multithread && mode == ZSTD_e_continue) {
+ ret = compress_mt_continue_lock_held(self, data);
+ }
+ else {
+ ret = compress_lock_held(self, data, mode);
+ }
+
+ if (ret) {
+ self->last_mode = mode;
+ }
+ else {
+ self->last_mode = ZSTD_e_end;
+
+ /* Resetting cctx's session never fail */
+ ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only);
+ }
+ PyMutex_Unlock(&self->lock);
+
+ return ret;
+}
+
+/*[clinic input]
+_zstd.ZstdCompressor.flush
+
+ mode: int(c_default="ZSTD_e_end") = ZstdCompressor.FLUSH_FRAME
+ Can be these 2 values ZstdCompressor.FLUSH_FRAME,
+ ZstdCompressor.FLUSH_BLOCK
+
+Finish the compression process.
+
+Flush any remaining data left in internal buffers. Since Zstandard data
+consists of one or more independent frames, the compressor object can still
+be used after this method is called.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdCompressor_flush_impl(ZstdCompressor *self, int mode)
+/*[clinic end generated code: output=b7cf2c8d64dcf2e3 input=0ab19627f323cdbc]*/
+{
+ PyObject *ret;
+
+ /* Check mode value */
+ if (mode != ZSTD_e_end && mode != ZSTD_e_flush) {
+ PyErr_SetString(PyExc_ValueError,
+ "mode argument wrong value, it should be "
+ "ZstdCompressor.FLUSH_FRAME or "
+ "ZstdCompressor.FLUSH_BLOCK.");
+ return NULL;
+ }
+
+ /* Thread-safe code */
+ PyMutex_Lock(&self->lock);
+
+ ret = compress_lock_held(self, NULL, mode);
+
+ if (ret) {
+ self->last_mode = mode;
+ }
+ else {
+ self->last_mode = ZSTD_e_end;
+
+ /* Resetting cctx's session never fail */
+ ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only);
+ }
+ PyMutex_Unlock(&self->lock);
+
+ return ret;
+}
+
+
+/*[clinic input]
+_zstd.ZstdCompressor.set_pledged_input_size
+
+ size: zstd_contentsize
+ The size of the uncompressed data to be provided to the compressor.
+ /
+
+Set the uncompressed content size to be written into the frame header.
+
+This method can be used to ensure the header of the frame about to be written
+includes the size of the data, unless the CompressionParameter.content_size_flag
+is set to False. If last_mode != FLUSH_FRAME, then a RuntimeError is raised.
+
+It is important to ensure that the pledged data size matches the actual data
+size. If they do not match the compressed output data may be corrupted and the
+final chunk written may be lost.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdCompressor_set_pledged_input_size_impl(ZstdCompressor *self,
+ unsigned long long size)
+/*[clinic end generated code: output=3a09e55cc0e3b4f9 input=afd8a7d78cff2eb5]*/
+{
+ // Error occured while converting argument, should be unreachable
+ assert(size != ZSTD_CONTENTSIZE_ERROR);
+
+ /* Thread-safe code */
+ PyMutex_Lock(&self->lock);
+
+ /* Check the current mode */
+ if (self->last_mode != ZSTD_e_end) {
+ PyErr_SetString(PyExc_ValueError,
+ "set_pledged_input_size() method must be called "
+ "when last_mode == FLUSH_FRAME");
+ PyMutex_Unlock(&self->lock);
+ return NULL;
+ }
+
+ /* Set pledged content size */
+ size_t zstd_ret = ZSTD_CCtx_setPledgedSrcSize(self->cctx, size);
+ PyMutex_Unlock(&self->lock);
+ if (ZSTD_isError(zstd_ret)) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ set_zstd_error(mod_state, ERR_SET_PLEDGED_INPUT_SIZE, zstd_ret);
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef ZstdCompressor_methods[] = {
+ _ZSTD_ZSTDCOMPRESSOR_COMPRESS_METHODDEF
+ _ZSTD_ZSTDCOMPRESSOR_FLUSH_METHODDEF
+ _ZSTD_ZSTDCOMPRESSOR_SET_PLEDGED_INPUT_SIZE_METHODDEF
+ {NULL, NULL}
+};
+
+PyDoc_STRVAR(ZstdCompressor_last_mode_doc,
+"The last mode used to this compressor object, its value can be .CONTINUE,\n"
+".FLUSH_BLOCK, .FLUSH_FRAME. Initialized to .FLUSH_FRAME.\n\n"
+"It can be used to get the current state of a compressor, such as, data\n"
+"flushed, or a frame ended.");
+
+static PyMemberDef ZstdCompressor_members[] = {
+ {"last_mode", Py_T_INT, offsetof(ZstdCompressor, last_mode),
+ Py_READONLY, ZstdCompressor_last_mode_doc},
+ {NULL}
+};
+
+static int
+ZstdCompressor_traverse(PyObject *ob, visitproc visit, void *arg)
+{
+ ZstdCompressor *self = ZstdCompressor_CAST(ob);
+ Py_VISIT(self->dict);
+ return 0;
+}
+
+static int
+ZstdCompressor_clear(PyObject *ob)
+{
+ ZstdCompressor *self = ZstdCompressor_CAST(ob);
+ Py_CLEAR(self->dict);
+ return 0;
+}
+
+static PyType_Slot zstdcompressor_slots[] = {
+ {Py_tp_new, _zstd_ZstdCompressor_new},
+ {Py_tp_dealloc, ZstdCompressor_dealloc},
+ {Py_tp_methods, ZstdCompressor_methods},
+ {Py_tp_members, ZstdCompressor_members},
+ {Py_tp_doc, (void *)_zstd_ZstdCompressor_new__doc__},
+ {Py_tp_traverse, ZstdCompressor_traverse},
+ {Py_tp_clear, ZstdCompressor_clear},
+ {0, 0}
+};
+
+PyType_Spec zstd_compressor_type_spec = {
+ .name = "compression.zstd.ZstdCompressor",
+ .basicsize = sizeof(ZstdCompressor),
+ // Py_TPFLAGS_IMMUTABLETYPE is not used here as several
+ // associated constants need to be added to the type.
+ // PyType_Freeze is called later to set the flag.
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+ .slots = zstdcompressor_slots,
+};
diff --git a/Modules/_zstd/decompressor.c b/Modules/_zstd/decompressor.c
new file mode 100644
index 00000000000..c53d6e4cb05
--- /dev/null
+++ b/Modules/_zstd/decompressor.c
@@ -0,0 +1,717 @@
+/* Low level interface to the Zstandard algorthm & the zstd library. */
+
+/* ZstdDecompressor class definition */
+
+/*[clinic input]
+module _zstd
+class _zstd.ZstdDecompressor "ZstdDecompressor *" "&zstd_decompressor_type_spec"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e2969ddf48a203e0]*/
+
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
+#endif
+
+#include "Python.h"
+
+#include "_zstdmodule.h"
+#include "buffer.h"
+#include "internal/pycore_lock.h" // PyMutex_IsLocked
+
+#include <stdbool.h> // bool
+#include <stddef.h> // offsetof()
+#include <zstd.h> // ZSTD_*()
+
+typedef struct {
+ PyObject_HEAD
+
+ /* Decompression context */
+ ZSTD_DCtx *dctx;
+
+ /* ZstdDict object in use */
+ PyObject *dict;
+
+ /* Unconsumed input data */
+ char *input_buffer;
+ size_t input_buffer_size;
+ size_t in_begin, in_end;
+
+ /* Unused data */
+ PyObject *unused_data;
+
+ /* 0 if decompressor has (or may has) unconsumed input data, 0 or 1. */
+ bool needs_input;
+
+ /* For ZstdDecompressor, 0 or 1.
+ 1 means the end of the first frame has been reached. */
+ bool eof;
+
+ /* Lock to protect the decompression context */
+ PyMutex lock;
+} ZstdDecompressor;
+
+#define ZstdDecompressor_CAST(op) ((ZstdDecompressor *)op)
+
+#include "clinic/decompressor.c.h"
+
+static inline ZSTD_DDict *
+_get_DDict(ZstdDict *self)
+{
+ assert(PyMutex_IsLocked(&self->lock));
+ ZSTD_DDict *ret;
+
+ if (self->d_dict == NULL) {
+ /* Create ZSTD_DDict instance from dictionary content */
+ Py_BEGIN_ALLOW_THREADS
+ ret = ZSTD_createDDict(self->dict_buffer, self->dict_len);
+ Py_END_ALLOW_THREADS
+ self->d_dict = ret;
+
+ if (self->d_dict == NULL) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state != NULL) {
+ PyErr_SetString(mod_state->ZstdError,
+ "Failed to create a ZSTD_DDict instance from "
+ "Zstandard dictionary content.");
+ }
+ }
+ }
+
+ return self->d_dict;
+}
+
+static int
+_zstd_set_d_parameters(ZstdDecompressor *self, PyObject *options)
+{
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state == NULL) {
+ return -1;
+ }
+
+ if (!PyDict_Check(options)) {
+ PyErr_Format(PyExc_TypeError,
+ "ZstdDecompressor() argument 'options' must be dict, not %T",
+ options);
+ return -1;
+ }
+
+ Py_ssize_t pos = 0;
+ PyObject *key, *value;
+ while (PyDict_Next(options, &pos, &key, &value)) {
+ /* Check key type */
+ if (Py_TYPE(key) == mod_state->CParameter_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "compression options dictionary key must not be a "
+ "CompressionParameter attribute");
+ return -1;
+ }
+
+ Py_INCREF(key);
+ Py_INCREF(value);
+ int key_v = PyLong_AsInt(key);
+ Py_DECREF(key);
+ if (key_v == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ int value_v = PyLong_AsInt(value);
+ Py_DECREF(value);
+ if (value_v == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ /* Set parameter to compression context */
+ size_t zstd_ret = ZSTD_DCtx_setParameter(self->dctx, key_v, value_v);
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ set_parameter_error(0, key_v, value_v);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+_zstd_load_impl(ZstdDecompressor *self, ZstdDict *zd,
+ _zstd_state *mod_state, int type)
+{
+ size_t zstd_ret;
+ if (type == DICT_TYPE_DIGESTED) {
+ /* Get ZSTD_DDict */
+ ZSTD_DDict *d_dict = _get_DDict(zd);
+ if (d_dict == NULL) {
+ return -1;
+ }
+ /* Reference a prepared dictionary */
+ zstd_ret = ZSTD_DCtx_refDDict(self->dctx, d_dict);
+ }
+ else if (type == DICT_TYPE_UNDIGESTED) {
+ /* Load a dictionary */
+ zstd_ret = ZSTD_DCtx_loadDictionary(self->dctx, zd->dict_buffer,
+ zd->dict_len);
+ }
+ else if (type == DICT_TYPE_PREFIX) {
+ /* Load a prefix */
+ zstd_ret = ZSTD_DCtx_refPrefix(self->dctx, zd->dict_buffer,
+ zd->dict_len);
+ }
+ else {
+ /* Impossible code path */
+ PyErr_SetString(PyExc_SystemError,
+ "load_d_dict() impossible code path");
+ return -1;
+ }
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ set_zstd_error(mod_state, ERR_LOAD_D_DICT, zstd_ret);
+ return -1;
+ }
+ return 0;
+}
+
+/* Load dictionary or prefix to decompression context */
+static int
+_zstd_load_d_dict(ZstdDecompressor *self, PyObject *dict)
+{
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ /* When decompressing, use digested dictionary by default. */
+ int type = DICT_TYPE_DIGESTED;
+ ZstdDict *zd = _Py_parse_zstd_dict(mod_state, dict, &type);
+ if (zd == NULL) {
+ return -1;
+ }
+ int ret;
+ PyMutex_Lock(&zd->lock);
+ ret = _zstd_load_impl(self, zd, mod_state, type);
+ PyMutex_Unlock(&zd->lock);
+ return ret;
+}
+
+/*
+ Decompress implementation in pseudo code:
+
+ initialize_output_buffer
+ while True:
+ decompress_data
+ set_object_flag # .eof
+
+ if output_buffer_exhausted:
+ if output_buffer_reached_max_length:
+ finish
+ grow_output_buffer
+ elif input_buffer_exhausted:
+ finish
+
+ ZSTD_decompressStream()'s size_t return value:
+ - 0 when a frame is completely decoded and fully flushed,
+ zstd's internal buffer has no data.
+ - An error code, which can be tested using ZSTD_isError().
+ - Or any other value > 0, which means there is still some decoding or
+ flushing to do to complete current frame.
+
+ Note, decompressing "an empty input" in any case will make it > 0.
+*/
+static PyObject *
+decompress_lock_held(ZstdDecompressor *self, ZSTD_inBuffer *in,
+ Py_ssize_t max_length)
+{
+ size_t zstd_ret;
+ ZSTD_outBuffer out;
+ _BlocksOutputBuffer buffer = {.list = NULL};
+ PyObject *ret;
+
+ /* Initialize the output buffer */
+ if (_OutputBuffer_InitAndGrow(&buffer, &out, max_length) < 0) {
+ goto error;
+ }
+ assert(out.pos == 0);
+
+ while (1) {
+ /* Decompress */
+ Py_BEGIN_ALLOW_THREADS
+ zstd_ret = ZSTD_decompressStream(self->dctx, &out, in);
+ Py_END_ALLOW_THREADS
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ set_zstd_error(mod_state, ERR_DECOMPRESS, zstd_ret);
+ goto error;
+ }
+
+ /* Set .eof flag */
+ if (zstd_ret == 0) {
+ /* Stop when a frame is decompressed */
+ self->eof = 1;
+ break;
+ }
+
+ /* Need to check out before in. Maybe zstd's internal buffer still has
+ a few bytes that can be output, grow the buffer and continue. */
+ if (out.pos == out.size) {
+ /* Output buffer exhausted */
+
+ /* Output buffer reached max_length */
+ if (_OutputBuffer_ReachedMaxLength(&buffer, &out)) {
+ break;
+ }
+
+ /* Grow output buffer */
+ if (_OutputBuffer_Grow(&buffer, &out) < 0) {
+ goto error;
+ }
+ assert(out.pos == 0);
+
+ }
+ else if (in->pos == in->size) {
+ /* Finished */
+ break;
+ }
+ }
+
+ /* Return a bytes object */
+ ret = _OutputBuffer_Finish(&buffer, &out);
+ if (ret != NULL) {
+ return ret;
+ }
+
+error:
+ _OutputBuffer_OnError(&buffer);
+ return NULL;
+}
+
+static void
+decompressor_reset_session_lock_held(ZstdDecompressor *self)
+{
+ assert(PyMutex_IsLocked(&self->lock));
+
+ /* Reset variables */
+ self->in_begin = 0;
+ self->in_end = 0;
+
+ Py_CLEAR(self->unused_data);
+
+ /* Reset variables in one operation */
+ self->needs_input = 1;
+ self->eof = 0;
+
+ /* Resetting session is guaranteed to never fail */
+ ZSTD_DCtx_reset(self->dctx, ZSTD_reset_session_only);
+}
+
+static PyObject *
+stream_decompress_lock_held(ZstdDecompressor *self, Py_buffer *data,
+ Py_ssize_t max_length)
+{
+ assert(PyMutex_IsLocked(&self->lock));
+ ZSTD_inBuffer in;
+ PyObject *ret = NULL;
+ int use_input_buffer;
+
+ /* Check .eof flag */
+ if (self->eof) {
+ PyErr_SetString(PyExc_EOFError,
+ "Already at the end of a Zstandard frame.");
+ assert(ret == NULL);
+ return NULL;
+ }
+
+ /* Prepare input buffer w/wo unconsumed data */
+ if (self->in_begin == self->in_end) {
+ /* No unconsumed data */
+ use_input_buffer = 0;
+
+ in.src = data->buf;
+ in.size = data->len;
+ in.pos = 0;
+ }
+ else if (data->len == 0) {
+ /* Has unconsumed data, fast path for b'' */
+ assert(self->in_begin < self->in_end);
+
+ use_input_buffer = 1;
+
+ in.src = self->input_buffer + self->in_begin;
+ in.size = self->in_end - self->in_begin;
+ in.pos = 0;
+ }
+ else {
+ /* Has unconsumed data */
+ use_input_buffer = 1;
+
+ /* Unconsumed data size in input_buffer */
+ size_t used_now = self->in_end - self->in_begin;
+ assert(self->in_end > self->in_begin);
+
+ /* Number of bytes we can append to input buffer */
+ size_t avail_now = self->input_buffer_size - self->in_end;
+ assert(self->input_buffer_size >= self->in_end);
+
+ /* Number of bytes we can append if we move existing contents to
+ beginning of buffer */
+ size_t avail_total = self->input_buffer_size - used_now;
+ assert(self->input_buffer_size >= used_now);
+
+ if (avail_total < (size_t) data->len) {
+ char *tmp;
+ size_t new_size = used_now + data->len;
+
+ /* Allocate with new size */
+ tmp = PyMem_Malloc(new_size);
+ if (tmp == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ /* Copy unconsumed data to the beginning of new buffer */
+ memcpy(tmp,
+ self->input_buffer + self->in_begin,
+ used_now);
+
+ /* Switch to new buffer */
+ PyMem_Free(self->input_buffer);
+ self->input_buffer = tmp;
+ self->input_buffer_size = new_size;
+
+ /* Set begin & end position */
+ self->in_begin = 0;
+ self->in_end = used_now;
+ }
+ else if (avail_now < (size_t) data->len) {
+ /* Move unconsumed data to the beginning.
+ Overlap is possible, so use memmove(). */
+ memmove(self->input_buffer,
+ self->input_buffer + self->in_begin,
+ used_now);
+
+ /* Set begin & end position */
+ self->in_begin = 0;
+ self->in_end = used_now;
+ }
+
+ /* Copy data to input buffer */
+ memcpy(self->input_buffer + self->in_end, data->buf, data->len);
+ self->in_end += data->len;
+
+ in.src = self->input_buffer + self->in_begin;
+ in.size = used_now + data->len;
+ in.pos = 0;
+ }
+ assert(in.pos == 0);
+
+ /* Decompress */
+ ret = decompress_lock_held(self, &in, max_length);
+ if (ret == NULL) {
+ goto error;
+ }
+
+ /* Unconsumed input data */
+ if (in.pos == in.size) {
+ if (Py_SIZE(ret) == max_length || self->eof) {
+ self->needs_input = 0;
+ }
+ else {
+ self->needs_input = 1;
+ }
+
+ if (use_input_buffer) {
+ /* Clear input_buffer */
+ self->in_begin = 0;
+ self->in_end = 0;
+ }
+ }
+ else {
+ size_t data_size = in.size - in.pos;
+
+ self->needs_input = 0;
+
+ if (!use_input_buffer) {
+ /* Discard buffer if it's too small
+ (resizing it may needlessly copy the current contents) */
+ if (self->input_buffer != NULL
+ && self->input_buffer_size < data_size)
+ {
+ PyMem_Free(self->input_buffer);
+ self->input_buffer = NULL;
+ self->input_buffer_size = 0;
+ }
+
+ /* Allocate if necessary */
+ if (self->input_buffer == NULL) {
+ self->input_buffer = PyMem_Malloc(data_size);
+ if (self->input_buffer == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ self->input_buffer_size = data_size;
+ }
+
+ /* Copy unconsumed data */
+ memcpy(self->input_buffer, (char*)in.src + in.pos, data_size);
+ self->in_begin = 0;
+ self->in_end = data_size;
+ }
+ else {
+ /* Use input buffer */
+ self->in_begin += in.pos;
+ }
+ }
+
+ return ret;
+
+error:
+ /* Reset decompressor's states/session */
+ decompressor_reset_session_lock_held(self);
+
+ Py_CLEAR(ret);
+ return NULL;
+}
+
+
+/*[clinic input]
+@classmethod
+_zstd.ZstdDecompressor.__new__ as _zstd_ZstdDecompressor_new
+ zstd_dict: object = None
+ A ZstdDict object, a pre-trained Zstandard dictionary.
+ options: object = None
+ A dict object that contains advanced decompression parameters.
+
+Create a decompressor object for decompressing data incrementally.
+
+Thread-safe at method level. For one-shot decompression, use the decompress()
+function instead.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDecompressor_new_impl(PyTypeObject *type, PyObject *zstd_dict,
+ PyObject *options)
+/*[clinic end generated code: output=590ca65c1102ff4a input=213daa57e3ea4062]*/
+{
+ ZstdDecompressor* self = PyObject_GC_New(ZstdDecompressor, type);
+ if (self == NULL) {
+ goto error;
+ }
+
+ self->input_buffer = NULL;
+ self->input_buffer_size = 0;
+ self->in_begin = -1;
+ self->in_end = -1;
+ self->unused_data = NULL;
+ self->eof = 0;
+ self->dict = NULL;
+ self->lock = (PyMutex){0};
+
+ /* needs_input flag */
+ self->needs_input = 1;
+
+ /* Decompression context */
+ self->dctx = ZSTD_createDCtx();
+ if (self->dctx == NULL) {
+ _zstd_state* mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state != NULL) {
+ PyErr_SetString(mod_state->ZstdError,
+ "Unable to create ZSTD_DCtx instance.");
+ }
+ goto error;
+ }
+
+ /* Load Zstandard dictionary to decompression context */
+ if (zstd_dict != Py_None) {
+ if (_zstd_load_d_dict(self, zstd_dict) < 0) {
+ goto error;
+ }
+ Py_INCREF(zstd_dict);
+ self->dict = zstd_dict;
+ }
+
+ /* Set options dictionary */
+ if (options != Py_None) {
+ if (_zstd_set_d_parameters(self, options) < 0) {
+ goto error;
+ }
+ }
+
+ // We can only start GC tracking once self->dict is set.
+ PyObject_GC_Track(self);
+
+ return (PyObject*)self;
+
+error:
+ Py_XDECREF(self);
+ return NULL;
+}
+
+static void
+ZstdDecompressor_dealloc(PyObject *ob)
+{
+ ZstdDecompressor *self = ZstdDecompressor_CAST(ob);
+
+ PyObject_GC_UnTrack(self);
+
+ /* Free decompression context */
+ if (self->dctx) {
+ ZSTD_freeDCtx(self->dctx);
+ }
+
+ assert(!PyMutex_IsLocked(&self->lock));
+
+ /* Py_CLEAR the dict after free decompression context */
+ Py_CLEAR(self->dict);
+
+ /* Free unconsumed input data buffer */
+ PyMem_Free(self->input_buffer);
+
+ /* Free unused data */
+ Py_CLEAR(self->unused_data);
+
+ PyTypeObject *tp = Py_TYPE(self);
+ PyObject_GC_Del(ob);
+ Py_DECREF(tp);
+}
+
+/*[clinic input]
+@getter
+_zstd.ZstdDecompressor.unused_data
+
+A bytes object of un-consumed input data.
+
+When ZstdDecompressor object stops after a frame is
+decompressed, unused input data after the frame. Otherwise this will be b''.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDecompressor_unused_data_get_impl(ZstdDecompressor *self)
+/*[clinic end generated code: output=f3a20940f11b6b09 input=54d41ecd681a3444]*/
+{
+ PyObject *ret;
+
+ PyMutex_Lock(&self->lock);
+
+ if (!self->eof) {
+ PyMutex_Unlock(&self->lock);
+ return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
+ }
+ else {
+ if (self->unused_data == NULL) {
+ self->unused_data = PyBytes_FromStringAndSize(
+ self->input_buffer + self->in_begin,
+ self->in_end - self->in_begin);
+ ret = self->unused_data;
+ Py_XINCREF(ret);
+ }
+ else {
+ ret = self->unused_data;
+ Py_INCREF(ret);
+ }
+ }
+
+ PyMutex_Unlock(&self->lock);
+ return ret;
+}
+
+/*[clinic input]
+_zstd.ZstdDecompressor.decompress
+
+ data: Py_buffer
+ A bytes-like object, Zstandard data to be decompressed.
+ max_length: Py_ssize_t = -1
+ Maximum size of returned data. When it is negative, the size of
+ output buffer is unlimited. When it is nonnegative, returns at
+ most max_length bytes of decompressed data.
+
+Decompress *data*, returning uncompressed bytes if possible, or b'' otherwise.
+
+If *max_length* is nonnegative, returns at most *max_length* bytes of
+decompressed data. If this limit is reached and further output can be
+produced, *self.needs_input* will be set to ``False``. In this case, the next
+call to *decompress()* may provide *data* as b'' to obtain more of the output.
+
+If all of the input data was decompressed and returned (either because this
+was less than *max_length* bytes, or because *max_length* was negative),
+*self.needs_input* will be set to True.
+
+Attempting to decompress data after the end of a frame is reached raises an
+EOFError. Any data found after the end of the frame is ignored and saved in
+the self.unused_data attribute.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDecompressor_decompress_impl(ZstdDecompressor *self,
+ Py_buffer *data,
+ Py_ssize_t max_length)
+/*[clinic end generated code: output=a4302b3c940dbec6 input=6463dfdf98091caa]*/
+{
+ PyObject *ret;
+ /* Thread-safe code */
+ PyMutex_Lock(&self->lock);
+ ret = stream_decompress_lock_held(self, data, max_length);
+ PyMutex_Unlock(&self->lock);
+ return ret;
+}
+
+static PyMethodDef ZstdDecompressor_methods[] = {
+ _ZSTD_ZSTDDECOMPRESSOR_DECOMPRESS_METHODDEF
+ {NULL, NULL}
+};
+
+PyDoc_STRVAR(ZstdDecompressor_eof_doc,
+"True means the end of the first frame has been reached. If decompress data\n"
+"after that, an EOFError exception will be raised.");
+
+PyDoc_STRVAR(ZstdDecompressor_needs_input_doc,
+"If the max_length output limit in .decompress() method has been reached,\n"
+"and the decompressor has (or may has) unconsumed input data, it will be set\n"
+"to False. In this case, passing b'' to the .decompress() method may output\n"
+"further data.");
+
+static PyMemberDef ZstdDecompressor_members[] = {
+ {"eof", Py_T_BOOL, offsetof(ZstdDecompressor, eof),
+ Py_READONLY, ZstdDecompressor_eof_doc},
+ {"needs_input", Py_T_BOOL, offsetof(ZstdDecompressor, needs_input),
+ Py_READONLY, ZstdDecompressor_needs_input_doc},
+ {NULL}
+};
+
+static PyGetSetDef ZstdDecompressor_getset[] = {
+ _ZSTD_ZSTDDECOMPRESSOR_UNUSED_DATA_GETSETDEF
+ {NULL}
+};
+
+static int
+ZstdDecompressor_traverse(PyObject *ob, visitproc visit, void *arg)
+{
+ ZstdDecompressor *self = ZstdDecompressor_CAST(ob);
+ Py_VISIT(self->dict);
+ return 0;
+}
+
+static int
+ZstdDecompressor_clear(PyObject *ob)
+{
+ ZstdDecompressor *self = ZstdDecompressor_CAST(ob);
+ Py_CLEAR(self->dict);
+ Py_CLEAR(self->unused_data);
+ return 0;
+}
+
+static PyType_Slot ZstdDecompressor_slots[] = {
+ {Py_tp_new, _zstd_ZstdDecompressor_new},
+ {Py_tp_dealloc, ZstdDecompressor_dealloc},
+ {Py_tp_methods, ZstdDecompressor_methods},
+ {Py_tp_members, ZstdDecompressor_members},
+ {Py_tp_getset, ZstdDecompressor_getset},
+ {Py_tp_doc, (void *)_zstd_ZstdDecompressor_new__doc__},
+ {Py_tp_traverse, ZstdDecompressor_traverse},
+ {Py_tp_clear, ZstdDecompressor_clear},
+ {0, 0}
+};
+
+PyType_Spec zstd_decompressor_type_spec = {
+ .name = "compression.zstd.ZstdDecompressor",
+ .basicsize = sizeof(ZstdDecompressor),
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE
+ | Py_TPFLAGS_HAVE_GC,
+ .slots = ZstdDecompressor_slots,
+};
diff --git a/Modules/_zstd/zstddict.c b/Modules/_zstd/zstddict.c
new file mode 100644
index 00000000000..14f74aaed46
--- /dev/null
+++ b/Modules/_zstd/zstddict.c
@@ -0,0 +1,273 @@
+/* Low level interface to the Zstandard algorthm & the zstd library. */
+
+/* ZstdDict class definitions */
+
+/*[clinic input]
+module _zstd
+class _zstd.ZstdDict "ZstdDict *" "&zstd_dict_type_spec"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=3dcc175ec974f81c]*/
+
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
+#endif
+
+#include "Python.h"
+
+#include "_zstdmodule.h"
+#include "clinic/zstddict.c.h"
+#include "internal/pycore_lock.h" // PyMutex_IsLocked
+
+#include <zstd.h> // ZSTD_freeDDict(), ZSTD_getDictID_fromDict()
+
+#define ZstdDict_CAST(op) ((ZstdDict *)op)
+
+/*[clinic input]
+@classmethod
+_zstd.ZstdDict.__new__ as _zstd_ZstdDict_new
+ dict_content: Py_buffer
+ The content of a Zstandard dictionary as a bytes-like object.
+ /
+ *
+ is_raw: bool = False
+ If true, perform no checks on *dict_content*, useful for some
+ advanced cases. Otherwise, check that the content represents
+ a Zstandard dictionary created by the zstd library or CLI.
+
+Represents a Zstandard dictionary.
+
+The dictionary can be used for compression or decompression, and can be shared
+by multiple ZstdCompressor or ZstdDecompressor objects.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDict_new_impl(PyTypeObject *type, Py_buffer *dict_content,
+ int is_raw)
+/*[clinic end generated code: output=685b7406a48b0949 input=9e8c493e31c98383]*/
+{
+ /* All dictionaries must be at least 8 bytes */
+ if (dict_content->len < 8) {
+ PyErr_SetString(PyExc_ValueError,
+ "Zstandard dictionary content too short "
+ "(must have at least eight bytes)");
+ return NULL;
+ }
+
+ ZstdDict* self = PyObject_GC_New(ZstdDict, type);
+ if (self == NULL) {
+ return NULL;
+ }
+
+ self->d_dict = NULL;
+ self->dict_buffer = NULL;
+ self->dict_id = 0;
+ self->lock = (PyMutex){0};
+
+ /* ZSTD_CDict dict */
+ self->c_dicts = PyDict_New();
+ if (self->c_dicts == NULL) {
+ goto error;
+ }
+
+ self->dict_buffer = PyMem_Malloc(dict_content->len);
+ if (!self->dict_buffer) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ memcpy(self->dict_buffer, dict_content->buf, dict_content->len);
+ self->dict_len = dict_content->len;
+
+ /* Get dict_id, 0 means "raw content" dictionary. */
+ self->dict_id = ZSTD_getDictID_fromDict(self->dict_buffer, self->dict_len);
+
+ /* Check validity for ordinary dictionary */
+ if (!is_raw && self->dict_id == 0) {
+ PyErr_SetString(PyExc_ValueError, "invalid Zstandard dictionary");
+ goto error;
+ }
+
+ PyObject_GC_Track(self);
+
+ return (PyObject *)self;
+
+error:
+ Py_XDECREF(self);
+ return NULL;
+}
+
+static void
+ZstdDict_dealloc(PyObject *ob)
+{
+ ZstdDict *self = ZstdDict_CAST(ob);
+
+ PyObject_GC_UnTrack(self);
+
+ /* Free ZSTD_DDict instance */
+ if (self->d_dict) {
+ ZSTD_freeDDict(self->d_dict);
+ }
+
+ assert(!PyMutex_IsLocked(&self->lock));
+
+ /* Release dict_buffer after freeing ZSTD_CDict/ZSTD_DDict instances */
+ PyMem_Free(self->dict_buffer);
+ Py_CLEAR(self->c_dicts);
+
+ PyTypeObject *tp = Py_TYPE(self);
+ tp->tp_free(self);
+ Py_DECREF(tp);
+}
+
+PyDoc_STRVAR(ZstdDict_dictid_doc,
+"the Zstandard dictionary, an int between 0 and 2**32.\n\n"
+"A non-zero value represents an ordinary Zstandard dictionary, "
+"conforming to the standardised format.\n\n"
+"The special value '0' means a 'raw content' dictionary,"
+"without any restrictions on format or content.");
+
+static PyObject *
+ZstdDict_repr(PyObject *ob)
+{
+ ZstdDict *dict = ZstdDict_CAST(ob);
+ return PyUnicode_FromFormat("<ZstdDict dict_id=%u dict_size=%zd>",
+ (unsigned int)dict->dict_id, dict->dict_len);
+}
+
+static PyMemberDef ZstdDict_members[] = {
+ {"dict_id", Py_T_UINT, offsetof(ZstdDict, dict_id), Py_READONLY, ZstdDict_dictid_doc},
+ {NULL}
+};
+
+/*[clinic input]
+@getter
+_zstd.ZstdDict.dict_content
+
+The content of a Zstandard dictionary, as a bytes object.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDict_dict_content_get_impl(ZstdDict *self)
+/*[clinic end generated code: output=0d05caa5b550eabb input=4ed526d1c151c596]*/
+{
+ return PyBytes_FromStringAndSize(self->dict_buffer, self->dict_len);
+}
+
+/*[clinic input]
+@getter
+_zstd.ZstdDict.as_digested_dict
+
+Load as a digested dictionary to compressor.
+
+Pass this attribute as zstd_dict argument:
+compress(dat, zstd_dict=zd.as_digested_dict)
+
+1. Some advanced compression parameters of compressor may be overridden
+ by parameters of digested dictionary.
+2. ZstdDict has a digested dictionaries cache for each compression level.
+ It's faster when loading again a digested dictionary with the same
+ compression level.
+3. No need to use this for decompression.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDict_as_digested_dict_get_impl(ZstdDict *self)
+/*[clinic end generated code: output=09b086e7a7320dbb input=ee45e1b4a48f6f2c]*/
+{
+ return Py_BuildValue("Oi", self, DICT_TYPE_DIGESTED);
+}
+
+/*[clinic input]
+@getter
+_zstd.ZstdDict.as_undigested_dict
+
+Load as an undigested dictionary to compressor.
+
+Pass this attribute as zstd_dict argument:
+compress(dat, zstd_dict=zd.as_undigested_dict)
+
+1. The advanced compression parameters of compressor will not be overridden.
+2. Loading an undigested dictionary is costly. If load an undigested dictionary
+ multiple times, consider reusing a compressor object.
+3. No need to use this for decompression.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDict_as_undigested_dict_get_impl(ZstdDict *self)
+/*[clinic end generated code: output=43c7a989e6d4253a input=d39210eedec76fed]*/
+{
+ return Py_BuildValue("Oi", self, DICT_TYPE_UNDIGESTED);
+}
+
+/*[clinic input]
+@getter
+_zstd.ZstdDict.as_prefix
+
+Load as a prefix to compressor/decompressor.
+
+Pass this attribute as zstd_dict argument:
+compress(dat, zstd_dict=zd.as_prefix)
+
+1. Prefix is compatible with long distance matching, while dictionary is not.
+2. It only works for the first frame, then the compressor/decompressor will
+ return to no prefix state.
+3. When decompressing, must use the same prefix as when compressing."
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDict_as_prefix_get_impl(ZstdDict *self)
+/*[clinic end generated code: output=6f7130c356595a16 input=d59757b0b5a9551a]*/
+{
+ return Py_BuildValue("Oi", self, DICT_TYPE_PREFIX);
+}
+
+static PyGetSetDef ZstdDict_getset[] = {
+ _ZSTD_ZSTDDICT_DICT_CONTENT_GETSETDEF
+ _ZSTD_ZSTDDICT_AS_DIGESTED_DICT_GETSETDEF
+ _ZSTD_ZSTDDICT_AS_UNDIGESTED_DICT_GETSETDEF
+ _ZSTD_ZSTDDICT_AS_PREFIX_GETSETDEF
+ {NULL}
+};
+
+static Py_ssize_t
+ZstdDict_length(PyObject *ob)
+{
+ ZstdDict *self = ZstdDict_CAST(ob);
+ return self->dict_len;
+}
+
+static int
+ZstdDict_traverse(PyObject *ob, visitproc visit, void *arg)
+{
+ ZstdDict *self = ZstdDict_CAST(ob);
+ Py_VISIT(self->c_dicts);
+ return 0;
+}
+
+static int
+ZstdDict_clear(PyObject *ob)
+{
+ ZstdDict *self = ZstdDict_CAST(ob);
+ Py_CLEAR(self->c_dicts);
+ return 0;
+}
+
+static PyType_Slot zstddict_slots[] = {
+ {Py_tp_members, ZstdDict_members},
+ {Py_tp_getset, ZstdDict_getset},
+ {Py_tp_new, _zstd_ZstdDict_new},
+ {Py_tp_dealloc, ZstdDict_dealloc},
+ {Py_tp_repr, ZstdDict_repr},
+ {Py_tp_doc, (void *)_zstd_ZstdDict_new__doc__},
+ {Py_sq_length, ZstdDict_length},
+ {Py_tp_traverse, ZstdDict_traverse},
+ {Py_tp_clear, ZstdDict_clear},
+ {0, 0}
+};
+
+PyType_Spec zstd_dict_type_spec = {
+ .name = "compression.zstd.ZstdDict",
+ .basicsize = sizeof(ZstdDict),
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE
+ | Py_TPFLAGS_HAVE_GC,
+ .slots = zstddict_slots,
+};
diff --git a/Modules/_zstd/zstddict.h b/Modules/_zstd/zstddict.h
new file mode 100644
index 00000000000..4a403416dbd
--- /dev/null
+++ b/Modules/_zstd/zstddict.h
@@ -0,0 +1,29 @@
+/* Low level interface to the Zstandard algorthm & the zstd library. */
+
+#ifndef ZSTD_DICT_H
+#define ZSTD_DICT_H
+
+#include <zstd.h> // ZSTD_DDict
+
+typedef struct {
+ PyObject_HEAD
+
+ /* Reusable compress/decompress dictionary, they are created once and
+ can be shared by multiple threads concurrently, since its usage is
+ read-only.
+ c_dicts is a dict, int(compressionLevel):PyCapsule(ZSTD_CDict*) */
+ ZSTD_DDict *d_dict;
+ PyObject *c_dicts;
+
+ /* Dictionary content. */
+ char *dict_buffer;
+ Py_ssize_t dict_len;
+
+ /* Dictionary id */
+ uint32_t dict_id;
+
+ /* Lock to protect the digested dictionaries */
+ PyMutex lock;
+} ZstdDict;
+
+#endif // !ZSTD_DICT_H
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index 401a3a7072b..5d07de2fba9 100644
--- a/Modules/arraymodule.c
+++ b/Modules/arraymodule.c
@@ -13,6 +13,7 @@
#include "pycore_ceval.h" // _PyEval_GetBuiltin()
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
#include "pycore_moduleobject.h" // _PyModule_GetState()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stddef.h> // offsetof()
#include <stdbool.h>
@@ -728,9 +729,7 @@ array_dealloc(PyObject *op)
PyObject_GC_UnTrack(op);
arrayobject *self = arrayobject_CAST(op);
- if (self->weakreflist != NULL) {
- PyObject_ClearWeakRefs(op);
- }
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
if (self->ob_item != NULL) {
PyMem_Free(self->ob_item);
}
diff --git a/Modules/blake2module.c b/Modules/blake2module.c
index f9acc57f1b2..163f238a426 100644
--- a/Modules/blake2module.c
+++ b/Modules/blake2module.c
@@ -2,6 +2,7 @@
* Written in 2013 by Dmitry Chestnykh <dmitry@codingrobots.com>
* Modified for CPython by Christian Heimes <christian@python.org>
* Updated to use HACL* by Jonathan Protzenko <jonathan@protzenko.fr>
+ * Additional work by Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
*
* To the extent possible under law, the author have dedicated all
* copyright and related and neighboring rights to this software to
@@ -13,7 +14,6 @@
# define Py_BUILD_CORE_MODULE 1
#endif
-#include "pyconfig.h"
#include "Python.h"
#include "hashlib.h"
#include "pycore_strhex.h" // _Py_strhex()
@@ -43,104 +43,27 @@
// SIMD256 can't be compiled on macOS ARM64, and performance of SIMD128 isn't
// great; but when compiling a universal2 binary, autoconf will set
-// HACL_CAN_COMPILE_SIMD128 and HACL_CAN_COMPILE_SIMD256 because they *can* be
-// compiled on x86_64. If we're on macOS ARM64, disable these preprocessor
-// symbols.
+// _Py_HACL_CAN_COMPILE_VEC{128,256} because they *can* be compiled on x86_64.
+// If we're on macOS ARM64, we however disable these preprocessor symbols.
#if defined(__APPLE__) && defined(__arm64__)
-# undef HACL_CAN_COMPILE_SIMD128
-# undef HACL_CAN_COMPILE_SIMD256
+# undef _Py_HACL_CAN_COMPILE_VEC128
+# undef _Py_HACL_CAN_COMPILE_VEC256
#endif
-// ECX
-#define ECX_SSE3 (1 << 0)
-#define ECX_SSSE3 (1 << 9)
-#define ECX_SSE4_1 (1 << 19)
-#define ECX_SSE4_2 (1 << 20)
-#define ECX_AVX (1 << 28)
-
-// EBX
-#define EBX_AVX2 (1 << 5)
-
-// EDX
-#define EDX_SSE (1 << 25)
-#define EDX_SSE2 (1 << 26)
-#define EDX_CMOV (1 << 15)
-
-// zero-initialized by default
-typedef struct {
- bool sse, sse2, sse3, sse41, sse42, cmov, avx, avx2;
- bool done;
-} cpu_flags;
+// HACL* expects HACL_CAN_COMPILE_VEC* macros to be set in order to enable
+// the corresponding SIMD instructions so we need to "forward" the values
+// we just deduced above.
+#define HACL_CAN_COMPILE_VEC128 _Py_HACL_CAN_COMPILE_VEC128
+#define HACL_CAN_COMPILE_VEC256 _Py_HACL_CAN_COMPILE_VEC256
-void detect_cpu_features(cpu_flags *flags) {
- if (!flags->done) {
- int eax1 = 0, ebx1 = 0, ecx1 = 0, edx1 = 0;
- int eax7 = 0, ebx7 = 0, ecx7 = 0, edx7 = 0;
-#if defined(__x86_64__) && defined(__GNUC__)
- __cpuid_count(1, 0, eax1, ebx1, ecx1, edx1);
- __cpuid_count(7, 0, eax7, ebx7, ecx7, edx7);
-#elif defined(_M_X64)
- int info1[4] = { 0 };
- int info7[4] = { 0 };
- __cpuidex(info1, 1, 0);
- __cpuidex(info7, 7, 0);
- eax1 = info1[0];
- ebx1 = info1[1];
- ecx1 = info1[2];
- edx1 = info1[3];
- eax7 = info7[0];
- ebx7 = info7[1];
- ecx7 = info7[2];
- edx7 = info7[3];
-#endif
- (void) eax1; (void) ebx1; (void) ecx1; (void) edx1;
- (void) eax7; (void) ebx7; (void) ecx7; (void) edx7;
-
-
- flags->avx = (ecx1 & ECX_AVX) != 0;
-
- flags->avx2 = (ebx7 & EBX_AVX2) != 0;
-
- flags->sse = (edx1 & EDX_SSE) != 0;
- flags->sse2 = (edx1 & EDX_SSE2) != 0;
- flags->cmov = (edx1 & EDX_CMOV) != 0;
-
- flags->sse3 = (ecx1 & ECX_SSE3) != 0;
- /* ssse3 = (ecx1 & ECX_SSSE3) != 0; */
- flags->sse41 = (ecx1 & ECX_SSE4_1) != 0;
- flags->sse42 = (ecx1 & ECX_SSE4_2) != 0;
-
- flags->done = true;
- }
-}
-
-#ifdef HACL_CAN_COMPILE_SIMD128
-static inline bool has_simd128(cpu_flags *flags) {
- // For now this is Intel-only, could conceivably be #ifdef'd to something
- // else.
- return flags->sse && flags->sse2 && flags->sse3 && flags->sse41 && flags->sse42 && flags->cmov;
-}
-#endif
-
-#ifdef HACL_CAN_COMPILE_SIMD256
-static inline bool has_simd256(cpu_flags *flags) {
- return flags->avx && flags->avx2;
-}
-#endif
-
-// Small mismatch between the variable names Python defines as part of configure
-// at the ones HACL* expects to be set in order to enable those headers.
-#define HACL_CAN_COMPILE_VEC128 HACL_CAN_COMPILE_SIMD128
-#define HACL_CAN_COMPILE_VEC256 HACL_CAN_COMPILE_SIMD256
-
-#include "_hacl/Hacl_Hash_Blake2b.h"
#include "_hacl/Hacl_Hash_Blake2s.h"
-#if HACL_CAN_COMPILE_SIMD256
-#include "_hacl/Hacl_Hash_Blake2b_Simd256.h"
-#endif
-#if HACL_CAN_COMPILE_SIMD128
+#include "_hacl/Hacl_Hash_Blake2b.h"
+#if _Py_HACL_CAN_COMPILE_VEC128
#include "_hacl/Hacl_Hash_Blake2s_Simd128.h"
#endif
+#if _Py_HACL_CAN_COMPILE_VEC256
+#include "_hacl/Hacl_Hash_Blake2b_Simd256.h"
+#endif
// MODULE TYPE SLOTS
@@ -148,16 +71,16 @@ static PyType_Spec blake2b_type_spec;
static PyType_Spec blake2s_type_spec;
PyDoc_STRVAR(blake2mod__doc__,
-"_blake2b provides BLAKE2b for hashlib\n"
-);
+ "_blake2 provides BLAKE2b and BLAKE2s for hashlib\n");
typedef struct {
- PyTypeObject* blake2b_type;
- PyTypeObject* blake2s_type;
- cpu_flags flags;
+ PyTypeObject *blake2b_type;
+ PyTypeObject *blake2s_type;
+ bool can_run_simd128;
+ bool can_run_simd256;
} Blake2State;
-static inline Blake2State*
+static inline Blake2State *
blake2_get_state(PyObject *module)
{
void *state = _PyModule_GetState(module);
@@ -165,8 +88,8 @@ blake2_get_state(PyObject *module)
return (Blake2State *)state;
}
-#if defined(HACL_CAN_COMPILE_SIMD128) || defined(HACL_CAN_COMPILE_SIMD256)
-static inline Blake2State*
+#if defined(_Py_HACL_CAN_COMPILE_VEC128) || defined(_Py_HACL_CAN_COMPILE_VEC256)
+static inline Blake2State *
blake2_get_state_from_type(PyTypeObject *module)
{
void *state = _PyType_GetModuleState(module);
@@ -203,31 +126,107 @@ _blake2_free(void *module)
(void)_blake2_clear((PyObject *)module);
}
-#define ADD_INT(d, name, value) do { \
- PyObject *x = PyLong_FromLong(value); \
- if (!x) \
- return -1; \
- if (PyDict_SetItemString(d, name, x) < 0) { \
- Py_DECREF(x); \
- return -1; \
- } \
- Py_DECREF(x); \
-} while(0)
-
-#define ADD_INT_CONST(NAME, VALUE) do { \
- if (PyModule_AddIntConstant(m, NAME, VALUE) < 0) { \
- return -1; \
- } \
-} while (0)
+static void
+blake2module_init_cpu_features(Blake2State *state)
+{
+ /* This must be kept in sync with hmacmodule_init_cpu_features()
+ * in hmacmodule.c */
+ int eax1 = 0, ebx1 = 0, ecx1 = 0, edx1 = 0;
+ int eax7 = 0, ebx7 = 0, ecx7 = 0, edx7 = 0;
+#if defined(__x86_64__) && defined(__GNUC__)
+ __cpuid_count(1, 0, eax1, ebx1, ecx1, edx1);
+ __cpuid_count(7, 0, eax7, ebx7, ecx7, edx7);
+#elif defined(_M_X64)
+ int info1[4] = {0};
+ __cpuidex(info1, 1, 0);
+ eax1 = info1[0], ebx1 = info1[1], ecx1 = info1[2], edx1 = info1[3];
+
+ int info7[4] = {0};
+ __cpuidex(info7, 7, 0);
+ eax7 = info7[0], ebx7 = info7[1], ecx7 = info7[2], edx7 = info7[3];
+#endif
+ // fmt: off
+ (void)eax1; (void)ebx1; (void)ecx1; (void)edx1;
+ (void)eax7; (void)ebx7; (void)ecx7; (void)edx7;
+ // fmt: on
+
+#define EBX_AVX2 (1 << 5)
+#define ECX_SSE3 (1 << 0)
+#define ECX_SSSE3 (1 << 9)
+#define ECX_SSE4_1 (1 << 19)
+#define ECX_SSE4_2 (1 << 20)
+#define ECX_AVX (1 << 28)
+#define EDX_SSE (1 << 25)
+#define EDX_SSE2 (1 << 26)
+#define EDX_CMOV (1 << 15)
+
+ bool avx = (ecx1 & ECX_AVX) != 0;
+ bool avx2 = (ebx7 & EBX_AVX2) != 0;
+
+ bool sse = (edx1 & EDX_SSE) != 0;
+ bool sse2 = (edx1 & EDX_SSE2) != 0;
+ bool cmov = (edx1 & EDX_CMOV) != 0;
+
+ bool sse3 = (ecx1 & ECX_SSE3) != 0;
+ bool sse41 = (ecx1 & ECX_SSE4_1) != 0;
+ bool sse42 = (ecx1 & ECX_SSE4_2) != 0;
+
+#undef EDX_CMOV
+#undef EDX_SSE2
+#undef EDX_SSE
+#undef ECX_AVX
+#undef ECX_SSE4_2
+#undef ECX_SSE4_1
+#undef ECX_SSSE3
+#undef ECX_SSE3
+#undef EBX_AVX2
+
+#if _Py_HACL_CAN_COMPILE_VEC128
+ // TODO(picnixz): use py_cpuid_features (gh-125022) to improve detection
+ state->can_run_simd128 = sse && sse2 && sse3 && sse41 && sse42 && cmov;
+#else
+ // fmt: off
+ (void)sse; (void)sse2; (void)sse3; (void)sse41; (void)sse42; (void)cmov;
+ // fmt: on
+ state->can_run_simd128 = false;
+#endif
+
+#if _Py_HACL_CAN_COMPILE_VEC256
+ // TODO(picnixz): use py_cpuid_features (gh-125022) to improve detection
+ state->can_run_simd256 = state->can_run_simd128 && avx && avx2;
+#else
+ // fmt: off
+ (void)avx; (void)avx2;
+ // fmt: on
+ state->can_run_simd256 = false;
+#endif
+}
static int
blake2_exec(PyObject *m)
{
- Blake2State* st = blake2_get_state(m);
-
- // This is called at module initialization-time, and so appears to be as
- // good a place as any to probe the CPU flags.
- detect_cpu_features(&st->flags);
+ Blake2State *st = blake2_get_state(m);
+ blake2module_init_cpu_features(st);
+
+#define ADD_INT(DICT, NAME, VALUE) \
+ do { \
+ PyObject *x = PyLong_FromLong(VALUE); \
+ if (x == NULL) { \
+ return -1; \
+ } \
+ int rc = PyDict_SetItemString(DICT, NAME, x); \
+ Py_DECREF(x); \
+ if (rc < 0) { \
+ return -1; \
+ } \
+ } while(0)
+
+#define ADD_INT_CONST(NAME, VALUE) \
+ do { \
+ if (PyModule_AddIntConstant(m, NAME, VALUE) < 0) { \
+ return -1; \
+ } \
+ } while (0)
ADD_INT_CONST("_GIL_MINSIZE", HASHLIB_GIL_MINSIZE);
@@ -237,7 +236,6 @@ blake2_exec(PyObject *m)
if (st->blake2b_type == NULL) {
return -1;
}
- /* BLAKE2b */
if (PyModule_AddType(m, st->blake2b_type) < 0) {
return -1;
}
@@ -257,9 +255,9 @@ blake2_exec(PyObject *m)
st->blake2s_type = (PyTypeObject *)PyType_FromModuleAndSpec(
m, &blake2s_type_spec, NULL);
- if (NULL == st->blake2s_type)
+ if (st->blake2s_type == NULL) {
return -1;
-
+ }
if (PyModule_AddType(m, st->blake2s_type) < 0) {
return -1;
}
@@ -275,12 +273,11 @@ blake2_exec(PyObject *m)
ADD_INT_CONST("BLAKE2S_MAX_KEY_SIZE", HACL_HASH_BLAKE2S_KEY_BYTES);
ADD_INT_CONST("BLAKE2S_MAX_DIGEST_SIZE", HACL_HASH_BLAKE2S_OUT_BYTES);
+#undef ADD_INT_CONST
+#undef ADD_INT
return 0;
}
-#undef ADD_INT
-#undef ADD_INT_CONST
-
static PyModuleDef_Slot _blake2_slots[] = {
{Py_mod_exec, blake2_exec},
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
@@ -320,65 +317,70 @@ PyInit__blake2(void)
// set.
typedef enum { Blake2s, Blake2b, Blake2s_128, Blake2b_256 } blake2_impl;
-static inline bool is_blake2b(blake2_impl impl) {
- return impl == Blake2b || impl == Blake2b_256;
+static inline bool
+is_blake2b(blake2_impl impl)
+{
+ return impl == Blake2b || impl == Blake2b_256;
}
-static inline bool is_blake2s(blake2_impl impl) {
- return !is_blake2b(impl);
+static inline bool
+is_blake2s(blake2_impl impl)
+{
+ return impl == Blake2s || impl == Blake2s_128;
}
-static inline blake2_impl type_to_impl(PyTypeObject *type) {
-#if defined(HACL_CAN_COMPILE_SIMD128) || defined(HACL_CAN_COMPILE_SIMD256)
- Blake2State* st = blake2_get_state_from_type(type);
+static inline blake2_impl
+type_to_impl(PyTypeObject *type)
+{
+#if defined(_Py_HACL_CAN_COMPILE_VEC128) || defined(_Py_HACL_CAN_COMPILE_VEC256)
+ Blake2State *st = blake2_get_state_from_type(type);
#endif
if (!strcmp(type->tp_name, blake2b_type_spec.name)) {
-#ifdef HACL_CAN_COMPILE_SIMD256
- if (has_simd256(&st->flags))
- return Blake2b_256;
- else
-#endif
+#if _Py_HACL_CAN_COMPILE_VEC256
+ return st->can_run_simd256 ? Blake2b_256 : Blake2b;
+#else
return Blake2b;
- } else if (!strcmp(type->tp_name, blake2s_type_spec.name)) {
-#ifdef HACL_CAN_COMPILE_SIMD128
- if (has_simd128(&st->flags))
- return Blake2s_128;
- else
#endif
+ }
+ else if (!strcmp(type->tp_name, blake2s_type_spec.name)) {
+#if _Py_HACL_CAN_COMPILE_VEC128
+ return st->can_run_simd128 ? Blake2s_128 : Blake2s;
+#else
return Blake2s;
- } else {
- Py_UNREACHABLE();
+#endif
}
+ Py_UNREACHABLE();
}
typedef struct {
- PyObject_HEAD
+ HASHLIB_OBJECT_HEAD
union {
Hacl_Hash_Blake2s_state_t *blake2s_state;
Hacl_Hash_Blake2b_state_t *blake2b_state;
-#ifdef HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
Hacl_Hash_Blake2s_Simd128_state_t *blake2s_128_state;
#endif
-#ifdef HACL_CAN_COMPILE_SIMD256
+#if _Py_HACL_CAN_COMPILE_VEC256
Hacl_Hash_Blake2b_Simd256_state_t *blake2b_256_state;
#endif
};
blake2_impl impl;
- bool use_mutex;
- PyMutex mutex;
} Blake2Object;
#define _Blake2Object_CAST(op) ((Blake2Object *)(op))
-#include "clinic/blake2module.c.h"
+// --- Module clinic configuration --------------------------------------------
/*[clinic input]
module _blake2
-class _blake2.blake2b "Blake2Object *" "&PyBlake2_BLAKE2bType"
-class _blake2.blake2s "Blake2Object *" "&PyBlake2_BLAKE2sType"
+class _blake2.blake2b "Blake2Object *" "&PyType_Type"
+class _blake2.blake2s "Blake2Object *" "&PyType_Type"
[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b7526666bd18af83]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=86b0972b0c41b3d0]*/
+
+#include "clinic/blake2module.c.h"
+// --- BLAKE-2 object interface -----------------------------------------------
static Blake2Object *
new_Blake2Object(PyTypeObject *type)
@@ -422,42 +424,127 @@ new_Blake2Object(PyTypeObject *type)
} while (0)
static void
-update(Blake2Object *self, uint8_t *buf, Py_ssize_t len)
+blake2_update_unlocked(Blake2Object *self, uint8_t *buf, Py_ssize_t len)
{
switch (self->impl) {
- // These need to be ifdef'd out otherwise it's an unresolved symbol at
- // link-time.
-#ifdef HACL_CAN_COMPILE_SIMD256
+ // blake2b_256_state and blake2s_128_state must be if'd since
+ // otherwise this results in an unresolved symbol at link-time.
+#if _Py_HACL_CAN_COMPILE_VEC256
case Blake2b_256:
- HACL_UPDATE(Hacl_Hash_Blake2b_Simd256_update,self->blake2b_256_state, buf, len);
+ HACL_UPDATE(Hacl_Hash_Blake2b_Simd256_update,
+ self->blake2b_256_state, buf, len);
return;
#endif
-#ifdef HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
case Blake2s_128:
- HACL_UPDATE(Hacl_Hash_Blake2s_Simd128_update,self->blake2s_128_state, buf, len);
+ HACL_UPDATE(Hacl_Hash_Blake2s_Simd128_update,
+ self->blake2s_128_state, buf, len);
return;
#endif
case Blake2b:
- HACL_UPDATE(Hacl_Hash_Blake2b_update,self->blake2b_state, buf, len);
+ HACL_UPDATE(Hacl_Hash_Blake2b_update,
+ self->blake2b_state, buf, len);
return;
case Blake2s:
- HACL_UPDATE(Hacl_Hash_Blake2s_update,self->blake2s_state, buf, len);
+ HACL_UPDATE(Hacl_Hash_Blake2s_update,
+ self->blake2s_state, buf, len);
return;
default:
Py_UNREACHABLE();
}
}
-static PyObject *
-py_blake2b_or_s_new(PyTypeObject *type, PyObject *data, int digest_size,
- Py_buffer *key, Py_buffer *salt, Py_buffer *person,
- int fanout, int depth, unsigned long leaf_size,
- unsigned long long node_offset, int node_depth,
- int inner_size, int last_node, int usedforsecurity)
+#define BLAKE2_IMPLNAME(SELF) \
+ (is_blake2b((SELF)->impl) ? "blake2b" : "blake2s")
+#define GET_BLAKE2_CONST(SELF, NAME) \
+ (is_blake2b((SELF)->impl) \
+ ? HACL_HASH_BLAKE2B_ ## NAME \
+ : HACL_HASH_BLAKE2S_ ## NAME)
+
+#define MAX_OUT_BYTES(SELF) GET_BLAKE2_CONST(SELF, OUT_BYTES)
+#define MAX_SALT_LENGTH(SELF) GET_BLAKE2_CONST(SELF, SALT_BYTES)
+#define MAX_KEY_BYTES(SELF) GET_BLAKE2_CONST(SELF, KEY_BYTES)
+#define MAX_PERSONAL_BYTES(SELF) GET_BLAKE2_CONST(SELF, PERSONAL_BYTES)
+static int
+py_blake2_validate_params(Blake2Object *self,
+ int digest_size,
+ Py_buffer *key, Py_buffer *salt, Py_buffer *person,
+ int fanout, int depth, unsigned long leaf_size,
+ unsigned long long node_offset, int node_depth,
+ int inner_size)
+{
+ /* Validate digest size. */
+ if (digest_size <= 0 || (unsigned int)digest_size > MAX_OUT_BYTES(self)) {
+ PyErr_Format(
+ PyExc_ValueError,
+ "digest_size for %s must be between 1 and %d bytes, got %d",
+ BLAKE2_IMPLNAME(self), MAX_OUT_BYTES(self), digest_size
+ );
+ goto error;
+ }
+
+#define CHECK_LENGTH(NAME, VALUE, MAX) \
+ do { \
+ if ((size_t)(VALUE) > (size_t)(MAX)) { \
+ PyErr_Format(PyExc_ValueError, \
+ "maximum %s length is %zu bytes, got %zd", \
+ (NAME), (size_t)(MAX), (Py_ssize_t)(VALUE)); \
+ goto error; \
+ } \
+ } while (0)
+ /* Validate key parameter. */
+ if (key->obj && key->len) {
+ CHECK_LENGTH("key", key->len, MAX_KEY_BYTES(self));
+ }
+ /* Validate salt parameter. */
+ if (salt->obj && salt->len) {
+ CHECK_LENGTH("salt", salt->len, MAX_SALT_LENGTH(self));
+ }
+ /* Validate personalization parameter. */
+ if (person->obj && person->len) {
+ CHECK_LENGTH("person", person->len, MAX_PERSONAL_BYTES(self));
+ }
+#undef CHECK_LENGTH
+#define CHECK_TREE(NAME, VALUE, MIN, MAX) \
+ do { \
+ if ((VALUE) < (MIN) || (size_t)(VALUE) > (size_t)(MAX)) { \
+ PyErr_Format(PyExc_ValueError, \
+ "'%s' must be between %zu and %zu", \
+ (NAME), (size_t)(MIN), (size_t)(MAX)); \
+ goto error; \
+ } \
+ } while (0)
+ /* Validate tree parameters. */
+ CHECK_TREE("fanout", fanout, 0, 255);
+ CHECK_TREE("depth", depth, 1, 255);
+ CHECK_TREE("node_depth", node_depth, 0, 255);
+ CHECK_TREE("inner_size", inner_size, 0, MAX_OUT_BYTES(self));
+#undef CHECK_TREE
+ if (leaf_size > 0xFFFFFFFFU) {
+ /* maximum: 2**32 - 1 */
+ PyErr_SetString(PyExc_OverflowError, "'leaf_size' is too large");
+ goto error;
+ }
+ if (is_blake2s(self->impl) && node_offset > 0xFFFFFFFFFFFFULL) {
+ /* maximum: 2**48 - 1 */
+ PyErr_SetString(PyExc_OverflowError, "'node_offset' is too large");
+ goto error;
+ }
+ return 0;
+error:
+ return -1;
+}
+
+
+static PyObject *
+py_blake2_new(PyTypeObject *type, PyObject *data, int digest_size,
+ Py_buffer *key, Py_buffer *salt, Py_buffer *person,
+ int fanout, int depth, unsigned long leaf_size,
+ unsigned long long node_offset, int node_depth,
+ int inner_size, int last_node, int usedforsecurity)
{
Blake2Object *self = NULL;
- Py_buffer buf;
self = new_Blake2Object(type);
if (self == NULL) {
@@ -468,12 +555,12 @@ py_blake2b_or_s_new(PyTypeObject *type, PyObject *data, int digest_size,
// Ensure that the states are NULL-initialized in case of an error.
// See: py_blake2_clear() for more details.
switch (self->impl) {
-#if HACL_CAN_COMPILE_SIMD256
+#if _Py_HACL_CAN_COMPILE_VEC256
case Blake2b_256:
self->blake2b_256_state = NULL;
break;
#endif
-#if HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
case Blake2s_128:
self->blake2s_128_state = NULL;
break;
@@ -487,96 +574,31 @@ py_blake2b_or_s_new(PyTypeObject *type, PyObject *data, int digest_size,
default:
Py_UNREACHABLE();
}
- // Using Blake2b because we statically know that these are greater than the
- // Blake2s sizes -- this avoids a VLA.
- uint8_t salt_[HACL_HASH_BLAKE2B_SALT_BYTES] = { 0 };
- uint8_t personal_[HACL_HASH_BLAKE2B_PERSONAL_BYTES] = { 0 };
- /* Validate digest size. */
- if (digest_size <= 0 ||
- (unsigned) digest_size > (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_OUT_BYTES : HACL_HASH_BLAKE2S_OUT_BYTES))
+ // Unlike the state types, the parameters share a single (client-friendly)
+ // structure.
+ if (py_blake2_validate_params(self,
+ digest_size,
+ key, salt, person,
+ fanout, depth, leaf_size,
+ node_offset, node_depth, inner_size) < 0)
{
- PyErr_Format(PyExc_ValueError,
- "digest_size for %s must be between 1 and %d bytes, here it is %d",
- is_blake2b(self->impl) ? "Blake2b" : "Blake2s",
- is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_OUT_BYTES : HACL_HASH_BLAKE2S_OUT_BYTES,
- digest_size);
- goto error;
- }
-
- /* Validate salt parameter. */
- if ((salt->obj != NULL) && salt->len) {
- if ((size_t)salt->len > (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_SALT_BYTES : HACL_HASH_BLAKE2S_SALT_BYTES)) {
- PyErr_Format(PyExc_ValueError,
- "maximum salt length is %d bytes",
- (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_SALT_BYTES : HACL_HASH_BLAKE2S_SALT_BYTES));
- goto error;
- }
- memcpy(salt_, salt->buf, salt->len);
- }
-
- /* Validate personalization parameter. */
- if ((person->obj != NULL) && person->len) {
- if ((size_t)person->len > (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_PERSONAL_BYTES : HACL_HASH_BLAKE2S_PERSONAL_BYTES)) {
- PyErr_Format(PyExc_ValueError,
- "maximum person length is %d bytes",
- (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_PERSONAL_BYTES : HACL_HASH_BLAKE2S_PERSONAL_BYTES));
- goto error;
- }
- memcpy(personal_, person->buf, person->len);
- }
-
- /* Validate tree parameters. */
- if (fanout < 0 || fanout > 255) {
- PyErr_SetString(PyExc_ValueError,
- "fanout must be between 0 and 255");
- goto error;
- }
-
- if (depth <= 0 || depth > 255) {
- PyErr_SetString(PyExc_ValueError,
- "depth must be between 1 and 255");
- goto error;
- }
-
- if (leaf_size > 0xFFFFFFFFU) {
- PyErr_SetString(PyExc_OverflowError, "leaf_size is too large");
- goto error;
- }
-
- if (is_blake2s(self->impl) && node_offset > 0xFFFFFFFFFFFFULL) {
- /* maximum 2**48 - 1 */
- PyErr_SetString(PyExc_OverflowError, "node_offset is too large");
- goto error;
- }
-
- if (node_depth < 0 || node_depth > 255) {
- PyErr_SetString(PyExc_ValueError,
- "node_depth must be between 0 and 255");
goto error;
}
- if (inner_size < 0 ||
- (unsigned) inner_size > (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_OUT_BYTES : HACL_HASH_BLAKE2S_OUT_BYTES)) {
- PyErr_Format(PyExc_ValueError,
- "inner_size must be between 0 and is %d",
- (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_OUT_BYTES : HACL_HASH_BLAKE2S_OUT_BYTES));
- goto error;
+ // Using Blake2b because we statically know that these are greater than the
+ // Blake2s sizes -- this avoids a VLA.
+ uint8_t salt_buffer[HACL_HASH_BLAKE2B_SALT_BYTES] = {0};
+ uint8_t personal_buffer[HACL_HASH_BLAKE2B_PERSONAL_BYTES] = {0};
+ if (salt->obj != NULL) {
+ assert(salt->buf != NULL);
+ memcpy(salt_buffer, salt->buf, salt->len);
}
-
- /* Set key length. */
- if ((key->obj != NULL) && key->len) {
- if ((size_t)key->len > (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_KEY_BYTES : HACL_HASH_BLAKE2S_KEY_BYTES)) {
- PyErr_Format(PyExc_ValueError,
- "maximum key length is %d bytes",
- (is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_KEY_BYTES : HACL_HASH_BLAKE2S_KEY_BYTES));
- goto error;
- }
+ if (person->obj != NULL) {
+ assert(person->buf != NULL);
+ memcpy(personal_buffer, person->buf, person->len);
}
- // Unlike the state types, the parameters share a single (client-friendly)
- // structure.
-
Hacl_Hash_Blake2b_blake2_params params = {
.digest_length = digest_size,
.key_length = (uint8_t)key->len,
@@ -586,63 +608,52 @@ py_blake2b_or_s_new(PyTypeObject *type, PyObject *data, int digest_size,
.node_offset = node_offset,
.node_depth = node_depth,
.inner_length = inner_size,
- .salt = salt_,
- .personal = personal_
+ .salt = salt_buffer,
+ .personal = personal_buffer
};
+#define BLAKE2_MALLOC(TYPE, STATE) \
+ do { \
+ STATE = Hacl_Hash_ ## TYPE ## _malloc_with_params_and_key( \
+ &params, last_node, key->buf); \
+ if (STATE == NULL) { \
+ (void)PyErr_NoMemory(); \
+ goto error; \
+ } \
+ } while (0)
+
switch (self->impl) {
-#if HACL_CAN_COMPILE_SIMD256
- case Blake2b_256: {
- self->blake2b_256_state = Hacl_Hash_Blake2b_Simd256_malloc_with_params_and_key(&params, last_node, key->buf);
- if (self->blake2b_256_state == NULL) {
- (void)PyErr_NoMemory();
- goto error;
- }
+#if _Py_HACL_CAN_COMPILE_VEC256
+ case Blake2b_256:
+ BLAKE2_MALLOC(Blake2b_Simd256, self->blake2b_256_state);
break;
- }
#endif
-#if HACL_CAN_COMPILE_SIMD128
- case Blake2s_128: {
- self->blake2s_128_state = Hacl_Hash_Blake2s_Simd128_malloc_with_params_and_key(&params, last_node, key->buf);
- if (self->blake2s_128_state == NULL) {
- (void)PyErr_NoMemory();
- goto error;
- }
+#if _Py_HACL_CAN_COMPILE_VEC128
+ case Blake2s_128:
+ BLAKE2_MALLOC(Blake2s_Simd128, self->blake2s_128_state);
break;
- }
#endif
- case Blake2b: {
- self->blake2b_state = Hacl_Hash_Blake2b_malloc_with_params_and_key(&params, last_node, key->buf);
- if (self->blake2b_state == NULL) {
- (void)PyErr_NoMemory();
- goto error;
- }
+ case Blake2b:
+ BLAKE2_MALLOC(Blake2b, self->blake2b_state);
break;
- }
- case Blake2s: {
- self->blake2s_state = Hacl_Hash_Blake2s_malloc_with_params_and_key(&params, last_node, key->buf);
- if (self->blake2s_state == NULL) {
- (void)PyErr_NoMemory();
- goto error;
- }
+ case Blake2s:
+ BLAKE2_MALLOC(Blake2s, self->blake2s_state);
break;
- }
default:
Py_UNREACHABLE();
}
+#undef BLAKE2_MALLOC
/* Process initial data if any. */
if (data != NULL) {
+ Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error);
-
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- Py_BEGIN_ALLOW_THREADS
- update(self, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- update(self, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ blake2_update_unlocked(self, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
}
@@ -655,8 +666,7 @@ error:
/*[clinic input]
@classmethod
_blake2.blake2b.__new__ as py_blake2b_new
- data: object(c_default="NULL") = b''
- /
+ data as data_obj: object(c_default="NULL") = b''
*
digest_size: int(c_default="HACL_HASH_BLAKE2B_OUT_BYTES") = _blake2.blake2b.MAX_DIGEST_SIZE
key: Py_buffer(c_default="NULL", py_default="b''") = None
@@ -670,26 +680,33 @@ _blake2.blake2b.__new__ as py_blake2b_new
inner_size: int = 0
last_node: bool = False
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Return a new BLAKE2b hash object.
[clinic start generated code]*/
static PyObject *
-py_blake2b_new_impl(PyTypeObject *type, PyObject *data, int digest_size,
+py_blake2b_new_impl(PyTypeObject *type, PyObject *data_obj, int digest_size,
Py_buffer *key, Py_buffer *salt, Py_buffer *person,
int fanout, int depth, unsigned long leaf_size,
unsigned long long node_offset, int node_depth,
- int inner_size, int last_node, int usedforsecurity)
-/*[clinic end generated code: output=32bfd8f043c6896f input=8fee2b7b11428b2d]*/
+ int inner_size, int last_node, int usedforsecurity,
+ PyObject *string)
+/*[clinic end generated code: output=de64bd850606b6a0 input=78cf60a2922d2f90]*/
{
- return py_blake2b_or_s_new(type, data, digest_size, key, salt, person, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node, usedforsecurity);
+ PyObject *data;
+ if (_Py_hashlib_data_argument(&data, data_obj, string) < 0) {
+ return NULL;
+ }
+ return py_blake2_new(type, data, digest_size, key, salt, person,
+ fanout, depth, leaf_size, node_offset, node_depth,
+ inner_size, last_node, usedforsecurity);
}
/*[clinic input]
@classmethod
_blake2.blake2s.__new__ as py_blake2s_new
- data: object(c_default="NULL") = b''
- /
+ data as data_obj: object(c_default="NULL") = b''
*
digest_size: int(c_default="HACL_HASH_BLAKE2S_OUT_BYTES") = _blake2.blake2s.MAX_DIGEST_SIZE
key: Py_buffer(c_default="NULL", py_default="b''") = None
@@ -703,61 +720,62 @@ _blake2.blake2s.__new__ as py_blake2s_new
inner_size: int = 0
last_node: bool = False
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Return a new BLAKE2s hash object.
[clinic start generated code]*/
static PyObject *
-py_blake2s_new_impl(PyTypeObject *type, PyObject *data, int digest_size,
+py_blake2s_new_impl(PyTypeObject *type, PyObject *data_obj, int digest_size,
Py_buffer *key, Py_buffer *salt, Py_buffer *person,
int fanout, int depth, unsigned long leaf_size,
unsigned long long node_offset, int node_depth,
- int inner_size, int last_node, int usedforsecurity)
-/*[clinic end generated code: output=556181f73905c686 input=8165a11980eac7f3]*/
+ int inner_size, int last_node, int usedforsecurity,
+ PyObject *string)
+/*[clinic end generated code: output=582a0c4295cc3a3c input=6843d6332eefd295]*/
{
- return py_blake2b_or_s_new(type, data, digest_size, key, salt, person, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node, usedforsecurity);
+ PyObject *data;
+ if (_Py_hashlib_data_argument(&data, data_obj, string) < 0) {
+ return NULL;
+ }
+ return py_blake2_new(type, data, digest_size, key, salt, person,
+ fanout, depth, leaf_size, node_offset, node_depth,
+ inner_size, last_node, usedforsecurity);
}
static int
-blake2_blake2b_copy_locked(Blake2Object *self, Blake2Object *cpy)
+blake2_blake2b_copy_unlocked(Blake2Object *self, Blake2Object *cpy)
{
assert(cpy != NULL);
+#define BLAKE2_COPY(TYPE, STATE_ATTR) \
+ do { \
+ cpy->STATE_ATTR = Hacl_Hash_ ## TYPE ## _copy(self->STATE_ATTR); \
+ if (cpy->STATE_ATTR == NULL) { \
+ goto error; \
+ } \
+ } while (0)
+
switch (self->impl) {
-#if HACL_CAN_COMPILE_SIMD256
- case Blake2b_256: {
- cpy->blake2b_256_state = Hacl_Hash_Blake2b_Simd256_copy(self->blake2b_256_state);
- if (cpy->blake2b_256_state == NULL) {
- goto error;
- }
+#if _Py_HACL_CAN_COMPILE_VEC256
+ case Blake2b_256:
+ BLAKE2_COPY(Blake2b_Simd256, blake2b_256_state);
break;
- }
#endif
-#if HACL_CAN_COMPILE_SIMD128
- case Blake2s_128: {
- cpy->blake2s_128_state = Hacl_Hash_Blake2s_Simd128_copy(self->blake2s_128_state);
- if (cpy->blake2s_128_state == NULL) {
- goto error;
- }
+#if _Py_HACL_CAN_COMPILE_VEC128
+ case Blake2s_128:
+ BLAKE2_COPY(Blake2s_Simd128, blake2s_128_state);
break;
- }
#endif
- case Blake2b: {
- cpy->blake2b_state = Hacl_Hash_Blake2b_copy(self->blake2b_state);
- if (cpy->blake2b_state == NULL) {
- goto error;
- }
+ case Blake2b:
+ BLAKE2_COPY(Blake2b, blake2b_state);
break;
- }
- case Blake2s: {
- cpy->blake2s_state = Hacl_Hash_Blake2s_copy(self->blake2s_state);
- if (cpy->blake2s_state == NULL) {
- goto error;
- }
+ case Blake2s:
+ BLAKE2_COPY(Blake2s, blake2s_state);
break;
- }
default:
Py_UNREACHABLE();
}
+#undef BLAKE2_COPY
cpy->impl = self->impl;
return 0;
@@ -769,23 +787,25 @@ error:
/*[clinic input]
_blake2.blake2b.copy
+ cls: defining_class
+
Return a copy of the hash object.
[clinic start generated code]*/
static PyObject *
-_blake2_blake2b_copy_impl(Blake2Object *self)
-/*[clinic end generated code: output=622d1c56b91c50d8 input=e383c2d199fd8a2e]*/
+_blake2_blake2b_copy_impl(Blake2Object *self, PyTypeObject *cls)
+/*[clinic end generated code: output=5f8ea31c56c52287 input=f38f3475e9aec98d]*/
{
int rc;
Blake2Object *cpy;
- if ((cpy = new_Blake2Object(Py_TYPE(self))) == NULL) {
+ if ((cpy = new_Blake2Object(cls)) == NULL) {
return NULL;
}
- ENTER_HASHLIB(self);
- rc = blake2_blake2b_copy_locked(self, cpy);
- LEAVE_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
+ rc = blake2_blake2b_copy_unlocked(self, cpy);
+ HASHLIB_RELEASE_LOCK(self);
if (rc < 0) {
Py_DECREF(cpy);
return NULL;
@@ -807,62 +827,52 @@ _blake2_blake2b_update_impl(Blake2Object *self, PyObject *data)
/*[clinic end generated code: output=99330230068e8c99 input=ffc4aa6a6a225d31]*/
{
Py_buffer buf;
-
GET_BUFFER_VIEW_OR_ERROUT(data, &buf);
-
- if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- update(self, buf.buf, buf.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- update(self, buf.buf, buf.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, buf.len,
+ blake2_update_unlocked(self, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
-
Py_RETURN_NONE;
}
-/*[clinic input]
-_blake2.blake2b.digest
-
-Return the digest value as a bytes object.
-[clinic start generated code]*/
-
-static PyObject *
-_blake2_blake2b_digest_impl(Blake2Object *self)
-/*[clinic end generated code: output=31ab8ad477f4a2f7 input=7d21659e9c5fff02]*/
+static uint8_t
+blake2_blake2b_compute_digest(Blake2Object *self, uint8_t *digest)
{
- uint8_t digest[HACL_HASH_BLAKE2B_OUT_BYTES];
-
- ENTER_HASHLIB(self);
- uint8_t digest_length = 0;
switch (self->impl) {
-#if HACL_CAN_COMPILE_SIMD256
+#if _Py_HACL_CAN_COMPILE_VEC256
case Blake2b_256:
- digest_length = Hacl_Hash_Blake2b_Simd256_digest(self->blake2b_256_state, digest);
- break;
+ return Hacl_Hash_Blake2b_Simd256_digest(
+ self->blake2b_256_state, digest);
#endif
-#if HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
case Blake2s_128:
- digest_length = Hacl_Hash_Blake2s_Simd128_digest(self->blake2s_128_state, digest);
- break;
+ return Hacl_Hash_Blake2s_Simd128_digest(
+ self->blake2s_128_state, digest);
#endif
case Blake2b:
- digest_length = Hacl_Hash_Blake2b_digest(self->blake2b_state, digest);
- break;
+ return Hacl_Hash_Blake2b_digest(self->blake2b_state, digest);
case Blake2s:
- digest_length = Hacl_Hash_Blake2s_digest(self->blake2s_state, digest);
- break;
+ return Hacl_Hash_Blake2s_digest(self->blake2s_state, digest);
default:
Py_UNREACHABLE();
}
- LEAVE_HASHLIB(self);
+}
+
+/*[clinic input]
+_blake2.blake2b.digest
+
+Return the digest value as a bytes object.
+[clinic start generated code]*/
+
+static PyObject *
+_blake2_blake2b_digest_impl(Blake2Object *self)
+/*[clinic end generated code: output=31ab8ad477f4a2f7 input=7d21659e9c5fff02]*/
+{
+ uint8_t digest_length = 0, digest[HACL_HASH_BLAKE2B_OUT_BYTES];
+ HASHLIB_ACQUIRE_LOCK(self);
+ digest_length = blake2_blake2b_compute_digest(self, digest);
+ HASHLIB_RELEASE_LOCK(self);
return PyBytes_FromStringAndSize((const char *)digest, digest_length);
}
@@ -876,31 +886,10 @@ static PyObject *
_blake2_blake2b_hexdigest_impl(Blake2Object *self)
/*[clinic end generated code: output=5ef54b138db6610a input=76930f6946351f56]*/
{
- uint8_t digest[HACL_HASH_BLAKE2B_OUT_BYTES];
-
- ENTER_HASHLIB(self);
- uint8_t digest_length = 0;
- switch (self->impl) {
-#if HACL_CAN_COMPILE_SIMD256
- case Blake2b_256:
- digest_length = Hacl_Hash_Blake2b_Simd256_digest(self->blake2b_256_state, digest);
- break;
-#endif
-#if HACL_CAN_COMPILE_SIMD128
- case Blake2s_128:
- digest_length = Hacl_Hash_Blake2s_Simd128_digest(self->blake2s_128_state, digest);
- break;
-#endif
- case Blake2b:
- digest_length = Hacl_Hash_Blake2b_digest(self->blake2b_state, digest);
- break;
- case Blake2s:
- digest_length = Hacl_Hash_Blake2s_digest(self->blake2s_state, digest);
- break;
- default:
- Py_UNREACHABLE();
- }
- LEAVE_HASHLIB(self);
+ uint8_t digest_length = 0, digest[HACL_HASH_BLAKE2B_OUT_BYTES];
+ HASHLIB_ACQUIRE_LOCK(self);
+ digest_length = blake2_blake2b_compute_digest(self, digest);
+ HASHLIB_RELEASE_LOCK(self);
return _Py_strhex((const char *)digest, digest_length);
}
@@ -918,43 +907,49 @@ static PyObject *
py_blake2b_get_name(PyObject *op, void *Py_UNUSED(closure))
{
Blake2Object *self = _Blake2Object_CAST(op);
- return PyUnicode_FromString(is_blake2b(self->impl) ? "blake2b" : "blake2s");
+ return PyUnicode_FromString(BLAKE2_IMPLNAME(self));
}
-
static PyObject *
py_blake2b_get_block_size(PyObject *op, void *Py_UNUSED(closure))
{
Blake2Object *self = _Blake2Object_CAST(op);
- return PyLong_FromLong(is_blake2b(self->impl) ? HACL_HASH_BLAKE2B_BLOCK_BYTES : HACL_HASH_BLAKE2S_BLOCK_BYTES);
+ return PyLong_FromLong(GET_BLAKE2_CONST(self, BLOCK_BYTES));
}
-
-static PyObject *
-py_blake2b_get_digest_size(PyObject *op, void *Py_UNUSED(closure))
+static Hacl_Hash_Blake2b_index
+hacl_get_blake2_info(Blake2Object *self)
{
- Blake2Object *self = _Blake2Object_CAST(op);
switch (self->impl) {
-#if HACL_CAN_COMPILE_SIMD256
+#if _Py_HACL_CAN_COMPILE_VEC256
case Blake2b_256:
- return PyLong_FromLong(Hacl_Hash_Blake2b_Simd256_info(self->blake2b_256_state).digest_length);
+ return Hacl_Hash_Blake2b_Simd256_info(self->blake2b_256_state);
#endif
-#if HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
case Blake2s_128:
- return PyLong_FromLong(Hacl_Hash_Blake2s_Simd128_info(self->blake2s_128_state).digest_length);
+ return Hacl_Hash_Blake2s_Simd128_info(self->blake2s_128_state);
#endif
case Blake2b:
- return PyLong_FromLong(Hacl_Hash_Blake2b_info(self->blake2b_state).digest_length);
+ return Hacl_Hash_Blake2b_info(self->blake2b_state);
case Blake2s:
- return PyLong_FromLong(Hacl_Hash_Blake2s_info(self->blake2s_state).digest_length);
+ return Hacl_Hash_Blake2s_info(self->blake2s_state);
default:
Py_UNREACHABLE();
}
}
+static PyObject *
+py_blake2b_get_digest_size(PyObject *op, void *Py_UNUSED(closure))
+{
+ Blake2Object *self = _Blake2Object_CAST(op);
+ Hacl_Hash_Blake2b_index info = hacl_get_blake2_info(self);
+ return PyLong_FromLong(info.digest_length);
+}
+
+
static PyGetSetDef py_blake2b_getsetters[] = {
{"name", py_blake2b_get_name, NULL, NULL, NULL},
{"block_size", py_blake2b_get_block_size, NULL, NULL, NULL},
@@ -971,38 +966,35 @@ py_blake2_clear(PyObject *op)
// initializes the HACL* internal state to NULL before allocating
// it. If an error occurs in the constructor, we should only free
// states that were allocated (i.e. that are not NULL).
+#define BLAKE2_FREE(TYPE, STATE) \
+ do { \
+ if (STATE != NULL) { \
+ Hacl_Hash_ ## TYPE ## _free(STATE); \
+ STATE = NULL; \
+ } \
+ } while (0)
+
switch (self->impl) {
-#if HACL_CAN_COMPILE_SIMD256
+#if _Py_HACL_CAN_COMPILE_VEC256
case Blake2b_256:
- if (self->blake2b_256_state != NULL) {
- Hacl_Hash_Blake2b_Simd256_free(self->blake2b_256_state);
- self->blake2b_256_state = NULL;
- }
+ BLAKE2_FREE(Blake2b_Simd256, self->blake2b_256_state);
break;
#endif
-#if HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
case Blake2s_128:
- if (self->blake2s_128_state != NULL) {
- Hacl_Hash_Blake2s_Simd128_free(self->blake2s_128_state);
- self->blake2s_128_state = NULL;
- }
+ BLAKE2_FREE(Blake2s_Simd128, self->blake2s_128_state);
break;
#endif
case Blake2b:
- if (self->blake2b_state != NULL) {
- Hacl_Hash_Blake2b_free(self->blake2b_state);
- self->blake2b_state = NULL;
- }
+ BLAKE2_FREE(Blake2b, self->blake2b_state);
break;
case Blake2s:
- if (self->blake2s_state != NULL) {
- Hacl_Hash_Blake2s_free(self->blake2s_state);
- self->blake2s_state = NULL;
- }
+ BLAKE2_FREE(Blake2s, self->blake2s_state);
break;
default:
Py_UNREACHABLE();
}
+#undef BLAKE2_FREE
return 0;
}
@@ -1031,7 +1023,7 @@ static PyType_Slot blake2b_type_slots[] = {
{Py_tp_methods, py_blake2b_methods},
{Py_tp_getset, py_blake2b_getsetters},
{Py_tp_new, py_blake2b_new},
- {0,0}
+ {0, 0}
};
static PyType_Slot blake2s_type_slots[] = {
@@ -1044,12 +1036,12 @@ static PyType_Slot blake2s_type_slots[] = {
// only the constructor differs, so that it can receive a clinic-generated
// default digest length suitable for blake2s
{Py_tp_new, py_blake2s_new},
- {0,0}
+ {0, 0}
};
static PyType_Spec blake2b_type_spec = {
.name = "_blake2.blake2b",
- .basicsize = sizeof(Blake2Object),
+ .basicsize = sizeof(Blake2Object),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HEAPTYPE,
.slots = blake2b_type_slots
@@ -1057,7 +1049,7 @@ static PyType_Spec blake2b_type_spec = {
static PyType_Spec blake2s_type_spec = {
.name = "_blake2.blake2s",
- .basicsize = sizeof(Blake2Object),
+ .basicsize = sizeof(Blake2Object),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HEAPTYPE,
.slots = blake2s_type_slots
diff --git a/Modules/clinic/_curses_panel.c.h b/Modules/clinic/_curses_panel.c.h
index 6f4966825ec..75cf067c8aa 100644
--- a/Modules/clinic/_curses_panel.c.h
+++ b/Modules/clinic/_curses_panel.c.h
@@ -2,10 +2,7 @@
preserve
[clinic start generated code]*/
-#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
-# include "pycore_runtime.h" // _Py_SINGLETON()
-#endif
-#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_curses_panel_panel_bottom__doc__,
"bottom($self, /)\n"
@@ -14,19 +11,15 @@ PyDoc_STRVAR(_curses_panel_panel_bottom__doc__,
"Push the panel to the bottom of the stack.");
#define _CURSES_PANEL_PANEL_BOTTOM_METHODDEF \
- {"bottom", _PyCFunction_CAST(_curses_panel_panel_bottom), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_bottom__doc__},
+ {"bottom", (PyCFunction)_curses_panel_panel_bottom, METH_NOARGS, _curses_panel_panel_bottom__doc__},
static PyObject *
-_curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls);
+_curses_panel_panel_bottom_impl(PyCursesPanelObject *self);
static PyObject *
-_curses_panel_panel_bottom(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_bottom(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
- PyErr_SetString(PyExc_TypeError, "bottom() takes no arguments");
- return NULL;
- }
- return _curses_panel_panel_bottom_impl((PyCursesPanelObject *)self, cls);
+ return _curses_panel_panel_bottom_impl((PyCursesPanelObject *)self);
}
PyDoc_STRVAR(_curses_panel_panel_hide__doc__,
@@ -38,19 +31,15 @@ PyDoc_STRVAR(_curses_panel_panel_hide__doc__,
"This does not delete the object, it just makes the window on screen invisible.");
#define _CURSES_PANEL_PANEL_HIDE_METHODDEF \
- {"hide", _PyCFunction_CAST(_curses_panel_panel_hide), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_hide__doc__},
+ {"hide", (PyCFunction)_curses_panel_panel_hide, METH_NOARGS, _curses_panel_panel_hide__doc__},
static PyObject *
-_curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls);
+_curses_panel_panel_hide_impl(PyCursesPanelObject *self);
static PyObject *
-_curses_panel_panel_hide(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_hide(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
- PyErr_SetString(PyExc_TypeError, "hide() takes no arguments");
- return NULL;
- }
- return _curses_panel_panel_hide_impl((PyCursesPanelObject *)self, cls);
+ return _curses_panel_panel_hide_impl((PyCursesPanelObject *)self);
}
PyDoc_STRVAR(_curses_panel_panel_show__doc__,
@@ -60,19 +49,15 @@ PyDoc_STRVAR(_curses_panel_panel_show__doc__,
"Display the panel (which might have been hidden).");
#define _CURSES_PANEL_PANEL_SHOW_METHODDEF \
- {"show", _PyCFunction_CAST(_curses_panel_panel_show), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_show__doc__},
+ {"show", (PyCFunction)_curses_panel_panel_show, METH_NOARGS, _curses_panel_panel_show__doc__},
static PyObject *
-_curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls);
+_curses_panel_panel_show_impl(PyCursesPanelObject *self);
static PyObject *
-_curses_panel_panel_show(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_show(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
- PyErr_SetString(PyExc_TypeError, "show() takes no arguments");
- return NULL;
- }
- return _curses_panel_panel_show_impl((PyCursesPanelObject *)self, cls);
+ return _curses_panel_panel_show_impl((PyCursesPanelObject *)self);
}
PyDoc_STRVAR(_curses_panel_panel_top__doc__,
@@ -82,19 +67,15 @@ PyDoc_STRVAR(_curses_panel_panel_top__doc__,
"Push panel to the top of the stack.");
#define _CURSES_PANEL_PANEL_TOP_METHODDEF \
- {"top", _PyCFunction_CAST(_curses_panel_panel_top), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_top__doc__},
+ {"top", (PyCFunction)_curses_panel_panel_top, METH_NOARGS, _curses_panel_panel_top__doc__},
static PyObject *
-_curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls);
+_curses_panel_panel_top_impl(PyCursesPanelObject *self);
static PyObject *
-_curses_panel_panel_top(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_top(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
- PyErr_SetString(PyExc_TypeError, "top() takes no arguments");
- return NULL;
- }
- return _curses_panel_panel_top_impl((PyCursesPanelObject *)self, cls);
+ return _curses_panel_panel_top_impl((PyCursesPanelObject *)self);
}
PyDoc_STRVAR(_curses_panel_panel_above__doc__,
@@ -158,36 +139,19 @@ PyDoc_STRVAR(_curses_panel_panel_move__doc__,
"Move the panel to the screen coordinates (y, x).");
#define _CURSES_PANEL_PANEL_MOVE_METHODDEF \
- {"move", _PyCFunction_CAST(_curses_panel_panel_move), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_move__doc__},
+ {"move", _PyCFunction_CAST(_curses_panel_panel_move), METH_FASTCALL, _curses_panel_panel_move__doc__},
static PyObject *
-_curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls,
- int y, int x);
+_curses_panel_panel_move_impl(PyCursesPanelObject *self, int y, int x);
static PyObject *
-_curses_panel_panel_move(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_move(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
- #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
- #else
- # define KWTUPLE NULL
- #endif
-
- static const char * const _keywords[] = {"", "", NULL};
- static _PyArg_Parser _parser = {
- .keywords = _keywords,
- .fname = "move",
- .kwtuple = KWTUPLE,
- };
- #undef KWTUPLE
- PyObject *argsbuf[2];
int y;
int x;
- args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
- /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
- if (!args) {
+ if (!_PyArg_CheckPositional("move", nargs, 2, 2)) {
goto exit;
}
y = PyLong_AsInt(args[0]);
@@ -198,7 +162,7 @@ _curses_panel_panel_move(PyObject *self, PyTypeObject *cls, PyObject *const *arg
if (x == -1 && PyErr_Occurred()) {
goto exit;
}
- return_value = _curses_panel_panel_move_impl((PyCursesPanelObject *)self, cls, y, x);
+ return_value = _curses_panel_panel_move_impl((PyCursesPanelObject *)self, y, x);
exit:
return return_value;
@@ -229,44 +193,24 @@ PyDoc_STRVAR(_curses_panel_panel_replace__doc__,
"Change the window associated with the panel to the window win.");
#define _CURSES_PANEL_PANEL_REPLACE_METHODDEF \
- {"replace", _PyCFunction_CAST(_curses_panel_panel_replace), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_replace__doc__},
+ {"replace", (PyCFunction)_curses_panel_panel_replace, METH_O, _curses_panel_panel_replace__doc__},
static PyObject *
_curses_panel_panel_replace_impl(PyCursesPanelObject *self,
- PyTypeObject *cls,
PyCursesWindowObject *win);
static PyObject *
-_curses_panel_panel_replace(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_replace(PyObject *self, PyObject *arg)
{
PyObject *return_value = NULL;
- #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
- #else
- # define KWTUPLE NULL
- #endif
-
- static const char * const _keywords[] = {"", NULL};
- static _PyArg_Parser _parser = {
- .keywords = _keywords,
- .fname = "replace",
- .kwtuple = KWTUPLE,
- };
- #undef KWTUPLE
- PyObject *argsbuf[1];
PyCursesWindowObject *win;
- args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
- /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
- if (!args) {
- goto exit;
- }
- if (!PyObject_TypeCheck(args[0], &PyCursesWindow_Type)) {
- _PyArg_BadArgument("replace", "argument 1", (&PyCursesWindow_Type)->tp_name, args[0]);
+ if (!PyObject_TypeCheck(arg, &PyCursesWindow_Type)) {
+ _PyArg_BadArgument("replace", "argument", (&PyCursesWindow_Type)->tp_name, arg);
goto exit;
}
- win = (PyCursesWindowObject *)args[0];
- return_value = _curses_panel_panel_replace_impl((PyCursesPanelObject *)self, cls, win);
+ win = (PyCursesWindowObject *)arg;
+ return_value = _curses_panel_panel_replace_impl((PyCursesPanelObject *)self, win);
exit:
return return_value;
@@ -279,41 +223,19 @@ PyDoc_STRVAR(_curses_panel_panel_set_userptr__doc__,
"Set the panel\'s user pointer to obj.");
#define _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF \
- {"set_userptr", _PyCFunction_CAST(_curses_panel_panel_set_userptr), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_set_userptr__doc__},
+ {"set_userptr", (PyCFunction)_curses_panel_panel_set_userptr, METH_O, _curses_panel_panel_set_userptr__doc__},
static PyObject *
_curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self,
- PyTypeObject *cls, PyObject *obj);
+ PyObject *obj);
static PyObject *
-_curses_panel_panel_set_userptr(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_set_userptr(PyObject *self, PyObject *obj)
{
PyObject *return_value = NULL;
- #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- # define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
- #else
- # define KWTUPLE NULL
- #endif
-
- static const char * const _keywords[] = {"", NULL};
- static _PyArg_Parser _parser = {
- .keywords = _keywords,
- .fname = "set_userptr",
- .kwtuple = KWTUPLE,
- };
- #undef KWTUPLE
- PyObject *argsbuf[1];
- PyObject *obj;
-
- args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
- /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
- if (!args) {
- goto exit;
- }
- obj = args[0];
- return_value = _curses_panel_panel_set_userptr_impl((PyCursesPanelObject *)self, cls, obj);
-exit:
+ return_value = _curses_panel_panel_set_userptr_impl((PyCursesPanelObject *)self, obj);
+
return return_value;
}
@@ -324,20 +246,15 @@ PyDoc_STRVAR(_curses_panel_panel_userptr__doc__,
"Return the user pointer for the panel.");
#define _CURSES_PANEL_PANEL_USERPTR_METHODDEF \
- {"userptr", _PyCFunction_CAST(_curses_panel_panel_userptr), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_userptr__doc__},
+ {"userptr", (PyCFunction)_curses_panel_panel_userptr, METH_NOARGS, _curses_panel_panel_userptr__doc__},
static PyObject *
-_curses_panel_panel_userptr_impl(PyCursesPanelObject *self,
- PyTypeObject *cls);
+_curses_panel_panel_userptr_impl(PyCursesPanelObject *self);
static PyObject *
-_curses_panel_panel_userptr(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_curses_panel_panel_userptr(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
- PyErr_SetString(PyExc_TypeError, "userptr() takes no arguments");
- return NULL;
- }
- return _curses_panel_panel_userptr_impl((PyCursesPanelObject *)self, cls);
+ return _curses_panel_panel_userptr_impl((PyCursesPanelObject *)self);
}
PyDoc_STRVAR(_curses_panel_bottom_panel__doc__,
@@ -424,4 +341,4 @@ _curses_panel_update_panels(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return _curses_panel_update_panels_impl(module);
}
-/*[clinic end generated code: output=36853ecb4a979814 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=db2fe491582784aa input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h
index 3a1c1698b1b..49c864318c8 100644
--- a/Modules/clinic/_cursesmodule.c.h
+++ b/Modules/clinic/_cursesmodule.c.h
@@ -733,23 +733,13 @@ PyDoc_STRVAR(_curses_window_getbkgd__doc__,
#define _CURSES_WINDOW_GETBKGD_METHODDEF \
{"getbkgd", (PyCFunction)_curses_window_getbkgd, METH_NOARGS, _curses_window_getbkgd__doc__},
-static long
+static PyObject *
_curses_window_getbkgd_impl(PyCursesWindowObject *self);
static PyObject *
_curses_window_getbkgd(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- PyObject *return_value = NULL;
- long _return_value;
-
- _return_value = _curses_window_getbkgd_impl((PyCursesWindowObject *)self);
- if ((_return_value == -1) && PyErr_Occurred()) {
- goto exit;
- }
- return_value = PyLong_FromLong(_return_value);
-
-exit:
- return return_value;
+ return _curses_window_getbkgd_impl((PyCursesWindowObject *)self);
}
PyDoc_STRVAR(_curses_window_getch__doc__,
@@ -768,7 +758,7 @@ PyDoc_STRVAR(_curses_window_getch__doc__,
#define _CURSES_WINDOW_GETCH_METHODDEF \
{"getch", (PyCFunction)_curses_window_getch, METH_VARARGS, _curses_window_getch__doc__},
-static int
+static PyObject *
_curses_window_getch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x);
@@ -779,7 +769,6 @@ _curses_window_getch(PyObject *self, PyObject *args)
int group_right_1 = 0;
int y = 0;
int x = 0;
- int _return_value;
switch (PyTuple_GET_SIZE(args)) {
case 0:
@@ -794,11 +783,7 @@ _curses_window_getch(PyObject *self, PyObject *args)
PyErr_SetString(PyExc_TypeError, "_curses.window.getch requires 0 to 2 arguments");
goto exit;
}
- _return_value = _curses_window_getch_impl((PyCursesWindowObject *)self, group_right_1, y, x);
- if ((_return_value == -1) && PyErr_Occurred()) {
- goto exit;
- }
- return_value = PyLong_FromLong((long)_return_value);
+ return_value = _curses_window_getch_impl((PyCursesWindowObject *)self, group_right_1, y, x);
exit:
return return_value;
@@ -1055,7 +1040,7 @@ PyDoc_STRVAR(_curses_window_inch__doc__,
#define _CURSES_WINDOW_INCH_METHODDEF \
{"inch", (PyCFunction)_curses_window_inch, METH_VARARGS, _curses_window_inch__doc__},
-static unsigned long
+static PyObject *
_curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x);
@@ -1066,7 +1051,6 @@ _curses_window_inch(PyObject *self, PyObject *args)
int group_right_1 = 0;
int y = 0;
int x = 0;
- unsigned long _return_value;
switch (PyTuple_GET_SIZE(args)) {
case 0:
@@ -1081,11 +1065,7 @@ _curses_window_inch(PyObject *self, PyObject *args)
PyErr_SetString(PyExc_TypeError, "_curses.window.inch requires 0 to 2 arguments");
goto exit;
}
- _return_value = _curses_window_inch_impl((PyCursesWindowObject *)self, group_right_1, y, x);
- if ((_return_value == (unsigned long)-1) && PyErr_Occurred()) {
- goto exit;
- }
- return_value = PyLong_FromUnsignedLong(_return_value);
+ return_value = _curses_window_inch_impl((PyCursesWindowObject *)self, group_right_1, y, x);
exit:
return return_value;
@@ -4263,10 +4243,7 @@ PyDoc_STRVAR(_curses_use_default_colors__doc__,
"use_default_colors($module, /)\n"
"--\n"
"\n"
-"Allow use of default values for colors on terminals supporting this feature.\n"
-"\n"
-"Use this to support transparency in your application. The default color\n"
-"is assigned to the color number -1.");
+"Equivalent to assume_default_colors(-1, -1).");
#define _CURSES_USE_DEFAULT_COLORS_METHODDEF \
{"use_default_colors", (PyCFunction)_curses_use_default_colors, METH_NOARGS, _curses_use_default_colors__doc__},
@@ -4282,6 +4259,51 @@ _curses_use_default_colors(PyObject *module, PyObject *Py_UNUSED(ignored))
#endif /* !defined(STRICT_SYSV_CURSES) */
+#if !defined(STRICT_SYSV_CURSES)
+
+PyDoc_STRVAR(_curses_assume_default_colors__doc__,
+"assume_default_colors($module, fg, bg, /)\n"
+"--\n"
+"\n"
+"Allow use of default values for colors on terminals supporting this feature.\n"
+"\n"
+"Assign terminal default foreground/background colors to color number -1.\n"
+"Change the definition of the color-pair 0 to (fg, bg).\n"
+"\n"
+"Use this to support transparency in your application.");
+
+#define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF \
+ {"assume_default_colors", _PyCFunction_CAST(_curses_assume_default_colors), METH_FASTCALL, _curses_assume_default_colors__doc__},
+
+static PyObject *
+_curses_assume_default_colors_impl(PyObject *module, int fg, int bg);
+
+static PyObject *
+_curses_assume_default_colors(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ int fg;
+ int bg;
+
+ if (!_PyArg_CheckPositional("assume_default_colors", nargs, 2, 2)) {
+ goto exit;
+ }
+ fg = PyLong_AsInt(args[0]);
+ if (fg == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ bg = PyLong_AsInt(args[1]);
+ if (bg == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = _curses_assume_default_colors_impl(module, fg, bg);
+
+exit:
+ return return_value;
+}
+
+#endif /* !defined(STRICT_SYSV_CURSES) */
+
PyDoc_STRVAR(_curses_has_extended_color_support__doc__,
"has_extended_color_support($module, /)\n"
"--\n"
@@ -4394,4 +4416,8 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
#ifndef _CURSES_USE_DEFAULT_COLORS_METHODDEF
#define _CURSES_USE_DEFAULT_COLORS_METHODDEF
#endif /* !defined(_CURSES_USE_DEFAULT_COLORS_METHODDEF) */
-/*[clinic end generated code: output=dbbbe86a4171799a input=a9049054013a1b77]*/
+
+#ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
+ #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
+#endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */
+/*[clinic end generated code: output=a083473003179b30 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_dbmmodule.c.h b/Modules/clinic/_dbmmodule.c.h
index 5e503194408..091ce9edc43 100644
--- a/Modules/clinic/_dbmmodule.c.h
+++ b/Modules/clinic/_dbmmodule.c.h
@@ -5,6 +5,7 @@ preserve
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
# include "pycore_runtime.h" // _Py_SINGLETON()
#endif
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(_dbm_dbm_close__doc__,
@@ -22,7 +23,13 @@ _dbm_dbm_close_impl(dbmobject *self);
static PyObject *
_dbm_dbm_close(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return _dbm_dbm_close_impl((dbmobject *)self);
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _dbm_dbm_close_impl((dbmobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
}
PyDoc_STRVAR(_dbm_dbm_keys__doc__,
@@ -40,11 +47,18 @@ _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls);
static PyObject *
_dbm_dbm_keys(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "keys() takes no arguments");
- return NULL;
+ goto exit;
}
- return _dbm_dbm_keys_impl((dbmobject *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _dbm_dbm_keys_impl((dbmobject *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(_dbm_dbm_get__doc__,
@@ -85,7 +99,9 @@ _dbm_dbm_get(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_
&key, &key_length, &default_value)) {
goto exit;
}
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _dbm_dbm_get_impl((dbmobject *)self, cls, key, key_length, default_value);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -131,7 +147,9 @@ _dbm_dbm_setdefault(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py
&key, &key_length, &default_value)) {
goto exit;
}
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _dbm_dbm_setdefault_impl((dbmobject *)self, cls, key, key_length, default_value);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -152,11 +170,18 @@ _dbm_dbm_clear_impl(dbmobject *self, PyTypeObject *cls);
static PyObject *
_dbm_dbm_clear(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "clear() takes no arguments");
- return NULL;
+ goto exit;
}
- return _dbm_dbm_clear_impl((dbmobject *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _dbm_dbm_clear_impl((dbmobject *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(dbmopen__doc__,
@@ -221,4 +246,4 @@ skip_optional:
exit:
return return_value;
}
-/*[clinic end generated code: output=3b456118f231b160 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=279511ea7cda38dd input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_gdbmmodule.c.h b/Modules/clinic/_gdbmmodule.c.h
index 00950f18e53..6fd6aa3da50 100644
--- a/Modules/clinic/_gdbmmodule.c.h
+++ b/Modules/clinic/_gdbmmodule.c.h
@@ -5,6 +5,7 @@ preserve
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
# include "pycore_runtime.h" // _Py_SINGLETON()
#endif
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_gdbm_gdbm_get__doc__,
@@ -70,7 +71,9 @@ _gdbm_gdbm_setdefault(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
}
default_value = args[1];
skip_optional:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _gdbm_gdbm_setdefault_impl((gdbmobject *)self, key, default_value);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -91,7 +94,13 @@ _gdbm_gdbm_close_impl(gdbmobject *self);
static PyObject *
_gdbm_gdbm_close(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return _gdbm_gdbm_close_impl((gdbmobject *)self);
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _gdbm_gdbm_close_impl((gdbmobject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
}
PyDoc_STRVAR(_gdbm_gdbm_keys__doc__,
@@ -109,11 +118,18 @@ _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls);
static PyObject *
_gdbm_gdbm_keys(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "keys() takes no arguments");
- return NULL;
+ goto exit;
}
- return _gdbm_gdbm_keys_impl((gdbmobject *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _gdbm_gdbm_keys_impl((gdbmobject *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(_gdbm_gdbm_firstkey__doc__,
@@ -135,11 +151,18 @@ _gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls);
static PyObject *
_gdbm_gdbm_firstkey(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "firstkey() takes no arguments");
- return NULL;
+ goto exit;
}
- return _gdbm_gdbm_firstkey_impl((gdbmobject *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _gdbm_gdbm_firstkey_impl((gdbmobject *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(_gdbm_gdbm_nextkey__doc__,
@@ -187,7 +210,9 @@ _gdbm_gdbm_nextkey(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_
&key, &key_length)) {
goto exit;
}
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _gdbm_gdbm_nextkey_impl((gdbmobject *)self, cls, key, key_length);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -214,11 +239,18 @@ _gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls);
static PyObject *
_gdbm_gdbm_reorganize(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "reorganize() takes no arguments");
- return NULL;
+ goto exit;
}
- return _gdbm_gdbm_reorganize_impl((gdbmobject *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _gdbm_gdbm_reorganize_impl((gdbmobject *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(_gdbm_gdbm_sync__doc__,
@@ -239,11 +271,18 @@ _gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls);
static PyObject *
_gdbm_gdbm_sync(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "sync() takes no arguments");
- return NULL;
+ goto exit;
}
- return _gdbm_gdbm_sync_impl((gdbmobject *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _gdbm_gdbm_sync_impl((gdbmobject *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(_gdbm_gdbm_clear__doc__,
@@ -261,11 +300,18 @@ _gdbm_gdbm_clear_impl(gdbmobject *self, PyTypeObject *cls);
static PyObject *
_gdbm_gdbm_clear(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
+ PyObject *return_value = NULL;
+
if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
PyErr_SetString(PyExc_TypeError, "clear() takes no arguments");
- return NULL;
+ goto exit;
}
- return _gdbm_gdbm_clear_impl((gdbmobject *)self, cls);
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _gdbm_gdbm_clear_impl((gdbmobject *)self, cls);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
}
PyDoc_STRVAR(dbmopen__doc__,
@@ -343,4 +389,4 @@ skip_optional:
exit:
return return_value;
}
-/*[clinic end generated code: output=d974cb39e4ee5d67 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=8bca34ce9d4493dd input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_hashopenssl.c.h b/Modules/clinic/_hashopenssl.c.h
index 59ab46ca3f0..61ea10e2a48 100644
--- a/Modules/clinic/_hashopenssl.c.h
+++ b/Modules/clinic/_hashopenssl.c.h
@@ -10,98 +10,98 @@ preserve
#include "pycore_long.h" // _PyLong_UnsignedLong_Converter()
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
-PyDoc_STRVAR(EVP_copy__doc__,
+PyDoc_STRVAR(_hashlib_HASH_copy__doc__,
"copy($self, /)\n"
"--\n"
"\n"
"Return a copy of the hash object.");
-#define EVP_COPY_METHODDEF \
- {"copy", (PyCFunction)EVP_copy, METH_NOARGS, EVP_copy__doc__},
+#define _HASHLIB_HASH_COPY_METHODDEF \
+ {"copy", (PyCFunction)_hashlib_HASH_copy, METH_NOARGS, _hashlib_HASH_copy__doc__},
static PyObject *
-EVP_copy_impl(EVPobject *self);
+_hashlib_HASH_copy_impl(HASHobject *self);
static PyObject *
-EVP_copy(PyObject *self, PyObject *Py_UNUSED(ignored))
+_hashlib_HASH_copy(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return EVP_copy_impl((EVPobject *)self);
+ return _hashlib_HASH_copy_impl((HASHobject *)self);
}
-PyDoc_STRVAR(EVP_digest__doc__,
+PyDoc_STRVAR(_hashlib_HASH_digest__doc__,
"digest($self, /)\n"
"--\n"
"\n"
"Return the digest value as a bytes object.");
-#define EVP_DIGEST_METHODDEF \
- {"digest", (PyCFunction)EVP_digest, METH_NOARGS, EVP_digest__doc__},
+#define _HASHLIB_HASH_DIGEST_METHODDEF \
+ {"digest", (PyCFunction)_hashlib_HASH_digest, METH_NOARGS, _hashlib_HASH_digest__doc__},
static PyObject *
-EVP_digest_impl(EVPobject *self);
+_hashlib_HASH_digest_impl(HASHobject *self);
static PyObject *
-EVP_digest(PyObject *self, PyObject *Py_UNUSED(ignored))
+_hashlib_HASH_digest(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return EVP_digest_impl((EVPobject *)self);
+ return _hashlib_HASH_digest_impl((HASHobject *)self);
}
-PyDoc_STRVAR(EVP_hexdigest__doc__,
+PyDoc_STRVAR(_hashlib_HASH_hexdigest__doc__,
"hexdigest($self, /)\n"
"--\n"
"\n"
"Return the digest value as a string of hexadecimal digits.");
-#define EVP_HEXDIGEST_METHODDEF \
- {"hexdigest", (PyCFunction)EVP_hexdigest, METH_NOARGS, EVP_hexdigest__doc__},
+#define _HASHLIB_HASH_HEXDIGEST_METHODDEF \
+ {"hexdigest", (PyCFunction)_hashlib_HASH_hexdigest, METH_NOARGS, _hashlib_HASH_hexdigest__doc__},
static PyObject *
-EVP_hexdigest_impl(EVPobject *self);
+_hashlib_HASH_hexdigest_impl(HASHobject *self);
static PyObject *
-EVP_hexdigest(PyObject *self, PyObject *Py_UNUSED(ignored))
+_hashlib_HASH_hexdigest(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- return EVP_hexdigest_impl((EVPobject *)self);
+ return _hashlib_HASH_hexdigest_impl((HASHobject *)self);
}
-PyDoc_STRVAR(EVP_update__doc__,
+PyDoc_STRVAR(_hashlib_HASH_update__doc__,
"update($self, obj, /)\n"
"--\n"
"\n"
"Update this hash object\'s state with the provided string.");
-#define EVP_UPDATE_METHODDEF \
- {"update", (PyCFunction)EVP_update, METH_O, EVP_update__doc__},
+#define _HASHLIB_HASH_UPDATE_METHODDEF \
+ {"update", (PyCFunction)_hashlib_HASH_update, METH_O, _hashlib_HASH_update__doc__},
static PyObject *
-EVP_update_impl(EVPobject *self, PyObject *obj);
+_hashlib_HASH_update_impl(HASHobject *self, PyObject *obj);
static PyObject *
-EVP_update(PyObject *self, PyObject *obj)
+_hashlib_HASH_update(PyObject *self, PyObject *obj)
{
PyObject *return_value = NULL;
- return_value = EVP_update_impl((EVPobject *)self, obj);
+ return_value = _hashlib_HASH_update_impl((HASHobject *)self, obj);
return return_value;
}
#if defined(PY_OPENSSL_HAS_SHAKE)
-PyDoc_STRVAR(EVPXOF_digest__doc__,
+PyDoc_STRVAR(_hashlib_HASHXOF_digest__doc__,
"digest($self, /, length)\n"
"--\n"
"\n"
"Return the digest value as a bytes object.");
-#define EVPXOF_DIGEST_METHODDEF \
- {"digest", _PyCFunction_CAST(EVPXOF_digest), METH_FASTCALL|METH_KEYWORDS, EVPXOF_digest__doc__},
+#define _HASHLIB_HASHXOF_DIGEST_METHODDEF \
+ {"digest", _PyCFunction_CAST(_hashlib_HASHXOF_digest), METH_FASTCALL|METH_KEYWORDS, _hashlib_HASHXOF_digest__doc__},
static PyObject *
-EVPXOF_digest_impl(EVPobject *self, Py_ssize_t length);
+_hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length);
static PyObject *
-EVPXOF_digest(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_hashlib_HASHXOF_digest(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)
@@ -151,7 +151,7 @@ EVPXOF_digest(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject
}
length = ival;
}
- return_value = EVPXOF_digest_impl((EVPobject *)self, length);
+ return_value = _hashlib_HASHXOF_digest_impl((HASHobject *)self, length);
exit:
return return_value;
@@ -161,20 +161,20 @@ exit:
#if defined(PY_OPENSSL_HAS_SHAKE)
-PyDoc_STRVAR(EVPXOF_hexdigest__doc__,
+PyDoc_STRVAR(_hashlib_HASHXOF_hexdigest__doc__,
"hexdigest($self, /, length)\n"
"--\n"
"\n"
"Return the digest value as a string of hexadecimal digits.");
-#define EVPXOF_HEXDIGEST_METHODDEF \
- {"hexdigest", _PyCFunction_CAST(EVPXOF_hexdigest), METH_FASTCALL|METH_KEYWORDS, EVPXOF_hexdigest__doc__},
+#define _HASHLIB_HASHXOF_HEXDIGEST_METHODDEF \
+ {"hexdigest", _PyCFunction_CAST(_hashlib_HASHXOF_hexdigest), METH_FASTCALL|METH_KEYWORDS, _hashlib_HASHXOF_hexdigest__doc__},
static PyObject *
-EVPXOF_hexdigest_impl(EVPobject *self, Py_ssize_t length);
+_hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length);
static PyObject *
-EVPXOF_hexdigest(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_hashlib_HASHXOF_hexdigest(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)
@@ -224,7 +224,7 @@ EVPXOF_hexdigest(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
}
length = ival;
}
- return_value = EVPXOF_hexdigest_impl((EVPobject *)self, length);
+ return_value = _hashlib_HASHXOF_hexdigest_impl((HASHobject *)self, length);
exit:
return return_value;
@@ -232,8 +232,8 @@ exit:
#endif /* defined(PY_OPENSSL_HAS_SHAKE) */
-PyDoc_STRVAR(EVP_new__doc__,
-"new($module, /, name, string=b\'\', *, usedforsecurity=True)\n"
+PyDoc_STRVAR(_hashlib_HASH_new__doc__,
+"new($module, /, name, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new hash object using the named algorithm.\n"
@@ -243,20 +243,20 @@ PyDoc_STRVAR(EVP_new__doc__,
"\n"
"The MD5 and SHA1 algorithms are always supported.");
-#define EVP_NEW_METHODDEF \
- {"new", _PyCFunction_CAST(EVP_new), METH_FASTCALL|METH_KEYWORDS, EVP_new__doc__},
+#define _HASHLIB_HASH_NEW_METHODDEF \
+ {"new", _PyCFunction_CAST(_hashlib_HASH_new), METH_FASTCALL|METH_KEYWORDS, _hashlib_HASH_new__doc__},
static PyObject *
-EVP_new_impl(PyObject *module, PyObject *name_obj, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_HASH_new_impl(PyObject *module, const char *name, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
-EVP_new(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+_hashlib_HASH_new(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 3
+ #define NUM_KEYWORDS 4
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -265,7 +265,7 @@ EVP_new(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwn
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(name), &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(name), &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -274,30 +274,43 @@ EVP_new(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwn
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"name", "string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"name", "data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "new",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[3];
+ PyObject *argsbuf[4];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
- PyObject *name_obj;
- PyObject *data_obj = NULL;
+ const char *name;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 1, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
if (!args) {
goto exit;
}
- name_obj = args[0];
+ if (!PyUnicode_Check(args[0])) {
+ _PyArg_BadArgument("new", "argument 'name'", "str", args[0]);
+ goto exit;
+ }
+ Py_ssize_t name_length;
+ name = PyUnicode_AsUTF8AndSize(args[0], &name_length);
+ if (name == NULL) {
+ goto exit;
+ }
+ if (strlen(name) != (size_t)name_length) {
+ PyErr_SetString(PyExc_ValueError, "embedded null character");
+ goto exit;
+ }
if (!noptargs) {
goto skip_optional_pos;
}
if (args[1]) {
- data_obj = args[1];
+ data = args[1];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -306,19 +319,25 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[2]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[2]) {
+ usedforsecurity = PyObject_IsTrue(args[2]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[3];
skip_optional_kwonly:
- return_value = EVP_new_impl(module, name_obj, data_obj, usedforsecurity);
+ return_value = _hashlib_HASH_new_impl(module, name, data, usedforsecurity, string);
exit:
return return_value;
}
PyDoc_STRVAR(_hashlib_openssl_md5__doc__,
-"openssl_md5($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_md5($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Returns a md5 hash object; optionally initialized with a string");
@@ -327,8 +346,8 @@ PyDoc_STRVAR(_hashlib_openssl_md5__doc__,
{"openssl_md5", _PyCFunction_CAST(_hashlib_openssl_md5), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_md5__doc__},
static PyObject *
-_hashlib_openssl_md5_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_md5_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -336,7 +355,7 @@ _hashlib_openssl_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -345,7 +364,7 @@ _hashlib_openssl_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -354,17 +373,18 @@ _hashlib_openssl_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_md5",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -375,7 +395,7 @@ _hashlib_openssl_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -384,19 +404,25 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_md5_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_md5_impl(module, data, usedforsecurity, string);
exit:
return return_value;
}
PyDoc_STRVAR(_hashlib_openssl_sha1__doc__,
-"openssl_sha1($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha1($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Returns a sha1 hash object; optionally initialized with a string");
@@ -405,8 +431,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha1__doc__,
{"openssl_sha1", _PyCFunction_CAST(_hashlib_openssl_sha1), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha1__doc__},
static PyObject *
-_hashlib_openssl_sha1_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha1_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -414,7 +440,7 @@ _hashlib_openssl_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -423,7 +449,7 @@ _hashlib_openssl_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -432,17 +458,18 @@ _hashlib_openssl_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha1",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -453,7 +480,7 @@ _hashlib_openssl_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -462,19 +489,26 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha1_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha1_impl(module, data, usedforsecurity, string);
exit:
return return_value;
}
PyDoc_STRVAR(_hashlib_openssl_sha224__doc__,
-"openssl_sha224($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha224($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha224 hash object; optionally initialized with a string");
@@ -483,8 +517,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha224__doc__,
{"openssl_sha224", _PyCFunction_CAST(_hashlib_openssl_sha224), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha224__doc__},
static PyObject *
-_hashlib_openssl_sha224_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha224_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha224(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -492,7 +526,7 @@ _hashlib_openssl_sha224(PyObject *module, PyObject *const *args, Py_ssize_t narg
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -501,7 +535,7 @@ _hashlib_openssl_sha224(PyObject *module, PyObject *const *args, Py_ssize_t narg
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -510,17 +544,18 @@ _hashlib_openssl_sha224(PyObject *module, PyObject *const *args, Py_ssize_t narg
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha224",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -531,7 +566,7 @@ _hashlib_openssl_sha224(PyObject *module, PyObject *const *args, Py_ssize_t narg
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -540,19 +575,26 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha224_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha224_impl(module, data, usedforsecurity, string);
exit:
return return_value;
}
PyDoc_STRVAR(_hashlib_openssl_sha256__doc__,
-"openssl_sha256($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha256($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha256 hash object; optionally initialized with a string");
@@ -561,8 +603,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha256__doc__,
{"openssl_sha256", _PyCFunction_CAST(_hashlib_openssl_sha256), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha256__doc__},
static PyObject *
-_hashlib_openssl_sha256_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha256_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -570,7 +612,7 @@ _hashlib_openssl_sha256(PyObject *module, PyObject *const *args, Py_ssize_t narg
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -579,7 +621,7 @@ _hashlib_openssl_sha256(PyObject *module, PyObject *const *args, Py_ssize_t narg
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -588,17 +630,18 @@ _hashlib_openssl_sha256(PyObject *module, PyObject *const *args, Py_ssize_t narg
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha256",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -609,7 +652,7 @@ _hashlib_openssl_sha256(PyObject *module, PyObject *const *args, Py_ssize_t narg
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -618,19 +661,26 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha256_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha256_impl(module, data, usedforsecurity, string);
exit:
return return_value;
}
PyDoc_STRVAR(_hashlib_openssl_sha384__doc__,
-"openssl_sha384($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha384($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha384 hash object; optionally initialized with a string");
@@ -639,8 +689,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha384__doc__,
{"openssl_sha384", _PyCFunction_CAST(_hashlib_openssl_sha384), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha384__doc__},
static PyObject *
-_hashlib_openssl_sha384_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha384_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -648,7 +698,7 @@ _hashlib_openssl_sha384(PyObject *module, PyObject *const *args, Py_ssize_t narg
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -657,7 +707,7 @@ _hashlib_openssl_sha384(PyObject *module, PyObject *const *args, Py_ssize_t narg
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -666,17 +716,18 @@ _hashlib_openssl_sha384(PyObject *module, PyObject *const *args, Py_ssize_t narg
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha384",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -687,7 +738,7 @@ _hashlib_openssl_sha384(PyObject *module, PyObject *const *args, Py_ssize_t narg
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -696,19 +747,26 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha384_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha384_impl(module, data, usedforsecurity, string);
exit:
return return_value;
}
PyDoc_STRVAR(_hashlib_openssl_sha512__doc__,
-"openssl_sha512($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha512($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha512 hash object; optionally initialized with a string");
@@ -717,8 +775,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha512__doc__,
{"openssl_sha512", _PyCFunction_CAST(_hashlib_openssl_sha512), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha512__doc__},
static PyObject *
-_hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha512_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha512(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -726,7 +784,7 @@ _hashlib_openssl_sha512(PyObject *module, PyObject *const *args, Py_ssize_t narg
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -735,7 +793,7 @@ _hashlib_openssl_sha512(PyObject *module, PyObject *const *args, Py_ssize_t narg
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -744,17 +802,18 @@ _hashlib_openssl_sha512(PyObject *module, PyObject *const *args, Py_ssize_t narg
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha512",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -765,7 +824,7 @@ _hashlib_openssl_sha512(PyObject *module, PyObject *const *args, Py_ssize_t narg
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -774,12 +833,18 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha512_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha512_impl(module, data, usedforsecurity, string);
exit:
return return_value;
@@ -788,7 +853,8 @@ exit:
#if defined(PY_OPENSSL_HAS_SHA3)
PyDoc_STRVAR(_hashlib_openssl_sha3_224__doc__,
-"openssl_sha3_224($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha3_224($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha3-224 hash object; optionally initialized with a string");
@@ -797,8 +863,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha3_224__doc__,
{"openssl_sha3_224", _PyCFunction_CAST(_hashlib_openssl_sha3_224), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha3_224__doc__},
static PyObject *
-_hashlib_openssl_sha3_224_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha3_224_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha3_224(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -806,7 +872,7 @@ _hashlib_openssl_sha3_224(PyObject *module, PyObject *const *args, Py_ssize_t na
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -815,7 +881,7 @@ _hashlib_openssl_sha3_224(PyObject *module, PyObject *const *args, Py_ssize_t na
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -824,17 +890,18 @@ _hashlib_openssl_sha3_224(PyObject *module, PyObject *const *args, Py_ssize_t na
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha3_224",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -845,7 +912,7 @@ _hashlib_openssl_sha3_224(PyObject *module, PyObject *const *args, Py_ssize_t na
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -854,12 +921,18 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha3_224_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha3_224_impl(module, data, usedforsecurity, string);
exit:
return return_value;
@@ -870,7 +943,8 @@ exit:
#if defined(PY_OPENSSL_HAS_SHA3)
PyDoc_STRVAR(_hashlib_openssl_sha3_256__doc__,
-"openssl_sha3_256($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha3_256($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha3-256 hash object; optionally initialized with a string");
@@ -879,8 +953,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha3_256__doc__,
{"openssl_sha3_256", _PyCFunction_CAST(_hashlib_openssl_sha3_256), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha3_256__doc__},
static PyObject *
-_hashlib_openssl_sha3_256_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha3_256_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha3_256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -888,7 +962,7 @@ _hashlib_openssl_sha3_256(PyObject *module, PyObject *const *args, Py_ssize_t na
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -897,7 +971,7 @@ _hashlib_openssl_sha3_256(PyObject *module, PyObject *const *args, Py_ssize_t na
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -906,17 +980,18 @@ _hashlib_openssl_sha3_256(PyObject *module, PyObject *const *args, Py_ssize_t na
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha3_256",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -927,7 +1002,7 @@ _hashlib_openssl_sha3_256(PyObject *module, PyObject *const *args, Py_ssize_t na
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -936,12 +1011,18 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha3_256_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha3_256_impl(module, data, usedforsecurity, string);
exit:
return return_value;
@@ -952,7 +1033,8 @@ exit:
#if defined(PY_OPENSSL_HAS_SHA3)
PyDoc_STRVAR(_hashlib_openssl_sha3_384__doc__,
-"openssl_sha3_384($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha3_384($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha3-384 hash object; optionally initialized with a string");
@@ -961,8 +1043,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha3_384__doc__,
{"openssl_sha3_384", _PyCFunction_CAST(_hashlib_openssl_sha3_384), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha3_384__doc__},
static PyObject *
-_hashlib_openssl_sha3_384_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha3_384_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha3_384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -970,7 +1052,7 @@ _hashlib_openssl_sha3_384(PyObject *module, PyObject *const *args, Py_ssize_t na
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -979,7 +1061,7 @@ _hashlib_openssl_sha3_384(PyObject *module, PyObject *const *args, Py_ssize_t na
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -988,17 +1070,18 @@ _hashlib_openssl_sha3_384(PyObject *module, PyObject *const *args, Py_ssize_t na
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha3_384",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -1009,7 +1092,7 @@ _hashlib_openssl_sha3_384(PyObject *module, PyObject *const *args, Py_ssize_t na
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -1018,12 +1101,18 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha3_384_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha3_384_impl(module, data, usedforsecurity, string);
exit:
return return_value;
@@ -1034,7 +1123,8 @@ exit:
#if defined(PY_OPENSSL_HAS_SHA3)
PyDoc_STRVAR(_hashlib_openssl_sha3_512__doc__,
-"openssl_sha3_512($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_sha3_512($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a sha3-512 hash object; optionally initialized with a string");
@@ -1043,8 +1133,8 @@ PyDoc_STRVAR(_hashlib_openssl_sha3_512__doc__,
{"openssl_sha3_512", _PyCFunction_CAST(_hashlib_openssl_sha3_512), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_sha3_512__doc__},
static PyObject *
-_hashlib_openssl_sha3_512_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_sha3_512_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_sha3_512(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -1052,7 +1142,7 @@ _hashlib_openssl_sha3_512(PyObject *module, PyObject *const *args, Py_ssize_t na
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -1061,7 +1151,7 @@ _hashlib_openssl_sha3_512(PyObject *module, PyObject *const *args, Py_ssize_t na
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -1070,17 +1160,18 @@ _hashlib_openssl_sha3_512(PyObject *module, PyObject *const *args, Py_ssize_t na
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_sha3_512",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -1091,7 +1182,7 @@ _hashlib_openssl_sha3_512(PyObject *module, PyObject *const *args, Py_ssize_t na
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -1100,12 +1191,18 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_sha3_512_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_sha3_512_impl(module, data, usedforsecurity, string);
exit:
return return_value;
@@ -1116,7 +1213,8 @@ exit:
#if defined(PY_OPENSSL_HAS_SHAKE)
PyDoc_STRVAR(_hashlib_openssl_shake_128__doc__,
-"openssl_shake_128($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_shake_128($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a shake-128 variable hash object; optionally initialized with a string");
@@ -1125,8 +1223,8 @@ PyDoc_STRVAR(_hashlib_openssl_shake_128__doc__,
{"openssl_shake_128", _PyCFunction_CAST(_hashlib_openssl_shake_128), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_shake_128__doc__},
static PyObject *
-_hashlib_openssl_shake_128_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_shake_128_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_shake_128(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -1134,7 +1232,7 @@ _hashlib_openssl_shake_128(PyObject *module, PyObject *const *args, Py_ssize_t n
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -1143,7 +1241,7 @@ _hashlib_openssl_shake_128(PyObject *module, PyObject *const *args, Py_ssize_t n
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -1152,17 +1250,18 @@ _hashlib_openssl_shake_128(PyObject *module, PyObject *const *args, Py_ssize_t n
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_shake_128",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -1173,7 +1272,7 @@ _hashlib_openssl_shake_128(PyObject *module, PyObject *const *args, Py_ssize_t n
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -1182,12 +1281,18 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_shake_128_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_shake_128_impl(module, data, usedforsecurity, string);
exit:
return return_value;
@@ -1198,7 +1303,8 @@ exit:
#if defined(PY_OPENSSL_HAS_SHAKE)
PyDoc_STRVAR(_hashlib_openssl_shake_256__doc__,
-"openssl_shake_256($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"openssl_shake_256($module, /, data=b\'\', *, usedforsecurity=True,\n"
+" string=None)\n"
"--\n"
"\n"
"Returns a shake-256 variable hash object; optionally initialized with a string");
@@ -1207,8 +1313,8 @@ PyDoc_STRVAR(_hashlib_openssl_shake_256__doc__,
{"openssl_shake_256", _PyCFunction_CAST(_hashlib_openssl_shake_256), METH_FASTCALL|METH_KEYWORDS, _hashlib_openssl_shake_256__doc__},
static PyObject *
-_hashlib_openssl_shake_256_impl(PyObject *module, PyObject *data_obj,
- int usedforsecurity);
+_hashlib_openssl_shake_256_impl(PyObject *module, PyObject *data,
+ int usedforsecurity, PyObject *string);
static PyObject *
_hashlib_openssl_shake_256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -1216,7 +1322,7 @@ _hashlib_openssl_shake_256(PyObject *module, PyObject *const *args, Py_ssize_t n
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -1225,7 +1331,7 @@ _hashlib_openssl_shake_256(PyObject *module, PyObject *const *args, Py_ssize_t n
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -1234,17 +1340,18 @@ _hashlib_openssl_shake_256(PyObject *module, PyObject *const *args, Py_ssize_t n
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "openssl_shake_256",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *data_obj = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -1255,7 +1362,7 @@ _hashlib_openssl_shake_256(PyObject *module, PyObject *const *args, Py_ssize_t n
goto skip_optional_pos;
}
if (args[0]) {
- data_obj = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -1264,12 +1371,18 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = args[2];
skip_optional_kwonly:
- return_value = _hashlib_openssl_shake_256_impl(module, data_obj, usedforsecurity);
+ return_value = _hashlib_openssl_shake_256_impl(module, data, usedforsecurity, string);
exit:
return return_value;
@@ -1836,13 +1949,13 @@ exit:
return return_value;
}
-#ifndef EVPXOF_DIGEST_METHODDEF
- #define EVPXOF_DIGEST_METHODDEF
-#endif /* !defined(EVPXOF_DIGEST_METHODDEF) */
+#ifndef _HASHLIB_HASHXOF_DIGEST_METHODDEF
+ #define _HASHLIB_HASHXOF_DIGEST_METHODDEF
+#endif /* !defined(_HASHLIB_HASHXOF_DIGEST_METHODDEF) */
-#ifndef EVPXOF_HEXDIGEST_METHODDEF
- #define EVPXOF_HEXDIGEST_METHODDEF
-#endif /* !defined(EVPXOF_HEXDIGEST_METHODDEF) */
+#ifndef _HASHLIB_HASHXOF_HEXDIGEST_METHODDEF
+ #define _HASHLIB_HASHXOF_HEXDIGEST_METHODDEF
+#endif /* !defined(_HASHLIB_HASHXOF_HEXDIGEST_METHODDEF) */
#ifndef _HASHLIB_OPENSSL_SHA3_224_METHODDEF
#define _HASHLIB_OPENSSL_SHA3_224_METHODDEF
@@ -1871,4 +1984,4 @@ exit:
#ifndef _HASHLIB_SCRYPT_METHODDEF
#define _HASHLIB_SCRYPT_METHODDEF
#endif /* !defined(_HASHLIB_SCRYPT_METHODDEF) */
-/*[clinic end generated code: output=2c78822e38be64a8 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=29f4aaf01714778e input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_heapqmodule.c.h b/Modules/clinic/_heapqmodule.c.h
index 90463079907..b43155b6c24 100644
--- a/Modules/clinic/_heapqmodule.c.h
+++ b/Modules/clinic/_heapqmodule.c.h
@@ -2,6 +2,7 @@
preserve
[clinic start generated code]*/
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_heapq_heappush__doc__,
@@ -32,7 +33,9 @@ _heapq_heappush(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
}
heap = args[0];
item = args[1];
+ Py_BEGIN_CRITICAL_SECTION(heap);
return_value = _heapq_heappush_impl(module, heap, item);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -61,7 +64,9 @@ _heapq_heappop(PyObject *module, PyObject *arg)
goto exit;
}
heap = arg;
+ Py_BEGIN_CRITICAL_SECTION(heap);
return_value = _heapq_heappop_impl(module, heap);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -103,7 +108,9 @@ _heapq_heapreplace(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
}
heap = args[0];
item = args[1];
+ Py_BEGIN_CRITICAL_SECTION(heap);
return_value = _heapq_heapreplace_impl(module, heap, item);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -140,7 +147,9 @@ _heapq_heappushpop(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
}
heap = args[0];
item = args[1];
+ Py_BEGIN_CRITICAL_SECTION(heap);
return_value = _heapq_heappushpop_impl(module, heap, item);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -169,102 +178,184 @@ _heapq_heapify(PyObject *module, PyObject *arg)
goto exit;
}
heap = arg;
+ Py_BEGIN_CRITICAL_SECTION(heap);
return_value = _heapq_heapify_impl(module, heap);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
}
-PyDoc_STRVAR(_heapq__heappop_max__doc__,
-"_heappop_max($module, heap, /)\n"
+PyDoc_STRVAR(_heapq_heappush_max__doc__,
+"heappush_max($module, heap, item, /)\n"
+"--\n"
+"\n"
+"Push item onto max heap, maintaining the heap invariant.");
+
+#define _HEAPQ_HEAPPUSH_MAX_METHODDEF \
+ {"heappush_max", _PyCFunction_CAST(_heapq_heappush_max), METH_FASTCALL, _heapq_heappush_max__doc__},
+
+static PyObject *
+_heapq_heappush_max_impl(PyObject *module, PyObject *heap, PyObject *item);
+
+static PyObject *
+_heapq_heappush_max(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyObject *heap;
+ PyObject *item;
+
+ if (!_PyArg_CheckPositional("heappush_max", nargs, 2, 2)) {
+ goto exit;
+ }
+ if (!PyList_Check(args[0])) {
+ _PyArg_BadArgument("heappush_max", "argument 1", "list", args[0]);
+ goto exit;
+ }
+ heap = args[0];
+ item = args[1];
+ Py_BEGIN_CRITICAL_SECTION(heap);
+ return_value = _heapq_heappush_max_impl(module, heap, item);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_heapq_heappop_max__doc__,
+"heappop_max($module, heap, /)\n"
"--\n"
"\n"
"Maxheap variant of heappop.");
-#define _HEAPQ__HEAPPOP_MAX_METHODDEF \
- {"_heappop_max", (PyCFunction)_heapq__heappop_max, METH_O, _heapq__heappop_max__doc__},
+#define _HEAPQ_HEAPPOP_MAX_METHODDEF \
+ {"heappop_max", (PyCFunction)_heapq_heappop_max, METH_O, _heapq_heappop_max__doc__},
static PyObject *
-_heapq__heappop_max_impl(PyObject *module, PyObject *heap);
+_heapq_heappop_max_impl(PyObject *module, PyObject *heap);
static PyObject *
-_heapq__heappop_max(PyObject *module, PyObject *arg)
+_heapq_heappop_max(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
PyObject *heap;
if (!PyList_Check(arg)) {
- _PyArg_BadArgument("_heappop_max", "argument", "list", arg);
+ _PyArg_BadArgument("heappop_max", "argument", "list", arg);
goto exit;
}
heap = arg;
- return_value = _heapq__heappop_max_impl(module, heap);
+ Py_BEGIN_CRITICAL_SECTION(heap);
+ return_value = _heapq_heappop_max_impl(module, heap);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
}
-PyDoc_STRVAR(_heapq__heapreplace_max__doc__,
-"_heapreplace_max($module, heap, item, /)\n"
+PyDoc_STRVAR(_heapq_heapreplace_max__doc__,
+"heapreplace_max($module, heap, item, /)\n"
"--\n"
"\n"
"Maxheap variant of heapreplace.");
-#define _HEAPQ__HEAPREPLACE_MAX_METHODDEF \
- {"_heapreplace_max", _PyCFunction_CAST(_heapq__heapreplace_max), METH_FASTCALL, _heapq__heapreplace_max__doc__},
+#define _HEAPQ_HEAPREPLACE_MAX_METHODDEF \
+ {"heapreplace_max", _PyCFunction_CAST(_heapq_heapreplace_max), METH_FASTCALL, _heapq_heapreplace_max__doc__},
static PyObject *
-_heapq__heapreplace_max_impl(PyObject *module, PyObject *heap,
- PyObject *item);
+_heapq_heapreplace_max_impl(PyObject *module, PyObject *heap, PyObject *item);
static PyObject *
-_heapq__heapreplace_max(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+_heapq_heapreplace_max(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
PyObject *heap;
PyObject *item;
- if (!_PyArg_CheckPositional("_heapreplace_max", nargs, 2, 2)) {
+ if (!_PyArg_CheckPositional("heapreplace_max", nargs, 2, 2)) {
goto exit;
}
if (!PyList_Check(args[0])) {
- _PyArg_BadArgument("_heapreplace_max", "argument 1", "list", args[0]);
+ _PyArg_BadArgument("heapreplace_max", "argument 1", "list", args[0]);
goto exit;
}
heap = args[0];
item = args[1];
- return_value = _heapq__heapreplace_max_impl(module, heap, item);
+ Py_BEGIN_CRITICAL_SECTION(heap);
+ return_value = _heapq_heapreplace_max_impl(module, heap, item);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
}
-PyDoc_STRVAR(_heapq__heapify_max__doc__,
-"_heapify_max($module, heap, /)\n"
+PyDoc_STRVAR(_heapq_heapify_max__doc__,
+"heapify_max($module, heap, /)\n"
"--\n"
"\n"
"Maxheap variant of heapify.");
-#define _HEAPQ__HEAPIFY_MAX_METHODDEF \
- {"_heapify_max", (PyCFunction)_heapq__heapify_max, METH_O, _heapq__heapify_max__doc__},
+#define _HEAPQ_HEAPIFY_MAX_METHODDEF \
+ {"heapify_max", (PyCFunction)_heapq_heapify_max, METH_O, _heapq_heapify_max__doc__},
static PyObject *
-_heapq__heapify_max_impl(PyObject *module, PyObject *heap);
+_heapq_heapify_max_impl(PyObject *module, PyObject *heap);
static PyObject *
-_heapq__heapify_max(PyObject *module, PyObject *arg)
+_heapq_heapify_max(PyObject *module, PyObject *arg)
{
PyObject *return_value = NULL;
PyObject *heap;
if (!PyList_Check(arg)) {
- _PyArg_BadArgument("_heapify_max", "argument", "list", arg);
+ _PyArg_BadArgument("heapify_max", "argument", "list", arg);
goto exit;
}
heap = arg;
- return_value = _heapq__heapify_max_impl(module, heap);
+ Py_BEGIN_CRITICAL_SECTION(heap);
+ return_value = _heapq_heapify_max_impl(module, heap);
+ Py_END_CRITICAL_SECTION();
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_heapq_heappushpop_max__doc__,
+"heappushpop_max($module, heap, item, /)\n"
+"--\n"
+"\n"
+"Maxheap variant of heappushpop.\n"
+"\n"
+"The combined action runs more efficiently than heappush_max() followed by\n"
+"a separate call to heappop_max().");
+
+#define _HEAPQ_HEAPPUSHPOP_MAX_METHODDEF \
+ {"heappushpop_max", _PyCFunction_CAST(_heapq_heappushpop_max), METH_FASTCALL, _heapq_heappushpop_max__doc__},
+
+static PyObject *
+_heapq_heappushpop_max_impl(PyObject *module, PyObject *heap, PyObject *item);
+
+static PyObject *
+_heapq_heappushpop_max(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyObject *heap;
+ PyObject *item;
+
+ if (!_PyArg_CheckPositional("heappushpop_max", nargs, 2, 2)) {
+ goto exit;
+ }
+ if (!PyList_Check(args[0])) {
+ _PyArg_BadArgument("heappushpop_max", "argument 1", "list", args[0]);
+ goto exit;
+ }
+ heap = args[0];
+ item = args[1];
+ Py_BEGIN_CRITICAL_SECTION(heap);
+ return_value = _heapq_heappushpop_max_impl(module, heap, item);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
}
-/*[clinic end generated code: output=05f2afdf3bc54c9d input=a9049054013a1b77]*/
+/*[clinic end generated code: output=e83d50002c29a96d input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_lsprof.c.h b/Modules/clinic/_lsprof.c.h
index 2918a6bc7ab..c426cd6fe02 100644
--- a/Modules/clinic/_lsprof.c.h
+++ b/Modules/clinic/_lsprof.c.h
@@ -82,6 +82,39 @@ exit:
return return_value;
}
+PyDoc_STRVAR(_lsprof_Profiler__pythrow_callback__doc__,
+"_pythrow_callback($self, code, instruction_offset, exception, /)\n"
+"--\n"
+"\n");
+
+#define _LSPROF_PROFILER__PYTHROW_CALLBACK_METHODDEF \
+ {"_pythrow_callback", _PyCFunction_CAST(_lsprof_Profiler__pythrow_callback), METH_FASTCALL, _lsprof_Profiler__pythrow_callback__doc__},
+
+static PyObject *
+_lsprof_Profiler__pythrow_callback_impl(ProfilerObject *self, PyObject *code,
+ PyObject *instruction_offset,
+ PyObject *exception);
+
+static PyObject *
+_lsprof_Profiler__pythrow_callback(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyObject *code;
+ PyObject *instruction_offset;
+ PyObject *exception;
+
+ if (!_PyArg_CheckPositional("_pythrow_callback", nargs, 3, 3)) {
+ goto exit;
+ }
+ code = args[0];
+ instruction_offset = args[1];
+ exception = args[2];
+ return_value = _lsprof_Profiler__pythrow_callback_impl((ProfilerObject *)self, code, instruction_offset, exception);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(_lsprof_Profiler__pyreturn_callback__doc__,
"_pyreturn_callback($self, code, instruction_offset, retval, /)\n"
"--\n"
@@ -411,4 +444,4 @@ skip_optional_pos:
exit:
return return_value;
}
-/*[clinic end generated code: output=fe231309776df7a7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=9e46985561166c37 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_randommodule.c.h b/Modules/clinic/_randommodule.c.h
index 1e989e970c9..2563a16aea0 100644
--- a/Modules/clinic/_randommodule.c.h
+++ b/Modules/clinic/_randommodule.c.h
@@ -3,6 +3,7 @@ preserve
[clinic start generated code]*/
#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
+#include "pycore_long.h" // _PyLong_UInt64_Converter()
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_random_Random_random__doc__,
@@ -124,16 +125,15 @@ PyDoc_STRVAR(_random_Random_getrandbits__doc__,
{"getrandbits", (PyCFunction)_random_Random_getrandbits, METH_O, _random_Random_getrandbits__doc__},
static PyObject *
-_random_Random_getrandbits_impl(RandomObject *self, int k);
+_random_Random_getrandbits_impl(RandomObject *self, uint64_t k);
static PyObject *
_random_Random_getrandbits(PyObject *self, PyObject *arg)
{
PyObject *return_value = NULL;
- int k;
+ uint64_t k;
- k = PyLong_AsInt(arg);
- if (k == -1 && PyErr_Occurred()) {
+ if (!_PyLong_UInt64_Converter(arg, &k)) {
goto exit;
}
Py_BEGIN_CRITICAL_SECTION(self);
@@ -143,4 +143,4 @@ _random_Random_getrandbits(PyObject *self, PyObject *arg)
exit:
return return_value;
}
-/*[clinic end generated code: output=4458b5a69201ebea input=a9049054013a1b77]*/
+/*[clinic end generated code: output=7ce97b2194eecaf7 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_remote_debugging_module.c.h b/Modules/clinic/_remote_debugging_module.c.h
new file mode 100644
index 00000000000..e80b24b54c0
--- /dev/null
+++ b/Modules/clinic/_remote_debugging_module.c.h
@@ -0,0 +1,276 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
+#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+
+PyDoc_STRVAR(_remote_debugging_RemoteUnwinder___init____doc__,
+"RemoteUnwinder(pid, *, all_threads=False, only_active_thread=False,\n"
+" debug=False)\n"
+"--\n"
+"\n"
+"Initialize a new RemoteUnwinder object for debugging a remote Python process.\n"
+"\n"
+"Args:\n"
+" pid: Process ID of the target Python process to debug\n"
+" all_threads: If True, initialize state for all threads in the process.\n"
+" If False, only initialize for the main thread.\n"
+" only_active_thread: If True, only sample the thread holding the GIL.\n"
+" Cannot be used together with all_threads=True.\n"
+" debug: If True, chain exceptions to explain the sequence of events that\n"
+" lead to the exception.\n"
+"\n"
+"The RemoteUnwinder provides functionality to inspect and debug a running Python\n"
+"process, including examining thread states, stack frames and other runtime data.\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\n"
+" ValueError: If both all_threads and only_active_thread are True");
+
+static int
+_remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
+ int pid, int all_threads,
+ int only_active_thread,
+ int debug);
+
+static int
+_remote_debugging_RemoteUnwinder___init__(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ int return_value = -1;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 4
+ 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_threads), &_Py_ID(only_active_thread), &_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", "all_threads", "only_active_thread", "debug", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "RemoteUnwinder",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[4];
+ 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 all_threads = 0;
+ int only_active_thread = 0;
+ 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;
+ }
+ if (fastargs[1]) {
+ all_threads = PyObject_IsTrue(fastargs[1]);
+ if (all_threads < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ if (fastargs[2]) {
+ only_active_thread = PyObject_IsTrue(fastargs[2]);
+ if (only_active_thread < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ debug = PyObject_IsTrue(fastargs[3]);
+ if (debug < 0) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = _remote_debugging_RemoteUnwinder___init___impl((RemoteUnwinderObject *)self, pid, all_threads, only_active_thread, debug);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_remote_debugging_RemoteUnwinder_get_stack_trace__doc__,
+"get_stack_trace($self, /)\n"
+"--\n"
+"\n"
+"Returns a list of stack traces for threads in the target process.\n"
+"\n"
+"Each element in the returned list is a tuple of (thread_id, frame_list), where:\n"
+"- thread_id is the OS thread identifier\n"
+"- frame_list is a list of tuples (function_name, filename, line_number) representing\n"
+" the Python stack frames for that thread, ordered from most recent to oldest\n"
+"\n"
+"The threads returned depend on the initialization parameters:\n"
+"- If only_active_thread was True: returns only the thread holding the GIL\n"
+"- If all_threads was True: returns all threads\n"
+"- Otherwise: returns only the main thread\n"
+"\n"
+"Example:\n"
+" [\n"
+" (1234, [\n"
+" (\'process_data\', \'worker.py\', 127),\n"
+" (\'run_worker\', \'worker.py\', 45),\n"
+" (\'main\', \'app.py\', 23)\n"
+" ]),\n"
+" (1235, [\n"
+" (\'handle_request\', \'server.py\', 89),\n"
+" (\'serve_forever\', \'server.py\', 52)\n"
+" ])\n"
+" ]\n"
+"\n"
+"Raises:\n"
+" RuntimeError: If there is an error copying memory from the target process\n"
+" OSError: If there is an error accessing the target process\n"
+" PermissionError: If access to the target process is denied\n"
+" UnicodeDecodeError: If there is an error decoding strings from the target process");
+
+#define _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_STACK_TRACE_METHODDEF \
+ {"get_stack_trace", (PyCFunction)_remote_debugging_RemoteUnwinder_get_stack_trace, METH_NOARGS, _remote_debugging_RemoteUnwinder_get_stack_trace__doc__},
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self);
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_stack_trace(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _remote_debugging_RemoteUnwinder_get_stack_trace_impl((RemoteUnwinderObject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_remote_debugging_RemoteUnwinder_get_all_awaited_by__doc__,
+"get_all_awaited_by($self, /)\n"
+"--\n"
+"\n"
+"Get all tasks and their awaited_by relationships from the remote process.\n"
+"\n"
+"This provides a tree structure showing which tasks are waiting for other tasks.\n"
+"\n"
+"For each task, returns:\n"
+"1. The call stack frames leading to where the task is currently executing\n"
+"2. The name of the task\n"
+"3. A list of tasks that this task is waiting for, with their own frames/names/etc\n"
+"\n"
+"Returns a list of [frames, task_name, subtasks] where:\n"
+"- frames: List of (func_name, filename, lineno) showing the call stack\n"
+"- task_name: String identifier for the task\n"
+"- subtasks: List of tasks being awaited by this task, in same format\n"
+"\n"
+"Raises:\n"
+" RuntimeError: If AsyncioDebug section is not available in the remote process\n"
+" MemoryError: If memory allocation fails\n"
+" OSError: If reading from the remote process fails\n"
+"\n"
+"Example output:\n"
+"[\n"
+" [\n"
+" [(\"c5\", \"script.py\", 10), (\"c4\", \"script.py\", 14)],\n"
+" \"c2_root\",\n"
+" [\n"
+" [\n"
+" [(\"c1\", \"script.py\", 23)],\n"
+" \"sub_main_2\",\n"
+" [...]\n"
+" ],\n"
+" [...]\n"
+" ]\n"
+" ]\n"
+"]");
+
+#define _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ALL_AWAITED_BY_METHODDEF \
+ {"get_all_awaited_by", (PyCFunction)_remote_debugging_RemoteUnwinder_get_all_awaited_by, METH_NOARGS, _remote_debugging_RemoteUnwinder_get_all_awaited_by__doc__},
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *self);
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_all_awaited_by(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl((RemoteUnwinderObject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_remote_debugging_RemoteUnwinder_get_async_stack_trace__doc__,
+"get_async_stack_trace($self, /)\n"
+"--\n"
+"\n"
+"Returns information about the currently running async task and its stack trace.\n"
+"\n"
+"Returns a tuple of (task_info, stack_frames) where:\n"
+"- task_info is a tuple of (task_id, task_name) identifying the task\n"
+"- stack_frames is a list of tuples (function_name, filename, line_number) representing\n"
+" the Python stack frames for the task, ordered from most recent to oldest\n"
+"\n"
+"Example:\n"
+" ((4345585712, \'Task-1\'), [\n"
+" (\'run_echo_server\', \'server.py\', 127),\n"
+" (\'serve_forever\', \'server.py\', 45),\n"
+" (\'main\', \'app.py\', 23)\n"
+" ])\n"
+"\n"
+"Raises:\n"
+" RuntimeError: If AsyncioDebug section is not available in the target process\n"
+" RuntimeError: If there is an error copying memory from the target process\n"
+" OSError: If there is an error accessing the target process\n"
+" PermissionError: If access to the target process is denied\n"
+" UnicodeDecodeError: If there is an error decoding strings from the target process");
+
+#define _REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ASYNC_STACK_TRACE_METHODDEF \
+ {"get_async_stack_trace", (PyCFunction)_remote_debugging_RemoteUnwinder_get_async_stack_trace, METH_NOARGS, _remote_debugging_RemoteUnwinder_get_async_stack_trace__doc__},
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject *self);
+
+static PyObject *
+_remote_debugging_RemoteUnwinder_get_async_stack_trace(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ return_value = _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl((RemoteUnwinderObject *)self);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
+}
+/*[clinic end generated code: output=a37ab223d5081b16 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_testclinic_depr.c.h b/Modules/clinic/_testclinic_depr.c.h
index a46d238801b..135197f06fd 100644
--- a/Modules/clinic/_testclinic_depr.c.h
+++ b/Modules/clinic/_testclinic_depr.c.h
@@ -19,16 +19,16 @@ PyDoc_STRVAR(depr_star_new__doc__,
"\n"
"Note: Passing positional arguments to _testclinic.DeprStarNew() is\n"
"deprecated. Parameter \'a\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
static PyObject *
depr_star_new_impl(PyTypeObject *type, PyObject *a);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprStarNew.__new__'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprStarNew.__new__'.")
# else
@@ -77,7 +77,7 @@ depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing positional arguments to _testclinic.DeprStarNew() is "
"deprecated. Parameter 'a' will become a keyword-only parameter "
- "in Python 3.14.", 1))
+ "in Python 3.37.", 1))
{
goto exit;
}
@@ -104,7 +104,7 @@ PyDoc_STRVAR(depr_star_new_clone__doc__,
"\n"
"Note: Passing positional arguments to _testclinic.DeprStarNew.cloned()\n"
"is deprecated. Parameter \'a\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_STAR_NEW_CLONE_METHODDEF \
@@ -113,10 +113,10 @@ PyDoc_STRVAR(depr_star_new_clone__doc__,
static PyObject *
depr_star_new_clone_impl(PyObject *type, PyObject *a);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprStarNew.cloned'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprStarNew.cloned'.")
# else
@@ -163,7 +163,7 @@ depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyO
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing positional arguments to _testclinic.DeprStarNew.cloned()"
" is deprecated. Parameter 'a' will become a keyword-only "
- "parameter in Python 3.14.", 1))
+ "parameter in Python 3.37.", 1))
{
goto exit;
}
@@ -192,16 +192,16 @@ PyDoc_STRVAR(depr_star_init__doc__,
"\n"
"Note: Passing positional arguments to _testclinic.DeprStarInit() is\n"
"deprecated. Parameter \'a\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
static int
depr_star_init_impl(PyObject *self, PyObject *a);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprStarInit.__init__'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprStarInit.__init__'.")
# else
@@ -250,7 +250,7 @@ depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs)
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing positional arguments to _testclinic.DeprStarInit() is "
"deprecated. Parameter 'a' will become a keyword-only parameter "
- "in Python 3.14.", 1))
+ "in Python 3.37.", 1))
{
goto exit;
}
@@ -277,7 +277,7 @@ PyDoc_STRVAR(depr_star_init_clone__doc__,
"\n"
"Note: Passing positional arguments to\n"
"_testclinic.DeprStarInit.cloned() is deprecated. Parameter \'a\' will\n"
-"become a keyword-only parameter in Python 3.14.\n"
+"become a keyword-only parameter in Python 3.37.\n"
"");
#define DEPR_STAR_INIT_CLONE_METHODDEF \
@@ -286,10 +286,10 @@ PyDoc_STRVAR(depr_star_init_clone__doc__,
static PyObject *
depr_star_init_clone_impl(PyObject *self, PyObject *a);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprStarInit.cloned'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprStarInit.cloned'.")
# else
@@ -336,7 +336,7 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing positional arguments to "
"_testclinic.DeprStarInit.cloned() is deprecated. Parameter 'a' "
- "will become a keyword-only parameter in Python 3.14.", 1))
+ "will become a keyword-only parameter in Python 3.37.", 1))
{
goto exit;
}
@@ -361,10 +361,10 @@ static int
depr_star_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprStarInitNoInline.__init__'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprStarInitNoInline.__init__'.")
# else
@@ -414,7 +414,7 @@ depr_star_init_noinline(PyObject *self, PyObject *args, PyObject *kwargs)
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing more than 1 positional argument to "
"_testclinic.DeprStarInitNoInline() is deprecated. Parameters 'b'"
- " and 'c' will become keyword-only parameters in Python 3.14.", 1))
+ " and 'c' will become keyword-only parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -436,16 +436,16 @@ PyDoc_STRVAR(depr_kwd_new__doc__,
"The deprecation message should use the class name instead of __new__.\n"
"\n"
"Note: Passing keyword argument \'a\' to _testclinic.DeprKwdNew() is\n"
-"deprecated. Parameter \'a\' will become positional-only in Python 3.14.\n"
+"deprecated. Parameter \'a\' will become positional-only in Python 3.37.\n"
"");
static PyObject *
depr_kwd_new_impl(PyTypeObject *type, PyObject *a);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprKwdNew.__new__'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprKwdNew.__new__'.")
# else
@@ -499,7 +499,7 @@ depr_kwd_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword argument 'a' to _testclinic.DeprKwdNew() is "
"deprecated. Parameter 'a' will become positional-only in Python "
- "3.14.", 1))
+ "3.37.", 1))
{
goto exit;
}
@@ -522,16 +522,16 @@ PyDoc_STRVAR(depr_kwd_init__doc__,
"The deprecation message should use the class name instead of __init__.\n"
"\n"
"Note: Passing keyword argument \'a\' to _testclinic.DeprKwdInit() is\n"
-"deprecated. Parameter \'a\' will become positional-only in Python 3.14.\n"
+"deprecated. Parameter \'a\' will become positional-only in Python 3.37.\n"
"");
static int
depr_kwd_init_impl(PyObject *self, PyObject *a);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprKwdInit.__init__'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprKwdInit.__init__'.")
# else
@@ -585,7 +585,7 @@ depr_kwd_init(PyObject *self, PyObject *args, PyObject *kwargs)
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword argument 'a' to _testclinic.DeprKwdInit() is "
"deprecated. Parameter 'a' will become positional-only in Python "
- "3.14.", 1))
+ "3.37.", 1))
{
goto exit;
}
@@ -605,10 +605,10 @@ static int
depr_kwd_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of '_testclinic.DeprKwdInitNoInline.__init__'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of '_testclinic.DeprKwdInitNoInline.__init__'.")
# else
@@ -665,7 +665,7 @@ depr_kwd_init_noinline(PyObject *self, PyObject *args, PyObject *kwargs)
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'b' and 'c' to "
"_testclinic.DeprKwdInitNoInline() is deprecated. Parameters 'b' "
- "and 'c' will become positional-only in Python 3.14.", 1))
+ "and 'c' will become positional-only in Python 3.37.", 1))
{
goto exit;
}
@@ -682,7 +682,7 @@ PyDoc_STRVAR(depr_star_pos0_len1__doc__,
"\n"
"Note: Passing positional arguments to depr_star_pos0_len1() is\n"
"deprecated. Parameter \'a\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_STAR_POS0_LEN1_METHODDEF \
@@ -691,10 +691,10 @@ PyDoc_STRVAR(depr_star_pos0_len1__doc__,
static PyObject *
depr_star_pos0_len1_impl(PyObject *module, PyObject *a);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos0_len1'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos0_len1'.")
# else
@@ -740,7 +740,7 @@ depr_star_pos0_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing positional arguments to depr_star_pos0_len1() is "
"deprecated. Parameter 'a' will become a keyword-only parameter "
- "in Python 3.14.", 1))
+ "in Python 3.37.", 1))
{
goto exit;
}
@@ -763,7 +763,7 @@ PyDoc_STRVAR(depr_star_pos0_len2__doc__,
"\n"
"Note: Passing positional arguments to depr_star_pos0_len2() is\n"
"deprecated. Parameters \'a\' and \'b\' will become keyword-only parameters\n"
-"in Python 3.14.\n"
+"in Python 3.37.\n"
"");
#define DEPR_STAR_POS0_LEN2_METHODDEF \
@@ -772,10 +772,10 @@ PyDoc_STRVAR(depr_star_pos0_len2__doc__,
static PyObject *
depr_star_pos0_len2_impl(PyObject *module, PyObject *a, PyObject *b);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos0_len2'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos0_len2'.")
# else
@@ -822,7 +822,7 @@ depr_star_pos0_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing positional arguments to depr_star_pos0_len2() is "
"deprecated. Parameters 'a' and 'b' will become keyword-only "
- "parameters in Python 3.14.", 1))
+ "parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -846,7 +846,7 @@ PyDoc_STRVAR(depr_star_pos0_len3_with_kwd__doc__,
"\n"
"Note: Passing positional arguments to depr_star_pos0_len3_with_kwd()\n"
"is deprecated. Parameters \'a\', \'b\' and \'c\' will become keyword-only\n"
-"parameters in Python 3.14.\n"
+"parameters in Python 3.37.\n"
"");
#define DEPR_STAR_POS0_LEN3_WITH_KWD_METHODDEF \
@@ -856,10 +856,10 @@ static PyObject *
depr_star_pos0_len3_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos0_len3_with_kwd'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos0_len3_with_kwd'.")
# else
@@ -908,7 +908,7 @@ depr_star_pos0_len3_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing positional arguments to depr_star_pos0_len3_with_kwd() "
"is deprecated. Parameters 'a', 'b' and 'c' will become "
- "keyword-only parameters in Python 3.14.", 1))
+ "keyword-only parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -934,7 +934,7 @@ PyDoc_STRVAR(depr_star_pos1_len1_opt__doc__,
"\n"
"Note: Passing 2 positional arguments to depr_star_pos1_len1_opt() is\n"
"deprecated. Parameter \'b\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_STAR_POS1_LEN1_OPT_METHODDEF \
@@ -943,10 +943,10 @@ PyDoc_STRVAR(depr_star_pos1_len1_opt__doc__,
static PyObject *
depr_star_pos1_len1_opt_impl(PyObject *module, PyObject *a, PyObject *b);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos1_len1_opt'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos1_len1_opt'.")
# else
@@ -994,7 +994,7 @@ depr_star_pos1_len1_opt(PyObject *module, PyObject *const *args, Py_ssize_t narg
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing 2 positional arguments to depr_star_pos1_len1_opt() is "
"deprecated. Parameter 'b' will become a keyword-only parameter "
- "in Python 3.14.", 1))
+ "in Python 3.37.", 1))
{
goto exit;
}
@@ -1022,7 +1022,7 @@ PyDoc_STRVAR(depr_star_pos1_len1__doc__,
"\n"
"Note: Passing 2 positional arguments to depr_star_pos1_len1() is\n"
"deprecated. Parameter \'b\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_STAR_POS1_LEN1_METHODDEF \
@@ -1031,10 +1031,10 @@ PyDoc_STRVAR(depr_star_pos1_len1__doc__,
static PyObject *
depr_star_pos1_len1_impl(PyObject *module, PyObject *a, PyObject *b);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos1_len1'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos1_len1'.")
# else
@@ -1081,7 +1081,7 @@ depr_star_pos1_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing 2 positional arguments to depr_star_pos1_len1() is "
"deprecated. Parameter 'b' will become a keyword-only parameter "
- "in Python 3.14.", 1))
+ "in Python 3.37.", 1))
{
goto exit;
}
@@ -1105,7 +1105,7 @@ PyDoc_STRVAR(depr_star_pos1_len2_with_kwd__doc__,
"\n"
"Note: Passing more than 1 positional argument to\n"
"depr_star_pos1_len2_with_kwd() is deprecated. Parameters \'b\' and \'c\'\n"
-"will become keyword-only parameters in Python 3.14.\n"
+"will become keyword-only parameters in Python 3.37.\n"
"");
#define DEPR_STAR_POS1_LEN2_WITH_KWD_METHODDEF \
@@ -1115,10 +1115,10 @@ static PyObject *
depr_star_pos1_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos1_len2_with_kwd'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos1_len2_with_kwd'.")
# else
@@ -1167,7 +1167,7 @@ depr_star_pos1_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing more than 1 positional argument to "
"depr_star_pos1_len2_with_kwd() is deprecated. Parameters 'b' and"
- " 'c' will become keyword-only parameters in Python 3.14.", 1))
+ " 'c' will become keyword-only parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -1193,7 +1193,7 @@ PyDoc_STRVAR(depr_star_pos2_len1__doc__,
"\n"
"Note: Passing 3 positional arguments to depr_star_pos2_len1() is\n"
"deprecated. Parameter \'c\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_STAR_POS2_LEN1_METHODDEF \
@@ -1203,10 +1203,10 @@ static PyObject *
depr_star_pos2_len1_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos2_len1'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos2_len1'.")
# else
@@ -1254,7 +1254,7 @@ depr_star_pos2_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing 3 positional arguments to depr_star_pos2_len1() is "
"deprecated. Parameter 'c' will become a keyword-only parameter "
- "in Python 3.14.", 1))
+ "in Python 3.37.", 1))
{
goto exit;
}
@@ -1279,7 +1279,7 @@ PyDoc_STRVAR(depr_star_pos2_len2__doc__,
"\n"
"Note: Passing more than 2 positional arguments to\n"
"depr_star_pos2_len2() is deprecated. Parameters \'c\' and \'d\' will\n"
-"become keyword-only parameters in Python 3.14.\n"
+"become keyword-only parameters in Python 3.37.\n"
"");
#define DEPR_STAR_POS2_LEN2_METHODDEF \
@@ -1289,10 +1289,10 @@ static PyObject *
depr_star_pos2_len2_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos2_len2'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos2_len2'.")
# else
@@ -1341,7 +1341,7 @@ depr_star_pos2_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing more than 2 positional arguments to "
"depr_star_pos2_len2() is deprecated. Parameters 'c' and 'd' will"
- " become keyword-only parameters in Python 3.14.", 1))
+ " become keyword-only parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -1367,7 +1367,7 @@ PyDoc_STRVAR(depr_star_pos2_len2_with_kwd__doc__,
"\n"
"Note: Passing more than 2 positional arguments to\n"
"depr_star_pos2_len2_with_kwd() is deprecated. Parameters \'c\' and \'d\'\n"
-"will become keyword-only parameters in Python 3.14.\n"
+"will become keyword-only parameters in Python 3.37.\n"
"");
#define DEPR_STAR_POS2_LEN2_WITH_KWD_METHODDEF \
@@ -1377,10 +1377,10 @@ static PyObject *
depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, PyObject *d, PyObject *e);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_pos2_len2_with_kwd'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_pos2_len2_with_kwd'.")
# else
@@ -1430,7 +1430,7 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing more than 2 positional arguments to "
"depr_star_pos2_len2_with_kwd() is deprecated. Parameters 'c' and"
- " 'd' will become keyword-only parameters in Python 3.14.", 1))
+ " 'd' will become keyword-only parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -1457,7 +1457,7 @@ PyDoc_STRVAR(depr_star_noinline__doc__,
"\n"
"Note: Passing more than 1 positional argument to depr_star_noinline()\n"
"is deprecated. Parameters \'b\' and \'c\' will become keyword-only\n"
-"parameters in Python 3.14.\n"
+"parameters in Python 3.37.\n"
"");
#define DEPR_STAR_NOINLINE_METHODDEF \
@@ -1467,10 +1467,10 @@ static PyObject *
depr_star_noinline_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_noinline'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_noinline'.")
# else
@@ -1519,7 +1519,7 @@ depr_star_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing more than 1 positional argument to depr_star_noinline() "
"is deprecated. Parameters 'b' and 'c' will become keyword-only "
- "parameters in Python 3.14.", 1))
+ "parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -1540,9 +1540,9 @@ PyDoc_STRVAR(depr_star_multi__doc__,
"\n"
"Note: Passing more than 1 positional argument to depr_star_multi() is\n"
"deprecated. Parameter \'b\' will become a keyword-only parameter in\n"
-"Python 3.16. Parameters \'c\' and \'d\' will become keyword-only\n"
-"parameters in Python 3.15. Parameters \'e\', \'f\' and \'g\' will become\n"
-"keyword-only parameters in Python 3.14.\n"
+"Python 3.39. Parameters \'c\' and \'d\' will become keyword-only\n"
+"parameters in Python 3.38. Parameters \'e\', \'f\' and \'g\' will become\n"
+"keyword-only parameters in Python 3.37.\n"
"");
#define DEPR_STAR_MULTI_METHODDEF \
@@ -1553,10 +1553,10 @@ depr_star_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g,
PyObject *h);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_star_multi'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_star_multi'.")
# else
@@ -1609,9 +1609,9 @@ depr_star_multi(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing more than 1 positional argument to depr_star_multi() is "
"deprecated. Parameter 'b' will become a keyword-only parameter "
- "in Python 3.16. Parameters 'c' and 'd' will become keyword-only "
- "parameters in Python 3.15. Parameters 'e', 'f' and 'g' will "
- "become keyword-only parameters in Python 3.14.", 1))
+ "in Python 3.39. Parameters 'c' and 'd' will become keyword-only "
+ "parameters in Python 3.38. Parameters 'e', 'f' and 'g' will "
+ "become keyword-only parameters in Python 3.37.", 1))
{
goto exit;
}
@@ -1640,7 +1640,7 @@ PyDoc_STRVAR(depr_kwd_required_1__doc__,
"--\n"
"\n"
"Note: Passing keyword argument \'b\' to depr_kwd_required_1() is\n"
-"deprecated. Parameter \'b\' will become positional-only in Python 3.14.\n"
+"deprecated. Parameter \'b\' will become positional-only in Python 3.37.\n"
"");
#define DEPR_KWD_REQUIRED_1_METHODDEF \
@@ -1649,10 +1649,10 @@ PyDoc_STRVAR(depr_kwd_required_1__doc__,
static PyObject *
depr_kwd_required_1_impl(PyObject *module, PyObject *a, PyObject *b);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_required_1'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_required_1'.")
# else
@@ -1704,7 +1704,7 @@ depr_kwd_required_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword argument 'b' to depr_kwd_required_1() is "
"deprecated. Parameter 'b' will become positional-only in Python "
- "3.14.", 1))
+ "3.37.", 1))
{
goto exit;
}
@@ -1723,7 +1723,7 @@ PyDoc_STRVAR(depr_kwd_required_2__doc__,
"\n"
"Note: Passing keyword arguments \'b\' and \'c\' to depr_kwd_required_2()\n"
"is deprecated. Parameters \'b\' and \'c\' will become positional-only in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_KWD_REQUIRED_2_METHODDEF \
@@ -1733,10 +1733,10 @@ static PyObject *
depr_kwd_required_2_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_required_2'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_required_2'.")
# else
@@ -1789,7 +1789,7 @@ depr_kwd_required_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'b' and 'c' to depr_kwd_required_2() "
"is deprecated. Parameters 'b' and 'c' will become "
- "positional-only in Python 3.14.", 1))
+ "positional-only in Python 3.37.", 1))
{
goto exit;
}
@@ -1808,7 +1808,7 @@ PyDoc_STRVAR(depr_kwd_optional_1__doc__,
"--\n"
"\n"
"Note: Passing keyword argument \'b\' to depr_kwd_optional_1() is\n"
-"deprecated. Parameter \'b\' will become positional-only in Python 3.14.\n"
+"deprecated. Parameter \'b\' will become positional-only in Python 3.37.\n"
"");
#define DEPR_KWD_OPTIONAL_1_METHODDEF \
@@ -1817,10 +1817,10 @@ PyDoc_STRVAR(depr_kwd_optional_1__doc__,
static PyObject *
depr_kwd_optional_1_impl(PyObject *module, PyObject *a, PyObject *b);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_optional_1'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_optional_1'.")
# else
@@ -1873,7 +1873,7 @@ depr_kwd_optional_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword argument 'b' to depr_kwd_optional_1() is "
"deprecated. Parameter 'b' will become positional-only in Python "
- "3.14.", 1))
+ "3.37.", 1))
{
goto exit;
}
@@ -1896,7 +1896,7 @@ PyDoc_STRVAR(depr_kwd_optional_2__doc__,
"\n"
"Note: Passing keyword arguments \'b\' and \'c\' to depr_kwd_optional_2()\n"
"is deprecated. Parameters \'b\' and \'c\' will become positional-only in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_KWD_OPTIONAL_2_METHODDEF \
@@ -1906,10 +1906,10 @@ static PyObject *
depr_kwd_optional_2_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_optional_2'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_optional_2'.")
# else
@@ -1963,7 +1963,7 @@ depr_kwd_optional_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'b' and 'c' to depr_kwd_optional_2() "
"is deprecated. Parameters 'b' and 'c' will become "
- "positional-only in Python 3.14.", 1))
+ "positional-only in Python 3.37.", 1))
{
goto exit;
}
@@ -1992,7 +1992,7 @@ PyDoc_STRVAR(depr_kwd_optional_3__doc__,
"\n"
"Note: Passing keyword arguments \'a\', \'b\' and \'c\' to\n"
"depr_kwd_optional_3() is deprecated. Parameters \'a\', \'b\' and \'c\' will\n"
-"become positional-only in Python 3.14.\n"
+"become positional-only in Python 3.37.\n"
"");
#define DEPR_KWD_OPTIONAL_3_METHODDEF \
@@ -2002,10 +2002,10 @@ static PyObject *
depr_kwd_optional_3_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_optional_3'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_optional_3'.")
# else
@@ -2059,7 +2059,7 @@ depr_kwd_optional_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'a', 'b' and 'c' to "
"depr_kwd_optional_3() is deprecated. Parameters 'a', 'b' and 'c'"
- " will become positional-only in Python 3.14.", 1))
+ " will become positional-only in Python 3.37.", 1))
{
goto exit;
}
@@ -2093,7 +2093,7 @@ PyDoc_STRVAR(depr_kwd_required_optional__doc__,
"\n"
"Note: Passing keyword arguments \'b\' and \'c\' to\n"
"depr_kwd_required_optional() is deprecated. Parameters \'b\' and \'c\'\n"
-"will become positional-only in Python 3.14.\n"
+"will become positional-only in Python 3.37.\n"
"");
#define DEPR_KWD_REQUIRED_OPTIONAL_METHODDEF \
@@ -2103,10 +2103,10 @@ static PyObject *
depr_kwd_required_optional_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_required_optional'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_required_optional'.")
# else
@@ -2160,7 +2160,7 @@ depr_kwd_required_optional(PyObject *module, PyObject *const *args, Py_ssize_t n
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'b' and 'c' to "
"depr_kwd_required_optional() is deprecated. Parameters 'b' and "
- "'c' will become positional-only in Python 3.14.", 1))
+ "'c' will become positional-only in Python 3.37.", 1))
{
goto exit;
}
@@ -2184,7 +2184,7 @@ PyDoc_STRVAR(depr_kwd_noinline__doc__,
"\n"
"Note: Passing keyword arguments \'b\' and \'c\' to depr_kwd_noinline() is\n"
"deprecated. Parameters \'b\' and \'c\' will become positional-only in\n"
-"Python 3.14.\n"
+"Python 3.37.\n"
"");
#define DEPR_KWD_NOINLINE_METHODDEF \
@@ -2194,10 +2194,10 @@ static PyObject *
depr_kwd_noinline_impl(PyObject *module, PyObject *a, PyObject *b,
PyObject *c, const char *d, Py_ssize_t d_length);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_noinline'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_noinline'.")
# else
@@ -2253,7 +2253,7 @@ depr_kwd_noinline(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'b' and 'c' to depr_kwd_noinline() is "
"deprecated. Parameters 'b' and 'c' will become positional-only "
- "in Python 3.14.", 1))
+ "in Python 3.37.", 1))
{
goto exit;
}
@@ -2270,9 +2270,9 @@ PyDoc_STRVAR(depr_kwd_multi__doc__,
"\n"
"Note: Passing keyword arguments \'b\', \'c\', \'d\', \'e\', \'f\' and \'g\' to\n"
"depr_kwd_multi() is deprecated. Parameter \'b\' will become positional-\n"
-"only in Python 3.14. Parameters \'c\' and \'d\' will become positional-\n"
-"only in Python 3.15. Parameters \'e\', \'f\' and \'g\' will become\n"
-"positional-only in Python 3.16.\n"
+"only in Python 3.37. Parameters \'c\' and \'d\' will become positional-\n"
+"only in Python 3.38. Parameters \'e\', \'f\' and \'g\' will become\n"
+"positional-only in Python 3.39.\n"
"");
#define DEPR_KWD_MULTI_METHODDEF \
@@ -2283,10 +2283,10 @@ depr_kwd_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g,
PyObject *h);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_kwd_multi'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_kwd_multi'.")
# else
@@ -2344,9 +2344,9 @@ depr_kwd_multi(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'b', 'c', 'd', 'e', 'f' and 'g' to "
"depr_kwd_multi() is deprecated. Parameter 'b' will become "
- "positional-only in Python 3.14. Parameters 'c' and 'd' will "
- "become positional-only in Python 3.15. Parameters 'e', 'f' and "
- "'g' will become positional-only in Python 3.16.", 1))
+ "positional-only in Python 3.37. Parameters 'c' and 'd' will "
+ "become positional-only in Python 3.38. Parameters 'e', 'f' and "
+ "'g' will become positional-only in Python 3.39.", 1))
{
goto exit;
}
@@ -2370,14 +2370,14 @@ PyDoc_STRVAR(depr_multi__doc__,
"--\n"
"\n"
"Note: Passing keyword arguments \'b\' and \'c\' to depr_multi() is\n"
-"deprecated. Parameter \'b\' will become positional-only in Python 3.14.\n"
-"Parameter \'c\' will become positional-only in Python 3.15.\n"
+"deprecated. Parameter \'b\' will become positional-only in Python 3.37.\n"
+"Parameter \'c\' will become positional-only in Python 3.38.\n"
"\n"
"\n"
"Note: Passing more than 4 positional arguments to depr_multi() is\n"
"deprecated. Parameter \'e\' will become a keyword-only parameter in\n"
-"Python 3.15. Parameter \'f\' will become a keyword-only parameter in\n"
-"Python 3.14.\n"
+"Python 3.38. Parameter \'f\' will become a keyword-only parameter in\n"
+"Python 3.37.\n"
"");
#define DEPR_MULTI_METHODDEF \
@@ -2387,10 +2387,10 @@ static PyObject *
depr_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
PyObject *d, PyObject *e, PyObject *f, PyObject *g);
-// Emit compiler warnings when we get to Python 3.14.
-#if PY_VERSION_HEX >= 0x030e00C0
+// Emit compiler warnings when we get to Python 3.37.
+#if PY_VERSION_HEX >= 0x032500C0
# error "Update the clinic input of 'depr_multi'."
-#elif PY_VERSION_HEX >= 0x030e00A0
+#elif PY_VERSION_HEX >= 0x032500A0
# ifdef _MSC_VER
# pragma message ("Update the clinic input of 'depr_multi'.")
# else
@@ -2442,8 +2442,8 @@ depr_multi(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing more than 4 positional arguments to depr_multi() is "
"deprecated. Parameter 'e' will become a keyword-only parameter "
- "in Python 3.15. Parameter 'f' will become a keyword-only "
- "parameter in Python 3.14.", 1))
+ "in Python 3.38. Parameter 'f' will become a keyword-only "
+ "parameter in Python 3.37.", 1))
{
goto exit;
}
@@ -2457,7 +2457,7 @@ depr_multi(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Passing keyword arguments 'b' and 'c' to depr_multi() is "
"deprecated. Parameter 'b' will become positional-only in Python "
- "3.14. Parameter 'c' will become positional-only in Python 3.15.", 1))
+ "3.37. Parameter 'c' will become positional-only in Python 3.38.", 1))
{
goto exit;
}
@@ -2474,4 +2474,4 @@ depr_multi(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
exit:
return return_value;
}
-/*[clinic end generated code: output=4e60af44fd6b7b94 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=517bb49913bafc4a input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_threadmodule.c.h b/Modules/clinic/_threadmodule.c.h
index 8930e54170c..fd8e32a2261 100644
--- a/Modules/clinic/_threadmodule.c.h
+++ b/Modules/clinic/_threadmodule.c.h
@@ -6,7 +6,53 @@ preserve
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
-#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+#include "pycore_modsupport.h" // _PyArg_NoKeywords()
+
+static PyObject *
+lock_new_impl(PyTypeObject *type);
+
+static PyObject *
+lock_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ PyObject *return_value = NULL;
+ PyTypeObject *base_tp = clinic_state()->lock_type;
+
+ if ((type == base_tp || type->tp_init == base_tp->tp_init) &&
+ !_PyArg_NoPositional("lock", args)) {
+ goto exit;
+ }
+ if ((type == base_tp || type->tp_init == base_tp->tp_init) &&
+ !_PyArg_NoKeywords("lock", kwargs)) {
+ goto exit;
+ }
+ return_value = lock_new_impl(type);
+
+exit:
+ return return_value;
+}
+
+static PyObject *
+rlock_new_impl(PyTypeObject *type);
+
+static PyObject *
+rlock_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ PyObject *return_value = NULL;
+ PyTypeObject *base_tp = clinic_state()->rlock_type;
+
+ if ((type == base_tp || type->tp_init == base_tp->tp_init) &&
+ !_PyArg_NoPositional("RLock", args)) {
+ goto exit;
+ }
+ if ((type == base_tp || type->tp_init == base_tp->tp_init) &&
+ !_PyArg_NoKeywords("RLock", kwargs)) {
+ goto exit;
+ }
+ return_value = rlock_new_impl(type);
+
+exit:
+ return return_value;
+}
#if (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS))
@@ -103,4 +149,4 @@ exit:
#ifndef _THREAD_SET_NAME_METHODDEF
#define _THREAD_SET_NAME_METHODDEF
#endif /* !defined(_THREAD_SET_NAME_METHODDEF) */
-/*[clinic end generated code: output=e978dc4615b9bc35 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b381ec5e313198e7 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h
index f8b623fca08..bd685e75d93 100644
--- a/Modules/clinic/_winapi.c.h
+++ b/Modules/clinic/_winapi.c.h
@@ -857,6 +857,8 @@ exit:
return return_value;
}
+#if (defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM))
+
PyDoc_STRVAR(_winapi_GetShortPathName__doc__,
"GetShortPathName($module, /, path)\n"
"--\n"
@@ -930,6 +932,8 @@ exit:
return return_value;
}
+#endif /* (defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)) */
+
PyDoc_STRVAR(_winapi_GetStdHandle__doc__,
"GetStdHandle($module, std_handle, /)\n"
"--\n"
@@ -1929,6 +1933,24 @@ _winapi_GetACP(PyObject *module, PyObject *Py_UNUSED(ignored))
return _winapi_GetACP_impl(module);
}
+PyDoc_STRVAR(_winapi_GetOEMCP__doc__,
+"GetOEMCP($module, /)\n"
+"--\n"
+"\n"
+"Get the current Windows ANSI code page identifier.");
+
+#define _WINAPI_GETOEMCP_METHODDEF \
+ {"GetOEMCP", (PyCFunction)_winapi_GetOEMCP, METH_NOARGS, _winapi_GetOEMCP__doc__},
+
+static PyObject *
+_winapi_GetOEMCP_impl(PyObject *module);
+
+static PyObject *
+_winapi_GetOEMCP(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ return _winapi_GetOEMCP_impl(module);
+}
+
PyDoc_STRVAR(_winapi_GetFileType__doc__,
"GetFileType($module, /, handle)\n"
"--\n"
@@ -2161,4 +2183,8 @@ exit:
return return_value;
}
-/*[clinic end generated code: output=6cd07628af447d0a input=a9049054013a1b77]*/
+
+#ifndef _WINAPI_GETSHORTPATHNAME_METHODDEF
+ #define _WINAPI_GETSHORTPATHNAME_METHODDEF
+#endif /* !defined(_WINAPI_GETSHORTPATHNAME_METHODDEF) */
+/*[clinic end generated code: output=4581fd481c3c6293 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/blake2module.c.h b/Modules/clinic/blake2module.c.h
index bb2e308574a..97d010d03a4 100644
--- a/Modules/clinic/blake2module.c.h
+++ b/Modules/clinic/blake2module.c.h
@@ -10,20 +10,21 @@ preserve
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(py_blake2b_new__doc__,
-"blake2b(data=b\'\', /, *, digest_size=_blake2.blake2b.MAX_DIGEST_SIZE,\n"
+"blake2b(data=b\'\', *, digest_size=_blake2.blake2b.MAX_DIGEST_SIZE,\n"
" key=b\'\', salt=b\'\', person=b\'\', fanout=1, depth=1, leaf_size=0,\n"
" node_offset=0, node_depth=0, inner_size=0, last_node=False,\n"
-" usedforsecurity=True)\n"
+" usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new BLAKE2b hash object.");
static PyObject *
-py_blake2b_new_impl(PyTypeObject *type, PyObject *data, int digest_size,
+py_blake2b_new_impl(PyTypeObject *type, PyObject *data_obj, int digest_size,
Py_buffer *key, Py_buffer *salt, Py_buffer *person,
int fanout, int depth, unsigned long leaf_size,
unsigned long long node_offset, int node_depth,
- int inner_size, int last_node, int usedforsecurity);
+ int inner_size, int last_node, int usedforsecurity,
+ PyObject *string);
static PyObject *
py_blake2b_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
@@ -31,7 +32,7 @@ py_blake2b_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 12
+ #define NUM_KEYWORDS 14
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -40,7 +41,7 @@ py_blake2b_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(digest_size), &_Py_ID(key), &_Py_ID(salt), &_Py_ID(person), &_Py_ID(fanout), &_Py_ID(depth), &_Py_ID(leaf_size), &_Py_ID(node_offset), &_Py_ID(node_depth), &_Py_ID(inner_size), &_Py_ID(last_node), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(digest_size), &_Py_ID(key), &_Py_ID(salt), &_Py_ID(person), &_Py_ID(fanout), &_Py_ID(depth), &_Py_ID(leaf_size), &_Py_ID(node_offset), &_Py_ID(node_depth), &_Py_ID(inner_size), &_Py_ID(last_node), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -49,18 +50,18 @@ py_blake2b_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"", "digest_size", "key", "salt", "person", "fanout", "depth", "leaf_size", "node_offset", "node_depth", "inner_size", "last_node", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "digest_size", "key", "salt", "person", "fanout", "depth", "leaf_size", "node_offset", "node_depth", "inner_size", "last_node", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "blake2b",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[13];
+ PyObject *argsbuf[14];
PyObject * const *fastargs;
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
- PyObject *data = NULL;
+ PyObject *data_obj = NULL;
int digest_size = HACL_HASH_BLAKE2B_OUT_BYTES;
Py_buffer key = {NULL, NULL};
Py_buffer salt = {NULL, NULL};
@@ -73,18 +74,23 @@ py_blake2b_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
int inner_size = 0;
int last_node = 0;
int usedforsecurity = 1;
+ PyObject *string = NULL;
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
if (!fastargs) {
goto exit;
}
- if (nargs < 1) {
- goto skip_optional_posonly;
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (fastargs[0]) {
+ data_obj = fastargs[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
}
- noptargs--;
- data = fastargs[0];
-skip_optional_posonly:
+skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
@@ -182,12 +188,18 @@ skip_optional_posonly:
goto skip_optional_kwonly;
}
}
- usedforsecurity = PyObject_IsTrue(fastargs[12]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (fastargs[12]) {
+ usedforsecurity = PyObject_IsTrue(fastargs[12]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = fastargs[13];
skip_optional_kwonly:
- return_value = py_blake2b_new_impl(type, data, digest_size, &key, &salt, &person, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node, usedforsecurity);
+ return_value = py_blake2b_new_impl(type, data_obj, digest_size, &key, &salt, &person, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node, usedforsecurity, string);
exit:
/* Cleanup for key */
@@ -207,20 +219,21 @@ exit:
}
PyDoc_STRVAR(py_blake2s_new__doc__,
-"blake2s(data=b\'\', /, *, digest_size=_blake2.blake2s.MAX_DIGEST_SIZE,\n"
+"blake2s(data=b\'\', *, digest_size=_blake2.blake2s.MAX_DIGEST_SIZE,\n"
" key=b\'\', salt=b\'\', person=b\'\', fanout=1, depth=1, leaf_size=0,\n"
" node_offset=0, node_depth=0, inner_size=0, last_node=False,\n"
-" usedforsecurity=True)\n"
+" usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new BLAKE2s hash object.");
static PyObject *
-py_blake2s_new_impl(PyTypeObject *type, PyObject *data, int digest_size,
+py_blake2s_new_impl(PyTypeObject *type, PyObject *data_obj, int digest_size,
Py_buffer *key, Py_buffer *salt, Py_buffer *person,
int fanout, int depth, unsigned long leaf_size,
unsigned long long node_offset, int node_depth,
- int inner_size, int last_node, int usedforsecurity);
+ int inner_size, int last_node, int usedforsecurity,
+ PyObject *string);
static PyObject *
py_blake2s_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
@@ -228,7 +241,7 @@ py_blake2s_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 12
+ #define NUM_KEYWORDS 14
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -237,7 +250,7 @@ py_blake2s_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(digest_size), &_Py_ID(key), &_Py_ID(salt), &_Py_ID(person), &_Py_ID(fanout), &_Py_ID(depth), &_Py_ID(leaf_size), &_Py_ID(node_offset), &_Py_ID(node_depth), &_Py_ID(inner_size), &_Py_ID(last_node), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(digest_size), &_Py_ID(key), &_Py_ID(salt), &_Py_ID(person), &_Py_ID(fanout), &_Py_ID(depth), &_Py_ID(leaf_size), &_Py_ID(node_offset), &_Py_ID(node_depth), &_Py_ID(inner_size), &_Py_ID(last_node), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -246,18 +259,18 @@ py_blake2s_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"", "digest_size", "key", "salt", "person", "fanout", "depth", "leaf_size", "node_offset", "node_depth", "inner_size", "last_node", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "digest_size", "key", "salt", "person", "fanout", "depth", "leaf_size", "node_offset", "node_depth", "inner_size", "last_node", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "blake2s",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[13];
+ PyObject *argsbuf[14];
PyObject * const *fastargs;
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
- PyObject *data = NULL;
+ PyObject *data_obj = NULL;
int digest_size = HACL_HASH_BLAKE2S_OUT_BYTES;
Py_buffer key = {NULL, NULL};
Py_buffer salt = {NULL, NULL};
@@ -270,18 +283,23 @@ py_blake2s_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
int inner_size = 0;
int last_node = 0;
int usedforsecurity = 1;
+ PyObject *string = NULL;
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
if (!fastargs) {
goto exit;
}
- if (nargs < 1) {
- goto skip_optional_posonly;
+ if (!noptargs) {
+ goto skip_optional_pos;
}
- noptargs--;
- data = fastargs[0];
-skip_optional_posonly:
+ if (fastargs[0]) {
+ data_obj = fastargs[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
@@ -379,12 +397,18 @@ skip_optional_posonly:
goto skip_optional_kwonly;
}
}
- usedforsecurity = PyObject_IsTrue(fastargs[12]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (fastargs[12]) {
+ usedforsecurity = PyObject_IsTrue(fastargs[12]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = fastargs[13];
skip_optional_kwonly:
- return_value = py_blake2s_new_impl(type, data, digest_size, &key, &salt, &person, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node, usedforsecurity);
+ return_value = py_blake2s_new_impl(type, data_obj, digest_size, &key, &salt, &person, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node, usedforsecurity, string);
exit:
/* Cleanup for key */
@@ -410,15 +434,19 @@ PyDoc_STRVAR(_blake2_blake2b_copy__doc__,
"Return a copy of the hash object.");
#define _BLAKE2_BLAKE2B_COPY_METHODDEF \
- {"copy", (PyCFunction)_blake2_blake2b_copy, METH_NOARGS, _blake2_blake2b_copy__doc__},
+ {"copy", _PyCFunction_CAST(_blake2_blake2b_copy), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _blake2_blake2b_copy__doc__},
static PyObject *
-_blake2_blake2b_copy_impl(Blake2Object *self);
+_blake2_blake2b_copy_impl(Blake2Object *self, PyTypeObject *cls);
static PyObject *
-_blake2_blake2b_copy(PyObject *self, PyObject *Py_UNUSED(ignored))
+_blake2_blake2b_copy(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
- return _blake2_blake2b_copy_impl((Blake2Object *)self);
+ if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
+ PyErr_SetString(PyExc_TypeError, "copy() takes no arguments");
+ return NULL;
+ }
+ return _blake2_blake2b_copy_impl((Blake2Object *)self, cls);
}
PyDoc_STRVAR(_blake2_blake2b_update__doc__,
@@ -478,4 +506,4 @@ _blake2_blake2b_hexdigest(PyObject *self, PyObject *Py_UNUSED(ignored))
{
return _blake2_blake2b_hexdigest_impl((Blake2Object *)self);
}
-/*[clinic end generated code: output=d30e8293bd8e2950 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=60a4abbcb8950fe5 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h
index 4c2c8acd8f6..a443c48faaa 100644
--- a/Modules/clinic/mathmodule.c.h
+++ b/Modules/clinic/mathmodule.c.h
@@ -84,6 +84,40 @@ PyDoc_STRVAR(math_floor__doc__,
#define MATH_FLOOR_METHODDEF \
{"floor", (PyCFunction)math_floor, METH_O, math_floor__doc__},
+PyDoc_STRVAR(math_signbit__doc__,
+"signbit($module, x, /)\n"
+"--\n"
+"\n"
+"Return True if the sign of x is negative and False otherwise.");
+
+#define MATH_SIGNBIT_METHODDEF \
+ {"signbit", (PyCFunction)math_signbit, METH_O, math_signbit__doc__},
+
+static PyObject *
+math_signbit_impl(PyObject *module, double x);
+
+static PyObject *
+math_signbit(PyObject *module, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ double x;
+
+ if (PyFloat_CheckExact(arg)) {
+ x = PyFloat_AS_DOUBLE(arg);
+ }
+ else
+ {
+ x = PyFloat_AsDouble(arg);
+ if (x == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ return_value = math_signbit_impl(module, x);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(math_fsum__doc__,
"fsum($module, seq, /)\n"
"--\n"
@@ -108,9 +142,7 @@ PyDoc_STRVAR(math_factorial__doc__,
"factorial($module, n, /)\n"
"--\n"
"\n"
-"Find n!.\n"
-"\n"
-"Raise a ValueError if x is negative or non-integral.");
+"Find n!.");
#define MATH_FACTORIAL_METHODDEF \
{"factorial", (PyCFunction)math_factorial, METH_O, math_factorial__doc__},
@@ -630,6 +662,74 @@ exit:
return return_value;
}
+PyDoc_STRVAR(math_isnormal__doc__,
+"isnormal($module, x, /)\n"
+"--\n"
+"\n"
+"Return True if x is normal, and False otherwise.");
+
+#define MATH_ISNORMAL_METHODDEF \
+ {"isnormal", (PyCFunction)math_isnormal, METH_O, math_isnormal__doc__},
+
+static PyObject *
+math_isnormal_impl(PyObject *module, double x);
+
+static PyObject *
+math_isnormal(PyObject *module, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ double x;
+
+ if (PyFloat_CheckExact(arg)) {
+ x = PyFloat_AS_DOUBLE(arg);
+ }
+ else
+ {
+ x = PyFloat_AsDouble(arg);
+ if (x == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ return_value = math_isnormal_impl(module, x);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(math_issubnormal__doc__,
+"issubnormal($module, x, /)\n"
+"--\n"
+"\n"
+"Return True if x is subnormal, and False otherwise.");
+
+#define MATH_ISSUBNORMAL_METHODDEF \
+ {"issubnormal", (PyCFunction)math_issubnormal, METH_O, math_issubnormal__doc__},
+
+static PyObject *
+math_issubnormal_impl(PyObject *module, double x);
+
+static PyObject *
+math_issubnormal(PyObject *module, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ double x;
+
+ if (PyFloat_CheckExact(arg)) {
+ x = PyFloat_AS_DOUBLE(arg);
+ }
+ else
+ {
+ x = PyFloat_AsDouble(arg);
+ if (x == -1.0 && PyErr_Occurred()) {
+ goto exit;
+ }
+ }
+ return_value = math_issubnormal_impl(module, x);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(math_isnan__doc__,
"isnan($module, x, /)\n"
"--\n"
@@ -1112,4 +1212,4 @@ math_ulp(PyObject *module, PyObject *arg)
exit:
return return_value;
}
-/*[clinic end generated code: output=634773bd18cd3f93 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=4e3fa94d026f027b input=a9049054013a1b77]*/
diff --git a/Modules/clinic/md5module.c.h b/Modules/clinic/md5module.c.h
index 9ca4f6528ce..f76902586dd 100644
--- a/Modules/clinic/md5module.c.h
+++ b/Modules/clinic/md5module.c.h
@@ -89,7 +89,7 @@ MD5Type_update(PyObject *self, PyObject *obj)
}
PyDoc_STRVAR(_md5_md5__doc__,
-"md5($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"md5($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new MD5 hash object; optionally initialized with a string.");
@@ -98,7 +98,8 @@ PyDoc_STRVAR(_md5_md5__doc__,
{"md5", _PyCFunction_CAST(_md5_md5), METH_FASTCALL|METH_KEYWORDS, _md5_md5__doc__},
static PyObject *
-_md5_md5_impl(PyObject *module, PyObject *string, int usedforsecurity);
+_md5_md5_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj);
static PyObject *
_md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -106,7 +107,7 @@ _md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -115,7 +116,7 @@ _md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -124,17 +125,18 @@ _md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "md5",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *string = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string_obj = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -145,7 +147,7 @@ _md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw
goto skip_optional_pos;
}
if (args[0]) {
- string = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -154,14 +156,20 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string_obj = args[2];
skip_optional_kwonly:
- return_value = _md5_md5_impl(module, string, usedforsecurity);
+ return_value = _md5_md5_impl(module, data, usedforsecurity, string_obj);
exit:
return return_value;
}
-/*[clinic end generated code: output=73f4d2034d9fcc63 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=920fe54b9ed06f92 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 0125e247ee4..3621a062541 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1471,7 +1471,7 @@ os_getcwdb(PyObject *module, PyObject *Py_UNUSED(ignored))
PyDoc_STRVAR(os_link__doc__,
"link($module, /, src, dst, *, src_dir_fd=None, dst_dir_fd=None,\n"
-" follow_symlinks=True)\n"
+" follow_symlinks=(os.name != \'nt\'))\n"
"--\n"
"\n"
"Create a hard link to a file.\n"
@@ -1530,7 +1530,7 @@ os_link(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwn
path_t dst = PATH_T_INITIALIZE_P("link", "dst", 0, 0, 0, 0);
int src_dir_fd = DEFAULT_DIR_FD;
int dst_dir_fd = DEFAULT_DIR_FD;
- int follow_symlinks = 1;
+ int follow_symlinks = -1;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -1659,7 +1659,7 @@ exit:
return return_value;
}
-#if defined(MS_WINDOWS)
+#if (defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM))
PyDoc_STRVAR(os_listdrives__doc__,
"listdrives($module, /)\n"
@@ -1681,9 +1681,9 @@ os_listdrives(PyObject *module, PyObject *Py_UNUSED(ignored))
return os_listdrives_impl(module);
}
-#endif /* defined(MS_WINDOWS) */
+#endif /* (defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)) */
-#if defined(MS_WINDOWS)
+#if (defined(MS_WINDOWS_APP) || defined(MS_WINDOWS_SYSTEM))
PyDoc_STRVAR(os_listvolumes__doc__,
"listvolumes($module, /)\n"
@@ -1705,9 +1705,9 @@ os_listvolumes(PyObject *module, PyObject *Py_UNUSED(ignored))
return os_listvolumes_impl(module);
}
-#endif /* defined(MS_WINDOWS) */
+#endif /* (defined(MS_WINDOWS_APP) || defined(MS_WINDOWS_SYSTEM)) */
-#if defined(MS_WINDOWS)
+#if (defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM))
PyDoc_STRVAR(os_listmounts__doc__,
"listmounts($module, /, volume)\n"
@@ -1774,7 +1774,7 @@ exit:
return return_value;
}
-#endif /* defined(MS_WINDOWS) */
+#endif /* (defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)) */
#if defined(MS_WINDOWS)
@@ -13398,4 +13398,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored))
#ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF
#define OS__EMSCRIPTEN_DEBUGGER_METHODDEF
#endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */
-/*[clinic end generated code: output=a5ca2541f2af5462 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=ae64df0389746258 input=a9049054013a1b77]*/
diff --git a/Modules/clinic/sha1module.c.h b/Modules/clinic/sha1module.c.h
index 3e5fd1a41ce..4a58d0cd9b8 100644
--- a/Modules/clinic/sha1module.c.h
+++ b/Modules/clinic/sha1module.c.h
@@ -89,7 +89,7 @@ SHA1Type_update(PyObject *self, PyObject *obj)
}
PyDoc_STRVAR(_sha1_sha1__doc__,
-"sha1($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"sha1($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new SHA1 hash object; optionally initialized with a string.");
@@ -98,7 +98,8 @@ PyDoc_STRVAR(_sha1_sha1__doc__,
{"sha1", _PyCFunction_CAST(_sha1_sha1), METH_FASTCALL|METH_KEYWORDS, _sha1_sha1__doc__},
static PyObject *
-_sha1_sha1_impl(PyObject *module, PyObject *string, int usedforsecurity);
+_sha1_sha1_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj);
static PyObject *
_sha1_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -106,7 +107,7 @@ _sha1_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -115,7 +116,7 @@ _sha1_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -124,17 +125,18 @@ _sha1_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "sha1",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *string = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string_obj = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -145,7 +147,7 @@ _sha1_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
goto skip_optional_pos;
}
if (args[0]) {
- string = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -154,14 +156,20 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string_obj = args[2];
skip_optional_kwonly:
- return_value = _sha1_sha1_impl(module, string, usedforsecurity);
+ return_value = _sha1_sha1_impl(module, data, usedforsecurity, string_obj);
exit:
return return_value;
}
-/*[clinic end generated code: output=06161e87e2d645d4 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=fd5a917404b68c4f input=a9049054013a1b77]*/
diff --git a/Modules/clinic/sha2module.c.h b/Modules/clinic/sha2module.c.h
index 26612125e75..07be91e4f6c 100644
--- a/Modules/clinic/sha2module.c.h
+++ b/Modules/clinic/sha2module.c.h
@@ -169,7 +169,7 @@ SHA512Type_update(PyObject *self, PyObject *obj)
}
PyDoc_STRVAR(_sha2_sha256__doc__,
-"sha256($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"sha256($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new SHA-256 hash object; optionally initialized with a string.");
@@ -178,7 +178,8 @@ PyDoc_STRVAR(_sha2_sha256__doc__,
{"sha256", _PyCFunction_CAST(_sha2_sha256), METH_FASTCALL|METH_KEYWORDS, _sha2_sha256__doc__},
static PyObject *
-_sha2_sha256_impl(PyObject *module, PyObject *string, int usedforsecurity);
+_sha2_sha256_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj);
static PyObject *
_sha2_sha256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -186,7 +187,7 @@ _sha2_sha256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -195,7 +196,7 @@ _sha2_sha256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -204,17 +205,18 @@ _sha2_sha256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "sha256",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *string = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string_obj = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -225,7 +227,7 @@ _sha2_sha256(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
goto skip_optional_pos;
}
if (args[0]) {
- string = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -234,19 +236,25 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string_obj = args[2];
skip_optional_kwonly:
- return_value = _sha2_sha256_impl(module, string, usedforsecurity);
+ return_value = _sha2_sha256_impl(module, data, usedforsecurity, string_obj);
exit:
return return_value;
}
PyDoc_STRVAR(_sha2_sha224__doc__,
-"sha224($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"sha224($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new SHA-224 hash object; optionally initialized with a string.");
@@ -255,7 +263,8 @@ PyDoc_STRVAR(_sha2_sha224__doc__,
{"sha224", _PyCFunction_CAST(_sha2_sha224), METH_FASTCALL|METH_KEYWORDS, _sha2_sha224__doc__},
static PyObject *
-_sha2_sha224_impl(PyObject *module, PyObject *string, int usedforsecurity);
+_sha2_sha224_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj);
static PyObject *
_sha2_sha224(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -263,7 +272,7 @@ _sha2_sha224(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -272,7 +281,7 @@ _sha2_sha224(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -281,17 +290,18 @@ _sha2_sha224(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "sha224",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *string = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string_obj = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -302,7 +312,7 @@ _sha2_sha224(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
goto skip_optional_pos;
}
if (args[0]) {
- string = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -311,19 +321,25 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string_obj = args[2];
skip_optional_kwonly:
- return_value = _sha2_sha224_impl(module, string, usedforsecurity);
+ return_value = _sha2_sha224_impl(module, data, usedforsecurity, string_obj);
exit:
return return_value;
}
PyDoc_STRVAR(_sha2_sha512__doc__,
-"sha512($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"sha512($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new SHA-512 hash object; optionally initialized with a string.");
@@ -332,7 +348,8 @@ PyDoc_STRVAR(_sha2_sha512__doc__,
{"sha512", _PyCFunction_CAST(_sha2_sha512), METH_FASTCALL|METH_KEYWORDS, _sha2_sha512__doc__},
static PyObject *
-_sha2_sha512_impl(PyObject *module, PyObject *string, int usedforsecurity);
+_sha2_sha512_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj);
static PyObject *
_sha2_sha512(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -340,7 +357,7 @@ _sha2_sha512(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -349,7 +366,7 @@ _sha2_sha512(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -358,17 +375,18 @@ _sha2_sha512(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "sha512",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *string = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string_obj = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -379,7 +397,7 @@ _sha2_sha512(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
goto skip_optional_pos;
}
if (args[0]) {
- string = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -388,19 +406,25 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string_obj = args[2];
skip_optional_kwonly:
- return_value = _sha2_sha512_impl(module, string, usedforsecurity);
+ return_value = _sha2_sha512_impl(module, data, usedforsecurity, string_obj);
exit:
return return_value;
}
PyDoc_STRVAR(_sha2_sha384__doc__,
-"sha384($module, /, string=b\'\', *, usedforsecurity=True)\n"
+"sha384($module, /, data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new SHA-384 hash object; optionally initialized with a string.");
@@ -409,7 +433,8 @@ PyDoc_STRVAR(_sha2_sha384__doc__,
{"sha384", _PyCFunction_CAST(_sha2_sha384), METH_FASTCALL|METH_KEYWORDS, _sha2_sha384__doc__},
static PyObject *
-_sha2_sha384_impl(PyObject *module, PyObject *string, int usedforsecurity);
+_sha2_sha384_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj);
static PyObject *
_sha2_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -417,7 +442,7 @@ _sha2_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -426,7 +451,7 @@ _sha2_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -435,17 +460,18 @@ _sha2_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "sha384",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
- PyObject *string = NULL;
+ PyObject *data = NULL;
int usedforsecurity = 1;
+ PyObject *string_obj = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -456,7 +482,7 @@ _sha2_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
goto skip_optional_pos;
}
if (args[0]) {
- string = args[0];
+ data = args[0];
if (!--noptargs) {
goto skip_optional_pos;
}
@@ -465,14 +491,20 @@ skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(args[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (args[1]) {
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string_obj = args[2];
skip_optional_kwonly:
- return_value = _sha2_sha384_impl(module, string, usedforsecurity);
+ return_value = _sha2_sha384_impl(module, data, usedforsecurity, string_obj);
exit:
return return_value;
}
-/*[clinic end generated code: output=af11090855b7c85a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=90625b237c774a9f input=a9049054013a1b77]*/
diff --git a/Modules/clinic/sha3module.c.h b/Modules/clinic/sha3module.c.h
index 25f72b74f80..1f631ff406e 100644
--- a/Modules/clinic/sha3module.c.h
+++ b/Modules/clinic/sha3module.c.h
@@ -6,17 +6,18 @@ preserve
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
-#include "pycore_long.h" // _PyLong_UnsignedLong_Converter()
+#include "pycore_abstract.h" // _PyNumber_Index()
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(py_sha3_new__doc__,
-"sha3_224(data=b\'\', /, *, usedforsecurity=True)\n"
+"sha3_224(data=b\'\', *, usedforsecurity=True, string=None)\n"
"--\n"
"\n"
"Return a new SHA3 hash object.");
static PyObject *
-py_sha3_new_impl(PyTypeObject *type, PyObject *data, int usedforsecurity);
+py_sha3_new_impl(PyTypeObject *type, PyObject *data_obj, int usedforsecurity,
+ PyObject *string);
static PyObject *
py_sha3_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
@@ -24,7 +25,7 @@ py_sha3_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 1
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -33,7 +34,7 @@ py_sha3_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(usedforsecurity), },
+ .ob_item = { &_Py_ID(data), &_Py_ID(usedforsecurity), &_Py_ID(string), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -42,40 +43,51 @@ py_sha3_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"", "usedforsecurity", NULL};
+ static const char * const _keywords[] = {"data", "usedforsecurity", "string", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "sha3_224",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
PyObject * const *fastargs;
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
- PyObject *data = NULL;
+ PyObject *data_obj = NULL;
int usedforsecurity = 1;
+ PyObject *string = NULL;
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser,
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
if (!fastargs) {
goto exit;
}
- if (nargs < 1) {
- goto skip_optional_posonly;
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (fastargs[0]) {
+ data_obj = fastargs[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
}
- noptargs--;
- data = fastargs[0];
-skip_optional_posonly:
+skip_optional_pos:
if (!noptargs) {
goto skip_optional_kwonly;
}
- usedforsecurity = PyObject_IsTrue(fastargs[1]);
- if (usedforsecurity < 0) {
- goto exit;
+ if (fastargs[1]) {
+ usedforsecurity = PyObject_IsTrue(fastargs[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
+ string = fastargs[2];
skip_optional_kwonly:
- return_value = py_sha3_new_impl(type, data, usedforsecurity);
+ return_value = py_sha3_new_impl(type, data_obj, usedforsecurity, string);
exit:
return return_value;
@@ -88,15 +100,19 @@ PyDoc_STRVAR(_sha3_sha3_224_copy__doc__,
"Return a copy of the hash object.");
#define _SHA3_SHA3_224_COPY_METHODDEF \
- {"copy", (PyCFunction)_sha3_sha3_224_copy, METH_NOARGS, _sha3_sha3_224_copy__doc__},
+ {"copy", _PyCFunction_CAST(_sha3_sha3_224_copy), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _sha3_sha3_224_copy__doc__},
static PyObject *
-_sha3_sha3_224_copy_impl(SHA3object *self);
+_sha3_sha3_224_copy_impl(SHA3object *self, PyTypeObject *cls);
static PyObject *
-_sha3_sha3_224_copy(PyObject *self, PyObject *Py_UNUSED(ignored))
+_sha3_sha3_224_copy(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
- return _sha3_sha3_224_copy_impl((SHA3object *)self);
+ if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
+ PyErr_SetString(PyExc_TypeError, "copy() takes no arguments");
+ return NULL;
+ }
+ return _sha3_sha3_224_copy_impl((SHA3object *)self, cls);
}
PyDoc_STRVAR(_sha3_sha3_224_digest__doc__,
@@ -158,26 +174,68 @@ _sha3_sha3_224_update(PyObject *self, PyObject *data)
}
PyDoc_STRVAR(_sha3_shake_128_digest__doc__,
-"digest($self, length, /)\n"
+"digest($self, /, length)\n"
"--\n"
"\n"
"Return the digest value as a bytes object.");
#define _SHA3_SHAKE_128_DIGEST_METHODDEF \
- {"digest", (PyCFunction)_sha3_shake_128_digest, METH_O, _sha3_shake_128_digest__doc__},
+ {"digest", _PyCFunction_CAST(_sha3_shake_128_digest), METH_FASTCALL|METH_KEYWORDS, _sha3_shake_128_digest__doc__},
static PyObject *
-_sha3_shake_128_digest_impl(SHA3object *self, unsigned long length);
+_sha3_shake_128_digest_impl(SHA3object *self, Py_ssize_t length);
static PyObject *
-_sha3_shake_128_digest(PyObject *self, PyObject *arg)
+_sha3_shake_128_digest(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
- unsigned long length;
+ #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(length), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
- if (!_PyLong_UnsignedLong_Converter(arg, &length)) {
+ static const char * const _keywords[] = {"length", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "digest",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ Py_ssize_t length;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
goto exit;
}
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[0]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ length = ival;
+ }
return_value = _sha3_shake_128_digest_impl((SHA3object *)self, length);
exit:
@@ -185,29 +243,71 @@ exit:
}
PyDoc_STRVAR(_sha3_shake_128_hexdigest__doc__,
-"hexdigest($self, length, /)\n"
+"hexdigest($self, /, length)\n"
"--\n"
"\n"
"Return the digest value as a string of hexadecimal digits.");
#define _SHA3_SHAKE_128_HEXDIGEST_METHODDEF \
- {"hexdigest", (PyCFunction)_sha3_shake_128_hexdigest, METH_O, _sha3_shake_128_hexdigest__doc__},
+ {"hexdigest", _PyCFunction_CAST(_sha3_shake_128_hexdigest), METH_FASTCALL|METH_KEYWORDS, _sha3_shake_128_hexdigest__doc__},
static PyObject *
-_sha3_shake_128_hexdigest_impl(SHA3object *self, unsigned long length);
+_sha3_shake_128_hexdigest_impl(SHA3object *self, Py_ssize_t length);
static PyObject *
-_sha3_shake_128_hexdigest(PyObject *self, PyObject *arg)
+_sha3_shake_128_hexdigest(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
- unsigned long length;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- if (!_PyLong_UnsignedLong_Converter(arg, &length)) {
+ #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(length), },
+ };
+ #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[] = {"length", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "hexdigest",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ Py_ssize_t length;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
goto exit;
}
+ {
+ Py_ssize_t ival = -1;
+ PyObject *iobj = _PyNumber_Index(args[0]);
+ if (iobj != NULL) {
+ ival = PyLong_AsSsize_t(iobj);
+ Py_DECREF(iobj);
+ }
+ if (ival == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ length = ival;
+ }
return_value = _sha3_shake_128_hexdigest_impl((SHA3object *)self, length);
exit:
return return_value;
}
-/*[clinic end generated code: output=5b3ac1c06c6899ea input=a9049054013a1b77]*/
+/*[clinic end generated code: output=48be77f8a31e8a3e input=a9049054013a1b77]*/
diff --git a/Modules/clinic/socketmodule.c.h b/Modules/clinic/socketmodule.c.h
index 70ebbaa876b..0cedab597db 100644
--- a/Modules/clinic/socketmodule.c.h
+++ b/Modules/clinic/socketmodule.c.h
@@ -6,7 +6,8 @@ preserve
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
-#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+#include "pycore_long.h" // _PyLong_UInt16_Converter()
+#include "pycore_modsupport.h" // _PyArg_CheckPositional()
PyDoc_STRVAR(_socket_socket_close__doc__,
"close($self, /)\n"
@@ -28,6 +29,170 @@ _socket_socket_close(PyObject *s, PyObject *Py_UNUSED(ignored))
return _socket_socket_close_impl((PySocketSockObject *)s);
}
+PyDoc_STRVAR(_socket_socket_send__doc__,
+"send($self, data, flags=0, /)\n"
+"--\n"
+"\n"
+"Send a data string to the socket.\n"
+"\n"
+"For the optional flags argument, see the Unix manual.\n"
+"Return the number of bytes sent; this may be less than len(data) if the network is busy.");
+
+#define _SOCKET_SOCKET_SEND_METHODDEF \
+ {"send", _PyCFunction_CAST(_socket_socket_send), METH_FASTCALL, _socket_socket_send__doc__},
+
+static PyObject *
+_socket_socket_send_impl(PySocketSockObject *s, Py_buffer *pbuf, int flags);
+
+static PyObject *
+_socket_socket_send(PyObject *s, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ Py_buffer pbuf = {NULL, NULL};
+ int flags = 0;
+
+ if (!_PyArg_CheckPositional("send", nargs, 1, 2)) {
+ goto exit;
+ }
+ if (PyObject_GetBuffer(args[0], &pbuf, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ if (nargs < 2) {
+ goto skip_optional;
+ }
+ flags = PyLong_AsInt(args[1]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional:
+ return_value = _socket_socket_send_impl((PySocketSockObject *)s, &pbuf, flags);
+
+exit:
+ /* Cleanup for pbuf */
+ if (pbuf.obj) {
+ PyBuffer_Release(&pbuf);
+ }
+
+ return return_value;
+}
+
+PyDoc_STRVAR(_socket_socket_sendall__doc__,
+"sendall($self, data, flags=0, /)\n"
+"--\n"
+"\n"
+"Send a data string to the socket.\n"
+"\n"
+"For the optional flags argument, see the Unix manual.\n"
+"This calls send() repeatedly until all data is sent.\n"
+"If an error occurs, it\'s impossible to tell how much data has been sent.");
+
+#define _SOCKET_SOCKET_SENDALL_METHODDEF \
+ {"sendall", _PyCFunction_CAST(_socket_socket_sendall), METH_FASTCALL, _socket_socket_sendall__doc__},
+
+static PyObject *
+_socket_socket_sendall_impl(PySocketSockObject *s, Py_buffer *pbuf,
+ int flags);
+
+static PyObject *
+_socket_socket_sendall(PyObject *s, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ Py_buffer pbuf = {NULL, NULL};
+ int flags = 0;
+
+ if (!_PyArg_CheckPositional("sendall", nargs, 1, 2)) {
+ goto exit;
+ }
+ if (PyObject_GetBuffer(args[0], &pbuf, PyBUF_SIMPLE) != 0) {
+ goto exit;
+ }
+ if (nargs < 2) {
+ goto skip_optional;
+ }
+ flags = PyLong_AsInt(args[1]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional:
+ return_value = _socket_socket_sendall_impl((PySocketSockObject *)s, &pbuf, flags);
+
+exit:
+ /* Cleanup for pbuf */
+ if (pbuf.obj) {
+ PyBuffer_Release(&pbuf);
+ }
+
+ return return_value;
+}
+
+#if defined(CMSG_LEN)
+
+PyDoc_STRVAR(_socket_socket_sendmsg__doc__,
+"sendmsg($self, buffers, ancdata=<unrepresentable>, flags=0,\n"
+" address=<unrepresentable>, /)\n"
+"--\n"
+"\n"
+"Send normal and ancillary data to the socket.\n"
+"\n"
+"It gathering the non-ancillary data from a series of buffers\n"
+"and concatenating it into a single message.\n"
+"The buffers argument specifies the non-ancillary\n"
+"data as an iterable of bytes-like objects (e.g. bytes objects).\n"
+"The ancdata argument specifies the ancillary data (control messages)\n"
+"as an iterable of zero or more tuples (cmsg_level, cmsg_type,\n"
+"cmsg_data), where cmsg_level and cmsg_type are integers specifying the\n"
+"protocol level and protocol-specific type respectively, and cmsg_data\n"
+"is a bytes-like object holding the associated data. The flags\n"
+"argument defaults to 0 and has the same meaning as for send(). If\n"
+"address is supplied and not None, it sets a destination address for\n"
+"the message. The return value is the number of bytes of non-ancillary\n"
+"data sent.");
+
+#define _SOCKET_SOCKET_SENDMSG_METHODDEF \
+ {"sendmsg", _PyCFunction_CAST(_socket_socket_sendmsg), METH_FASTCALL, _socket_socket_sendmsg__doc__},
+
+static PyObject *
+_socket_socket_sendmsg_impl(PySocketSockObject *s, PyObject *data_arg,
+ PyObject *cmsg_arg, int flags,
+ PyObject *addr_arg);
+
+static PyObject *
+_socket_socket_sendmsg(PyObject *s, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyObject *data_arg;
+ PyObject *cmsg_arg = NULL;
+ int flags = 0;
+ PyObject *addr_arg = NULL;
+
+ if (!_PyArg_CheckPositional("sendmsg", nargs, 1, 4)) {
+ goto exit;
+ }
+ data_arg = args[0];
+ if (nargs < 2) {
+ goto skip_optional;
+ }
+ cmsg_arg = args[1];
+ if (nargs < 3) {
+ goto skip_optional;
+ }
+ flags = PyLong_AsInt(args[2]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (nargs < 4) {
+ goto skip_optional;
+ }
+ addr_arg = args[3];
+skip_optional:
+ return_value = _socket_socket_sendmsg_impl((PySocketSockObject *)s, data_arg, cmsg_arg, flags, addr_arg);
+
+exit:
+ return return_value;
+}
+
+#endif /* defined(CMSG_LEN) */
+
static int
sock_initobj_impl(PySocketSockObject *self, int family, int type, int proto,
PyObject *fdobj);
@@ -358,6 +523,10 @@ exit:
#endif /* (defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS)) */
+#ifndef _SOCKET_SOCKET_SENDMSG_METHODDEF
+ #define _SOCKET_SOCKET_SENDMSG_METHODDEF
+#endif /* !defined(_SOCKET_SOCKET_SENDMSG_METHODDEF) */
+
#ifndef _SOCKET_INET_NTOA_METHODDEF
#define _SOCKET_INET_NTOA_METHODDEF
#endif /* !defined(_SOCKET_INET_NTOA_METHODDEF) */
@@ -369,4 +538,4 @@ exit:
#ifndef _SOCKET_IF_INDEXTONAME_METHODDEF
#define _SOCKET_IF_INDEXTONAME_METHODDEF
#endif /* !defined(_SOCKET_IF_INDEXTONAME_METHODDEF) */
-/*[clinic end generated code: output=c971b79d2193b426 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=0376c46b76ae2bce input=a9049054013a1b77]*/
diff --git a/Modules/clinic/zlibmodule.c.h b/Modules/clinic/zlibmodule.c.h
index 2710f65a840..146a7e25001 100644
--- a/Modules/clinic/zlibmodule.c.h
+++ b/Modules/clinic/zlibmodule.c.h
@@ -1044,6 +1044,65 @@ exit:
return return_value;
}
+PyDoc_STRVAR(zlib_adler32_combine__doc__,
+"adler32_combine($module, adler1, adler2, len2, /)\n"
+"--\n"
+"\n"
+"Combine two Adler-32 checksums into one.\n"
+"\n"
+" adler1\n"
+" Adler-32 checksum for sequence A\n"
+" adler2\n"
+" Adler-32 checksum for sequence B\n"
+" len2\n"
+" Length of sequence B\n"
+"\n"
+"Given the Adler-32 checksum \'adler1\' of a sequence A and the\n"
+"Adler-32 checksum \'adler2\' of a sequence B of length \'len2\',\n"
+"return the Adler-32 checksum of A and B concatenated.");
+
+#define ZLIB_ADLER32_COMBINE_METHODDEF \
+ {"adler32_combine", _PyCFunction_CAST(zlib_adler32_combine), METH_FASTCALL, zlib_adler32_combine__doc__},
+
+static unsigned int
+zlib_adler32_combine_impl(PyObject *module, unsigned int adler1,
+ unsigned int adler2, PyObject *len2);
+
+static PyObject *
+zlib_adler32_combine(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ unsigned int adler1;
+ unsigned int adler2;
+ PyObject *len2;
+ unsigned int _return_value;
+
+ if (!_PyArg_CheckPositional("adler32_combine", nargs, 3, 3)) {
+ goto exit;
+ }
+ adler1 = (unsigned int)PyLong_AsUnsignedLongMask(args[0]);
+ if (adler1 == (unsigned int)-1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ adler2 = (unsigned int)PyLong_AsUnsignedLongMask(args[1]);
+ if (adler2 == (unsigned int)-1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!PyLong_Check(args[2])) {
+ _PyArg_BadArgument("adler32_combine", "argument 3", "int", args[2]);
+ goto exit;
+ }
+ len2 = args[2];
+ _return_value = zlib_adler32_combine_impl(module, adler1, adler2, len2);
+ if ((_return_value == (unsigned int)-1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyLong_FromUnsignedLong((unsigned long)_return_value);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(zlib_crc32__doc__,
"crc32($module, data, value=0, /)\n"
"--\n"
@@ -1098,6 +1157,65 @@ exit:
return return_value;
}
+PyDoc_STRVAR(zlib_crc32_combine__doc__,
+"crc32_combine($module, crc1, crc2, len2, /)\n"
+"--\n"
+"\n"
+"Combine two CRC-32 checksums into one.\n"
+"\n"
+" crc1\n"
+" CRC-32 checksum for sequence A\n"
+" crc2\n"
+" CRC-32 checksum for sequence B\n"
+" len2\n"
+" Length of sequence B\n"
+"\n"
+"Given the CRC-32 checksum \'crc1\' of a sequence A and the\n"
+"CRC-32 checksum \'crc2\' of a sequence B of length \'len2\',\n"
+"return the CRC-32 checksum of A and B concatenated.");
+
+#define ZLIB_CRC32_COMBINE_METHODDEF \
+ {"crc32_combine", _PyCFunction_CAST(zlib_crc32_combine), METH_FASTCALL, zlib_crc32_combine__doc__},
+
+static unsigned int
+zlib_crc32_combine_impl(PyObject *module, unsigned int crc1,
+ unsigned int crc2, PyObject *len2);
+
+static PyObject *
+zlib_crc32_combine(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ unsigned int crc1;
+ unsigned int crc2;
+ PyObject *len2;
+ unsigned int _return_value;
+
+ if (!_PyArg_CheckPositional("crc32_combine", nargs, 3, 3)) {
+ goto exit;
+ }
+ crc1 = (unsigned int)PyLong_AsUnsignedLongMask(args[0]);
+ if (crc1 == (unsigned int)-1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ crc2 = (unsigned int)PyLong_AsUnsignedLongMask(args[1]);
+ if (crc2 == (unsigned int)-1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!PyLong_Check(args[2])) {
+ _PyArg_BadArgument("crc32_combine", "argument 3", "int", args[2]);
+ goto exit;
+ }
+ len2 = args[2];
+ _return_value = zlib_crc32_combine_impl(module, crc1, crc2, len2);
+ if ((_return_value == (unsigned int)-1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyLong_FromUnsignedLong((unsigned long)_return_value);
+
+exit:
+ return return_value;
+}
+
#ifndef ZLIB_COMPRESS_COPY_METHODDEF
#define ZLIB_COMPRESS_COPY_METHODDEF
#endif /* !defined(ZLIB_COMPRESS_COPY_METHODDEF) */
@@ -1121,4 +1239,4 @@ exit:
#ifndef ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF
#define ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF
#endif /* !defined(ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF) */
-/*[clinic end generated code: output=33938c7613a8c1c7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=3f7692eb3b5d5a0c input=a9049054013a1b77]*/
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
index d49ce794d88..73bea8172c7 100644
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -6,7 +6,6 @@
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_signal.h" // Py_NSIG
-#include "pycore_sysmodule.h" // _PySys_GetRequiredAttr()
#include "pycore_time.h" // _PyTime_FromSecondsObject()
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
#ifdef HAVE_UNISTD_H
@@ -98,7 +97,7 @@ faulthandler_get_fileno(PyObject **file_ptr)
PyObject *file = *file_ptr;
if (file == NULL || file == Py_None) {
- file = _PySys_GetRequiredAttr(&_Py_ID(stderr));
+ file = PySys_GetAttr(&_Py_ID(stderr));
if (file == NULL) {
return -1;
}
@@ -1069,18 +1068,6 @@ faulthandler_suppress_crash_report(void)
#endif
}
-static PyObject* _Py_NO_SANITIZE_UNDEFINED
-faulthandler_read_null(PyObject *self, PyObject *args)
-{
- volatile int *x;
- volatile int y;
-
- faulthandler_suppress_crash_report();
- x = NULL;
- y = *x;
- return PyLong_FromLong(y);
-
-}
static void
faulthandler_raise_sigsegv(void)
@@ -1158,23 +1145,12 @@ faulthandler_fatal_error_c_thread(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
-static PyObject* _Py_NO_SANITIZE_UNDEFINED
+static PyObject*
faulthandler_sigfpe(PyObject *self, PyObject *Py_UNUSED(dummy))
{
faulthandler_suppress_crash_report();
-
- /* Do an integer division by zero: raise a SIGFPE on Intel CPU, but not on
- PowerPC. Use volatile to disable compile-time optimizations. */
- volatile int x = 1, y = 0, z;
- z = x / y;
-
- /* If the division by zero didn't raise a SIGFPE (e.g. on PowerPC),
- raise it manually. */
raise(SIGFPE);
-
- /* This line is never reached, but we pretend to make something with z
- to silence a compiler warning. */
- return PyLong_FromLong(z);
+ Py_UNREACHABLE();
}
static PyObject *
@@ -1316,10 +1292,6 @@ static PyMethodDef module_methods[] = {
"Unregister the handler of the signal "
"'signum' registered by register().")},
#endif
- {"_read_null", faulthandler_read_null, METH_NOARGS,
- PyDoc_STR("_read_null($module, /)\n--\n\n"
- "Read from NULL, raise "
- "a SIGSEGV or SIGBUS signal depending on the platform.")},
{"_sigsegv", faulthandler_sigsegv, METH_VARARGS,
PyDoc_STR("_sigsegv($module, release_gil=False, /)\n--\n\n"
"Raise a SIGSEGV signal.")},
diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c
index 220ee9ecdff..90363b9dca3 100644
--- a/Modules/fcntlmodule.c
+++ b/Modules/fcntlmodule.c
@@ -93,29 +93,54 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg)
return NULL;
}
Py_ssize_t len = view.len;
- if (len > FCNTL_BUFSZ) {
- PyErr_SetString(PyExc_ValueError,
- "fcntl argument 3 is too long");
+ if (len <= FCNTL_BUFSZ) {
+ memcpy(buf, view.buf, len);
+ memcpy(buf + len, guard, GUARDSZ);
PyBuffer_Release(&view);
- return NULL;
- }
- memcpy(buf, view.buf, len);
- memcpy(buf + len, guard, GUARDSZ);
- PyBuffer_Release(&view);
- do {
- Py_BEGIN_ALLOW_THREADS
- ret = fcntl(fd, code, buf);
- Py_END_ALLOW_THREADS
- } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
- if (ret < 0) {
- return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
+ do {
+ Py_BEGIN_ALLOW_THREADS
+ ret = fcntl(fd, code, buf);
+ Py_END_ALLOW_THREADS
+ } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
+ if (ret < 0) {
+ return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
+ }
+ if (memcmp(buf + len, guard, GUARDSZ) != 0) {
+ PyErr_SetString(PyExc_SystemError, "buffer overflow");
+ return NULL;
+ }
+ return PyBytes_FromStringAndSize(buf, len);
}
- if (memcmp(buf + len, guard, GUARDSZ) != 0) {
- PyErr_SetString(PyExc_SystemError, "buffer overflow");
- return NULL;
+ else {
+ PyObject *result = PyBytes_FromStringAndSize(NULL, len);
+ if (result == NULL) {
+ PyBuffer_Release(&view);
+ return NULL;
+ }
+ char *ptr = PyBytes_AsString(result);
+ memcpy(ptr, view.buf, len);
+ PyBuffer_Release(&view);
+
+ do {
+ Py_BEGIN_ALLOW_THREADS
+ ret = fcntl(fd, code, ptr);
+ Py_END_ALLOW_THREADS
+ } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
+ if (ret < 0) {
+ if (!async_err) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ }
+ Py_DECREF(result);
+ return NULL;
+ }
+ if (ptr[len] != '\0') {
+ PyErr_SetString(PyExc_SystemError, "buffer overflow");
+ Py_DECREF(result);
+ return NULL;
+ }
+ return result;
}
- return PyBytes_FromStringAndSize(buf, len);
#undef FCNTL_BUFSZ
}
PyErr_Format(PyExc_TypeError,
@@ -251,29 +276,54 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long code, PyObject *arg,
return NULL;
}
Py_ssize_t len = view.len;
- if (len > IOCTL_BUFSZ) {
- PyErr_SetString(PyExc_ValueError,
- "ioctl argument 3 is too long");
+ if (len <= IOCTL_BUFSZ) {
+ memcpy(buf, view.buf, len);
+ memcpy(buf + len, guard, GUARDSZ);
PyBuffer_Release(&view);
- return NULL;
- }
- memcpy(buf, view.buf, len);
- memcpy(buf + len, guard, GUARDSZ);
- PyBuffer_Release(&view);
- do {
- Py_BEGIN_ALLOW_THREADS
- ret = ioctl(fd, code, buf);
- Py_END_ALLOW_THREADS
- } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
- if (ret < 0) {
- return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
+ do {
+ Py_BEGIN_ALLOW_THREADS
+ ret = ioctl(fd, code, buf);
+ Py_END_ALLOW_THREADS
+ } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
+ if (ret < 0) {
+ return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL;
+ }
+ if (memcmp(buf + len, guard, GUARDSZ) != 0) {
+ PyErr_SetString(PyExc_SystemError, "buffer overflow");
+ return NULL;
+ }
+ return PyBytes_FromStringAndSize(buf, len);
}
- if (memcmp(buf + len, guard, GUARDSZ) != 0) {
- PyErr_SetString(PyExc_SystemError, "buffer overflow");
- return NULL;
+ else {
+ PyObject *result = PyBytes_FromStringAndSize(NULL, len);
+ if (result == NULL) {
+ PyBuffer_Release(&view);
+ return NULL;
+ }
+ char *ptr = PyBytes_AsString(result);
+ memcpy(ptr, view.buf, len);
+ PyBuffer_Release(&view);
+
+ do {
+ Py_BEGIN_ALLOW_THREADS
+ ret = ioctl(fd, code, ptr);
+ Py_END_ALLOW_THREADS
+ } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
+ if (ret < 0) {
+ if (!async_err) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ }
+ Py_DECREF(result);
+ return NULL;
+ }
+ if (ptr[len] != '\0') {
+ PyErr_SetString(PyExc_SystemError, "buffer overflow");
+ Py_DECREF(result);
+ return NULL;
+ }
+ return result;
}
- return PyBytes_FromStringAndSize(buf, len);
#undef IOCTL_BUFSZ
}
PyErr_Format(PyExc_TypeError,
diff --git a/Modules/hashlib.h b/Modules/hashlib.h
index 7105e68af7b..9a7e72f34a7 100644
--- a/Modules/hashlib.h
+++ b/Modules/hashlib.h
@@ -34,45 +34,114 @@
/*
* Helper code to synchronize access to the hash object when the GIL is
- * released around a CPU consuming hashlib operation. All code paths that
- * access a mutable part of obj must be enclosed in an ENTER_HASHLIB /
- * LEAVE_HASHLIB block or explicitly acquire and release the lock inside
- * a PY_BEGIN / END_ALLOW_THREADS block if they wish to release the GIL for
- * an operation.
+ * released around a CPU consuming hashlib operation.
*
- * These only drop the GIL if the lock acquisition itself is likely to
- * block. Thus the non-blocking acquire gating the GIL release for a
- * blocking lock acquisition. The intent of these macros is to surround
- * the assumed always "fast" operations that you aren't releasing the
- * GIL around. Otherwise use code similar to what you see in hash
- * function update() methods.
+ * Code accessing a mutable part of the hash object must be enclosed in
+ * an HASHLIB_{ACQUIRE,RELEASE}_LOCK block or explicitly acquire and release
+ * the mutex inside a Py_BEGIN_ALLOW_THREADS -- Py_END_ALLOW_THREADS block if
+ * they wish to release the GIL for an operation.
*/
-#include "pythread.h"
-#define ENTER_HASHLIB(obj) \
- if ((obj)->use_mutex) { \
- PyMutex_Lock(&(obj)->mutex); \
- }
-#define LEAVE_HASHLIB(obj) \
- if ((obj)->use_mutex) { \
- PyMutex_Unlock(&(obj)->mutex); \
- }
+#define HASHLIB_OBJECT_HEAD \
+ PyObject_HEAD \
+ /* Guard against race conditions during incremental update(). */ \
+ PyMutex mutex;
-#ifdef Py_GIL_DISABLED
-#define HASHLIB_INIT_MUTEX(obj) \
- do { \
- (obj)->mutex = (PyMutex){0}; \
- (obj)->use_mutex = true; \
+#define HASHLIB_INIT_MUTEX(OBJ) \
+ do { \
+ (OBJ)->mutex = (PyMutex){0}; \
} while (0)
-#else
-#define HASHLIB_INIT_MUTEX(obj) \
- do { \
- (obj)->mutex = (PyMutex){0}; \
- (obj)->use_mutex = false; \
+
+#define HASHLIB_ACQUIRE_LOCK(OBJ) PyMutex_Lock(&(OBJ)->mutex)
+#define HASHLIB_RELEASE_LOCK(OBJ) PyMutex_Unlock(&(OBJ)->mutex)
+
+/*
+ * Message length above which the GIL is to be released
+ * when performing hashing operations.
+ */
+#define HASHLIB_GIL_MINSIZE 2048
+
+// Macros for executing code while conditionally holding the GIL.
+//
+// These only drop the GIL if the lock acquisition itself is likely to
+// block. Thus the non-blocking acquire gating the GIL release for a
+// blocking lock acquisition. The intent of these macros is to surround
+// the assumed always "fast" operations that you aren't releasing the
+// GIL around.
+
+/*
+ * Execute a suite of C statements 'STATEMENTS'.
+ *
+ * The GIL is held if 'SIZE' is below the HASHLIB_GIL_MINSIZE threshold.
+ */
+#define HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(SIZE, STATEMENTS) \
+ do { \
+ if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
+ Py_BEGIN_ALLOW_THREADS \
+ STATEMENTS; \
+ Py_END_ALLOW_THREADS \
+ } \
+ else { \
+ STATEMENTS; \
+ } \
} while (0)
-#endif
-/* TODO(gpshead): We should make this a module or class attribute
- * to allow the user to optimize based on the platform they're using. */
-#define HASHLIB_GIL_MINSIZE 2048
+/*
+ * Lock 'OBJ' and execute a suite of C statements 'STATEMENTS'.
+ *
+ * The GIL is held if 'SIZE' is below the HASHLIB_GIL_MINSIZE threshold.
+ */
+#define HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(OBJ, SIZE, STATEMENTS) \
+ do { \
+ if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
+ Py_BEGIN_ALLOW_THREADS \
+ HASHLIB_ACQUIRE_LOCK(OBJ); \
+ STATEMENTS; \
+ HASHLIB_RELEASE_LOCK(OBJ); \
+ Py_END_ALLOW_THREADS \
+ } \
+ else { \
+ HASHLIB_ACQUIRE_LOCK(OBJ); \
+ STATEMENTS; \
+ HASHLIB_RELEASE_LOCK(OBJ); \
+ } \
+ } while (0)
+static inline int
+_Py_hashlib_data_argument(PyObject **res, PyObject *data, PyObject *string)
+{
+ if (data != NULL && string == NULL) {
+ // called as H(data) or H(data=...)
+ *res = data;
+ return 1;
+ }
+ else if (data == NULL && string != NULL) {
+ // called as H(string=...)
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ "the 'string' keyword parameter is deprecated since "
+ "Python 3.15 and slated for removal in Python 3.19; "
+ "use the 'data' keyword parameter or pass the data "
+ "to hash as a positional argument instead", 1) < 0)
+ {
+ *res = NULL;
+ return -1;
+ }
+ *res = string;
+ return 1;
+ }
+ else if (data == NULL && string == NULL) {
+ // fast path when no data is given
+ assert(!PyErr_Occurred());
+ *res = NULL;
+ return 0;
+ }
+ else {
+ // called as H(data=..., string)
+ *res = NULL;
+ PyErr_SetString(PyExc_TypeError,
+ "'data' and 'string' are mutually exclusive "
+ "and support for 'string' keyword parameter "
+ "is slated for removal in a future version.");
+ return -1;
+ }
+}
diff --git a/Modules/hmacmodule.c b/Modules/hmacmodule.c
index c7b49d4dee3..95e400231bb 100644
--- a/Modules/hmacmodule.c
+++ b/Modules/hmacmodule.c
@@ -31,14 +31,15 @@
#endif
#if defined(__APPLE__) && defined(__arm64__)
-# undef HACL_CAN_COMPILE_SIMD128
-# undef HACL_CAN_COMPILE_SIMD256
+# undef _Py_HACL_CAN_COMPILE_VEC128
+# undef _Py_HACL_CAN_COMPILE_VEC256
#endif
-// Small mismatch between the variable names Python defines as part of configure
-// at the ones HACL* expects to be set in order to enable those headers.
-#define HACL_CAN_COMPILE_VEC128 HACL_CAN_COMPILE_SIMD128
-#define HACL_CAN_COMPILE_VEC256 HACL_CAN_COMPILE_SIMD256
+// HACL* expects HACL_CAN_COMPILE_VEC* macros to be set in order to enable
+// the corresponding SIMD instructions so we need to "forward" the values
+// we just deduced above.
+#define HACL_CAN_COMPILE_VEC128 _Py_HACL_CAN_COMPILE_VEC128
+#define HACL_CAN_COMPILE_VEC256 _Py_HACL_CAN_COMPILE_VEC256
#include "_hacl/Hacl_HMAC.h"
#include "_hacl/Hacl_Streaming_HMAC.h" // Hacl_Agile_Hash_* identifiers
@@ -216,105 +217,6 @@ typedef struct py_hmac_hacl_api {
#endif
/*
- * Call the HACL* HMAC-HASH update function on the given data.
- *
- * The magnitude of 'LEN' is not checked and thus 'LEN' must be
- * safely convertible to a uint32_t value.
- */
-#define Py_HMAC_HACL_UPDATE_CALL(HACL_STATE, BUF, LEN) \
- Hacl_Streaming_HMAC_update(HACL_STATE, BUF, (uint32_t)(LEN))
-
-/*
- * Call the HACL* HMAC-HASH update function on the given data.
- *
- * On DEBUG builds, the 'ERRACTION' statements are executed if
- * the update() call returned a non-successful HACL* exit code.
- *
- * The buffer 'BUF' and its length 'LEN' are left untouched.
- *
- * The formal signature of this macro is:
- *
- * (HACL_HMAC_state *, uint8_t *, uint32_t, PyObject *, (C statements))
- */
-#ifndef NDEBUG
-#define Py_HMAC_HACL_UPDATE_ONCE( \
- HACL_STATE, BUF, LEN, \
- ALGORITHM, ERRACTION \
-) \
- do { \
- Py_CHECK_HACL_UINT32_T_LENGTH(LEN); \
- hacl_errno_t code = Py_HMAC_HACL_UPDATE_CALL(HACL_STATE, BUF, LEN); \
- if (_hacl_convert_errno(code, (ALGORITHM)) < 0) { \
- ERRACTION; \
- } \
- } while (0)
-#else
-#define Py_HMAC_HACL_UPDATE_ONCE( \
- HACL_STATE, BUF, LEN, \
- _ALGORITHM, _ERRACTION \
-) \
- do { \
- (void)Py_HMAC_HACL_UPDATE_CALL(HACL_STATE, BUF, (LEN)); \
- } while (0)
-#endif
-
-/*
- * Repetivively call the HACL* HMAC-HASH update function on the given
- * data until the buffer length 'LEN' is strictly less than UINT32_MAX.
- *
- * On builds with PY_SSIZE_T_MAX <= UINT32_MAX, this is a no-op.
- *
- * The buffer 'BUF' (resp. 'LEN') is advanced (resp. decremented)
- * by UINT32_MAX after each update. On DEBUG builds, each update()
- * call is verified and the 'ERRACTION' statements are executed if
- * a non-successful HACL* exit code is being returned.
- *
- * In particular, 'BUF' and 'LEN' must be variable names and not
- * expressions on their own.
- *
- * The formal signature of this macro is:
- *
- * (HACL_HMAC_state *, uint8_t *, C integer, PyObject *, (C statements))
- */
-#ifdef Py_HMAC_SSIZE_LARGER_THAN_UINT32
-#define Py_HMAC_HACL_UPDATE_LOOP( \
- HACL_STATE, BUF, LEN, \
- ALGORITHM, ERRACTION \
-) \
- do { \
- while ((Py_ssize_t)LEN > UINT32_MAX_AS_SSIZE_T) { \
- Py_HMAC_HACL_UPDATE_ONCE(HACL_STATE, BUF, UINT32_MAX, \
- ALGORITHM, ERRACTION); \
- BUF += UINT32_MAX; \
- LEN -= UINT32_MAX; \
- } \
- } while (0)
-#else
-#define Py_HMAC_HACL_UPDATE_LOOP( \
- HACL_STATE, BUF, LEN, \
- _ALGORITHM, _ERRACTION \
-)
-#endif
-
-/*
- * Perform the HMAC-HASH update() operation in a streaming fashion.
- *
- * The formal signature of this macro is:
- *
- * (HACL_HMAC_state *, uint8_t *, C integer, PyObject *, (C statements))
- */
-#define Py_HMAC_HACL_UPDATE( \
- HACL_STATE, BUF, LEN, \
- ALGORITHM, ERRACTION \
-) \
- do { \
- Py_HMAC_HACL_UPDATE_LOOP(HACL_STATE, BUF, LEN, \
- ALGORITHM, ERRACTION); \
- Py_HMAC_HACL_UPDATE_ONCE(HACL_STATE, BUF, LEN, \
- ALGORITHM, ERRACTION); \
- } while (0)
-
-/*
* HMAC underlying hash function static information.
*/
typedef struct py_hmac_hinfo {
@@ -382,11 +284,7 @@ get_hmacmodule_state_by_cls(PyTypeObject *cls)
typedef Hacl_Streaming_HMAC_agile_state HACL_HMAC_state;
typedef struct HMACObject {
- PyObject_HEAD
-
- bool use_mutex;
- PyMutex mutex;
-
+ HASHLIB_OBJECT_HEAD
// Hash function information
PyObject *name; // rendered name (exact unicode object)
HMAC_Hash_Kind kind; // can be used for runtime dispatch (must be known)
@@ -464,7 +362,7 @@ narrow_hmac_hash_kind(hmacmodule_state *state, HMAC_Hash_Kind kind)
{
switch (kind) {
case Py_hmac_kind_hmac_blake2s_32: {
-#if HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
if (state->can_run_simd128) {
return Py_hmac_kind_hmac_vectorized_blake2s_32;
}
@@ -472,7 +370,7 @@ narrow_hmac_hash_kind(hmacmodule_state *state, HMAC_Hash_Kind kind)
return kind;
}
case Py_hmac_kind_hmac_blake2b_32: {
-#if HACL_CAN_COMPILE_SIMD256
+#if _Py_HACL_CAN_COMPILE_VEC256
if (state->can_run_simd256) {
return Py_hmac_kind_hmac_vectorized_blake2b_32;
}
@@ -491,38 +389,40 @@ narrow_hmac_hash_kind(hmacmodule_state *state, HMAC_Hash_Kind kind)
* Otherwise, this sets an appropriate exception and returns -1.
*/
static int
-_hacl_convert_errno(hacl_errno_t code, PyObject *algorithm)
+_hacl_convert_errno(hacl_errno_t code)
{
+ assert(PyGILState_GetThisThreadState() != NULL);
+ if (code == Hacl_Streaming_Types_Success) {
+ return 0;
+ }
+
+ PyGILState_STATE gstate = PyGILState_Ensure();
switch (code) {
- case Hacl_Streaming_Types_Success: {
- return 0;
- }
case Hacl_Streaming_Types_InvalidAlgorithm: {
- // only makes sense if an algorithm is known at call time
- assert(algorithm != NULL);
- assert(PyUnicode_CheckExact(algorithm));
- PyErr_Format(PyExc_ValueError, "invalid algorithm: %U", algorithm);
- return -1;
+ PyErr_SetString(PyExc_ValueError, "invalid HACL* algorithm");
+ break;
}
case Hacl_Streaming_Types_InvalidLength: {
PyErr_SetString(PyExc_ValueError, "invalid length");
- return -1;
+ break;
}
case Hacl_Streaming_Types_MaximumLengthExceeded: {
PyErr_SetString(PyExc_OverflowError, "maximum length exceeded");
- return -1;
+ break;
}
case Hacl_Streaming_Types_OutOfMemory: {
PyErr_NoMemory();
- return -1;
+ break;
}
default: {
PyErr_Format(PyExc_RuntimeError,
- "HACL* internal routine failed with error code: %d",
+ "HACL* internal routine failed with error code: %u",
code);
- return -1;
+ break;
}
}
+ PyGILState_Release(gstate);
+ return -1;
}
/*
@@ -536,7 +436,7 @@ _hacl_hmac_state_new(HMAC_Hash_Kind kind, uint8_t *key, uint32_t len)
assert(kind != Py_hmac_kind_hash_unknown);
HACL_HMAC_state *state = NULL;
hacl_errno_t retcode = Hacl_Streaming_HMAC_malloc_(kind, key, len, &state);
- if (_hacl_convert_errno(retcode, NULL) < 0) {
+ if (_hacl_convert_errno(retcode) < 0) {
assert(state == NULL);
return NULL;
}
@@ -554,6 +454,51 @@ _hacl_hmac_state_free(HACL_HMAC_state *state)
}
}
+/*
+ * Call the HACL* HMAC-HASH update function on the given data.
+ *
+ * On DEBUG builds, the update() call is verified.
+ *
+ * Return 0 on success; otherwise, set an exception and return -1 on failure.
+*/
+static int
+_hacl_hmac_state_update_once(HACL_HMAC_state *state,
+ uint8_t *buf, uint32_t len)
+{
+#ifndef NDEBUG
+ hacl_errno_t code = Hacl_Streaming_HMAC_update(state, buf, len);
+ return _hacl_convert_errno(code);
+#else
+ (void)Hacl_Streaming_HMAC_update(state, buf, len);
+ return 0;
+#endif
+}
+
+/*
+ * Perform the HMAC-HASH update() operation in a streaming fashion.
+ *
+ * On DEBUG builds, each update() call is verified.
+ *
+ * Return 0 on success; otherwise, set an exception and return -1 on failure.
+ */
+static int
+_hacl_hmac_state_update(HACL_HMAC_state *state, uint8_t *buf, Py_ssize_t len)
+{
+ assert(len >= 0);
+#ifdef Py_HMAC_SSIZE_LARGER_THAN_UINT32
+ while (len > UINT32_MAX_AS_SSIZE_T) {
+ if (_hacl_hmac_state_update_once(state, buf, UINT32_MAX) < 0) {
+ assert(PyErr_Occurred());
+ return -1;
+ }
+ buf += UINT32_MAX;
+ len -= UINT32_MAX;
+ }
+#endif
+ Py_CHECK_HACL_UINT32_T_LENGTH(len);
+ return _hacl_hmac_state_update_once(state, buf, (uint32_t)len);
+}
+
/* Static information used to construct the hash table. */
static const py_hmac_hinfo py_hmac_static_hinfo[] = {
#define Py_HMAC_HINFO_HACL_API(HACL_HID) \
@@ -784,45 +729,6 @@ hmac_new_initial_state(HMACObject *self, uint8_t *key, Py_ssize_t len)
return self->state == NULL ? -1 : 0;
}
-/*
- * Feed initial data.
- *
- * This function MUST only be called by the HMAC object constructor
- * and after hmac_set_hinfo() and hmac_new_initial_state() have been
- * called, lest the behaviour is undefined.
- *
- * Return 0 on success; otherwise, set an exception and return -1 on failure.
- */
-static int
-hmac_feed_initial_data(HMACObject *self, uint8_t *msg, Py_ssize_t len)
-{
- assert(self->name != NULL);
- assert(self->state != NULL);
- if (len == 0) {
- // do nothing if the buffer is empty
- return 0;
- }
-
- if (len < HASHLIB_GIL_MINSIZE) {
- Py_HMAC_HACL_UPDATE(self->state, msg, len, self->name, return -1);
- return 0;
- }
-
- int res = 0;
- Py_BEGIN_ALLOW_THREADS
- Py_HMAC_HACL_UPDATE(self->state, msg, len, self->name, goto error);
- goto done;
-#ifndef NDEBUG
-error:
- res = -1;
-#else
- Py_UNREACHABLE();
-#endif
-done:
- Py_END_ALLOW_THREADS
- return res;
-}
-
/*[clinic input]
_hmac.new
@@ -869,7 +775,12 @@ _hmac_new_impl(PyObject *module, PyObject *keyobj, PyObject *msgobj,
if (msgobj != NULL && msgobj != Py_None) {
Py_buffer msg;
GET_BUFFER_VIEW_OR_ERROR(msgobj, &msg, goto error);
- rc = hmac_feed_initial_data(self, msg.buf, msg.len);
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ msg.len,
+ rc = _hacl_hmac_state_update(self->state, msg.buf, msg.len)
+ );
PyBuffer_Release(&msg);
#ifndef NDEBUG
if (rc < 0) {
@@ -946,12 +857,12 @@ _hmac_HMAC_copy_impl(HMACObject *self, PyTypeObject *cls)
return NULL;
}
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
/* copy hash information */
hmac_copy_hinfo(copy, self);
/* copy internal state */
int rc = hmac_copy_state(copy, self);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
if (rc < 0) {
Py_DECREF(copy);
@@ -963,78 +874,6 @@ _hmac_HMAC_copy_impl(HMACObject *self, PyTypeObject *cls)
return (PyObject *)copy;
}
-/*
- * Update the HMAC object with the given buffer.
- *
- * This unconditionally acquires the lock on the HMAC object.
- *
- * On DEBUG builds, each update() call is verified.
- *
- * Return 0 on success; otherwise, set an exception and return -1 on failure.
- */
-static int
-hmac_update_state_with_lock(HMACObject *self, uint8_t *buf, Py_ssize_t len)
-{
- int res = 0;
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex); // unconditionally acquire a lock
- Py_HMAC_HACL_UPDATE(self->state, buf, len, self->name, goto error);
- goto done;
-#ifndef NDEBUG
-error:
- res = -1;
-#else
- Py_UNREACHABLE();
-#endif
-done:
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- return res;
-}
-
-/*
- * Update the HMAC object with the given buffer.
- *
- * This conditionally acquires the lock on the HMAC object.
- *
- * On DEBUG builds, each update() call is verified.
- *
- * Return 0 on success; otherwise, set an exception and return -1 on failure.
- */
-static int
-hmac_update_state_cond_lock(HMACObject *self, uint8_t *buf, Py_ssize_t len)
-{
- ENTER_HASHLIB(self); // conditionally acquire a lock
- Py_HMAC_HACL_UPDATE(self->state, buf, len, self->name, goto error);
- LEAVE_HASHLIB(self);
- return 0;
-
-#ifndef NDEBUG
-error:
- LEAVE_HASHLIB(self);
- return -1;
-#else
- Py_UNREACHABLE();
-#endif
-}
-
-/*
- * Update the internal HMAC state with the given buffer.
- *
- * Return 0 on success; otherwise, set an exception and return -1 on failure.
- */
-static inline int
-hmac_update_state(HMACObject *self, uint8_t *buf, Py_ssize_t len)
-{
- assert(buf != 0);
- assert(len >= 0);
- return len == 0
- ? 0 /* nothing to do */
- : len < HASHLIB_GIL_MINSIZE
- ? hmac_update_state_cond_lock(self, buf, len)
- : hmac_update_state_with_lock(self, buf, len);
-}
-
/*[clinic input]
_hmac.HMAC.update
@@ -1047,9 +886,13 @@ static PyObject *
_hmac_HMAC_update_impl(HMACObject *self, PyObject *msgobj)
/*[clinic end generated code: output=962134ada5e55985 input=7c0ea830efb03367]*/
{
+ int rc = 0;
Py_buffer msg;
GET_BUFFER_VIEW_OR_ERROUT(msgobj, &msg);
- int rc = hmac_update_state(self, msg.buf, msg.len);
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, msg.len,
+ rc = _hacl_hmac_state_update(self->state, msg.buf, msg.len)
+ );
PyBuffer_Release(&msg);
return rc < 0 ? NULL : Py_None;
}
@@ -1065,18 +908,18 @@ _hmac_HMAC_update_impl(HMACObject *self, PyObject *msgobj)
* Note: this function may raise a MemoryError.
*/
static int
-hmac_digest_compute_cond_lock(HMACObject *self, uint8_t *digest)
+hmac_digest_compute_locked(HMACObject *self, uint8_t *digest)
{
assert(digest != NULL);
hacl_errno_t rc;
- ENTER_HASHLIB(self); // conditionally acquire a lock
+ HASHLIB_ACQUIRE_LOCK(self);
rc = Hacl_Streaming_HMAC_digest(self->state, digest, self->digest_size);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
assert(
rc == Hacl_Streaming_Types_Success ||
rc == Hacl_Streaming_Types_OutOfMemory
);
- return _hacl_convert_errno(rc, NULL);
+ return _hacl_convert_errno(rc);
}
/*[clinic input]
@@ -1093,7 +936,7 @@ _hmac_HMAC_digest_impl(HMACObject *self)
{
assert(self->digest_size <= Py_hmac_hash_max_digest_size);
uint8_t digest[Py_hmac_hash_max_digest_size];
- if (hmac_digest_compute_cond_lock(self, digest) < 0) {
+ if (hmac_digest_compute_locked(self, digest) < 0) {
return NULL;
}
return PyBytes_FromStringAndSize((const char *)digest, self->digest_size);
@@ -1116,7 +959,7 @@ _hmac_HMAC_hexdigest_impl(HMACObject *self)
{
assert(self->digest_size <= Py_hmac_hash_max_digest_size);
uint8_t digest[Py_hmac_hash_max_digest_size];
- if (hmac_digest_compute_cond_lock(self, digest) < 0) {
+ if (hmac_digest_compute_locked(self, digest) < 0) {
return NULL;
}
return _Py_strhex((const char *)digest, self->digest_size);
@@ -1715,11 +1558,11 @@ hmacmodule_init_cpu_features(hmacmodule_state *state)
__cpuid_count(1, 0, eax1, ebx1, ecx1, edx1);
__cpuid_count(7, 0, eax7, ebx7, ecx7, edx7);
#elif defined(_M_X64)
- int info1[4] = { 0 };
+ int info1[4] = {0};
__cpuidex(info1, 1, 0);
eax1 = info1[0], ebx1 = info1[1], ecx1 = info1[2], edx1 = info1[3];
- int info7[4] = { 0 };
+ int info7[4] = {0};
__cpuidex(info7, 7, 0);
eax7 = info7[0], ebx7 = info7[1], ecx7 = info7[2], edx7 = info7[3];
#endif
@@ -1759,7 +1602,7 @@ hmacmodule_init_cpu_features(hmacmodule_state *state)
#undef ECX_SSE3
#undef EBX_AVX2
-#if HACL_CAN_COMPILE_SIMD128
+#if _Py_HACL_CAN_COMPILE_VEC128
// TODO(picnixz): use py_cpuid_features (gh-125022) to improve detection
state->can_run_simd128 = sse && sse2 && sse3 && sse41 && sse42 && cmov;
#else
@@ -1769,7 +1612,7 @@ hmacmodule_init_cpu_features(hmacmodule_state *state)
state->can_run_simd128 = false;
#endif
-#if HACL_CAN_COMPILE_SIMD256
+#if _Py_HACL_CAN_COMPILE_VEC256
// TODO(picnixz): use py_cpuid_features (gh-125022) to improve detection
state->can_run_simd256 = state->can_run_simd128 && avx && avx2;
#else
diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c
index 943c1e8607b..cc1a5580015 100644
--- a/Modules/itertoolsmodule.c
+++ b/Modules/itertoolsmodule.c
@@ -1124,7 +1124,6 @@ typedef struct {
PyObject *it;
PyObject *saved;
Py_ssize_t index;
- int firstpass;
} cycleobject;
#define cycleobject_CAST(op) ((cycleobject *)(op))
@@ -1165,8 +1164,7 @@ itertools_cycle_impl(PyTypeObject *type, PyObject *iterable)
}
lz->it = it;
lz->saved = saved;
- lz->index = 0;
- lz->firstpass = 0;
+ lz->index = -1;
return (PyObject *)lz;
}
@@ -1199,11 +1197,11 @@ cycle_next(PyObject *op)
cycleobject *lz = cycleobject_CAST(op);
PyObject *item;
- if (lz->it != NULL) {
+ Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(lz->index);
+
+ if (index < 0) {
item = PyIter_Next(lz->it);
if (item != NULL) {
- if (lz->firstpass)
- return item;
if (PyList_Append(lz->saved, item)) {
Py_DECREF(item);
return NULL;
@@ -1213,15 +1211,22 @@ cycle_next(PyObject *op)
/* Note: StopIteration is already cleared by PyIter_Next() */
if (PyErr_Occurred())
return NULL;
+ index = 0;
+ FT_ATOMIC_STORE_SSIZE_RELAXED(lz->index, 0);
+#ifndef Py_GIL_DISABLED
Py_CLEAR(lz->it);
+#endif
}
if (PyList_GET_SIZE(lz->saved) == 0)
return NULL;
- item = PyList_GET_ITEM(lz->saved, lz->index);
- lz->index++;
- if (lz->index >= PyList_GET_SIZE(lz->saved))
- lz->index = 0;
- return Py_NewRef(item);
+ item = PyList_GetItemRef(lz->saved, index);
+ assert(item);
+ index++;
+ if (index >= PyList_GET_SIZE(lz->saved)) {
+ index = 0;
+ }
+ FT_ATOMIC_STORE_SSIZE_RELAXED(lz->index, index);
+ return item;
}
static PyType_Slot cycle_slots[] = {
@@ -1875,8 +1880,8 @@ chain_traverse(PyObject *op, visitproc visit, void *arg)
return 0;
}
-static PyObject *
-chain_next(PyObject *op)
+static inline PyObject *
+chain_next_lock_held(PyObject *op)
{
chainobject *lz = chainobject_CAST(op);
PyObject *item;
@@ -1914,6 +1919,16 @@ chain_next(PyObject *op)
return NULL;
}
+static PyObject *
+chain_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = chain_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
PyDoc_STRVAR(chain_doc,
"chain(*iterables)\n\
--\n\
@@ -2081,7 +2096,7 @@ product_traverse(PyObject *op, visitproc visit, void *arg)
}
static PyObject *
-product_next(PyObject *op)
+product_next_lock_held(PyObject *op)
{
productobject *lz = productobject_CAST(op);
PyObject *pool;
@@ -2167,6 +2182,16 @@ empty:
return NULL;
}
+static PyObject *
+product_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = product_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
static PyMethodDef product_methods[] = {
{"__sizeof__", product_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
@@ -2314,7 +2339,7 @@ combinations_traverse(PyObject *op, visitproc visit, void *arg)
}
static PyObject *
-combinations_next(PyObject *op)
+combinations_next_lock_held(PyObject *op)
{
combinationsobject *co = combinationsobject_CAST(op);
PyObject *elem;
@@ -2399,6 +2424,16 @@ empty:
return NULL;
}
+static PyObject *
+combinations_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = combinations_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
static PyMethodDef combinations_methods[] = {
{"__sizeof__", combinations_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
diff --git a/Modules/main.c b/Modules/main.c
index ea1239ecc57..74e48c94732 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -128,7 +128,7 @@ pymain_get_importer(const wchar_t *filename, PyObject **importer_p, int *exitcod
{
PyObject *sys_path0 = NULL, *importer;
- sys_path0 = PyUnicode_FromWideChar(filename, wcslen(filename));
+ sys_path0 = PyUnicode_FromWideChar(filename, -1);
if (sys_path0 == NULL) {
goto error;
}
@@ -269,13 +269,14 @@ error:
static int
-pymain_start_pyrepl_no_main(void)
+pymain_start_pyrepl(int pythonstartup)
{
int res = 0;
PyObject *console = NULL;
PyObject *empty_tuple = NULL;
PyObject *kwargs = NULL;
PyObject *console_result = NULL;
+ PyObject *main_module = NULL;
PyObject *pyrepl = PyImport_ImportModule("_pyrepl.main");
if (pyrepl == NULL) {
@@ -299,7 +300,13 @@ pymain_start_pyrepl_no_main(void)
res = pymain_exit_err_print();
goto done;
}
- if (!PyDict_SetItemString(kwargs, "pythonstartup", _PyLong_GetOne())) {
+ main_module = PyImport_AddModuleRef("__main__");
+ if (main_module == NULL) {
+ res = pymain_exit_err_print();
+ goto done;
+ }
+ if (!PyDict_SetItemString(kwargs, "mainmodule", main_module)
+ && !PyDict_SetItemString(kwargs, "pythonstartup", pythonstartup ? Py_True : Py_False)) {
console_result = PyObject_Call(console, empty_tuple, kwargs);
if (console_result == NULL) {
res = pymain_exit_err_print();
@@ -311,6 +318,7 @@ done:
Py_XDECREF(empty_tuple);
Py_XDECREF(console);
Py_XDECREF(pyrepl);
+ Py_XDECREF(main_module);
return res;
}
@@ -328,7 +336,7 @@ pymain_run_module(const wchar_t *modname, int set_argv0)
fprintf(stderr, "Could not import runpy._run_module_as_main\n");
return pymain_exit_err_print();
}
- module = PyUnicode_FromWideChar(modname, wcslen(modname));
+ module = PyUnicode_FromWideChar(modname, -1);
if (module == NULL) {
fprintf(stderr, "Could not convert module name to unicode\n");
Py_DECREF(runmodule);
@@ -439,7 +447,7 @@ pymain_run_startup(PyConfig *config, int *exitcode)
if (env == NULL || env[0] == L'\0') {
return 0;
}
- startup = PyUnicode_FromWideChar(env, wcslen(env));
+ startup = PyUnicode_FromWideChar(env, -1);
if (startup == NULL) {
goto error;
}
@@ -489,16 +497,13 @@ error:
static int
pymain_run_interactive_hook(int *exitcode)
{
- PyObject *hook = PyImport_ImportModuleAttrString("sys",
- "__interactivehook__");
- if (hook == NULL) {
- if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
- // no sys.__interactivehook__ attribute
- PyErr_Clear();
- return 0;
- }
+ PyObject *hook;
+ if (PySys_GetOptionalAttrString("__interactivehook__", &hook) < 0) {
goto error;
}
+ if (hook == NULL) {
+ return 0;
+ }
if (PySys_Audit("cpython.run_interactivehook", "O", hook) < 0) {
goto error;
@@ -562,7 +567,7 @@ pymain_run_stdin(PyConfig *config)
int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
return (run != 0);
}
- return pymain_run_module(L"_pyrepl", 0);
+ return pymain_start_pyrepl(0);
}
@@ -595,7 +600,7 @@ pymain_repl(PyConfig *config, int *exitcode)
*exitcode = (run != 0);
return;
}
- int run = pymain_start_pyrepl_no_main();
+ int run = pymain_start_pyrepl(1);
*exitcode = (run != 0);
return;
}
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 11d9b7418a2..033de0b2907 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -1233,6 +1233,23 @@ FUNC2(remainder, m_remainder,
"Return x - n*y where n*y is the closest integer multiple of y.\n"
"In the case where x is exactly halfway between two multiples of\n"
"y, the nearest even value of n is used. The result is always exact.")
+
+/*[clinic input]
+math.signbit
+
+ x: double
+ /
+
+Return True if the sign of x is negative and False otherwise.
+[clinic start generated code]*/
+
+static PyObject *
+math_signbit_impl(PyObject *module, double x)
+/*[clinic end generated code: output=20c5f20156a9b871 input=3d3493fbcb5bdb3e]*/
+{
+ return PyBool_FromLong(signbit(x));
+}
+
FUNC1D(sin, sin, 0,
"sin($module, x, /)\n--\n\n"
"Return the sine of x (measured in radians).",
@@ -2008,13 +2025,11 @@ math.factorial
/
Find n!.
-
-Raise a ValueError if x is negative or non-integral.
[clinic start generated code]*/
static PyObject *
math_factorial(PyObject *module, PyObject *arg)
-/*[clinic end generated code: output=6686f26fae00e9ca input=713fb771677e8c31]*/
+/*[clinic end generated code: output=6686f26fae00e9ca input=366cc321df3d4773]*/
{
long x, two_valuation;
int overflow;
@@ -2163,6 +2178,27 @@ math_ldexp_impl(PyObject *module, double x, PyObject *i)
} else {
errno = 0;
r = ldexp(x, (int)exp);
+#ifdef _MSC_VER
+ if (DBL_MIN > r && r > -DBL_MIN) {
+ /* Denormal (or zero) results can be incorrectly rounded here (rather,
+ truncated). Fixed in newer versions of the C runtime, included
+ with Windows 11. */
+ int original_exp;
+ frexp(x, &original_exp);
+ if (original_exp > DBL_MIN_EXP) {
+ /* Shift down to the smallest normal binade. No bits lost. */
+ int shift = DBL_MIN_EXP - original_exp;
+ x = ldexp(x, shift);
+ exp -= shift;
+ }
+ /* Multiplying by 2**exp finishes the job, and the HW will round as
+ appropriate. Note: if exp < -DBL_MANT_DIG, all of x is shifted
+ to be < 0.5ULP of smallest denorm, so should be thrown away. If
+ exp is so very negative that ldexp underflows to 0, that's fine;
+ no need to check in advance. */
+ r = x*ldexp(1.0, (int)exp);
+ }
+#endif
if (isinf(r))
errno = ERANGE;
}
@@ -3100,6 +3136,44 @@ math_isfinite_impl(PyObject *module, double x)
/*[clinic input]
+math.isnormal
+
+ x: double
+ /
+
+Return True if x is normal, and False otherwise.
+[clinic start generated code]*/
+
+static PyObject *
+math_isnormal_impl(PyObject *module, double x)
+/*[clinic end generated code: output=c7b302b5b89c3541 input=fdaa00c58aa7bc17]*/
+{
+ return PyBool_FromLong(isnormal(x));
+}
+
+
+/*[clinic input]
+math.issubnormal
+
+ x: double
+ /
+
+Return True if x is subnormal, and False otherwise.
+[clinic start generated code]*/
+
+static PyObject *
+math_issubnormal_impl(PyObject *module, double x)
+/*[clinic end generated code: output=4e76ac98ddcae761 input=9a20aba7107d0d95]*/
+{
+#if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
+ return PyBool_FromLong(issubnormal(x));
+#else
+ return PyBool_FromLong(isfinite(x) && x && !isnormal(x));
+#endif
+}
+
+
+/*[clinic input]
math.isnan
x: double
@@ -4126,6 +4200,8 @@ static PyMethodDef math_methods[] = {
MATH_HYPOT_METHODDEF
MATH_ISCLOSE_METHODDEF
MATH_ISFINITE_METHODDEF
+ MATH_ISNORMAL_METHODDEF
+ MATH_ISSUBNORMAL_METHODDEF
MATH_ISINF_METHODDEF
MATH_ISNAN_METHODDEF
MATH_ISQRT_METHODDEF
@@ -4140,6 +4216,7 @@ static PyMethodDef math_methods[] = {
MATH_POW_METHODDEF
MATH_RADIANS_METHODDEF
{"remainder", _PyCFunction_CAST(math_remainder), METH_FASTCALL, math_remainder_doc},
+ MATH_SIGNBIT_METHODDEF
{"sin", math_sin, METH_O, math_sin_doc},
{"sinh", math_sinh, METH_O, math_sinh_doc},
{"sqrt", math_sqrt, METH_O, math_sqrt_doc},
diff --git a/Modules/md5module.c b/Modules/md5module.c
index c36eb41d4d2..8b6dd4a8195 100644
--- a/Modules/md5module.c
+++ b/Modules/md5module.c
@@ -8,6 +8,7 @@
Andrew Kuchling (amk@amk.ca)
Greg Stein (gstein@lyra.org)
Trevor Perrin (trevp@trevp.net)
+ Bénédikt Tran (10796600+picnixz@users.noreply.github.com)
Copyright (C) 2005-2007 Gregory P. Smith (greg@krypto.org)
Licensed to PSF under a Contributor Agreement.
@@ -21,34 +22,27 @@
#endif
#include "Python.h"
+#include "pycore_strhex.h" // _Py_strhex()
+
#include "hashlib.h"
-/*[clinic input]
-module _md5
-class MD5Type "MD5object *" "&PyType_Type"
-[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6e5261719957a912]*/
+#include "_hacl/Hacl_Hash_MD5.h"
/* The MD5 block size and message digest sizes, in bytes */
#define MD5_BLOCKSIZE 64
#define MD5_DIGESTSIZE 16
-#include "_hacl/Hacl_Hash_MD5.h"
-
+// --- Module objects ---------------------------------------------------------
typedef struct {
- PyObject_HEAD
- // Prevents undefined behavior via multiple threads entering the C API.
- bool use_mutex;
- PyMutex mutex;
+ HASHLIB_OBJECT_HEAD
Hacl_Hash_MD5_state_t *hash_state;
} MD5object;
#define _MD5object_CAST(op) ((MD5object *)(op))
-#include "clinic/md5module.c.h"
-
+// --- Module state -----------------------------------------------------------
typedef struct {
PyTypeObject* md5_type;
@@ -62,6 +56,18 @@ md5_get_state(PyObject *module)
return (MD5State *)state;
}
+// --- Module clinic configuration --------------------------------------------
+
+/*[clinic input]
+module _md5
+class MD5Type "MD5object *" "&PyType_Type"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6e5261719957a912]*/
+
+#include "clinic/md5module.c.h"
+
+// --- MD5 object interface ---------------------------------------------------
+
static MD5object *
newMD5object(MD5State * st)
{
@@ -116,11 +122,11 @@ MD5Type_copy_impl(MD5object *self, PyTypeObject *cls)
return NULL;
}
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
newobj->hash_state = Hacl_Hash_MD5_copy(self->hash_state);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
if (newobj->hash_state == NULL) {
- Py_DECREF(self);
+ Py_DECREF(newobj);
return PyErr_NoMemory();
}
return (PyObject *)newobj;
@@ -136,10 +142,10 @@ static PyObject *
MD5Type_digest_impl(MD5object *self)
/*[clinic end generated code: output=eb691dc4190a07ec input=bc0c4397c2994be6]*/
{
- unsigned char digest[MD5_DIGESTSIZE];
- ENTER_HASHLIB(self);
+ uint8_t digest[MD5_DIGESTSIZE];
+ HASHLIB_ACQUIRE_LOCK(self);
Hacl_Hash_MD5_digest(self->hash_state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return PyBytes_FromStringAndSize((const char *)digest, MD5_DIGESTSIZE);
}
@@ -153,20 +159,11 @@ static PyObject *
MD5Type_hexdigest_impl(MD5object *self)
/*[clinic end generated code: output=17badced1f3ac932 input=b60b19de644798dd]*/
{
- unsigned char digest[MD5_DIGESTSIZE];
- ENTER_HASHLIB(self);
+ uint8_t digest[MD5_DIGESTSIZE];
+ HASHLIB_ACQUIRE_LOCK(self);
Hacl_Hash_MD5_digest(self->hash_state, digest);
- LEAVE_HASHLIB(self);
-
- const char *hexdigits = "0123456789abcdef";
- char digest_hex[MD5_DIGESTSIZE * 2];
- char *str = digest_hex;
- for (size_t i=0; i < MD5_DIGESTSIZE; i++) {
- unsigned char byte = digest[i];
- *str++ = hexdigits[byte >> 4];
- *str++ = hexdigits[byte & 0x0f];
- }
- return PyUnicode_FromStringAndSize(digest_hex, sizeof(digest_hex));
+ HASHLIB_RELEASE_LOCK(self);
+ return _Py_strhex((const char *)digest, MD5_DIGESTSIZE);
}
static void
@@ -177,6 +174,7 @@ update(Hacl_Hash_MD5_state_t *state, uint8_t *buf, Py_ssize_t len)
* take more than 1 billion years to overflow the maximum admissible length
* for MD5 (2^61 - 1).
*/
+ assert(len >= 0);
#if PY_SSIZE_T_MAX > UINT32_MAX
while (len > UINT32_MAX) {
(void)Hacl_Hash_MD5_update(state, buf, UINT32_MAX);
@@ -202,22 +200,11 @@ MD5Type_update_impl(MD5object *self, PyObject *obj)
/*[clinic end generated code: output=b0fed9a7ce7ad253 input=6e1efcd9ecf17032]*/
{
Py_buffer buf;
-
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
-
- if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- update(self->hash_state, buf.buf, buf.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- update(self->hash_state, buf.buf, buf.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, buf.len,
+ update(self->hash_state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
@@ -276,17 +263,24 @@ static PyType_Spec md5_type_spec = {
/*[clinic input]
_md5.md5
- string: object(c_default="NULL") = b''
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string as string_obj: object(c_default="NULL") = None
Return a new MD5 hash object; optionally initialized with a string.
[clinic start generated code]*/
static PyObject *
-_md5_md5_impl(PyObject *module, PyObject *string, int usedforsecurity)
-/*[clinic end generated code: output=587071f76254a4ac input=7a144a1905636985]*/
+_md5_md5_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj)
+/*[clinic end generated code: output=d45e187d3d16f3a8 input=7ea5c5366dbb44bf]*/
{
+ PyObject *string;
+ if (_Py_hashlib_data_argument(&string, data, string_obj) < 0) {
+ return NULL;
+ }
+
MD5object *new;
Py_buffer buf;
@@ -312,16 +306,12 @@ _md5_md5_impl(PyObject *module, PyObject *string, int usedforsecurity)
}
if (string) {
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- update(new->hash_state, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- update(new->hash_state, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ update(new->hash_state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
}
diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c
index 6a385562845..142ff1a2131 100644
--- a/Modules/mmapmodule.c
+++ b/Modules/mmapmodule.c
@@ -25,6 +25,7 @@
#include <Python.h>
#include "pycore_bytesobject.h" // _PyBytes_Find()
#include "pycore_fileutils.h" // _Py_stat_struct
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stddef.h> // offsetof()
#ifndef MS_WINDOWS
@@ -163,8 +164,7 @@ mmap_object_dealloc(PyObject *op)
Py_END_ALLOW_THREADS
#endif /* UNIX */
- if (m_obj->weakreflist != NULL)
- PyObject_ClearWeakRefs(op);
+ FT_CLEAR_WEAKREFS(op, m_obj->weakreflist);
tp->tp_free(m_obj);
Py_DECREF(tp);
@@ -290,6 +290,24 @@ filter_page_exception_method(mmap_object *self, EXCEPTION_POINTERS *ptrs,
}
return EXCEPTION_CONTINUE_SEARCH;
}
+
+static void
+_PyErr_SetFromNTSTATUS(ULONG status)
+{
+#if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)
+ PyErr_SetFromWindowsErr(LsaNtStatusToWinError((NTSTATUS)status));
+#else
+ if (status & 0x80000000) {
+ // HRESULT-shaped codes are supported by PyErr_SetFromWindowsErr
+ PyErr_SetFromWindowsErr((int)status);
+ }
+ else {
+ // No mapping for NTSTATUS values, so just return it for diagnostic purposes
+ // If we provide it as winerror it could incorrectly change the type of the exception.
+ PyErr_Format(PyExc_OSError, "Operating system error NTSTATUS=0x%08lX", status);
+ }
+#endif
+}
#endif
#if defined(MS_WINDOWS) && !defined(DONT_USE_SEH)
@@ -303,9 +321,7 @@ do { \
assert(record.ExceptionCode == EXCEPTION_IN_PAGE_ERROR || \
record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION); \
if (record.ExceptionCode == EXCEPTION_IN_PAGE_ERROR) { \
- NTSTATUS status = (NTSTATUS) record.ExceptionInformation[2]; \
- ULONG code = LsaNtStatusToWinError(status); \
- PyErr_SetFromWindowsErr(code); \
+ _PyErr_SetFromNTSTATUS((ULONG)record.ExceptionInformation[2]); \
} \
else if (record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { \
PyErr_SetFromWindowsErr(ERROR_NOACCESS); \
@@ -332,9 +348,7 @@ do { \
assert(record.ExceptionCode == EXCEPTION_IN_PAGE_ERROR || \
record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION); \
if (record.ExceptionCode == EXCEPTION_IN_PAGE_ERROR) { \
- NTSTATUS status = (NTSTATUS) record.ExceptionInformation[2]; \
- ULONG code = LsaNtStatusToWinError(status); \
- PyErr_SetFromWindowsErr(code); \
+ _PyErr_SetFromNTSTATUS((ULONG)record.ExceptionInformation[2]); \
} \
else if (record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { \
PyErr_SetFromWindowsErr(ERROR_NOACCESS); \
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 04c3b9e987a..b570f81b7cf 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -573,7 +573,11 @@ extern char *ctermid_r(char *);
# define HAVE_FACCESSAT_RUNTIME 1
# define HAVE_FCHMODAT_RUNTIME 1
# define HAVE_FCHOWNAT_RUNTIME 1
+#ifdef __wasi__
+# define HAVE_LINKAT_RUNTIME 0
+# else
# define HAVE_LINKAT_RUNTIME 1
+# endif
# define HAVE_FDOPENDIR_RUNTIME 1
# define HAVE_MKDIRAT_RUNTIME 1
# define HAVE_RENAMEAT_RUNTIME 1
@@ -681,7 +685,8 @@ static void
reset_remotedebug_data(PyThreadState *tstate)
{
tstate->remote_debugger_support.debugger_pending_call = 0;
- memset(tstate->remote_debugger_support.debugger_script_path, 0, MAX_SCRIPT_PATH_SIZE);
+ memset(tstate->remote_debugger_support.debugger_script_path, 0,
+ Py_MAX_SCRIPT_PATH_SIZE);
}
@@ -1778,7 +1783,7 @@ convertenviron(void)
return NULL;
}
#ifdef MS_WINDOWS
- v = PyUnicode_FromWideChar(p+1, wcslen(p+1));
+ v = PyUnicode_FromWideChar(p+1, -1);
#else
v = PyBytes_FromStringAndSize(p+1, strlen(p+1));
#endif
@@ -4203,7 +4208,7 @@ posix_getcwd(int use_bytes)
terminating \0. If the buffer is too small, len includes
the space needed for the terminator. */
if (len >= Py_ARRAY_LENGTH(wbuf)) {
- if (len <= PY_SSIZE_T_MAX / sizeof(wchar_t)) {
+ if ((Py_ssize_t)len <= PY_SSIZE_T_MAX / sizeof(wchar_t)) {
wbuf2 = PyMem_RawMalloc(len * sizeof(wchar_t));
}
else {
@@ -4346,7 +4351,7 @@ os.link
*
src_dir_fd : dir_fd = None
dst_dir_fd : dir_fd = None
- follow_symlinks: bool = True
+ follow_symlinks: bool(c_default="-1", py_default="(os.name != 'nt')") = PLACEHOLDER
Create a hard link to a file.
@@ -4364,31 +4369,46 @@ src_dir_fd, dst_dir_fd, and follow_symlinks may not be implemented on your
static PyObject *
os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd,
int dst_dir_fd, int follow_symlinks)
-/*[clinic end generated code: output=7f00f6007fd5269a input=b0095ebbcbaa7e04]*/
+/*[clinic end generated code: output=7f00f6007fd5269a input=1d5e602d115fed7b]*/
{
#ifdef MS_WINDOWS
BOOL result = FALSE;
#else
int result;
#endif
-#if defined(HAVE_LINKAT)
- int linkat_unavailable = 0;
-#endif
-#ifndef HAVE_LINKAT
- if ((src_dir_fd != DEFAULT_DIR_FD) || (dst_dir_fd != DEFAULT_DIR_FD)) {
- argument_unavailable_error("link", "src_dir_fd and dst_dir_fd");
- return NULL;
+#ifdef HAVE_LINKAT
+ if (HAVE_LINKAT_RUNTIME) {
+ if (follow_symlinks < 0) {
+ follow_symlinks = 1;
+ }
}
+ else
#endif
-
-#ifndef MS_WINDOWS
- if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) {
- PyErr_SetString(PyExc_NotImplementedError,
- "link: src and dst must be the same type");
- return NULL;
- }
+ {
+ if ((src_dir_fd != DEFAULT_DIR_FD) || (dst_dir_fd != DEFAULT_DIR_FD)) {
+ argument_unavailable_error("link", "src_dir_fd and dst_dir_fd");
+ return NULL;
+ }
+/* See issue 85527: link() on Linux works like linkat without AT_SYMLINK_FOLLOW,
+ but on Mac it works like linkat *with* AT_SYMLINK_FOLLOW. */
+#if defined(MS_WINDOWS) || defined(__linux__)
+ if (follow_symlinks == 1) {
+ argument_unavailable_error("link", "follow_symlinks=True");
+ return NULL;
+ }
+#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || (defined(__sun) && defined(__SVR4))
+ if (follow_symlinks == 0) {
+ argument_unavailable_error("link", "follow_symlinks=False");
+ return NULL;
+ }
+#else
+ if (follow_symlinks >= 0) {
+ argument_unavailable_error("link", "follow_symlinks");
+ return NULL;
+ }
#endif
+ }
if (PySys_Audit("os.link", "OOii", src->object, dst->object,
src_dir_fd == DEFAULT_DIR_FD ? -1 : src_dir_fd,
@@ -4406,44 +4426,18 @@ os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd,
#else
Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_LINKAT
- if ((src_dir_fd != DEFAULT_DIR_FD) ||
- (dst_dir_fd != DEFAULT_DIR_FD) ||
- (!follow_symlinks)) {
-
- if (HAVE_LINKAT_RUNTIME) {
-
- result = linkat(src_dir_fd, src->narrow,
- dst_dir_fd, dst->narrow,
- follow_symlinks ? AT_SYMLINK_FOLLOW : 0);
-
- }
-#ifdef __APPLE__
- else {
- if (src_dir_fd == DEFAULT_DIR_FD && dst_dir_fd == DEFAULT_DIR_FD) {
- /* See issue 41355: This matches the behaviour of !HAVE_LINKAT */
- result = link(src->narrow, dst->narrow);
- } else {
- linkat_unavailable = 1;
- }
- }
-#endif
+ if (HAVE_LINKAT_RUNTIME) {
+ result = linkat(src_dir_fd, src->narrow,
+ dst_dir_fd, dst->narrow,
+ follow_symlinks ? AT_SYMLINK_FOLLOW : 0);
}
else
-#endif /* HAVE_LINKAT */
+#endif
+ {
+ /* linkat not available */
result = link(src->narrow, dst->narrow);
- Py_END_ALLOW_THREADS
-
-#ifdef HAVE_LINKAT
- if (linkat_unavailable) {
- /* Either or both dir_fd arguments were specified */
- if (src_dir_fd != DEFAULT_DIR_FD) {
- argument_unavailable_error("link", "src_dir_fd");
- } else {
- argument_unavailable_error("link", "dst_dir_fd");
- }
- return NULL;
}
-#endif
+ Py_END_ALLOW_THREADS
if (result)
return path_error2(src, dst);
@@ -4705,7 +4699,7 @@ os_listdir_impl(PyObject *module, path_t *path)
}
-#ifdef MS_WINDOWS
+#if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)
/*[clinic input]
os.listdrives
@@ -4754,6 +4748,10 @@ os_listdrives_impl(PyObject *module)
return result;
}
+#endif /* MS_WINDOWS_DESKTOP || MS_WINDOWS_SYSTEM */
+
+#if defined(MS_WINDOWS_APP) || defined(MS_WINDOWS_SYSTEM)
+
/*[clinic input]
os.listvolumes
@@ -4815,6 +4813,9 @@ os_listvolumes_impl(PyObject *module)
return result;
}
+#endif /* MS_WINDOWS_APP || MS_WINDOWS_SYSTEM */
+
+#if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM)
/*[clinic input]
os.listmounts
@@ -4895,6 +4896,9 @@ exit:
return result;
}
+#endif /* MS_WINDOWS_DESKTOP || MS_WINDOWS_SYSTEM */
+
+#ifdef MS_WINDOWS
/*[clinic input]
os._path_isdevdrive
@@ -5049,7 +5053,7 @@ os__getfullpathname_impl(PyObject *module, path_t *path)
return PyErr_NoMemory();
}
- PyObject *str = PyUnicode_FromWideChar(abspath, wcslen(abspath));
+ PyObject *str = PyUnicode_FromWideChar(abspath, -1);
PyMem_RawFree(abspath);
if (str == NULL) {
return NULL;
@@ -5165,7 +5169,7 @@ os__findfirstfile_impl(PyObject *module, path_t *path)
}
wRealFileName = wFileData.cFileName;
- result = PyUnicode_FromWideChar(wRealFileName, wcslen(wRealFileName));
+ result = PyUnicode_FromWideChar(wRealFileName, -1);
FindClose(hFindFile);
return result;
}
@@ -5209,7 +5213,7 @@ os__getvolumepathname_impl(PyObject *module, path_t *path)
result = win32_error_object("_getvolumepathname", path->object);
goto exit;
}
- result = PyUnicode_FromWideChar(mountpath, wcslen(mountpath));
+ result = PyUnicode_FromWideChar(mountpath, -1);
if (PyBytes_Check(path->object))
Py_SETREF(result, PyUnicode_EncodeFSDefault(result));
@@ -5733,6 +5737,9 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd)
#ifdef MS_WINDOWS
Py_BEGIN_ALLOW_THREADS
+ // For API sets that don't support these APIs, we have no choice
+ // but to silently create a directory with default ACL.
+#if defined(MS_WINDOWS_APP) || defined(MS_WINDOWS_SYSTEM)
if (mode == 0700 /* 0o700 */) {
ULONG sdSize;
pSecAttr = &secAttr;
@@ -5748,6 +5755,7 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd)
error = GetLastError();
}
}
+#endif
if (!error) {
result = CreateDirectoryW(path->wide, pSecAttr);
if (secAttr.lpSecurityDescriptor &&
@@ -5935,12 +5943,6 @@ internal_rename(path_t *src, path_t *dst, int src_dir_fd, int dst_dir_fd, int is
return path_error2(src, dst);
#else
- if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) {
- PyErr_Format(PyExc_ValueError,
- "%s: src and dst must be the same type", function_name);
- return NULL;
- }
-
Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_RENAMEAT
if (dir_fd_specified) {
@@ -8819,14 +8821,14 @@ os_ptsname_impl(PyObject *module, int fd)
#if defined(HAVE_OPENPTY) || defined(HAVE_FORKPTY) || defined(HAVE_LOGIN_TTY) || defined(HAVE_DEV_PTMX)
#ifdef HAVE_PTY_H
#include <pty.h>
-#ifdef HAVE_UTMP_H
-#include <utmp.h>
-#endif /* HAVE_UTMP_H */
#elif defined(HAVE_LIBUTIL_H)
#include <libutil.h>
#elif defined(HAVE_UTIL_H)
#include <util.h>
#endif /* HAVE_PTY_H */
+#ifdef HAVE_UTMP_H
+#include <utmp.h>
+#endif /* HAVE_UTMP_H */
#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif
@@ -9561,6 +9563,24 @@ os_getlogin_impl(PyObject *module)
}
else
result = PyErr_SetFromWindowsErr(GetLastError());
+#elif defined (HAVE_GETLOGIN_R)
+# if defined (HAVE_MAXLOGNAME)
+ char name[MAXLOGNAME + 1];
+# elif defined (HAVE_UT_NAMESIZE)
+ char name[UT_NAMESIZE + 1];
+# else
+ char name[256];
+# endif
+ int err = getlogin_r(name, sizeof(name));
+ if (err) {
+ int old_errno = errno;
+ errno = -err;
+ posix_error();
+ errno = old_errno;
+ }
+ else {
+ result = PyUnicode_DecodeFSDefault(name);
+ }
#else
char *name;
int old_errno = errno;
@@ -10613,12 +10633,6 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst,
#else
- if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) {
- PyErr_SetString(PyExc_ValueError,
- "symlink: src and dst must be the same type");
- return NULL;
- }
-
Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_SYMLINKAT
if (dir_fd != DEFAULT_DIR_FD) {
@@ -16884,12 +16898,16 @@ static PyObject *
os__supports_virtual_terminal_impl(PyObject *module)
/*[clinic end generated code: output=bd0556a6d9d99fe6 input=0752c98e5d321542]*/
{
+#ifdef HAVE_WINDOWS_CONSOLE_IO
DWORD mode = 0;
HANDLE handle = GetStdHandle(STD_ERROR_HANDLE);
if (!GetConsoleMode(handle, &mode)) {
Py_RETURN_FALSE;
}
return PyBool_FromLong(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+#else
+ Py_RETURN_FALSE;
+#endif /* HAVE_WINDOWS_CONSOLE_IO */
}
#endif
diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c
index 2240e2078b2..c5a8cead19a 100644
--- a/Modules/pwdmodule.c
+++ b/Modules/pwdmodule.c
@@ -301,18 +301,33 @@ pwd_getpwall_impl(PyObject *module)
struct passwd *p;
if ((d = PyList_New(0)) == NULL)
return NULL;
+
+#ifdef Py_GIL_DISABLED
+ static PyMutex getpwall_mutex = {0};
+ PyMutex_Lock(&getpwall_mutex);
+#endif
+ int failure = 0;
+ PyObject *v = NULL;
setpwent();
while ((p = getpwent()) != NULL) {
- PyObject *v = mkpwent(module, p);
+ v = mkpwent(module, p);
if (v == NULL || PyList_Append(d, v) != 0) {
- Py_XDECREF(v);
- Py_DECREF(d);
- endpwent();
- return NULL;
+ /* NOTE: cannot dec-ref here, while holding the mutex. */
+ failure = 1;
+ goto done;
}
Py_DECREF(v);
}
+
+done:
endpwent();
+#ifdef Py_GIL_DISABLED
+ PyMutex_Unlock(&getpwall_mutex);
+#endif
+ if (failure) {
+ Py_XDECREF(v);
+ Py_CLEAR(d);
+ }
return d;
}
#endif
diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c
index fa153d86543..c449dd848d1 100644
--- a/Modules/pyexpat.c
+++ b/Modules/pyexpat.c
@@ -98,7 +98,11 @@ typedef struct {
#define CHARACTER_DATA_BUFFER_SIZE 8192
-typedef const void *xmlhandler;
+// A generic function type for storage.
+// To avoid undefined behaviors, a handler must be cast to the correct
+// function type before it's called; see SETTER_WRAPPER below.
+typedef void (*xmlhandler)(void);
+
typedef void (*xmlhandlersetter)(XML_Parser self, xmlhandler handler);
struct HandlerInfo {
@@ -110,9 +114,7 @@ struct HandlerInfo {
static struct HandlerInfo handler_info[64];
-// gh-111178: Use _Py_NO_SANITIZE_UNDEFINED, rather than using the exact
-// handler API for each handler.
-static inline void _Py_NO_SANITIZE_UNDEFINED
+static inline void
CALL_XML_HANDLER_SETTER(const struct HandlerInfo *handler_info,
XML_Parser xml_parser, xmlhandler xml_handler)
{
@@ -1365,7 +1367,7 @@ xmlparse_handler_setter(PyObject *op, PyObject *v, void *closure)
elaborate system of handlers and state could remove the
C handler more effectively. */
if (handlernum == CharacterData && self->in_callback) {
- c_handler = noop_character_data_handler;
+ c_handler = (xmlhandler)noop_character_data_handler;
}
v = NULL;
}
@@ -2222,13 +2224,84 @@ clear_handlers(xmlparseobject *self, int initial)
}
}
+/* To avoid undefined behaviors, a function must be *called* via a function
+ * pointer of the correct type.
+ * So, for each `XML_Set*` function, we define a wrapper that calls `XML_Set*`
+ * with its argument cast to the appropriate type.
+ */
+
+typedef void (*parser_only)(void *);
+typedef int (*not_standalone)(void *);
+typedef void (*parser_and_data)(void *, const XML_Char *);
+typedef void (*parser_and_data_and_int)(void *, const XML_Char *, int);
+typedef void (*parser_and_data_and_data)(
+ void *, const XML_Char *, const XML_Char *);
+typedef void (*start_element)(void *, const XML_Char *, const XML_Char **);
+typedef void (*element_decl)(void *, const XML_Char *, XML_Content *);
+typedef void (*xml_decl)(
+ void *, const XML_Char *, const XML_Char *, int);
+typedef void (*start_doctype_decl)(
+ void *, const XML_Char *, const XML_Char *, const XML_Char *, int);
+typedef void (*notation_decl)(
+ void *,
+ const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *);
+typedef void (*attlist_decl)(
+ void *,
+ const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *,
+ int);
+typedef void (*unparsed_entity_decl)(
+ void *,
+ const XML_Char *, const XML_Char *,
+ const XML_Char *, const XML_Char *, const XML_Char *);
+typedef void (*entity_decl)(
+ void *,
+ const XML_Char *, int,
+ const XML_Char *, int,
+ const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *);
+typedef int (*external_entity_ref)(
+ XML_Parser,
+ const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *);
+
+#define SETTER_WRAPPER(NAME, TYPE) \
+ static inline void \
+ pyexpat_Set ## NAME (XML_Parser parser, xmlhandler handler) \
+ { \
+ (void)XML_Set ## NAME (parser, (TYPE)handler); \
+ }
+
+SETTER_WRAPPER(StartElementHandler, start_element)
+SETTER_WRAPPER(EndElementHandler, parser_and_data)
+SETTER_WRAPPER(ProcessingInstructionHandler, parser_and_data_and_data)
+SETTER_WRAPPER(CharacterDataHandler, parser_and_data_and_int)
+SETTER_WRAPPER(UnparsedEntityDeclHandler, unparsed_entity_decl)
+SETTER_WRAPPER(NotationDeclHandler, notation_decl)
+SETTER_WRAPPER(StartNamespaceDeclHandler, parser_and_data_and_data)
+SETTER_WRAPPER(EndNamespaceDeclHandler, parser_and_data)
+SETTER_WRAPPER(CommentHandler, parser_and_data)
+SETTER_WRAPPER(StartCdataSectionHandler, parser_only)
+SETTER_WRAPPER(EndCdataSectionHandler, parser_only)
+SETTER_WRAPPER(DefaultHandler, parser_and_data_and_int)
+SETTER_WRAPPER(DefaultHandlerExpand, parser_and_data_and_int)
+SETTER_WRAPPER(NotStandaloneHandler, not_standalone)
+SETTER_WRAPPER(ExternalEntityRefHandler, external_entity_ref)
+SETTER_WRAPPER(StartDoctypeDeclHandler, start_doctype_decl)
+SETTER_WRAPPER(EndDoctypeDeclHandler, parser_only)
+SETTER_WRAPPER(EntityDeclHandler, entity_decl)
+SETTER_WRAPPER(XmlDeclHandler, xml_decl)
+SETTER_WRAPPER(ElementDeclHandler, element_decl)
+SETTER_WRAPPER(AttlistDeclHandler, attlist_decl)
+#if XML_COMBINED_VERSION >= 19504
+SETTER_WRAPPER(SkippedEntityHandler, parser_and_data_and_int)
+#endif
+#undef SETTER_WRAPPER
+
static struct HandlerInfo handler_info[] = {
// The cast to `xmlhandlersetter` is needed as the signature of XML
// handler functions is not compatible with `xmlhandlersetter` since
// their second parameter is narrower than a `const void *`.
#define HANDLER_INFO(name) \
- {#name, (xmlhandlersetter)XML_Set##name, my_##name},
+ {#name, (xmlhandlersetter)pyexpat_Set##name, (xmlhandler)my_##name},
HANDLER_INFO(StartElementHandler)
HANDLER_INFO(EndElementHandler)
diff --git a/Modules/sha1module.c b/Modules/sha1module.c
index f4a00cdb422..faa9dcccc57 100644
--- a/Modules/sha1module.c
+++ b/Modules/sha1module.c
@@ -8,13 +8,13 @@
Andrew Kuchling (amk@amk.ca)
Greg Stein (gstein@lyra.org)
Trevor Perrin (trevp@trevp.net)
+ Bénédikt Tran (10796600+picnixz@users.noreply.github.com)
Copyright (C) 2005-2007 Gregory P. Smith (greg@krypto.org)
Licensed to PSF under a Contributor Agreement.
*/
-/* SHA1 objects */
#ifndef Py_BUILD_CORE_BUILTIN
# define Py_BUILD_CORE_MODULE 1
#endif
@@ -24,32 +24,23 @@
#include "pycore_strhex.h" // _Py_strhex()
#include "pycore_typeobject.h" // _PyType_GetModuleState()
-/*[clinic input]
-module _sha1
-class SHA1Type "SHA1object *" "&PyType_Type"
-[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=3dc9a20d1becb759]*/
+#include "_hacl/Hacl_Hash_SHA1.h"
/* The SHA1 block size and message digest sizes, in bytes */
#define SHA1_BLOCKSIZE 64
#define SHA1_DIGESTSIZE 20
-#include "_hacl/Hacl_Hash_SHA1.h"
+// --- Module objects ---------------------------------------------------------
typedef struct {
- PyObject_HEAD
- // Prevents undefined behavior via multiple threads entering the C API.
- bool use_mutex;
- PyMutex mutex;
- PyThread_type_lock lock;
+ HASHLIB_OBJECT_HEAD
Hacl_Hash_SHA1_state_t *hash_state;
} SHA1object;
#define _SHA1object_CAST(op) ((SHA1object *)(op))
-#include "clinic/sha1module.c.h"
-
+// --- Module state -----------------------------------------------------------
typedef struct {
PyTypeObject* sha1_type;
@@ -63,6 +54,18 @@ sha1_get_state(PyObject *module)
return (SHA1State *)state;
}
+// --- Module clinic configuration --------------------------------------------
+
+/*[clinic input]
+module _sha1
+class SHA1Type "SHA1object *" "&PyType_Type"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=3dc9a20d1becb759]*/
+
+#include "clinic/sha1module.c.h"
+
+// --- SHA-1 object interface configuration -----------------------------------
+
static SHA1object *
newSHA1object(SHA1State *st)
{
@@ -121,9 +124,9 @@ SHA1Type_copy_impl(SHA1object *self, PyTypeObject *cls)
return NULL;
}
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
newobj->hash_state = Hacl_Hash_SHA1_copy(self->hash_state);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
if (newobj->hash_state == NULL) {
Py_DECREF(newobj);
return PyErr_NoMemory();
@@ -142,9 +145,9 @@ SHA1Type_digest_impl(SHA1object *self)
/*[clinic end generated code: output=2f05302a7aa2b5cb input=13824b35407444bd]*/
{
unsigned char digest[SHA1_DIGESTSIZE];
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
Hacl_Hash_SHA1_digest(self->hash_state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return PyBytes_FromStringAndSize((const char *)digest, SHA1_DIGESTSIZE);
}
@@ -159,9 +162,9 @@ SHA1Type_hexdigest_impl(SHA1object *self)
/*[clinic end generated code: output=4161fd71e68c6659 input=97691055c0c74ab0]*/
{
unsigned char digest[SHA1_DIGESTSIZE];
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
Hacl_Hash_SHA1_digest(self->hash_state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return _Py_strhex((const char *)digest, SHA1_DIGESTSIZE);
}
@@ -198,22 +201,11 @@ SHA1Type_update_impl(SHA1object *self, PyObject *obj)
/*[clinic end generated code: output=cdc8e0e106dbec5f input=aad8e07812edbba3]*/
{
Py_buffer buf;
-
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
-
- if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- update(self->hash_state, buf.buf, buf.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- update(self->hash_state, buf.buf, buf.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, buf.len,
+ update(self->hash_state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
@@ -272,19 +264,25 @@ static PyType_Spec sha1_type_spec = {
/*[clinic input]
_sha1.sha1
- string: object(c_default="NULL") = b''
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string as string_obj: object(c_default="NULL") = None
Return a new SHA1 hash object; optionally initialized with a string.
[clinic start generated code]*/
static PyObject *
-_sha1_sha1_impl(PyObject *module, PyObject *string, int usedforsecurity)
-/*[clinic end generated code: output=6f8b3af05126e18e input=bd54b68e2bf36a8a]*/
+_sha1_sha1_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj)
+/*[clinic end generated code: output=0d453775924f88a7 input=807f25264e0ac656]*/
{
SHA1object *new;
Py_buffer buf;
+ PyObject *string;
+ if (_Py_hashlib_data_argument(&string, data, string_obj) < 0) {
+ return NULL;
+ }
if (string) {
GET_BUFFER_VIEW_OR_ERROUT(string, &buf);
@@ -308,16 +306,12 @@ _sha1_sha1_impl(PyObject *module, PyObject *string, int usedforsecurity)
return PyErr_NoMemory();
}
if (string) {
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- update(new->hash_state, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- update(new->hash_state, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ update(new->hash_state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
}
diff --git a/Modules/sha2module.c b/Modules/sha2module.c
index e88d7cb2d45..36300ba899f 100644
--- a/Modules/sha2module.c
+++ b/Modules/sha2module.c
@@ -9,32 +9,25 @@
Greg Stein (gstein@lyra.org)
Trevor Perrin (trevp@trevp.net)
Jonathan Protzenko (jonathan@protzenko.fr)
+ Bénédikt Tran (10796600+picnixz@users.noreply.github.com)
Copyright (C) 2005-2007 Gregory P. Smith (greg@krypto.org)
Licensed to PSF under a Contributor Agreement.
*/
-/* SHA objects */
#ifndef Py_BUILD_CORE_BUILTIN
# define Py_BUILD_CORE_MODULE 1
#endif
#include "Python.h"
-#include "pycore_bitutils.h" // _Py_bswap32()
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_typeobject.h" // _PyType_GetModuleState()
#include "pycore_strhex.h" // _Py_strhex()
#include "hashlib.h"
-/*[clinic input]
-module _sha2
-class SHA256Type "SHA256object *" "&PyType_Type"
-class SHA512Type "SHA512object *" "&PyType_Type"
-[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b5315a7b611c9afc]*/
-
+#include "_hacl/Hacl_Hash_SHA2.h"
/* The SHA block sizes and maximum message digest sizes, in bytes */
@@ -43,34 +36,26 @@ class SHA512Type "SHA512object *" "&PyType_Type"
#define SHA512_BLOCKSIZE 128
#define SHA512_DIGESTSIZE 64
-/* Our SHA2 implementations defer to the HACL* verified library. */
-
-#include "_hacl/Hacl_Hash_SHA2.h"
+// --- Module objects ---------------------------------------------------------
// TODO: Get rid of int digestsize in favor of Hacl state info?
typedef struct {
- PyObject_HEAD
+ HASHLIB_OBJECT_HEAD
int digestsize;
- // Prevents undefined behavior via multiple threads entering the C API.
- bool use_mutex;
- PyMutex mutex;
Hacl_Hash_SHA2_state_t_256 *state;
} SHA256object;
typedef struct {
- PyObject_HEAD
+ HASHLIB_OBJECT_HEAD
int digestsize;
- // Prevents undefined behavior via multiple threads entering the C API.
- bool use_mutex;
- PyMutex mutex;
Hacl_Hash_SHA2_state_t_512 *state;
} SHA512object;
#define _SHA256object_CAST(op) ((SHA256object *)(op))
#define _SHA512object_CAST(op) ((SHA512object *)(op))
-#include "clinic/sha2module.c.h"
+// --- Module state -----------------------------------------------------------
/* We shall use run-time type information in the remainder of this module to
* tell apart SHA2-224 and SHA2-256 */
@@ -89,6 +74,19 @@ sha2_get_state(PyObject *module)
return (sha2_state *)state;
}
+// --- Module clinic configuration --------------------------------------------
+
+/*[clinic input]
+module _sha2
+class SHA256Type "SHA256object *" "&PyType_Type"
+class SHA512Type "SHA512object *" "&PyType_Type"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b5315a7b611c9afc]*/
+
+#include "clinic/sha2module.c.h"
+
+// --- SHA-2 object interface -------------------------------------------------
+
static int
SHA256copy(SHA256object *src, SHA256object *dest)
{
@@ -272,9 +270,9 @@ SHA256Type_copy_impl(SHA256object *self, PyTypeObject *cls)
}
}
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
rc = SHA256copy(self, newobj);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
if (rc < 0) {
Py_DECREF(newobj);
return NULL;
@@ -309,9 +307,9 @@ SHA512Type_copy_impl(SHA512object *self, PyTypeObject *cls)
}
}
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
rc = SHA512copy(self, newobj);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
if (rc < 0) {
Py_DECREF(newobj);
return NULL;
@@ -331,11 +329,11 @@ SHA256Type_digest_impl(SHA256object *self)
{
uint8_t digest[SHA256_DIGESTSIZE];
assert(self->digestsize <= SHA256_DIGESTSIZE);
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
// HACL* performs copies under the hood so that self->state remains valid
// after this call.
Hacl_Hash_SHA2_digest_256(self->state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return PyBytes_FromStringAndSize((const char *)digest, self->digestsize);
}
@@ -351,11 +349,11 @@ SHA512Type_digest_impl(SHA512object *self)
{
uint8_t digest[SHA512_DIGESTSIZE];
assert(self->digestsize <= SHA512_DIGESTSIZE);
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
// HACL* performs copies under the hood so that self->state remains valid
// after this call.
Hacl_Hash_SHA2_digest_512(self->state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return PyBytes_FromStringAndSize((const char *)digest, self->digestsize);
}
@@ -371,9 +369,9 @@ SHA256Type_hexdigest_impl(SHA256object *self)
{
uint8_t digest[SHA256_DIGESTSIZE];
assert(self->digestsize <= SHA256_DIGESTSIZE);
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
Hacl_Hash_SHA2_digest_256(self->state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return _Py_strhex((const char *)digest, self->digestsize);
}
@@ -389,9 +387,9 @@ SHA512Type_hexdigest_impl(SHA512object *self)
{
uint8_t digest[SHA512_DIGESTSIZE];
assert(self->digestsize <= SHA512_DIGESTSIZE);
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
Hacl_Hash_SHA2_digest_512(self->state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return _Py_strhex((const char *)digest, self->digestsize);
}
@@ -409,22 +407,11 @@ SHA256Type_update_impl(SHA256object *self, PyObject *obj)
/*[clinic end generated code: output=dc58a580cf8905a5 input=b2d449d5b30f0f5a]*/
{
Py_buffer buf;
-
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
-
- if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- update_256(self->state, buf.buf, buf.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- update_256(self->state, buf.buf, buf.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, buf.len,
+ update_256(self->state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
@@ -443,22 +430,11 @@ SHA512Type_update_impl(SHA512object *self, PyObject *obj)
/*[clinic end generated code: output=9af211766c0b7365 input=ded2b46656566283]*/
{
Py_buffer buf;
-
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
-
- if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- update_512(self->state, buf.buf, buf.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- update_512(self->state, buf.buf, buf.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, buf.len,
+ update_512(self->state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
@@ -594,18 +570,24 @@ static PyType_Spec sha512_type_spec = {
/*[clinic input]
_sha2.sha256
- string: object(c_default="NULL") = b''
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string as string_obj: object(c_default="NULL") = None
Return a new SHA-256 hash object; optionally initialized with a string.
[clinic start generated code]*/
static PyObject *
-_sha2_sha256_impl(PyObject *module, PyObject *string, int usedforsecurity)
-/*[clinic end generated code: output=243c9dd289931f87 input=6249da1de607280a]*/
+_sha2_sha256_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj)
+/*[clinic end generated code: output=49828a7bcd418f45 input=9ce1d70e669abc14]*/
{
Py_buffer buf;
+ PyObject *string;
+ if (_Py_hashlib_data_argument(&string, data, string_obj) < 0) {
+ return NULL;
+ }
if (string) {
GET_BUFFER_VIEW_OR_ERROUT(string, &buf);
@@ -632,16 +614,12 @@ _sha2_sha256_impl(PyObject *module, PyObject *string, int usedforsecurity)
return PyErr_NoMemory();
}
if (string) {
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- update_256(new->state, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- update_256(new->state, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ update_256(new->state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
}
@@ -651,18 +629,25 @@ _sha2_sha256_impl(PyObject *module, PyObject *string, int usedforsecurity)
/*[clinic input]
_sha2.sha224
- string: object(c_default="NULL") = b''
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string as string_obj: object(c_default="NULL") = None
Return a new SHA-224 hash object; optionally initialized with a string.
[clinic start generated code]*/
static PyObject *
-_sha2_sha224_impl(PyObject *module, PyObject *string, int usedforsecurity)
-/*[clinic end generated code: output=68191f232e4a3843 input=c42bcba47fd7d2b7]*/
+_sha2_sha224_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj)
+/*[clinic end generated code: output=2163cb03b6cf6157 input=612f7682a889bc2a]*/
{
Py_buffer buf;
+ PyObject *string;
+ if (_Py_hashlib_data_argument(&string, data, string_obj) < 0) {
+ return NULL;
+ }
+
if (string) {
GET_BUFFER_VIEW_OR_ERROUT(string, &buf);
}
@@ -687,16 +672,12 @@ _sha2_sha224_impl(PyObject *module, PyObject *string, int usedforsecurity)
return PyErr_NoMemory();
}
if (string) {
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- update_256(new->state, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- update_256(new->state, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ update_256(new->state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
}
@@ -706,19 +687,25 @@ _sha2_sha224_impl(PyObject *module, PyObject *string, int usedforsecurity)
/*[clinic input]
_sha2.sha512
- string: object(c_default="NULL") = b''
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string as string_obj: object(c_default="NULL") = None
Return a new SHA-512 hash object; optionally initialized with a string.
[clinic start generated code]*/
static PyObject *
-_sha2_sha512_impl(PyObject *module, PyObject *string, int usedforsecurity)
-/*[clinic end generated code: output=d55c8996eca214d7 input=0576ae2a6ebfad25]*/
+_sha2_sha512_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj)
+/*[clinic end generated code: output=cc3fcfce001a4538 input=19c9f2c06d59563a]*/
{
SHA512object *new;
Py_buffer buf;
+ PyObject *string;
+ if (_Py_hashlib_data_argument(&string, data, string_obj) < 0) {
+ return NULL;
+ }
sha2_state *state = sha2_get_state(module);
@@ -744,16 +731,12 @@ _sha2_sha512_impl(PyObject *module, PyObject *string, int usedforsecurity)
return PyErr_NoMemory();
}
if (string) {
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- update_512(new->state, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- update_512(new->state, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ update_512(new->state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
}
@@ -763,19 +746,25 @@ _sha2_sha512_impl(PyObject *module, PyObject *string, int usedforsecurity)
/*[clinic input]
_sha2.sha384
- string: object(c_default="NULL") = b''
+ data: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string as string_obj: object(c_default="NULL") = None
Return a new SHA-384 hash object; optionally initialized with a string.
[clinic start generated code]*/
static PyObject *
-_sha2_sha384_impl(PyObject *module, PyObject *string, int usedforsecurity)
-/*[clinic end generated code: output=b29a0d81d51d1368 input=4e9199d8de0d2f9b]*/
+_sha2_sha384_impl(PyObject *module, PyObject *data, int usedforsecurity,
+ PyObject *string_obj)
+/*[clinic end generated code: output=b6e3db593b5a0330 input=9fd50c942ad9e0bf]*/
{
SHA512object *new;
Py_buffer buf;
+ PyObject *string;
+ if (_Py_hashlib_data_argument(&string, data, string_obj) < 0) {
+ return NULL;
+ }
sha2_state *state = sha2_get_state(module);
@@ -801,16 +790,12 @@ _sha2_sha384_impl(PyObject *module, PyObject *string, int usedforsecurity)
return PyErr_NoMemory();
}
if (string) {
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- update_512(new->state, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- update_512(new->state, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ update_512(new->state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
}
diff --git a/Modules/sha3module.c b/Modules/sha3module.c
index a7edf5c66a1..5764556bb68 100644
--- a/Modules/sha3module.c
+++ b/Modules/sha3module.c
@@ -9,6 +9,7 @@
* Greg Stein (gstein@lyra.org)
* Trevor Perrin (trevp@trevp.net)
* Gregory P. Smith (greg@krypto.org)
+ * Bénédikt Tran (10796600+picnixz@users.noreply.github.com)
*
* Copyright (C) 2012-2022 Christian Heimes (christian@python.org)
* Licensed to PSF under a Contributor Agreement.
@@ -24,8 +25,23 @@
#include "pycore_typeobject.h" // _PyType_GetModuleState()
#include "hashlib.h"
+#include "_hacl/Hacl_Hash_SHA3.h"
+
+/*
+ * Assert that 'LEN' can be safely casted to uint32_t.
+ *
+ * The 'LEN' parameter should be convertible to Py_ssize_t.
+ */
+#if !defined(NDEBUG) && (PY_SSIZE_T_MAX > UINT32_MAX)
+#define CHECK_HACL_UINT32_T_LENGTH(LEN) assert((LEN) < (Py_ssize_t)UINT32_MAX)
+#else
+#define CHECK_HACL_UINT32_T_LENGTH(LEN)
+#endif
+
#define SHA3_MAX_DIGESTSIZE 64 /* 64 Bytes (512 Bits) for 224 to 512 */
+// --- Module state -----------------------------------------------------------
+
typedef struct {
PyTypeObject *sha3_224_type;
PyTypeObject *sha3_256_type;
@@ -43,33 +59,34 @@ sha3_get_state(PyObject *module)
return (SHA3State *)state;
}
-/*[clinic input]
-module _sha3
-class _sha3.sha3_224 "SHA3object *" "&SHA3_224typ"
-class _sha3.sha3_256 "SHA3object *" "&SHA3_256typ"
-class _sha3.sha3_384 "SHA3object *" "&SHA3_384typ"
-class _sha3.sha3_512 "SHA3object *" "&SHA3_512typ"
-class _sha3.shake_128 "SHA3object *" "&SHAKE128type"
-class _sha3.shake_256 "SHA3object *" "&SHAKE256type"
-[clinic start generated code]*/
-/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b8a53680f370285a]*/
+// --- Module objects ---------------------------------------------------------
/* The structure for storing SHA3 info */
-#include "_hacl/Hacl_Hash_SHA3.h"
-
typedef struct {
- PyObject_HEAD
- // Prevents undefined behavior via multiple threads entering the C API.
- bool use_mutex;
- PyMutex mutex;
+ HASHLIB_OBJECT_HEAD
Hacl_Hash_SHA3_state_t *hash_state;
} SHA3object;
#define _SHA3object_CAST(op) ((SHA3object *)(op))
+// --- Module clinic configuration --------------------------------------------
+
+/*[clinic input]
+module _sha3
+class _sha3.sha3_224 "SHA3object *" "&PyType_Type"
+class _sha3.sha3_256 "SHA3object *" "&PyType_Type"
+class _sha3.sha3_384 "SHA3object *" "&PyType_Type"
+class _sha3.sha3_512 "SHA3object *" "&PyType_Type"
+class _sha3.shake_128 "SHA3object *" "&PyType_Type"
+class _sha3.shake_256 "SHA3object *" "&PyType_Type"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ccd22550c7fb99bf]*/
+
#include "clinic/sha3module.c.h"
+// --- SHA-3 object interface -------------------------------------------------
+
static SHA3object *
newSHA3object(PyTypeObject *type)
{
@@ -105,18 +122,25 @@ sha3_update(Hacl_Hash_SHA3_state_t *state, uint8_t *buf, Py_ssize_t len)
/*[clinic input]
@classmethod
_sha3.sha3_224.__new__ as py_sha3_new
- data: object(c_default="NULL") = b''
- /
+
+ data as data_obj: object(c_default="NULL") = b''
*
usedforsecurity: bool = True
+ string: object(c_default="NULL") = None
Return a new SHA3 hash object.
[clinic start generated code]*/
static PyObject *
-py_sha3_new_impl(PyTypeObject *type, PyObject *data, int usedforsecurity)
-/*[clinic end generated code: output=90409addc5d5e8b0 input=637e5f8f6a93982a]*/
+py_sha3_new_impl(PyTypeObject *type, PyObject *data_obj, int usedforsecurity,
+ PyObject *string)
+/*[clinic end generated code: output=dcec1eca20395f2a input=c106e0b4e2d67d58]*/
{
+ PyObject *data;
+ if (_Py_hashlib_data_argument(&data, data_obj, string) < 0) {
+ return NULL;
+ }
+
Py_buffer buf = {NULL, NULL};
SHA3State *state = _PyType_GetModuleState(type);
SHA3object *self = newSHA3object(type);
@@ -156,16 +180,12 @@ py_sha3_new_impl(PyTypeObject *type, PyObject *data, int usedforsecurity)
if (data) {
GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error);
- if (buf.len >= HASHLIB_GIL_MINSIZE) {
- /* We do not initialize self->lock here as this is the constructor
- * where it is not yet possible to have concurrent access. */
- Py_BEGIN_ALLOW_THREADS
- sha3_update(self->hash_state, buf.buf, buf.len);
- Py_END_ALLOW_THREADS
- }
- else {
- sha3_update(self->hash_state, buf.buf, buf.len);
- }
+ /* Do not use self->mutex here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
+ buf.len,
+ sha3_update(self->hash_state, buf.buf, buf.len)
+ );
}
PyBuffer_Release(&buf);
@@ -219,21 +239,22 @@ SHA3_traverse(PyObject *self, visitproc visit, void *arg)
/*[clinic input]
_sha3.sha3_224.copy
+ cls: defining_class
+
Return a copy of the hash object.
[clinic start generated code]*/
static PyObject *
-_sha3_sha3_224_copy_impl(SHA3object *self)
-/*[clinic end generated code: output=6c537411ecdcda4c input=93a44aaebea51ba8]*/
+_sha3_sha3_224_copy_impl(SHA3object *self, PyTypeObject *cls)
+/*[clinic end generated code: output=13958b44c244013e input=7134b4dc0a2fbcac]*/
{
SHA3object *newobj;
-
- if ((newobj = newSHA3object(Py_TYPE(self))) == NULL) {
+ if ((newobj = newSHA3object(cls)) == NULL) {
return NULL;
}
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
newobj->hash_state = Hacl_Hash_SHA3_copy(self->hash_state);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
if (newobj->hash_state == NULL) {
Py_DECREF(newobj);
return PyErr_NoMemory();
@@ -255,9 +276,9 @@ _sha3_sha3_224_digest_impl(SHA3object *self)
unsigned char digest[SHA3_MAX_DIGESTSIZE];
// This function errors out if the algorithm is SHAKE. Here, we know this
// not to be the case, and therefore do not perform error checking.
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return PyBytes_FromStringAndSize((const char *)digest,
Hacl_Hash_SHA3_hash_len(self->hash_state));
}
@@ -274,9 +295,9 @@ _sha3_sha3_224_hexdigest_impl(SHA3object *self)
/*[clinic end generated code: output=75ad03257906918d input=2d91bb6e0d114ee3]*/
{
unsigned char digest[SHA3_MAX_DIGESTSIZE];
- ENTER_HASHLIB(self);
+ HASHLIB_ACQUIRE_LOCK(self);
(void)Hacl_Hash_SHA3_digest(self->hash_state, digest);
- LEAVE_HASHLIB(self);
+ HASHLIB_RELEASE_LOCK(self);
return _Py_strhex((const char *)digest,
Hacl_Hash_SHA3_hash_len(self->hash_state));
}
@@ -296,22 +317,11 @@ _sha3_sha3_224_update_impl(SHA3object *self, PyObject *data)
/*[clinic end generated code: output=390b7abf7c9795a5 input=a887f54dcc4ae227]*/
{
Py_buffer buf;
-
GET_BUFFER_VIEW_OR_ERROUT(data, &buf);
-
- if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->use_mutex = true;
- }
- if (self->use_mutex) {
- Py_BEGIN_ALLOW_THREADS
- PyMutex_Lock(&self->mutex);
- sha3_update(self->hash_state, buf.buf, buf.len);
- PyMutex_Unlock(&self->mutex);
- Py_END_ALLOW_THREADS
- } else {
- sha3_update(self->hash_state, buf.buf, buf.len);
- }
-
+ HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
+ self, buf.len,
+ sha3_update(self->hash_state, buf.buf, buf.len)
+ );
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
@@ -465,71 +475,94 @@ SHA3_TYPE_SPEC(sha3_384_spec, "sha3_384", sha3_384_slots);
SHA3_TYPE_SLOTS(sha3_512_slots, sha3_512__doc__, SHA3_methods, SHA3_getseters);
SHA3_TYPE_SPEC(sha3_512_spec, "sha3_512", sha3_512_slots);
-static PyObject *
-_SHAKE_digest(PyObject *op, unsigned long digestlen, int hex)
+static int
+sha3_shake_check_digest_length(Py_ssize_t length)
{
- unsigned char *digest = NULL;
- PyObject *result = NULL;
- SHA3object *self = _SHA3object_CAST(op);
-
- if (digestlen >= (1 << 29)) {
- PyErr_SetString(PyExc_ValueError, "length is too large");
- return NULL;
- }
- digest = (unsigned char*)PyMem_Malloc(digestlen);
- if (digest == NULL) {
- return PyErr_NoMemory();
- }
-
- /* Get the raw (binary) digest value. The HACL functions errors out if:
- * - the algorithm is not shake -- not the case here
- * - the output length is zero -- we follow the existing behavior and return
- * an empty digest, without raising an error */
- if (digestlen > 0) {
- (void)Hacl_Hash_SHA3_squeeze(self->hash_state, digest, digestlen);
- }
- if (hex) {
- result = _Py_strhex((const char *)digest, digestlen);
+ if (length < 0) {
+ PyErr_SetString(PyExc_ValueError, "negative digest length");
+ return -1;
}
- else {
- result = PyBytes_FromStringAndSize((const char *)digest, digestlen);
+ if ((size_t)length >= (1 << 29)) {
+ /*
+ * Raise OverflowError to match the semantics of OpenSSL SHAKE
+ * when the digest length exceeds the range of a 'Py_ssize_t';
+ * the exception message will however be different in this case.
+ */
+ PyErr_SetString(PyExc_OverflowError, "digest length is too large");
+ return -1;
}
- PyMem_Free(digest);
- return result;
+ return 0;
}
/*[clinic input]
_sha3.shake_128.digest
- length: unsigned_long
- /
+ length: Py_ssize_t
Return the digest value as a bytes object.
[clinic start generated code]*/
static PyObject *
-_sha3_shake_128_digest_impl(SHA3object *self, unsigned long length)
-/*[clinic end generated code: output=2313605e2f87bb8f input=418ef6a36d2e6082]*/
+_sha3_shake_128_digest_impl(SHA3object *self, Py_ssize_t length)
+/*[clinic end generated code: output=6c53fb71a6cff0a0 input=be03ade4b31dd54c]*/
{
- return _SHAKE_digest((PyObject *)self, length, 0);
+ if (sha3_shake_check_digest_length(length) < 0) {
+ return NULL;
+ }
+
+ /*
+ * Hacl_Hash_SHA3_squeeze() fails if the algorithm is not SHAKE,
+ * or if the length is 0. In the latter case, we follow OpenSSL's
+ * behavior and return an empty digest, without raising an error.
+ */
+ if (length == 0) {
+ return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
+ }
+
+ CHECK_HACL_UINT32_T_LENGTH(length);
+ PyObject *digest = PyBytes_FromStringAndSize(NULL, length);
+ uint8_t *buffer = (uint8_t *)PyBytes_AS_STRING(digest);
+ HASHLIB_ACQUIRE_LOCK(self);
+ (void)Hacl_Hash_SHA3_squeeze(self->hash_state, buffer, (uint32_t)length);
+ HASHLIB_RELEASE_LOCK(self);
+ return digest;
}
/*[clinic input]
_sha3.shake_128.hexdigest
- length: unsigned_long
- /
+ length: Py_ssize_t
Return the digest value as a string of hexadecimal digits.
[clinic start generated code]*/
static PyObject *
-_sha3_shake_128_hexdigest_impl(SHA3object *self, unsigned long length)
-/*[clinic end generated code: output=bf8e2f1e490944a8 input=69fb29b0926ae321]*/
+_sha3_shake_128_hexdigest_impl(SHA3object *self, Py_ssize_t length)
+/*[clinic end generated code: output=a27412d404f64512 input=0d84d05d7a8ccd37]*/
{
- return _SHAKE_digest((PyObject *)self, length, 1);
+ if (sha3_shake_check_digest_length(length) < 0) {
+ return NULL;
+ }
+
+ /* See _sha3_shake_128_digest_impl() for the fast path rationale. */
+ if (length == 0) {
+ return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
+ }
+
+ CHECK_HACL_UINT32_T_LENGTH(length);
+ uint8_t *buffer = PyMem_Malloc(length);
+ if (buffer == NULL) {
+ return PyErr_NoMemory();
+ }
+
+ HASHLIB_ACQUIRE_LOCK(self);
+ (void)Hacl_Hash_SHA3_squeeze(self->hash_state, buffer, (uint32_t)length);
+ HASHLIB_RELEASE_LOCK(self);
+ PyObject *digest = _Py_strhex((const char *)buffer, length);
+ PyMem_Free(buffer);
+ return digest;
}
static PyObject *
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index 47958379263..f3ad01854de 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -111,7 +111,6 @@ Local naming conventions:
#include "pycore_moduleobject.h" // _PyModule_GetState
#include "pycore_time.h" // _PyTime_AsMilliseconds()
#include "pycore_pystate.h" // _Py_AssertHoldsTstate()
-#include "pycore_pyatomic_ft_wrappers.h"
#ifdef _Py_MEMORY_SANITIZER
# include <sanitizer/msan_interface.h>
@@ -565,7 +564,6 @@ static int sock_cloexec_works = -1;
static inline void
set_sock_fd(PySocketSockObject *s, SOCKET_T fd)
{
-#ifdef Py_GIL_DISABLED
#if SIZEOF_SOCKET_T == SIZEOF_INT
_Py_atomic_store_int_relaxed((int *)&s->sock_fd, (int)fd);
#elif SIZEOF_SOCKET_T == SIZEOF_LONG
@@ -575,15 +573,11 @@ set_sock_fd(PySocketSockObject *s, SOCKET_T fd)
#else
#error "Unsupported SIZEOF_SOCKET_T"
#endif
-#else
- s->sock_fd = fd;
-#endif
}
static inline SOCKET_T
get_sock_fd(PySocketSockObject *s)
{
-#ifdef Py_GIL_DISABLED
#if SIZEOF_SOCKET_T == SIZEOF_INT
return (SOCKET_T)_Py_atomic_load_int_relaxed((int *)&s->sock_fd);
#elif SIZEOF_SOCKET_T == SIZEOF_LONG
@@ -593,9 +587,6 @@ get_sock_fd(PySocketSockObject *s)
#else
#error "Unsupported SIZEOF_SOCKET_T"
#endif
-#else
- return s->sock_fd;
-#endif
}
#define _PySocketSockObject_CAST(op) ((PySocketSockObject *)(op))
@@ -638,33 +629,22 @@ _PyLong_##NAME##_Converter(PyObject *obj, void *ptr) \
return 1; \
}
-UNSIGNED_INT_CONVERTER(UInt16, uint16_t)
-UNSIGNED_INT_CONVERTER(UInt32, uint32_t)
-
#if defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS)
# ifdef MS_WINDOWS
UNSIGNED_INT_CONVERTER(NetIfindex, NET_IFINDEX)
# else
- UNSIGNED_INT_CONVERTER(NetIfindex, unsigned int)
+# define _PyLong_NetIfindex_Converter _PyLong_UnsignedInt_Converter
# define NET_IFINDEX unsigned int
# endif
#endif // defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS)
/*[python input]
-class uint16_converter(CConverter):
- type = "uint16_t"
- converter = '_PyLong_UInt16_Converter'
-
-class uint32_converter(CConverter):
- type = "uint32_t"
- converter = '_PyLong_UInt32_Converter'
-
class NET_IFINDEX_converter(CConverter):
type = "NET_IFINDEX"
converter = '_PyLong_NetIfindex_Converter'
[python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=3de2e4a03fbf83b8]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=1cf809c40a407c34]*/
/*[clinic input]
module _socket
@@ -736,12 +716,6 @@ select_error(void)
# define SOCK_INPROGRESS_ERR EINPROGRESS
#endif
-#ifdef _MSC_VER
-# define SUPPRESS_DEPRECATED_CALL __pragma(warning(suppress: 4996))
-#else
-# define SUPPRESS_DEPRECATED_CALL
-#endif
-
/* Convenience function to raise an error according to errno
and return a NULL pointer from a function. */
@@ -3386,7 +3360,7 @@ sock_setsockopt(PyObject *self, PyObject *args)
&level, &optname, &flag)) {
#ifdef MS_WINDOWS
if (optname == SIO_TCP_SET_ACK_FREQUENCY) {
- int dummy;
+ DWORD dummy;
res = WSAIoctl(get_sock_fd(s), SIO_TCP_SET_ACK_FREQUENCY, &flag,
sizeof(flag), NULL, 0, &dummy, NULL, NULL);
if (res >= 0) {
@@ -4618,55 +4592,62 @@ sock_send_impl(PySocketSockObject *s, void *data)
return (ctx->result >= 0);
}
-/* s.send(data [,flags]) method */
+/*[clinic input]
+_socket.socket.send
+ self as s: self(type="PySocketSockObject *")
+ data as pbuf: Py_buffer
+ flags: int = 0
+ /
+
+Send a data string to the socket.
+
+For the optional flags argument, see the Unix manual.
+Return the number of bytes sent; this may be less than len(data) if the network is busy.
+[clinic start generated code]*/
static PyObject *
-sock_send(PyObject *self, PyObject *args)
-{
- PySocketSockObject *s = _PySocketSockObject_CAST(self);
+_socket_socket_send_impl(PySocketSockObject *s, Py_buffer *pbuf, int flags)
+/*[clinic end generated code: output=3ddf83f17d0c875b input=befe7d7790ccb035]*/
- int flags = 0;
- Py_buffer pbuf;
+{
struct sock_send ctx;
- if (!PyArg_ParseTuple(args, "y*|i:send", &pbuf, &flags))
- return NULL;
-
if (!IS_SELECTABLE(s)) {
- PyBuffer_Release(&pbuf);
return select_error();
}
- ctx.buf = pbuf.buf;
- ctx.len = pbuf.len;
+ ctx.buf = pbuf->buf;
+ ctx.len = pbuf->len;
ctx.flags = flags;
if (sock_call(s, 1, sock_send_impl, &ctx) < 0) {
- PyBuffer_Release(&pbuf);
return NULL;
}
- PyBuffer_Release(&pbuf);
return PyLong_FromSsize_t(ctx.result);
}
-PyDoc_STRVAR(send_doc,
-"send(data[, flags]) -> count\n\
-\n\
-Send a data string to the socket. For the optional flags\n\
-argument, see the Unix manual. Return the number of bytes\n\
-sent; this may be less than len(data) if the network is busy.");
+/*[clinic input]
+_socket.socket.sendall
+ self as s: self(type="PySocketSockObject *")
+ data as pbuf: Py_buffer
+ flags: int = 0
+ /
-/* s.sendall(data [,flags]) method */
+Send a data string to the socket.
+
+For the optional flags argument, see the Unix manual.
+This calls send() repeatedly until all data is sent.
+If an error occurs, it's impossible to tell how much data has been sent.
+[clinic start generated code]*/
static PyObject *
-sock_sendall(PyObject *self, PyObject *args)
-{
- PySocketSockObject *s = _PySocketSockObject_CAST(self);
+_socket_socket_sendall_impl(PySocketSockObject *s, Py_buffer *pbuf,
+ int flags)
+/*[clinic end generated code: output=ec92861424d3faa8 input=732b15b9ca64dce6]*/
+{
char *buf;
Py_ssize_t len, n;
- int flags = 0;
- Py_buffer pbuf;
struct sock_send ctx;
int has_timeout = (s->sock_timeout > 0);
PyTime_t timeout = s->sock_timeout;
@@ -4674,13 +4655,10 @@ sock_sendall(PyObject *self, PyObject *args)
int deadline_initialized = 0;
PyObject *res = NULL;
- if (!PyArg_ParseTuple(args, "y*|i:sendall", &pbuf, &flags))
- return NULL;
- buf = pbuf.buf;
- len = pbuf.len;
+ buf = pbuf->buf;
+ len = pbuf->len;
if (!IS_SELECTABLE(s)) {
- PyBuffer_Release(&pbuf);
return select_error();
}
@@ -4718,23 +4696,13 @@ sock_sendall(PyObject *self, PyObject *args)
if (PyErr_CheckSignals())
goto done;
} while (len > 0);
- PyBuffer_Release(&pbuf);
res = Py_NewRef(Py_None);
done:
- PyBuffer_Release(&pbuf);
return res;
}
-PyDoc_STRVAR(sendall_doc,
-"sendall(data[, flags])\n\
-\n\
-Send a data string to the socket. For the optional flags\n\
-argument, see the Unix manual. This calls send() repeatedly\n\
-until all data is sent. If an error occurs, it's impossible\n\
-to tell how much data has been sent.");
-
#ifdef HAVE_SENDTO
struct sock_sendto {
@@ -4884,10 +4852,8 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
}
}
for (; ndatabufs < ndataparts; ndatabufs++) {
- if (!PyArg_Parse(PySequence_Fast_GET_ITEM(data_fast, ndatabufs),
- "y*;sendmsg() argument 1 must be an iterable of "
- "bytes-like objects",
- &databufs[ndatabufs]))
+ if (PyObject_GetBuffer(PySequence_Fast_GET_ITEM(data_fast, ndatabufs),
+ &databufs[ndatabufs], PyBUF_SIMPLE) < 0)
goto finally;
iovs[ndatabufs].iov_base = databufs[ndatabufs].buf;
iovs[ndatabufs].iov_len = databufs[ndatabufs].len;
@@ -4909,13 +4875,39 @@ sock_sendmsg_impl(PySocketSockObject *s, void *data)
return (ctx->result >= 0);
}
-/* s.sendmsg(buffers[, ancdata[, flags[, address]]]) method */
+/*[clinic input]
+_socket.socket.sendmsg
+ self as s: self(type="PySocketSockObject *")
+ buffers as data_arg: object
+ ancdata as cmsg_arg: object = NULL
+ flags: int = 0
+ address as addr_arg: object = NULL
+ /
+
+Send normal and ancillary data to the socket.
+
+It gathering the non-ancillary data from a series of buffers
+and concatenating it into a single message.
+The buffers argument specifies the non-ancillary
+data as an iterable of bytes-like objects (e.g. bytes objects).
+The ancdata argument specifies the ancillary data (control messages)
+as an iterable of zero or more tuples (cmsg_level, cmsg_type,
+cmsg_data), where cmsg_level and cmsg_type are integers specifying the
+protocol level and protocol-specific type respectively, and cmsg_data
+is a bytes-like object holding the associated data. The flags
+argument defaults to 0 and has the same meaning as for send(). If
+address is supplied and not None, it sets a destination address for
+the message. The return value is the number of bytes of non-ancillary
+data sent.
+[clinic start generated code]*/
static PyObject *
-sock_sendmsg(PyObject *self, PyObject *args)
-{
- PySocketSockObject *s = _PySocketSockObject_CAST(self);
+_socket_socket_sendmsg_impl(PySocketSockObject *s, PyObject *data_arg,
+ PyObject *cmsg_arg, int flags,
+ PyObject *addr_arg)
+/*[clinic end generated code: output=3b4cb1110644ce39 input=479c13d90bd2f88b]*/
+{
Py_ssize_t i, ndatabufs = 0, ncmsgs, ncmsgbufs = 0;
Py_buffer *databufs = NULL;
sock_addr_t addrbuf;
@@ -4927,16 +4919,10 @@ sock_sendmsg(PyObject *self, PyObject *args)
} *cmsgs = NULL;
void *controlbuf = NULL;
size_t controllen, controllen_last;
- int addrlen, flags = 0;
- PyObject *data_arg, *cmsg_arg = NULL, *addr_arg = NULL,
- *cmsg_fast = NULL, *retval = NULL;
+ int addrlen;
+ PyObject *cmsg_fast = NULL, *retval = NULL;
struct sock_sendmsg ctx;
- if (!PyArg_ParseTuple(args, "O|OiO:sendmsg",
- &data_arg, &cmsg_arg, &flags, &addr_arg)) {
- return NULL;
- }
-
memset(&msg, 0, sizeof(msg));
/* Parse destination address. */
@@ -5098,22 +5084,6 @@ finally:
return retval;
}
-PyDoc_STRVAR(sendmsg_doc,
-"sendmsg(buffers[, ancdata[, flags[, address]]]) -> count\n\
-\n\
-Send normal and ancillary data to the socket, gathering the\n\
-non-ancillary data from a series of buffers and concatenating it into\n\
-a single message. The buffers argument specifies the non-ancillary\n\
-data as an iterable of bytes-like objects (e.g. bytes objects).\n\
-The ancdata argument specifies the ancillary data (control messages)\n\
-as an iterable of zero or more tuples (cmsg_level, cmsg_type,\n\
-cmsg_data), where cmsg_level and cmsg_type are integers specifying the\n\
-protocol level and protocol-specific type respectively, and cmsg_data\n\
-is a bytes-like object holding the associated data. The flags\n\
-argument defaults to 0 and has the same meaning as for send(). If\n\
-address is supplied and not None, it sets a destination address for\n\
-the message. The return value is the number of bytes of non-ancillary\n\
-data sent.");
#endif /* CMSG_LEN */
#ifdef HAVE_SOCKADDR_ALG
@@ -5450,8 +5420,8 @@ static PyMethodDef sock_methods[] = {
recvfrom_into_doc
},
#endif
- {"send", sock_send, METH_VARARGS, send_doc},
- {"sendall", sock_sendall, METH_VARARGS, sendall_doc},
+ _SOCKET_SOCKET_SEND_METHODDEF
+ _SOCKET_SOCKET_SENDALL_METHODDEF
#ifdef HAVE_SENDTO
{"sendto", sock_sendto, METH_VARARGS, sendto_doc},
#endif
@@ -5471,7 +5441,7 @@ static PyMethodDef sock_methods[] = {
#ifdef CMSG_LEN
{"recvmsg", sock_recvmsg, METH_VARARGS, recvmsg_doc},
{"recvmsg_into", sock_recvmsg_into, METH_VARARGS, recvmsg_into_doc},
- {"sendmsg", sock_sendmsg, METH_VARARGS, sendmsg_doc},
+ _SOCKET_SOCKET_SENDMSG_METHODDEF
#endif
#ifdef HAVE_SOCKADDR_ALG
{
@@ -6215,8 +6185,10 @@ socket_gethostbyname_ex(PyObject *self, PyObject *args)
#ifdef USE_GETHOSTBYNAME_LOCK
PyThread_acquire_lock(netdb_lock, 1);
#endif
- SUPPRESS_DEPRECATED_CALL
+ _Py_COMP_DIAG_PUSH
+ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
h = gethostbyname(name);
+ _Py_COMP_DIAG_POP
#endif /* HAVE_GETHOSTBYNAME_R */
Py_END_ALLOW_THREADS
/* Some C libraries would require addr.__ss_family instead of
@@ -6320,8 +6292,10 @@ socket_gethostbyaddr(PyObject *self, PyObject *args)
#ifdef USE_GETHOSTBYNAME_LOCK
PyThread_acquire_lock(netdb_lock, 1);
#endif
- SUPPRESS_DEPRECATED_CALL
+ _Py_COMP_DIAG_PUSH
+ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
h = gethostbyaddr(ap, al, af);
+ _Py_COMP_DIAG_POP
#endif /* HAVE_GETHOSTBYNAME_R */
Py_END_ALLOW_THREADS
ret = gethost_common(state, h, SAS2SA(&addr), sizeof(addr), af);
@@ -6738,8 +6712,10 @@ _socket_inet_aton_impl(PyObject *module, const char *ip_addr)
packed_addr = INADDR_BROADCAST;
} else {
- SUPPRESS_DEPRECATED_CALL
+ _Py_COMP_DIAG_PUSH
+ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
packed_addr = inet_addr(ip_addr);
+ _Py_COMP_DIAG_POP
if (packed_addr == INADDR_NONE) { /* invalid address */
PyErr_SetString(PyExc_OSError,
@@ -6782,8 +6758,10 @@ _socket_inet_ntoa_impl(PyObject *module, Py_buffer *packed_ip)
memcpy(&packed_addr, packed_ip->buf, packed_ip->len);
PyBuffer_Release(packed_ip);
- SUPPRESS_DEPRECATED_CALL
+ _Py_COMP_DIAG_PUSH
+ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
return PyUnicode_FromString(inet_ntoa(packed_addr));
+ _Py_COMP_DIAG_POP
}
#endif // HAVE_INET_NTOA
diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h
index 63624d511c3..200b2b8c7d8 100644
--- a/Modules/socketmodule.h
+++ b/Modules/socketmodule.h
@@ -259,7 +259,7 @@ typedef int SOCKET_T;
#endif
// AF_HYPERV is only supported on Windows
-#if defined(AF_HYPERV) && defined(MS_WINDOWS)
+#if defined(AF_HYPERV) && (defined(MS_WINDOWS_APP) || defined(MS_WINDOWS_SYSTEM))
# define HAVE_AF_HYPERV
#endif
diff --git a/Modules/syslogmodule.c b/Modules/syslogmodule.c
index 9c54af51402..ab20fff1509 100644
--- a/Modules/syslogmodule.c
+++ b/Modules/syslogmodule.c
@@ -56,7 +56,6 @@ Revision history:
#include "Python.h"
#include "osdefs.h" // SEP
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
#include <syslog.h>
@@ -92,7 +91,7 @@ syslog_get_argv(void)
Py_ssize_t slash;
PyObject *argv;
- if (_PySys_GetOptionalAttrString("argv", &argv) <= 0) {
+ if (PySys_GetOptionalAttrString("argv", &argv) <= 0) {
return NULL;
}
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 1bfbf3f6a0b..3271d87ddc2 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -187,7 +187,7 @@ time_clockid_converter(PyObject *obj, clockid_t *p)
{
#ifdef _AIX
long long clk_id = PyLong_AsLongLong(obj);
-#elif defined(__DragonFly__)
+#elif defined(__DragonFly__) || defined(__CYGWIN__)
long clk_id = PyLong_AsLong(obj);
#else
int clk_id = PyLong_AsInt(obj);
diff --git a/Modules/xxlimited.c b/Modules/xxlimited.c
index 26ac35734fb..0480fb08498 100644
--- a/Modules/xxlimited.c
+++ b/Modules/xxlimited.c
@@ -424,6 +424,13 @@ xx_clear(PyObject *module)
return 0;
}
+static void
+xx_free(void *module)
+{
+ // allow xx_modexec to omit calling xx_clear on error
+ (void)xx_clear((PyObject *)module);
+}
+
static struct PyModuleDef xxmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "xxlimited",
@@ -433,9 +440,7 @@ static struct PyModuleDef xxmodule = {
.m_slots = xx_slots,
.m_traverse = xx_traverse,
.m_clear = xx_clear,
- /* m_free is not necessary here: xx_clear clears all references,
- * and the module state is deallocated along with the module.
- */
+ .m_free = xx_free,
};
diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c
index d4b4b91697c..f7009364644 100644
--- a/Modules/zlibmodule.c
+++ b/Modules/zlibmodule.c
@@ -17,6 +17,16 @@
#error "At least zlib version 1.2.2.1 is required"
#endif
+#if (SIZEOF_OFF_T == SIZEOF_SIZE_T)
+# define convert_to_z_off_t PyLong_AsSsize_t
+#elif (SIZEOF_OFF_T == SIZEOF_LONG_LONG)
+# define convert_to_z_off_t PyLong_AsLongLong
+#elif (SIZEOF_OFF_T == SIZEOF_LONG)
+# define convert_to_z_off_t PyLong_AsLong
+#else
+# error off_t does not match either size_t, long, or long long!
+#endif
+
// Blocks output buffer wrappers
#include "pycore_blocks_output_buffer.h"
@@ -1877,6 +1887,44 @@ zlib_adler32_impl(PyObject *module, Py_buffer *data, unsigned int value)
}
/*[clinic input]
+zlib.adler32_combine -> unsigned_int
+
+ adler1: unsigned_int(bitwise=True)
+ Adler-32 checksum for sequence A
+
+ adler2: unsigned_int(bitwise=True)
+ Adler-32 checksum for sequence B
+
+ len2: object(subclass_of='&PyLong_Type')
+ Length of sequence B
+ /
+
+Combine two Adler-32 checksums into one.
+
+Given the Adler-32 checksum 'adler1' of a sequence A and the
+Adler-32 checksum 'adler2' of a sequence B of length 'len2',
+return the Adler-32 checksum of A and B concatenated.
+[clinic start generated code]*/
+
+static unsigned int
+zlib_adler32_combine_impl(PyObject *module, unsigned int adler1,
+ unsigned int adler2, PyObject *len2)
+/*[clinic end generated code: output=61842cefb16afb1b input=51bb045c95130c6f]*/
+{
+#if defined(Z_WANT64)
+ z_off64_t len = convert_to_z_off_t(len2);
+#else
+ z_off_t len = convert_to_z_off_t(len2);
+#endif
+ if (PyErr_Occurred()) {
+ return (unsigned int)-1;
+ }
+ return adler32_combine(adler1, adler2, len);
+}
+
+
+
+/*[clinic input]
zlib.crc32 -> unsigned_int
data: Py_buffer
@@ -1923,13 +1971,50 @@ zlib_crc32_impl(PyObject *module, Py_buffer *data, unsigned int value)
return value;
}
+/*[clinic input]
+zlib.crc32_combine -> unsigned_int
+
+ crc1: unsigned_int(bitwise=True)
+ CRC-32 checksum for sequence A
+
+ crc2: unsigned_int(bitwise=True)
+ CRC-32 checksum for sequence B
+
+ len2: object(subclass_of='&PyLong_Type')
+ Length of sequence B
+ /
+
+Combine two CRC-32 checksums into one.
+
+Given the CRC-32 checksum 'crc1' of a sequence A and the
+CRC-32 checksum 'crc2' of a sequence B of length 'len2',
+return the CRC-32 checksum of A and B concatenated.
+[clinic start generated code]*/
+
+static unsigned int
+zlib_crc32_combine_impl(PyObject *module, unsigned int crc1,
+ unsigned int crc2, PyObject *len2)
+/*[clinic end generated code: output=c4def907c602e6eb input=9c8a065d9040dc66]*/
+{
+#if defined(Z_WANT64)
+ z_off64_t len = convert_to_z_off_t(len2);
+#else
+ z_off_t len = convert_to_z_off_t(len2);
+#endif
+ if (PyErr_Occurred()) {
+ return (unsigned int)-1;
+ }
+ return crc32_combine(crc1, crc2, len);
+}
static PyMethodDef zlib_methods[] =
{
ZLIB_ADLER32_METHODDEF
+ ZLIB_ADLER32_COMBINE_METHODDEF
ZLIB_COMPRESS_METHODDEF
ZLIB_COMPRESSOBJ_METHODDEF
ZLIB_CRC32_METHODDEF
+ ZLIB_CRC32_COMBINE_METHODDEF
ZLIB_DECOMPRESS_METHODDEF
ZLIB_DECOMPRESSOBJ_METHODDEF
{NULL, NULL}
@@ -1981,14 +2066,17 @@ static PyType_Spec ZlibDecompressor_type_spec = {
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE),
.slots = ZlibDecompressor_type_slots,
};
+
PyDoc_STRVAR(zlib_module_documentation,
"The functions in this module allow compression and decompression using the\n"
"zlib library, which is based on GNU zip.\n"
"\n"
"adler32(string[, start]) -- Compute an Adler-32 checksum.\n"
+"adler32_combine(adler1, adler2, len2, /) -- Combine two Adler-32 checksums.\n"
"compress(data[, level]) -- Compress data, with compression level 0-9 or -1.\n"
"compressobj([level[, ...]]) -- Return a compressor object.\n"
"crc32(string[, start]) -- Compute a CRC-32 checksum.\n"
+"crc32_combine(crc1, crc2, len2, /) -- Combine two CRC-32 checksums.\n"
"decompress(string,[wbits],[bufsize]) -- Decompresses a compressed string.\n"
"decompressobj([wbits[, zdict]]) -- Return a decompressor object.\n"
"\n"
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index fc407ec6bf9..87ea1162e03 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -1075,10 +1075,11 @@ _PyBytes_FormatEx(const char *format, Py_ssize_t format_len,
}
/* Unescape a backslash-escaped string. */
-PyObject *_PyBytes_DecodeEscape(const char *s,
+PyObject *_PyBytes_DecodeEscape2(const char *s,
Py_ssize_t len,
const char *errors,
- const char **first_invalid_escape)
+ int *first_invalid_escape_char,
+ const char **first_invalid_escape_ptr)
{
int c;
char *p;
@@ -1092,7 +1093,8 @@ PyObject *_PyBytes_DecodeEscape(const char *s,
return NULL;
writer.overallocate = 1;
- *first_invalid_escape = NULL;
+ *first_invalid_escape_char = -1;
+ *first_invalid_escape_ptr = NULL;
end = s + len;
while (s < end) {
@@ -1130,9 +1132,10 @@ PyObject *_PyBytes_DecodeEscape(const char *s,
c = (c<<3) + *s++ - '0';
}
if (c > 0377) {
- if (*first_invalid_escape == NULL) {
- *first_invalid_escape = s-3; /* Back up 3 chars, since we've
- already incremented s. */
+ if (*first_invalid_escape_char == -1) {
+ *first_invalid_escape_char = c;
+ /* Back up 3 chars, since we've already incremented s. */
+ *first_invalid_escape_ptr = s - 3;
}
}
*p++ = c;
@@ -1173,9 +1176,10 @@ PyObject *_PyBytes_DecodeEscape(const char *s,
break;
default:
- if (*first_invalid_escape == NULL) {
- *first_invalid_escape = s-1; /* Back up one char, since we've
- already incremented s. */
+ if (*first_invalid_escape_char == -1) {
+ *first_invalid_escape_char = (unsigned char)s[-1];
+ /* Back up one char, since we've already incremented s. */
+ *first_invalid_escape_ptr = s - 1;
}
*p++ = '\\';
s--;
@@ -1195,18 +1199,19 @@ PyObject *PyBytes_DecodeEscape(const char *s,
Py_ssize_t Py_UNUSED(unicode),
const char *Py_UNUSED(recode_encoding))
{
- const char* first_invalid_escape;
- PyObject *result = _PyBytes_DecodeEscape(s, len, errors,
- &first_invalid_escape);
+ int first_invalid_escape_char;
+ const char *first_invalid_escape_ptr;
+ PyObject *result = _PyBytes_DecodeEscape2(s, len, errors,
+ &first_invalid_escape_char,
+ &first_invalid_escape_ptr);
if (result == NULL)
return NULL;
- if (first_invalid_escape != NULL) {
- unsigned char c = *first_invalid_escape;
- if ('4' <= c && c <= '7') {
+ if (first_invalid_escape_char != -1) {
+ if (first_invalid_escape_char > 0xff) {
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
- "b\"\\%.3s\" is an invalid octal escape sequence. "
+ "b\"\\%o\" is an invalid octal escape sequence. "
"Such sequences will not work in the future. ",
- first_invalid_escape) < 0)
+ first_invalid_escape_char) < 0)
{
Py_DECREF(result);
return NULL;
@@ -1216,7 +1221,7 @@ PyObject *PyBytes_DecodeEscape(const char *s,
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"b\"\\%c\" is an invalid escape sequence. "
"Such sequences will not work in the future. ",
- c) < 0)
+ first_invalid_escape_char) < 0)
{
Py_DECREF(result);
return NULL;
diff --git a/Objects/call.c b/Objects/call.c
index b1610dababd..c9a18bcc3da 100644
--- a/Objects/call.c
+++ b/Objects/call.c
@@ -834,12 +834,15 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
assert(PyVectorcall_NARGS(nargsf) >= 1);
PyThreadState *tstate = _PyThreadState_GET();
- PyObject *callable = NULL;
+ _PyCStackRef method;
+ _PyThreadState_PushCStackRef(tstate, &method);
/* Use args[0] as "self" argument */
- int unbound = _PyObject_GetMethod(args[0], name, &callable);
- if (callable == NULL) {
+ int unbound = _PyObject_GetMethodStackRef(tstate, args[0], name, &method.ref);
+ if (PyStackRef_IsNull(method.ref)) {
+ _PyThreadState_PopCStackRef(tstate, &method);
return NULL;
}
+ PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
if (unbound) {
/* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since
@@ -855,7 +858,7 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
args, nargsf, kwnames);
- Py_DECREF(callable);
+ _PyThreadState_PopCStackRef(tstate, &method);
return result;
}
@@ -868,11 +871,14 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
return null_error(tstate);
}
- PyObject *callable = NULL;
- int is_method = _PyObject_GetMethod(obj, name, &callable);
- if (callable == NULL) {
+ _PyCStackRef method;
+ _PyThreadState_PushCStackRef(tstate, &method);
+ int is_method = _PyObject_GetMethodStackRef(tstate, obj, name, &method.ref);
+ if (PyStackRef_IsNull(method.ref)) {
+ _PyThreadState_PopCStackRef(tstate, &method);
return NULL;
}
+ PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
obj = is_method ? obj : NULL;
va_list vargs;
@@ -880,7 +886,7 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
PyObject *result = object_vacall(tstate, obj, callable, vargs);
va_end(vargs);
- Py_DECREF(callable);
+ _PyThreadState_PopCStackRef(tstate, &method);
return result;
}
@@ -897,12 +903,15 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)
if (!oname) {
return NULL;
}
-
- PyObject *callable = NULL;
- int is_method = _PyObject_GetMethod(obj, oname, &callable);
- if (callable == NULL) {
+ _PyCStackRef method;
+ _PyThreadState_PushCStackRef(tstate, &method);
+ int is_method = _PyObject_GetMethodStackRef(tstate, obj, oname, &method.ref);
+ if (PyStackRef_IsNull(method.ref)) {
+ _PyThreadState_PopCStackRef(tstate, &method);
return NULL;
}
+ PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
+
obj = is_method ? obj : NULL;
va_list vargs;
@@ -910,7 +919,7 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)
PyObject *result = object_vacall(tstate, obj, callable, vargs);
va_end(vargs);
- Py_DECREF(callable);
+ _PyThreadState_PopCStackRef(tstate, &method);
return result;
}
diff --git a/Objects/classobject.c b/Objects/classobject.c
index b5df3d1a41b..e71f301f2ef 100644
--- a/Objects/classobject.c
+++ b/Objects/classobject.c
@@ -7,6 +7,7 @@
#include "pycore_object.h"
#include "pycore_pyerrors.h"
#include "pycore_pystate.h" // _PyThreadState_GET()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "clinic/classobject.c.h"
@@ -244,9 +245,8 @@ static void
method_dealloc(PyObject *self)
{
PyMethodObject *im = _PyMethodObject_CAST(self);
- PyObject_GC_UnTrack(im);
- if (im->im_weakreflist != NULL)
- PyObject_ClearWeakRefs((PyObject *)im);
+ _PyObject_GC_UNTRACK(im);
+ FT_CLEAR_WEAKREFS(self, im->im_weakreflist);
Py_DECREF(im->im_func);
Py_XDECREF(im->im_self);
assert(Py_IS_TYPE(self, &PyMethod_Type));
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index bf24a4af445..ba178abc0c0 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -17,6 +17,7 @@
#include "pycore_tuple.h" // _PyTuple_ITEMS()
#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal()
#include "pycore_uniqueid.h" // _PyObject_AssignUniqueId()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "clinic/codeobject.c.h"
#include <stdbool.h>
@@ -1690,12 +1691,414 @@ PyCode_GetFreevars(PyCodeObject *code)
}
+#define GET_OPARG(co, i, initial) (initial)
+// We may want to move these macros to pycore_opcode_utils.h
+// and use them in Python/bytecodes.c.
+#define LOAD_GLOBAL_NAME_INDEX(oparg) ((oparg)>>1)
+#define LOAD_ATTR_NAME_INDEX(oparg) ((oparg)>>1)
+
+#ifndef Py_DEBUG
+#define GETITEM(v, i) PyTuple_GET_ITEM((v), (i))
+#else
+static inline PyObject *
+GETITEM(PyObject *v, Py_ssize_t i)
+{
+ assert(PyTuple_Check(v));
+ assert(i >= 0);
+ assert(i < PyTuple_GET_SIZE(v));
+ assert(PyTuple_GET_ITEM(v, i) != NULL);
+ return PyTuple_GET_ITEM(v, i);
+}
+#endif
+
+static int
+identify_unbound_names(PyThreadState *tstate, PyCodeObject *co,
+ PyObject *globalnames, PyObject *attrnames,
+ PyObject *globalsns, PyObject *builtinsns,
+ struct co_unbound_counts *counts, int *p_numdupes)
+{
+ // This function is inspired by inspect.getclosurevars().
+ // It would be nicer if we had something similar to co_localspluskinds,
+ // but for co_names.
+ assert(globalnames != NULL);
+ assert(PySet_Check(globalnames));
+ assert(PySet_GET_SIZE(globalnames) == 0 || counts != NULL);
+ assert(attrnames != NULL);
+ assert(PySet_Check(attrnames));
+ assert(PySet_GET_SIZE(attrnames) == 0 || counts != NULL);
+ assert(globalsns == NULL || PyDict_Check(globalsns));
+ assert(builtinsns == NULL || PyDict_Check(builtinsns));
+ assert(counts == NULL || counts->total == 0);
+ struct co_unbound_counts unbound = {0};
+ int numdupes = 0;
+ Py_ssize_t len = Py_SIZE(co);
+ for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) {
+ _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i);
+ if (inst.op.code == LOAD_ATTR) {
+ int oparg = GET_OPARG(co, i, inst.op.arg);
+ int index = LOAD_ATTR_NAME_INDEX(oparg);
+ PyObject *name = GETITEM(co->co_names, index);
+ if (PySet_Contains(attrnames, name)) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ continue;
+ }
+ unbound.total += 1;
+ unbound.numattrs += 1;
+ if (PySet_Add(attrnames, name) < 0) {
+ return -1;
+ }
+ if (PySet_Contains(globalnames, name)) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ numdupes += 1;
+ }
+ }
+ else if (inst.op.code == LOAD_GLOBAL) {
+ int oparg = GET_OPARG(co, i, inst.op.arg);
+ int index = LOAD_ATTR_NAME_INDEX(oparg);
+ PyObject *name = GETITEM(co->co_names, index);
+ if (PySet_Contains(globalnames, name)) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ continue;
+ }
+ unbound.total += 1;
+ unbound.globals.total += 1;
+ if (globalsns != NULL && PyDict_Contains(globalsns, name)) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ unbound.globals.numglobal += 1;
+ }
+ else if (builtinsns != NULL && PyDict_Contains(builtinsns, name)) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ unbound.globals.numbuiltin += 1;
+ }
+ else {
+ unbound.globals.numunknown += 1;
+ }
+ if (PySet_Add(globalnames, name) < 0) {
+ return -1;
+ }
+ if (PySet_Contains(attrnames, name)) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ numdupes += 1;
+ }
+ }
+ }
+ if (counts != NULL) {
+ *counts = unbound;
+ }
+ if (p_numdupes != NULL) {
+ *p_numdupes = numdupes;
+ }
+ return 0;
+}
+
+
+void
+_PyCode_GetVarCounts(PyCodeObject *co, _PyCode_var_counts_t *counts)
+{
+ assert(counts != NULL);
+
+ // Count the locals, cells, and free vars.
+ struct co_locals_counts locals = {0};
+ int numfree = 0;
+ PyObject *kinds = co->co_localspluskinds;
+ Py_ssize_t numlocalplusfree = PyBytes_GET_SIZE(kinds);
+ for (int i = 0; i < numlocalplusfree; i++) {
+ _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
+ if (kind & CO_FAST_FREE) {
+ assert(!(kind & CO_FAST_LOCAL));
+ assert(!(kind & CO_FAST_HIDDEN));
+ assert(!(kind & CO_FAST_ARG));
+ numfree += 1;
+ }
+ else {
+ // Apparently not all non-free vars a CO_FAST_LOCAL.
+ assert(kind);
+ locals.total += 1;
+ if (kind & CO_FAST_ARG) {
+ locals.args.total += 1;
+ if (kind & CO_FAST_ARG_VAR) {
+ if (kind & CO_FAST_ARG_POS) {
+ assert(!(kind & CO_FAST_ARG_KW));
+ assert(!locals.args.varargs);
+ locals.args.varargs = 1;
+ }
+ else {
+ assert(kind & CO_FAST_ARG_KW);
+ assert(!locals.args.varkwargs);
+ locals.args.varkwargs = 1;
+ }
+ }
+ else if (kind & CO_FAST_ARG_POS) {
+ if (kind & CO_FAST_ARG_KW) {
+ locals.args.numposorkw += 1;
+ }
+ else {
+ locals.args.numposonly += 1;
+ }
+ }
+ else {
+ assert(kind & CO_FAST_ARG_KW);
+ locals.args.numkwonly += 1;
+ }
+ if (kind & CO_FAST_CELL) {
+ locals.cells.total += 1;
+ locals.cells.numargs += 1;
+ }
+ // Args are never hidden currently.
+ assert(!(kind & CO_FAST_HIDDEN));
+ }
+ else {
+ if (kind & CO_FAST_CELL) {
+ locals.cells.total += 1;
+ locals.cells.numothers += 1;
+ if (kind & CO_FAST_HIDDEN) {
+ locals.hidden.total += 1;
+ locals.hidden.numcells += 1;
+ }
+ }
+ else {
+ locals.numpure += 1;
+ if (kind & CO_FAST_HIDDEN) {
+ locals.hidden.total += 1;
+ locals.hidden.numpure += 1;
+ }
+ }
+ }
+ }
+ }
+ assert(locals.args.total == (
+ co->co_argcount + co->co_kwonlyargcount
+ + !!(co->co_flags & CO_VARARGS)
+ + !!(co->co_flags & CO_VARKEYWORDS)));
+ assert(locals.args.numposonly == co->co_posonlyargcount);
+ assert(locals.args.numposonly + locals.args.numposorkw == co->co_argcount);
+ assert(locals.args.numkwonly == co->co_kwonlyargcount);
+ assert(locals.cells.total == co->co_ncellvars);
+ assert(locals.args.total + locals.numpure == co->co_nlocals);
+ assert(locals.total + locals.cells.numargs == co->co_nlocals + co->co_ncellvars);
+ assert(locals.total + numfree == co->co_nlocalsplus);
+ assert(numfree == co->co_nfreevars);
+
+ // Get the unbound counts.
+ assert(PyTuple_GET_SIZE(co->co_names) >= 0);
+ assert(PyTuple_GET_SIZE(co->co_names) < INT_MAX);
+ int numunbound = (int)PyTuple_GET_SIZE(co->co_names);
+ struct co_unbound_counts unbound = {
+ .total = numunbound,
+ // numglobal and numattrs can be set later
+ // with _PyCode_SetUnboundVarCounts().
+ .numunknown = numunbound,
+ };
+
+ // "Return" the result.
+ *counts = (_PyCode_var_counts_t){
+ .total = locals.total + numfree + unbound.total,
+ .locals = locals,
+ .numfree = numfree,
+ .unbound = unbound,
+ };
+}
+
+int
+_PyCode_SetUnboundVarCounts(PyThreadState *tstate,
+ PyCodeObject *co, _PyCode_var_counts_t *counts,
+ PyObject *globalnames, PyObject *attrnames,
+ PyObject *globalsns, PyObject *builtinsns)
+{
+ int res = -1;
+ PyObject *globalnames_owned = NULL;
+ PyObject *attrnames_owned = NULL;
+
+ // Prep the name sets.
+ if (globalnames == NULL) {
+ globalnames_owned = PySet_New(NULL);
+ if (globalnames_owned == NULL) {
+ goto finally;
+ }
+ globalnames = globalnames_owned;
+ }
+ else if (!PySet_Check(globalnames)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "expected a set for \"globalnames\", got %R", globalnames);
+ goto finally;
+ }
+ if (attrnames == NULL) {
+ attrnames_owned = PySet_New(NULL);
+ if (attrnames_owned == NULL) {
+ goto finally;
+ }
+ attrnames = attrnames_owned;
+ }
+ else if (!PySet_Check(attrnames)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "expected a set for \"attrnames\", got %R", attrnames);
+ goto finally;
+ }
+
+ // Fill in unbound.globals and unbound.numattrs.
+ struct co_unbound_counts unbound = {0};
+ int numdupes = 0;
+ Py_BEGIN_CRITICAL_SECTION(co);
+ res = identify_unbound_names(
+ tstate, co, globalnames, attrnames, globalsns, builtinsns,
+ &unbound, &numdupes);
+ Py_END_CRITICAL_SECTION();
+ if (res < 0) {
+ goto finally;
+ }
+ assert(unbound.numunknown == 0);
+ assert(unbound.total - numdupes <= counts->unbound.total);
+ assert(counts->unbound.numunknown == counts->unbound.total);
+ // There may be a name that is both a global and an attr.
+ int totalunbound = counts->unbound.total + numdupes;
+ unbound.numunknown = totalunbound - unbound.total;
+ unbound.total = totalunbound;
+ counts->unbound = unbound;
+ counts->total += numdupes;
+ res = 0;
+
+finally:
+ Py_XDECREF(globalnames_owned);
+ Py_XDECREF(attrnames_owned);
+ return res;
+}
+
+
+int
+_PyCode_CheckNoInternalState(PyCodeObject *co, const char **p_errmsg)
+{
+ const char *errmsg = NULL;
+ // We don't worry about co_executors, co_instrumentation,
+ // or co_monitoring. They are essentially ephemeral.
+ if (co->co_extra != NULL) {
+ errmsg = "only basic code objects are supported";
+ }
+
+ if (errmsg != NULL) {
+ if (p_errmsg != NULL) {
+ *p_errmsg = errmsg;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+int
+_PyCode_CheckNoExternalState(PyCodeObject *co, _PyCode_var_counts_t *counts,
+ const char **p_errmsg)
+{
+ const char *errmsg = NULL;
+ if (counts->numfree > 0) { // It's a closure.
+ errmsg = "closures not supported";
+ }
+ else if (counts->unbound.globals.numglobal > 0) {
+ errmsg = "globals not supported";
+ }
+ else if (counts->unbound.globals.numbuiltin > 0
+ && counts->unbound.globals.numunknown > 0)
+ {
+ errmsg = "globals not supported";
+ }
+ // Otherwise we don't check counts.unbound.globals.numunknown since we can't
+ // distinguish beween globals and builtins here.
+
+ if (errmsg != NULL) {
+ if (p_errmsg != NULL) {
+ *p_errmsg = errmsg;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+int
+_PyCode_VerifyStateless(PyThreadState *tstate,
+ PyCodeObject *co, PyObject *globalnames,
+ PyObject *globalsns, PyObject *builtinsns)
+{
+ const char *errmsg;
+ _PyCode_var_counts_t counts = {0};
+ _PyCode_GetVarCounts(co, &counts);
+ if (_PyCode_SetUnboundVarCounts(
+ tstate, co, &counts, globalnames, NULL,
+ globalsns, builtinsns) < 0)
+ {
+ return -1;
+ }
+ // We may consider relaxing the internal state constraints
+ // if it becomes a problem.
+ if (!_PyCode_CheckNoInternalState(co, &errmsg)) {
+ _PyErr_SetString(tstate, PyExc_ValueError, errmsg);
+ return -1;
+ }
+ if (builtinsns != NULL) {
+ // Make sure the next check will fail for globals,
+ // even if there aren't any builtins.
+ counts.unbound.globals.numbuiltin += 1;
+ }
+ if (!_PyCode_CheckNoExternalState(co, &counts, &errmsg)) {
+ _PyErr_SetString(tstate, PyExc_ValueError, errmsg);
+ return -1;
+ }
+ // Note that we don't check co->co_flags & CO_NESTED for anything here.
+ return 0;
+}
+
+
+int
+_PyCode_CheckPureFunction(PyCodeObject *co, const char **p_errmsg)
+{
+ const char *errmsg = NULL;
+ if (co->co_flags & CO_GENERATOR) {
+ errmsg = "generators not supported";
+ }
+ else if (co->co_flags & CO_COROUTINE) {
+ errmsg = "coroutines not supported";
+ }
+ else if (co->co_flags & CO_ITERABLE_COROUTINE) {
+ errmsg = "coroutines not supported";
+ }
+ else if (co->co_flags & CO_ASYNC_GENERATOR) {
+ errmsg = "generators not supported";
+ }
+
+ if (errmsg != NULL) {
+ if (p_errmsg != NULL) {
+ *p_errmsg = errmsg;
+ }
+ return 0;
+ }
+ return 1;
+}
+
/* Here "value" means a non-None value, since a bare return is identical
* to returning None explicitly. Likewise a missing return statement
* at the end of the function is turned into "return None". */
-int
-_PyCode_ReturnsOnlyNone(PyCodeObject *co)
+static int
+code_returns_only_none(PyCodeObject *co)
{
+ if (!_PyCode_CheckPureFunction(co, NULL)) {
+ return 0;
+ }
+ int len = (int)Py_SIZE(co);
+ assert(len > 0);
+
+ // The last instruction either returns or raises. We can take advantage
+ // of that for a quick exit.
+ _Py_CODEUNIT final = _Py_GetBaseCodeUnit(co, len-1);
+
// Look up None in co_consts.
Py_ssize_t nconsts = PyTuple_Size(co->co_consts);
int none_index = 0;
@@ -1706,31 +2109,57 @@ _PyCode_ReturnsOnlyNone(PyCodeObject *co)
}
if (none_index == nconsts) {
// None wasn't there, which means there was no implicit return,
- // "return", or "return None". That means there must be
- // an explicit return (non-None).
- return 0;
- }
+ // "return", or "return None".
- // Walk the bytecode, looking for RETURN_VALUE.
- Py_ssize_t len = Py_SIZE(co);
- for (int i = 0; i < len; i++) {
- _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i);
- if (IS_RETURN_OPCODE(inst.op.code)) {
- assert(i != 0);
- // Ignore it if it returns None.
- _Py_CODEUNIT prev = _Py_GetBaseCodeUnit(co, i-1);
- if (prev.op.code == LOAD_CONST) {
- // We don't worry about EXTENDED_ARG for now.
- if (prev.op.arg == none_index) {
- continue;
+ // That means there must be
+ // an explicit return (non-None), or it only raises.
+ if (IS_RETURN_OPCODE(final.op.code)) {
+ // It was an explicit return (non-None).
+ return 0;
+ }
+ // It must end with a raise then. We still have to walk the
+ // bytecode to see if there's any explicit return (non-None).
+ assert(IS_RAISE_OPCODE(final.op.code));
+ for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) {
+ _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i);
+ if (IS_RETURN_OPCODE(inst.op.code)) {
+ // We alraedy know it isn't returning None.
+ return 0;
+ }
+ }
+ // It must only raise.
+ }
+ else {
+ // Walk the bytecode, looking for RETURN_VALUE.
+ for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) {
+ _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i);
+ if (IS_RETURN_OPCODE(inst.op.code)) {
+ assert(i != 0);
+ // Ignore it if it returns None.
+ _Py_CODEUNIT prev = _Py_GetBaseCodeUnit(co, i-1);
+ if (prev.op.code == LOAD_CONST) {
+ // We don't worry about EXTENDED_ARG for now.
+ if (prev.op.arg == none_index) {
+ continue;
+ }
}
+ return 0;
}
- return 0;
}
}
return 1;
}
+int
+_PyCode_ReturnsOnlyNone(PyCodeObject *co)
+{
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(co);
+ res = code_returns_only_none(co);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
#ifdef _Py_TIER2
@@ -1955,6 +2384,8 @@ free_monitoring_data(_PyCoMonitoringData *data)
static void
code_dealloc(PyObject *self)
{
+ PyThreadState *tstate = PyThreadState_GET();
+ _Py_atomic_add_uint64(&tstate->interp->_code_object_generation, 1);
PyCodeObject *co = _PyCodeObject_CAST(self);
_PyObject_ResurrectStart(self);
notify_code_watchers(PY_CODE_EVENT_DESTROY, co);
@@ -2006,9 +2437,7 @@ code_dealloc(PyObject *self)
Py_XDECREF(co->_co_cached->_co_varnames);
PyMem_Free(co->_co_cached);
}
- if (co->co_weakreflist != NULL) {
- PyObject_ClearWeakRefs(self);
- }
+ FT_CLEAR_WEAKREFS(self, co->co_weakreflist);
free_monitoring_data(co->_co_monitoring);
#ifdef Py_GIL_DISABLED
// The first element always points to the mutable bytecode at the end of
@@ -2939,7 +3368,7 @@ create_tlbc_lock_held(PyCodeObject *co, Py_ssize_t idx)
}
memcpy(new_tlbc->entries, tlbc->entries, tlbc->size * sizeof(void *));
_Py_atomic_store_ptr_release(&co->co_tlbc, new_tlbc);
- _PyMem_FreeDelayed(tlbc);
+ _PyMem_FreeDelayed(tlbc, tlbc->size * sizeof(void *));
tlbc = new_tlbc;
}
char *bc = PyMem_Calloc(1, _PyCode_NBYTES(co));
diff --git a/Objects/complexobject.c b/Objects/complexobject.c
index c2dd320ae73..b66ebe131ae 100644
--- a/Objects/complexobject.c
+++ b/Objects/complexobject.c
@@ -1,4 +1,3 @@
-
/* Complex object implementation */
/* Borrows heavily from floatobject.c */
@@ -9,6 +8,7 @@
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_complexobject.h" // _PyComplex_FormatAdvancedWriter()
#include "pycore_floatobject.h" // _Py_convert_int_to_double()
+#include "pycore_freelist.h" // _Py_FREELIST_FREE(), _Py_FREELIST_POP()
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_object.h" // _PyObject_Init()
#include "pycore_pymath.h" // _Py_ADJUST_ERANGE2()
@@ -410,16 +410,32 @@ complex_subtype_from_c_complex(PyTypeObject *type, Py_complex cval)
PyObject *
PyComplex_FromCComplex(Py_complex cval)
{
- /* Inline PyObject_New */
- PyComplexObject *op = PyObject_Malloc(sizeof(PyComplexObject));
+ PyComplexObject *op = _Py_FREELIST_POP(PyComplexObject, complexes);
+
if (op == NULL) {
- return PyErr_NoMemory();
+ /* Inline PyObject_New */
+ op = PyObject_Malloc(sizeof(PyComplexObject));
+ if (op == NULL) {
+ return PyErr_NoMemory();
+ }
+ _PyObject_Init((PyObject*)op, &PyComplex_Type);
}
- _PyObject_Init((PyObject*)op, &PyComplex_Type);
op->cval = cval;
return (PyObject *) op;
}
+static void
+complex_dealloc(PyObject *op)
+{
+ assert(PyComplex_Check(op));
+ if (PyComplex_CheckExact(op)) {
+ _Py_FREELIST_FREE(complexes, op, PyObject_Free);
+ }
+ else {
+ Py_TYPE(op)->tp_free(op);
+ }
+}
+
static PyObject *
complex_subtype_from_doubles(PyTypeObject *type, double real, double imag)
{
@@ -1383,7 +1399,7 @@ PyTypeObject PyComplex_Type = {
"complex",
sizeof(PyComplexObject),
0,
- 0, /* tp_dealloc */
+ complex_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
diff --git a/Objects/descrobject.c b/Objects/descrobject.c
index 10c465b95ac..d3d17e92b6d 100644
--- a/Objects/descrobject.c
+++ b/Objects/descrobject.c
@@ -1233,7 +1233,10 @@ static PyObject *
mappingproxy_richcompare(PyObject *self, PyObject *w, int op)
{
mappingproxyobject *v = (mappingproxyobject *)self;
- return PyObject_RichCompare(v->mapping, w, op);
+ if (op == Py_EQ || op == Py_NE) {
+ return PyObject_RichCompare(v->mapping, w, op);
+ }
+ Py_RETURN_NOTIMPLEMENTED;
}
static int
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 59b0cf1ce7d..6b7b150f0e2 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -547,13 +547,13 @@ static inline uint8_t
calculate_log2_keysize(Py_ssize_t minsize)
{
#if SIZEOF_LONG == SIZEOF_SIZE_T
- minsize = (minsize | PyDict_MINSIZE) - 1;
- return _Py_bit_length(minsize | (PyDict_MINSIZE-1));
+ minsize = Py_MAX(minsize, PyDict_MINSIZE);
+ return _Py_bit_length(minsize - 1);
#elif defined(_MSC_VER)
- // On 64bit Windows, sizeof(long) == 4.
- minsize = (minsize | PyDict_MINSIZE) - 1;
+ // On 64bit Windows, sizeof(long) == 4. We cannot use _Py_bit_length.
+ minsize = Py_MAX(minsize, PyDict_MINSIZE);
unsigned long msb;
- _BitScanReverse64(&msb, (uint64_t)minsize);
+ _BitScanReverse64(&msb, (uint64_t)minsize - 1);
return (uint8_t)(msb + 1);
#else
uint8_t log2_size;
@@ -813,7 +813,7 @@ free_keys_object(PyDictKeysObject *keys, bool use_qsbr)
{
#ifdef Py_GIL_DISABLED
if (use_qsbr) {
- _PyMem_FreeDelayed(keys);
+ _PyMem_FreeDelayed(keys, _PyDict_KeysSize(keys));
return;
}
#endif
@@ -858,7 +858,7 @@ free_values(PyDictValues *values, bool use_qsbr)
assert(values->embedded == 0);
#ifdef Py_GIL_DISABLED
if (use_qsbr) {
- _PyMem_FreeDelayed(values);
+ _PyMem_FreeDelayed(values, values_size_from_count(values->capacity));
return;
}
#endif
@@ -2916,6 +2916,11 @@ clear_lock_held(PyObject *op)
}
void
+_PyDict_Clear_LockHeld(PyObject *op) {
+ clear_lock_held(op);
+}
+
+void
PyDict_Clear(PyObject *op)
{
Py_BEGIN_CRITICAL_SECTION(op);
@@ -3178,9 +3183,10 @@ dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
Py_ssize_t pos = 0;
PyObject *key;
Py_hash_t hash;
-
- if (dictresize(interp, mp,
- estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) {
+ uint8_t new_size = Py_MAX(
+ estimate_log2_keysize(PySet_GET_SIZE(iterable)),
+ DK_LOG_SIZE(mp->ma_keys));
+ if (dictresize(interp, mp, new_size, 0)) {
Py_DECREF(mp);
return NULL;
}
@@ -3852,7 +3858,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
}
}
- Py_ssize_t orig_size = other->ma_keys->dk_nentries;
+ Py_ssize_t orig_size = other->ma_used;
Py_ssize_t pos = 0;
Py_hash_t hash;
PyObject *key, *value;
@@ -3886,7 +3892,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
if (err != 0)
return -1;
- if (orig_size != other->ma_keys->dk_nentries) {
+ if (orig_size != other->ma_used) {
PyErr_SetString(PyExc_RuntimeError,
"dict mutated during update");
return -1;
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 76b52efccf8..601fc69c4b1 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -1386,6 +1386,10 @@ mark_stacks(PyCodeObject *code_obj, int len)
stacks[j] = next_stack;
break;
case GET_ITER:
+ next_stack = push_value(pop_value(next_stack), Iterator);
+ next_stack = push_value(next_stack, Iterator);
+ stacks[next_i] = next_stack;
+ break;
case GET_AITER:
next_stack = push_value(pop_value(next_stack), Iterator);
stacks[next_i] = next_stack;
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 6d71dbb5a6a..9532c21fc70 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -1,13 +1,16 @@
/* Function object implementation */
#include "Python.h"
+#include "pycore_code.h" // _PyCode_VerifyStateless()
#include "pycore_dict.h" // _Py_INCREF_DICT()
#include "pycore_function.h" // _PyFunction_Vectorcall
#include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_pyerrors.h" // _PyErr_Occurred()
+#include "pycore_setobject.h" // _PySet_NextEntry()
#include "pycore_stats.h"
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
static const char *
@@ -1146,9 +1149,7 @@ func_dealloc(PyObject *self)
return;
}
_PyObject_GC_UNTRACK(op);
- if (op->func_weakreflist != NULL) {
- PyObject_ClearWeakRefs((PyObject *) op);
- }
+ FT_CLEAR_WEAKREFS(self, op->func_weakreflist);
(void)func_clear((PyObject*)op);
// These aren't cleared by func_clear().
_Py_DECREF_CODE((PyCodeObject *)op->func_code);
@@ -1240,6 +1241,64 @@ PyTypeObject PyFunction_Type = {
};
+int
+_PyFunction_VerifyStateless(PyThreadState *tstate, PyObject *func)
+{
+ assert(!PyErr_Occurred());
+ assert(PyFunction_Check(func));
+
+ // Check the globals.
+ PyObject *globalsns = PyFunction_GET_GLOBALS(func);
+ if (globalsns != NULL && !PyDict_Check(globalsns)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "unsupported globals %R", globalsns);
+ return -1;
+ }
+ // Check the builtins.
+ PyObject *builtinsns = _PyFunction_GET_BUILTINS(func);
+ if (builtinsns != NULL && !PyDict_Check(builtinsns)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "unsupported builtins %R", builtinsns);
+ return -1;
+ }
+ // Disallow __defaults__.
+ PyObject *defaults = PyFunction_GET_DEFAULTS(func);
+ if (defaults != NULL) {
+ assert(PyTuple_Check(defaults)); // per PyFunction_New()
+ if (PyTuple_GET_SIZE(defaults) > 0) {
+ _PyErr_SetString(tstate, PyExc_ValueError,
+ "defaults not supported");
+ return -1;
+ }
+ }
+ // Disallow __kwdefaults__.
+ PyObject *kwdefaults = PyFunction_GET_KW_DEFAULTS(func);
+ if (kwdefaults != NULL) {
+ assert(PyDict_Check(kwdefaults)); // per PyFunction_New()
+ if (PyDict_Size(kwdefaults) > 0) {
+ _PyErr_SetString(tstate, PyExc_ValueError,
+ "keyword defaults not supported");
+ return -1;
+ }
+ }
+ // Disallow __closure__.
+ PyObject *closure = PyFunction_GET_CLOSURE(func);
+ if (closure != NULL) {
+ assert(PyTuple_Check(closure)); // per PyFunction_New()
+ if (PyTuple_GET_SIZE(closure) > 0) {
+ _PyErr_SetString(tstate, PyExc_ValueError, "closures not supported");
+ return -1;
+ }
+ }
+ // Check the code.
+ PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
+ if (_PyCode_VerifyStateless(tstate, co, NULL, globalsns, builtinsns) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+
static int
functools_copy_attr(PyObject *wrapper, PyObject *wrapped, PyObject *name)
{
@@ -1484,6 +1543,11 @@ static PyGetSetDef cm_getsetlist[] = {
{NULL} /* Sentinel */
};
+static PyMethodDef cm_methodlist[] = {
+ {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, NULL},
+ {NULL} /* Sentinel */
+};
+
static PyObject*
cm_repr(PyObject *self)
{
@@ -1542,7 +1606,7 @@ PyTypeObject PyClassMethod_Type = {
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
- 0, /* tp_methods */
+ cm_methodlist, /* tp_methods */
cm_memberlist, /* tp_members */
cm_getsetlist, /* tp_getset */
0, /* tp_base */
@@ -1716,6 +1780,11 @@ static PyGetSetDef sm_getsetlist[] = {
{NULL} /* Sentinel */
};
+static PyMethodDef sm_methodlist[] = {
+ {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, NULL},
+ {NULL} /* Sentinel */
+};
+
static PyObject*
sm_repr(PyObject *self)
{
@@ -1772,7 +1841,7 @@ PyTypeObject PyStaticMethod_Type = {
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
- 0, /* tp_methods */
+ sm_methodlist, /* tp_methods */
sm_memberlist, /* tp_members */
sm_getsetlist, /* tp_getset */
0, /* tp_base */
diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c
index ec3d01f00a3..3bb961aa2b6 100644
--- a/Objects/genericaliasobject.c
+++ b/Objects/genericaliasobject.c
@@ -7,6 +7,7 @@
#include "pycore_typevarobject.h" // _Py_typing_type_repr
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
#include "pycore_unionobject.h" // _Py_union_type_or, _PyGenericAlias_Check
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stdbool.h>
@@ -33,9 +34,7 @@ ga_dealloc(PyObject *self)
gaobject *alias = (gaobject *)self;
_PyObject_GC_UNTRACK(self);
- if (alias->weakreflist != NULL) {
- PyObject_ClearWeakRefs((PyObject *)alias);
- }
+ FT_CLEAR_WEAKREFS(self, alias->weakreflist);
Py_XDECREF(alias->origin);
Py_XDECREF(alias->args);
Py_XDECREF(alias->parameters);
@@ -65,7 +64,7 @@ ga_repr_items_list(PyUnicodeWriter *writer, PyObject *p)
for (Py_ssize_t i = 0; i < len; i++) {
if (i > 0) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
return -1;
}
}
@@ -109,7 +108,7 @@ ga_repr(PyObject *self)
}
for (Py_ssize_t i = 0; i < len; i++) {
if (i > 0) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
goto error;
}
}
@@ -126,7 +125,7 @@ ga_repr(PyObject *self)
}
if (len == 0) {
// for something like tuple[()] we should print a "()"
- if (PyUnicodeWriter_WriteUTF8(writer, "()", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, "()", 2) < 0) {
goto error;
}
}
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 98b2c5004df..3e7d6257006 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -17,6 +17,7 @@
#include "pycore_pyerrors.h" // _PyErr_ClearExcState()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_warnings.h" // _PyErr_WarnUnawaitedCoroutine()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "opcode_ids.h" // RESUME, etc
@@ -161,8 +162,7 @@ gen_dealloc(PyObject *self)
_PyObject_GC_UNTRACK(gen);
- if (gen->gi_weakreflist != NULL)
- PyObject_ClearWeakRefs(self);
+ FT_CLEAR_WEAKREFS(self, gen->gi_weakreflist);
_PyObject_GC_TRACK(self);
@@ -704,7 +704,8 @@ static PyObject *
gen_get_name(PyObject *self, void *Py_UNUSED(ignored))
{
PyGenObject *op = _PyGen_CAST(self);
- return Py_NewRef(op->gi_name);
+ PyObject *name = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->gi_name);
+ return Py_NewRef(name);
}
static int
@@ -718,7 +719,11 @@ gen_set_name(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
"__name__ must be set to a string object");
return -1;
}
- Py_XSETREF(op->gi_name, Py_NewRef(value));
+ Py_BEGIN_CRITICAL_SECTION(self);
+ // gh-133931: To prevent use-after-free from other threads that reference
+ // the gi_name.
+ _PyObject_XSetRefDelayed(&op->gi_name, Py_NewRef(value));
+ Py_END_CRITICAL_SECTION();
return 0;
}
@@ -726,7 +731,8 @@ static PyObject *
gen_get_qualname(PyObject *self, void *Py_UNUSED(ignored))
{
PyGenObject *op = _PyGen_CAST(self);
- return Py_NewRef(op->gi_qualname);
+ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->gi_qualname);
+ return Py_NewRef(qualname);
}
static int
@@ -740,7 +746,11 @@ gen_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
"__qualname__ must be set to a string object");
return -1;
}
- Py_XSETREF(op->gi_qualname, Py_NewRef(value));
+ Py_BEGIN_CRITICAL_SECTION(self);
+ // gh-133931: To prevent use-after-free from other threads that reference
+ // the gi_qualname.
+ _PyObject_XSetRefDelayed(&op->gi_qualname, Py_NewRef(value));
+ Py_END_CRITICAL_SECTION();
return 0;
}
@@ -1451,7 +1461,9 @@ typedef struct PyAsyncGenAThrow {
/* Can be NULL, when in the "aclose()" mode
(equivalent of "athrow(GeneratorExit)") */
- PyObject *agt_args;
+ PyObject *agt_typ;
+ PyObject *agt_tb;
+ PyObject *agt_val;
AwaitableState agt_state;
} PyAsyncGenAThrow;
@@ -2078,7 +2090,9 @@ async_gen_athrow_dealloc(PyObject *self)
_PyObject_GC_UNTRACK(self);
Py_CLEAR(agt->agt_gen);
- Py_CLEAR(agt->agt_args);
+ Py_XDECREF(agt->agt_typ);
+ Py_XDECREF(agt->agt_tb);
+ Py_XDECREF(agt->agt_val);
PyObject_GC_Del(self);
}
@@ -2088,7 +2102,9 @@ async_gen_athrow_traverse(PyObject *self, visitproc visit, void *arg)
{
PyAsyncGenAThrow *agt = _PyAsyncGenAThrow_CAST(self);
Py_VISIT(agt->agt_gen);
- Py_VISIT(agt->agt_args);
+ Py_VISIT(agt->agt_typ);
+ Py_VISIT(agt->agt_tb);
+ Py_VISIT(agt->agt_val);
return 0;
}
@@ -2116,7 +2132,7 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
if (o->agt_state == AWAITABLE_STATE_INIT) {
if (o->agt_gen->ag_running_async) {
o->agt_state = AWAITABLE_STATE_CLOSED;
- if (o->agt_args == NULL) {
+ if (o->agt_typ == NULL) {
PyErr_SetString(
PyExc_RuntimeError,
"aclose(): asynchronous generator is already running");
@@ -2143,7 +2159,7 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
o->agt_state = AWAITABLE_STATE_ITER;
o->agt_gen->ag_running_async = 1;
- if (o->agt_args == NULL) {
+ if (o->agt_typ == NULL) {
/* aclose() mode */
o->agt_gen->ag_closed = 1;
@@ -2157,19 +2173,10 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
goto yield_close;
}
} else {
- PyObject *typ;
- PyObject *tb = NULL;
- PyObject *val = NULL;
-
- if (!PyArg_UnpackTuple(o->agt_args, "athrow", 1, 3,
- &typ, &val, &tb)) {
- return NULL;
- }
-
retval = _gen_throw((PyGenObject *)gen,
0, /* Do not close generator when
PyExc_GeneratorExit is passed */
- typ, val, tb);
+ o->agt_typ, o->agt_val, o->agt_tb);
retval = async_gen_unwrap_value(o->agt_gen, retval);
}
if (retval == NULL) {
@@ -2181,7 +2188,7 @@ async_gen_athrow_send(PyObject *self, PyObject *arg)
assert(o->agt_state == AWAITABLE_STATE_ITER);
retval = gen_send((PyObject *)gen, arg);
- if (o->agt_args) {
+ if (o->agt_typ) {
return async_gen_unwrap_value(o->agt_gen, retval);
} else {
/* aclose() mode */
@@ -2212,7 +2219,7 @@ check_error:
if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||
PyErr_ExceptionMatches(PyExc_GeneratorExit))
{
- if (o->agt_args == NULL) {
+ if (o->agt_typ == NULL) {
/* when aclose() is called we don't want to propagate
StopAsyncIteration or GeneratorExit; just raise
StopIteration, signalling that this 'aclose()' await
@@ -2241,7 +2248,7 @@ async_gen_athrow_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
if (o->agt_state == AWAITABLE_STATE_INIT) {
if (o->agt_gen->ag_running_async) {
o->agt_state = AWAITABLE_STATE_CLOSED;
- if (o->agt_args == NULL) {
+ if (o->agt_typ == NULL) {
PyErr_SetString(
PyExc_RuntimeError,
"aclose(): asynchronous generator is already running");
@@ -2259,7 +2266,7 @@ async_gen_athrow_throw(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
}
PyObject *retval = gen_throw((PyObject*)o->agt_gen, args, nargs);
- if (o->agt_args) {
+ if (o->agt_typ) {
retval = async_gen_unwrap_value(o->agt_gen, retval);
if (retval == NULL) {
o->agt_gen->ag_running_async = 0;
@@ -2334,7 +2341,7 @@ async_gen_athrow_finalize(PyObject *op)
{
PyAsyncGenAThrow *o = (PyAsyncGenAThrow*)op;
if (o->agt_state == AWAITABLE_STATE_INIT) {
- PyObject *method = o->agt_args ? &_Py_ID(athrow) : &_Py_ID(aclose);
+ PyObject *method = o->agt_typ ? &_Py_ID(athrow) : &_Py_ID(aclose);
_PyErr_WarnUnawaitedAgenMethod(o->agt_gen, method);
}
}
@@ -2403,13 +2410,23 @@ PyTypeObject _PyAsyncGenAThrow_Type = {
static PyObject *
async_gen_athrow_new(PyAsyncGenObject *gen, PyObject *args)
{
+ PyObject *typ = NULL;
+ PyObject *tb = NULL;
+ PyObject *val = NULL;
+ if (args && !PyArg_UnpackTuple(args, "athrow", 1, 3, &typ, &val, &tb)) {
+ return NULL;
+ }
+
PyAsyncGenAThrow *o;
o = PyObject_GC_New(PyAsyncGenAThrow, &_PyAsyncGenAThrow_Type);
if (o == NULL) {
return NULL;
}
o->agt_gen = (PyAsyncGenObject*)Py_NewRef(gen);
- o->agt_args = Py_XNewRef(args);
+ o->agt_typ = Py_XNewRef(typ);
+ o->agt_tb = Py_XNewRef(tb);
+ o->agt_val = Py_XNewRef(val);
+
o->agt_state = AWAITABLE_STATE_INIT;
_PyObject_GC_TRACK((PyObject*)o);
return (PyObject*)o;
diff --git a/Objects/interpolationobject.c b/Objects/interpolationobject.c
index aaea3b8c067..a5d407a7b0e 100644
--- a/Objects/interpolationobject.c
+++ b/Objects/interpolationobject.c
@@ -137,6 +137,8 @@ interpolation_reduce(PyObject *op, PyObject *Py_UNUSED(dummy))
static PyMethodDef interpolation_methods[] = {
{"__reduce__", interpolation_reduce, METH_NOARGS,
PyDoc_STR("__reduce__() -> (cls, state)")},
+ {"__class_getitem__", Py_GenericAlias,
+ METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
{NULL, NULL},
};
diff --git a/Objects/listobject.c b/Objects/listobject.c
index c5895645a2d..1b36f4c25ab 100644
--- a/Objects/listobject.c
+++ b/Objects/listobject.c
@@ -61,7 +61,8 @@ free_list_items(PyObject** items, bool use_qsbr)
#ifdef Py_GIL_DISABLED
_PyListArray *array = _Py_CONTAINER_OF(items, _PyListArray, ob_item);
if (use_qsbr) {
- _PyMem_FreeDelayed(array);
+ size_t size = sizeof(_PyListArray) + array->allocated * sizeof(PyObject *);
+ _PyMem_FreeDelayed(array, size);
}
else {
PyMem_Free(array);
@@ -1684,10 +1685,7 @@ sortslice_advance(sortslice *slice, Py_ssize_t n)
/* Avoid malloc for small temp arrays. */
#define MERGESTATE_TEMP_SIZE 256
-/* The largest value of minrun. This must be a power of 2, and >= 1, so that
- * the compute_minrun() algorithm guarantees to return a result no larger than
- * this,
- */
+/* The largest value of minrun. This must be a power of 2, and >= 1 */
#define MAX_MINRUN 64
#if ((MAX_MINRUN) < 1) || ((MAX_MINRUN) & ((MAX_MINRUN) - 1))
#error "MAX_MINRUN must be a power of 2, and >= 1"
@@ -1748,6 +1746,11 @@ struct s_MergeState {
* of tuples. It may be set to safe_object_compare, but the idea is that hopefully
* we can assume more, and use one of the special-case compares. */
int (*tuple_elem_compare)(PyObject *, PyObject *, MergeState *);
+
+ /* Varisbles used for minrun computation. The "ideal" minrun length is
+ * the infinite precision listlen / 2**e. See listsort.txt.
+ */
+ Py_ssize_t mr_current, mr_e, mr_mask;
};
/* binarysort is the best method for sorting small arrays: it does few
@@ -2209,6 +2212,14 @@ merge_init(MergeState *ms, Py_ssize_t list_size, int has_keyfunc,
ms->min_gallop = MIN_GALLOP;
ms->listlen = list_size;
ms->basekeys = lo->keys;
+
+ /* State for generating minrun values. See listsort.txt. */
+ ms->mr_e = 0;
+ while (list_size >> ms->mr_e >= MAX_MINRUN) {
+ ++ms->mr_e;
+ }
+ ms->mr_mask = (1 << ms->mr_e) - 1;
+ ms->mr_current = 0;
}
/* Free all the temp memory owned by the MergeState. This must be called
@@ -2686,27 +2697,15 @@ merge_force_collapse(MergeState *ms)
return 0;
}
-/* Compute a good value for the minimum run length; natural runs shorter
- * than this are boosted artificially via binary insertion.
- *
- * If n < MAX_MINRUN return n (it's too small to bother with fancy stuff).
- * Else if n is an exact power of 2, return MAX_MINRUN / 2.
- * Else return an int k, MAX_MINRUN / 2 <= k <= MAX_MINRUN, such that n/k is
- * close to, but strictly less than, an exact power of 2.
- *
- * See listsort.txt for more info.
- */
-static Py_ssize_t
-merge_compute_minrun(Py_ssize_t n)
+/* Return the next minrun value to use. See listsort.txt. */
+Py_LOCAL_INLINE(Py_ssize_t)
+minrun_next(MergeState *ms)
{
- Py_ssize_t r = 0; /* becomes 1 if any 1 bits are shifted off */
-
- assert(n >= 0);
- while (n >= MAX_MINRUN) {
- r |= n & 1;
- n >>= 1;
- }
- return n + r;
+ ms->mr_current += ms->listlen;
+ assert(ms->mr_current >= 0); /* no overflow */
+ Py_ssize_t result = ms->mr_current >> ms->mr_e;
+ ms->mr_current &= ms->mr_mask;
+ return result;
}
/* Here we define custom comparison functions to optimize for the cases one commonly
@@ -3074,7 +3073,6 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse)
/* March over the array once, left to right, finding natural runs,
* and extending short natural runs to minrun elements.
*/
- minrun = merge_compute_minrun(nremaining);
do {
Py_ssize_t n;
@@ -3083,6 +3081,7 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse)
if (n < 0)
goto fail;
/* If short, extend to min(minrun, nremaining). */
+ minrun = minrun_next(&ms);
if (n < minrun) {
const Py_ssize_t force = nremaining <= minrun ?
nremaining : minrun;
diff --git a/Objects/listsort.txt b/Objects/listsort.txt
index f387d9c116e..5b2fc7d50a2 100644
--- a/Objects/listsort.txt
+++ b/Objects/listsort.txt
@@ -270,8 +270,8 @@ result. This has two primary good effects:
Computing minrun
----------------
-If N < MAX_MINRUN, minrun is N. IOW, binary insertion sort is used for the
-whole array then; it's hard to beat that given the overheads of trying
+If N < MAX_MINRUN, minrun is N. IOW, binary insertion sort is used for the
+whole array then; it's hard to beat that given the overheads of trying
something fancier (see note BINSORT).
When N is a power of 2, testing on random data showed that minrun values of
@@ -288,7 +288,6 @@ that 32 isn't a good choice for the general case! Consider N=2112:
>>> divmod(2112, 32)
(66, 0)
->>>
If the data is randomly ordered, we're very likely to end up with 66 runs
each of length 32. The first 64 of these trigger a sequence of perfectly
@@ -301,22 +300,94 @@ to get 64 elements into place).
If we take minrun=33 in this case, then we're very likely to end up with 64
runs each of length 33, and then all merges are perfectly balanced. Better!
-What we want to avoid is picking minrun such that in
+The original code used a cheap heuristic to pick a minrun that avoided the
+very worst cases of imbalance for the final merge, but "pretty bad" cases
+still existed.
- q, r = divmod(N, minrun)
+In 2025, Stefan Pochmann found a much better approach, based on letting minrun
+vary a bit from one run to the next. Under his scheme, at _all_ levels of the
+merge tree:
-q is a power of 2 and r>0 (then the last merge only gets r elements into
-place, and r < minrun is small compared to N), or q a little larger than a
-power of 2 regardless of r (then we've got a case similar to "2112", again
-leaving too little work for the last merge to do).
+- The number of runs is a power of 2.
+- At most two different run lengths appear.
+- When two do appear, the smaller is one less than the larger.
+- The lengths of run pairs merged never differ by more than one.
-Instead we pick a minrun in range(MAX_MINRUN / 2, MAX_MINRUN + 1) such that
-N/minrun is exactly a power of 2, or if that isn't possible, is close to, but
-strictly less than, a power of 2. This is easier to do than it may sound:
-take the first log2(MAX_MINRUN) bits of N, and add 1 if any of the remaining
-bits are set. In fact, that rule covers every case in this section, including
-small N and exact powers of 2; merge_compute_minrun() is a deceptively simple
-function.
+So, in all respects, as perfectly balanced as possible.
+
+For the 2112 case, that also keeps minrun at 33, but we were lucky there
+that 2112 is 33 times a power of 2. The new approach doesn't rely on luck.
+
+For example, with 315 random elements, the old scheme uses fixed minrun=40 and
+produces runs of length 40, except for the last. The new scheme produces a
+mix of lengths 39 and 40:
+
+old: 40 40 40 40 40 40 40 35
+new: 39 39 40 39 39 40 39 40
+
+Both schemes produce eight runs, a power of 2. That's good for a balanced
+merge tree. But the new scheme allows merges where left and right length
+never differ by more than 1:
+
+39 39 40 39 39 40 39 40
+ 78 79 79 79
+ 157 158
+ 315
+
+(This shows merges downward, e.g., two runs of length 39 are merged and
+become a run of length 78.)
+
+With larger lists, the old scheme can get even more unbalanced. For example,
+with 32769 elements (that's 2**15 + 1), it uses minrun=33 and produces 993
+runs (of length 33). That's not even a power of 2. The new scheme instead
+produces 1024 runs, all with length 32 except for the last one with length 33.
+
+How does it work? Ideally, all runs would be exactly equally long. For the
+above example, each run would have 315/8 = 39.375 elements. Which of course
+doesn't work. But we can get close:
+
+For the first run, we'd like 39.375 elements. Since that's impossible, we
+instead use 39 (the floor) and remember the current leftover fraction 0.375.
+For the second run, we add 0.375 + 39.375 = 39.75. Again impossible, so we
+instead use 39 and remember 0.75. For the third run, we add 0.75 + 39.375 =
+40.125. This time we get 40 and remember 0.125. And so on. Here's a Python
+generator doing that:
+
+def gen_minruns_with_floats(n):
+ mr = n
+ while mr >= MAX_MINRUN:
+ mr /= 2
+
+ mr_current = 0
+ while True:
+ mr_current += mr
+ yield int(mr_current)
+ mr_current %= 1
+
+But while all arithmetic here can be done exactly using binery floating point,
+floats have less precision that a Py_ssize_t, and mixing floats with ints is
+needlessly expensive anyway.
+
+So here's an integer version, where the internal numbers are scaled up by
+2**e, or rather not divided by 2**e. Instead, only each yielded minrun gets
+divided (by right-shifting). For example instead of adding 39.375 and
+reducing modulo 1, it just adds 315 and reduces modulo 8. And always divides
+by 8 to get each actual minrun value:
+
+def gen_minruns_simpler(n):
+ e = 0
+ while (n >> e) >= MAX_MINRUN:
+ e += 1
+ mask = (1 << e) - 1
+
+ mr_current = 0
+ while True:
+ mr_current += n
+ yield mr_current >> e
+ mr_current &= mask
+
+See note MINRUN CODE for a full implementation and a driver that exhaustively
+verifies the claims above for all list lengths through 2 million.
The Merge Pattern
@@ -820,3 +891,75 @@ partially mitigated by pre-scanning the data to determine whether the data is
homogeneous with respect to type. If so, it is sometimes possible to
substitute faster type-specific comparisons for the slower, generic
PyObject_RichCompareBool.
+
+MINRUN CODE
+from itertools import accumulate
+try:
+ from itertools import batched
+except ImportError:
+ from itertools import islice
+ def batched(xs, k):
+ it = iter(xs)
+ while chunk := tuple(islice(it, k)):
+ yield chunk
+
+MAX_MINRUN = 64
+
+def gen_minruns(n):
+ # In listobject.c, initialization is done in merge_init(), and
+ # the body of the loop in minrun_next().
+ mr_e = 0
+ while (n >> mr_e) >= MAX_MINRUN:
+ mr_e += 1
+ mr_mask = (1 << mr_e) - 1
+
+ mr_current = 0
+ while True:
+ mr_current += n
+ yield mr_current >> mr_e
+ mr_current &= mr_mask
+
+def chew(n, show=False):
+ if n < 1:
+ return
+
+ sizes = []
+ tot = 0
+ for size in gen_minruns(n):
+ sizes.append(size)
+ tot += size
+ if tot >= n:
+ break
+ assert tot == n
+ print(n, len(sizes))
+
+ small, large = MAX_MINRUN // 2, MAX_MINRUN
+ while len(sizes) > 1:
+ assert not len(sizes) & 1
+ assert len(sizes).bit_count() == 1 # i.e., power of 2
+ assert sum(sizes) == n
+ assert min(sizes) >= min(n, small)
+ assert max(sizes) <= large
+
+ d = set(sizes)
+ assert len(d) <= 2
+ if len(d) == 2:
+ lo, hi = sorted(d)
+ assert lo + 1 == hi
+
+ mr = n / len(sizes)
+ for i, s in enumerate(accumulate(sizes, initial=0)):
+ assert int(mr * i) == s
+
+ newsizes = []
+ for a, b in batched(sizes, 2):
+ assert abs(a - b) <= 1
+ newsizes.append(a + b)
+ sizes = newsizes
+ smsll = large
+ large *= 2
+
+ assert sizes[0] == n
+
+for n in range(2_000_001):
+ chew(n) \ No newline at end of file
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 40d90ecf4fa..557bb6e1dd9 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -10,6 +10,7 @@
#include "pycore_long.h" // _Py_SmallInts
#include "pycore_object.h" // _PyObject_Init()
#include "pycore_runtime.h" // _PY_NSMALLPOSINTS
+#include "pycore_stackref.h"
#include "pycore_structseq.h" // _PyStructSequence_FiniBuiltin()
#include "pycore_unicodeobject.h" // _PyUnicode_Equal()
@@ -316,6 +317,33 @@ _PyLong_FromSTwoDigits(stwodigits x)
return (PyLongObject*)_PyLong_FromLarge(x);
}
+/* Create a new medium int object from a medium int.
+ * Do not raise. Return NULL if not medium or can't allocate. */
+static inline _PyStackRef
+medium_from_stwodigits(stwodigits x)
+{
+ if (IS_SMALL_INT(x)) {
+ return PyStackRef_FromPyObjectBorrow(get_small_int((sdigit)x));
+ }
+ assert(x != 0);
+ if(!is_medium_int(x)) {
+ return PyStackRef_NULL;
+ }
+ PyLongObject *v = (PyLongObject *)_Py_FREELIST_POP(PyLongObject, ints);
+ if (v == NULL) {
+ v = PyObject_Malloc(sizeof(PyLongObject));
+ if (v == NULL) {
+ return PyStackRef_NULL;
+ }
+ _PyObject_Init((PyObject*)v, &PyLong_Type);
+ }
+ digit abs_x = x < 0 ? (digit)(-x) : (digit)x;
+ _PyLong_SetSignAndDigitCount(v, x<0?-1:1, 1);
+ v->long_value.ob_digit[0] = abs_x;
+ return PyStackRef_FromPyObjectStealMortal((PyObject *)v);
+}
+
+
/* If a freshly-allocated int is already shared, it must
be a small integer, so negating it must go to PyLong_FromLong */
Py_LOCAL_INLINE(void)
@@ -971,16 +999,9 @@ _PyLong_FromByteArray(const unsigned char* bytes, size_t n,
++numsignificantbytes;
}
- /* How many Python int digits do we need? We have
- 8*numsignificantbytes bits, and each Python int digit has
- PyLong_SHIFT bits, so it's the ceiling of the quotient. */
- /* catch overflow before it happens */
- if (numsignificantbytes > (PY_SSIZE_T_MAX - PyLong_SHIFT) / 8) {
- PyErr_SetString(PyExc_OverflowError,
- "byte array too long to convert to int");
- return NULL;
- }
- ndigits = (numsignificantbytes * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT;
+ /* avoid integer overflow */
+ ndigits = numsignificantbytes / PyLong_SHIFT * 8
+ + (numsignificantbytes % PyLong_SHIFT * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT;
v = long_alloc(ndigits);
if (v == NULL)
return NULL;
@@ -1760,6 +1781,10 @@ UNSIGNED_INT_CONVERTER(UnsignedInt, unsigned int)
UNSIGNED_INT_CONVERTER(UnsignedLong, unsigned long)
UNSIGNED_INT_CONVERTER(UnsignedLongLong, unsigned long long)
UNSIGNED_INT_CONVERTER(Size_t, size_t)
+UNSIGNED_INT_CONVERTER(UInt8, uint8_t)
+UNSIGNED_INT_CONVERTER(UInt16, uint16_t)
+UNSIGNED_INT_CONVERTER(UInt32, uint32_t)
+UNSIGNED_INT_CONVERTER(UInt64, uint64_t)
#define CHECK_BINOP(v,w) \
@@ -3774,10 +3799,12 @@ long_add(PyLongObject *a, PyLongObject *b)
return z;
}
-PyObject *
-_PyLong_Add(PyLongObject *a, PyLongObject *b)
+_PyStackRef
+_PyCompactLong_Add(PyLongObject *a, PyLongObject *b)
{
- return (PyObject*)long_add(a, b);
+ assert(_PyLong_BothAreCompact(a, b));
+ stwodigits v = medium_value(a) + medium_value(b);
+ return medium_from_stwodigits(v);
}
static PyObject *
@@ -3817,10 +3844,12 @@ long_sub(PyLongObject *a, PyLongObject *b)
return z;
}
-PyObject *
-_PyLong_Subtract(PyLongObject *a, PyLongObject *b)
+_PyStackRef
+_PyCompactLong_Subtract(PyLongObject *a, PyLongObject *b)
{
- return (PyObject*)long_sub(a, b);
+ assert(_PyLong_BothAreCompact(a, b));
+ stwodigits v = medium_value(a) - medium_value(b);
+ return medium_from_stwodigits(v);
}
static PyObject *
@@ -4264,10 +4293,14 @@ long_mul(PyLongObject *a, PyLongObject *b)
return z;
}
-PyObject *
-_PyLong_Multiply(PyLongObject *a, PyLongObject *b)
+/* This function returns NULL if the result is not compact,
+ * or if it fails to allocate, but never raises */
+_PyStackRef
+_PyCompactLong_Multiply(PyLongObject *a, PyLongObject *b)
{
- return (PyObject*)long_mul(a, b);
+ assert(_PyLong_BothAreCompact(a, b));
+ stwodigits v = medium_value(a) * medium_value(b);
+ return medium_from_stwodigits(v);
}
static PyObject *
diff --git a/Objects/methodobject.c b/Objects/methodobject.c
index c3dcd09ad1c..e6e469ca270 100644
--- a/Objects/methodobject.c
+++ b/Objects/methodobject.c
@@ -8,6 +8,7 @@
#include "pycore_object.h"
#include "pycore_pyerrors.h"
#include "pycore_pystate.h" // _PyThreadState_GET()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
/* undefine macro trampoline to PyCFunction_NewEx */
@@ -167,9 +168,7 @@ meth_dealloc(PyObject *self)
{
PyCFunctionObject *m = _PyCFunctionObject_CAST(self);
PyObject_GC_UnTrack(m);
- if (m->m_weakreflist != NULL) {
- PyObject_ClearWeakRefs((PyObject*) m);
- }
+ FT_CLEAR_WEAKREFS(self, m->m_weakreflist);
// We need to access ml_flags here rather than later.
// `m->m_ml` might have the same lifetime
// as `m_self` when it's dynamically allocated.
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index f363ef173cb..862395e7881 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -12,8 +12,8 @@
#include "pycore_object.h" // _PyType_AllocNoTrack
#include "pycore_pyerrors.h" // _PyErr_FormatFromCause()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "osdefs.h" // MAXPATHLEN
@@ -827,8 +827,7 @@ module_dealloc(PyObject *self)
if (verbose && m->md_name) {
PySys_FormatStderr("# destroy %U\n", m->md_name);
}
- if (m->md_weaklist != NULL)
- PyObject_ClearWeakRefs((PyObject *) m);
+ FT_CLEAR_WEAKREFS(self, m->md_weaklist);
/* bpo-39824: Don't call m_free() if m_size > 0 and md_state=NULL */
if (m->md_def && m->md_def->m_free
@@ -1058,7 +1057,7 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
int is_possibly_shadowing_stdlib = 0;
if (is_possibly_shadowing) {
PyObject *stdlib_modules;
- if (_PySys_GetOptionalAttrString("stdlib_module_names", &stdlib_modules) < 0) {
+ if (PySys_GetOptionalAttrString("stdlib_module_names", &stdlib_modules) < 0) {
goto done;
}
if (stdlib_modules && PyAnySet_Check(stdlib_modules)) {
diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c
index caebe6bf543..201cb8a7df8 100644
--- a/Objects/namespaceobject.c
+++ b/Objects/namespaceobject.c
@@ -124,9 +124,10 @@ namespace_repr(PyObject *ns)
if (PyUnicode_Check(key) && PyUnicode_GET_LENGTH(key) > 0) {
PyObject *value, *item;
- value = PyDict_GetItemWithError(d, key);
- if (value != NULL) {
+ int has_key = PyDict_GetItemRef(d, key, &value);
+ if (has_key == 1) {
item = PyUnicode_FromFormat("%U=%R", key, value);
+ Py_DECREF(value);
if (item == NULL) {
loop_error = 1;
}
@@ -135,7 +136,7 @@ namespace_repr(PyObject *ns)
Py_DECREF(item);
}
}
- else if (PyErr_Occurred()) {
+ else if (has_key < 0) {
loop_error = 1;
}
}
@@ -193,10 +194,14 @@ namespace_clear(PyObject *op)
static PyObject *
namespace_richcompare(PyObject *self, PyObject *other, int op)
{
- if (PyObject_TypeCheck(self, &_PyNamespace_Type) &&
- PyObject_TypeCheck(other, &_PyNamespace_Type))
+ if (
+ (op == Py_EQ || op == Py_NE) &&
+ PyObject_TypeCheck(self, &_PyNamespace_Type) &&
+ PyObject_TypeCheck(other, &_PyNamespace_Type)
+ ) {
return PyObject_RichCompare(((_PyNamespaceObject *)self)->ns_dict,
((_PyNamespaceObject *)other)->ns_dict, op);
+ }
Py_RETURN_NOTIMPLEMENTED;
}
diff --git a/Objects/object.c b/Objects/object.c
index 0974a231ec1..3ed7d55593d 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -925,6 +925,7 @@ _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization)
// In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear()
// In the default build, freelists are per-interpreter and cleared in finalize_interp_types()
clear_freelist(&freelists->floats, is_finalization, free_object);
+ clear_freelist(&freelists->complexes, is_finalization, free_object);
for (Py_ssize_t i = 0; i < PyTuple_MAXSAVESIZE; i++) {
clear_freelist(&freelists->tuples[i], is_finalization, free_object);
}
@@ -1130,11 +1131,14 @@ PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
res = PyObject_RichCompare(v, w, op);
if (res == NULL)
return -1;
- if (PyBool_Check(res))
+ if (PyBool_Check(res)) {
ok = (res == Py_True);
- else
+ assert(_Py_IsImmortal(res));
+ }
+ else {
ok = PyObject_IsTrue(res);
- Py_DECREF(res);
+ Py_DECREF(res);
+ }
return ok;
}
@@ -1209,16 +1213,27 @@ PyObject_HasAttrString(PyObject *obj, const char *name)
int
PyObject_SetAttrString(PyObject *v, const char *name, PyObject *w)
{
- PyObject *s;
- int res;
+ PyThreadState *tstate = _PyThreadState_GET();
+ if (w == NULL && _PyErr_Occurred(tstate)) {
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ _PyErr_SetString(tstate, PyExc_SystemError,
+ "PyObject_SetAttrString() must not be called with NULL value "
+ "and an exception set");
+ _PyErr_ChainExceptions1Tstate(tstate, exc);
+ return -1;
+ }
- if (Py_TYPE(v)->tp_setattr != NULL)
+ if (Py_TYPE(v)->tp_setattr != NULL) {
return (*Py_TYPE(v)->tp_setattr)(v, (char*)name, w);
- s = PyUnicode_InternFromString(name);
- if (s == NULL)
+ }
+
+ PyObject *s = PyUnicode_InternFromString(name);
+ if (s == NULL) {
return -1;
- res = PyObject_SetAttr(v, s, w);
- Py_XDECREF(s);
+ }
+
+ int res = PyObject_SetAttr(v, s, w);
+ Py_DECREF(s);
return res;
}
@@ -1436,6 +1451,16 @@ PyObject_HasAttr(PyObject *obj, PyObject *name)
int
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
{
+ PyThreadState *tstate = _PyThreadState_GET();
+ if (value == NULL && _PyErr_Occurred(tstate)) {
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ _PyErr_SetString(tstate, PyExc_SystemError,
+ "PyObject_SetAttr() must not be called with NULL value "
+ "and an exception set");
+ _PyErr_ChainExceptions1Tstate(tstate, exc);
+ return -1;
+ }
+
PyTypeObject *tp = Py_TYPE(v);
int err;
@@ -1447,8 +1472,7 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
}
Py_INCREF(name);
- PyInterpreterState *interp = _PyInterpreterState_GET();
- _PyUnicode_InternMortal(interp, &name);
+ _PyUnicode_InternMortal(tstate->interp, &name);
if (tp->tp_setattro != NULL) {
err = (*tp->tp_setattro)(v, name, value);
Py_DECREF(name);
@@ -1664,6 +1688,116 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
return 0;
}
+int
+_PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj,
+ PyObject *name, _PyStackRef *method)
+{
+ int meth_found = 0;
+
+ assert(PyStackRef_IsNull(*method));
+
+ PyTypeObject *tp = Py_TYPE(obj);
+ if (!_PyType_IsReady(tp)) {
+ if (PyType_Ready(tp) < 0) {
+ return 0;
+ }
+ }
+
+ if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) {
+ PyObject *res = PyObject_GetAttr(obj, name);
+ if (res != NULL) {
+ *method = PyStackRef_FromPyObjectSteal(res);
+ }
+ return 0;
+ }
+
+ _PyType_LookupStackRefAndVersion(tp, name, method);
+ PyObject *descr = PyStackRef_AsPyObjectBorrow(*method);
+ descrgetfunc f = NULL;
+ if (descr != NULL) {
+ if (_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
+ meth_found = 1;
+ }
+ else {
+ f = Py_TYPE(descr)->tp_descr_get;
+ if (f != NULL && PyDescr_IsData(descr)) {
+ PyObject *value = f(descr, obj, (PyObject *)Py_TYPE(obj));
+ PyStackRef_CLEAR(*method);
+ if (value != NULL) {
+ *method = PyStackRef_FromPyObjectSteal(value);
+ }
+ return 0;
+ }
+ }
+ }
+ PyObject *dict, *attr;
+ if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
+ _PyObject_TryGetInstanceAttribute(obj, name, &attr)) {
+ if (attr != NULL) {
+ PyStackRef_CLEAR(*method);
+ *method = PyStackRef_FromPyObjectSteal(attr);
+ return 0;
+ }
+ dict = NULL;
+ }
+ else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
+ dict = (PyObject *)_PyObject_GetManagedDict(obj);
+ }
+ else {
+ PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
+ if (dictptr != NULL) {
+ dict = FT_ATOMIC_LOAD_PTR_ACQUIRE(*dictptr);
+ }
+ else {
+ dict = NULL;
+ }
+ }
+ if (dict != NULL) {
+ // TODO: use _Py_dict_lookup_threadsafe_stackref
+ Py_INCREF(dict);
+ PyObject *value;
+ if (PyDict_GetItemRef(dict, name, &value) != 0) {
+ // found or error
+ Py_DECREF(dict);
+ PyStackRef_CLEAR(*method);
+ if (value != NULL) {
+ *method = PyStackRef_FromPyObjectSteal(value);
+ }
+ return 0;
+ }
+ // not found
+ Py_DECREF(dict);
+ }
+
+ if (meth_found) {
+ assert(!PyStackRef_IsNull(*method));
+ return 1;
+ }
+
+ if (f != NULL) {
+ PyObject *value = f(descr, obj, (PyObject *)Py_TYPE(obj));
+ PyStackRef_CLEAR(*method);
+ if (value) {
+ *method = PyStackRef_FromPyObjectSteal(value);
+ }
+ return 0;
+ }
+
+ if (descr != NULL) {
+ assert(!PyStackRef_IsNull(*method));
+ return 0;
+ }
+
+ PyErr_Format(PyExc_AttributeError,
+ "'%.100s' object has no attribute '%U'",
+ tp->tp_name, name);
+
+ _PyObject_SetAttributeErrorContext(obj, name);
+ assert(PyStackRef_IsNull(*method));
+ return 0;
+}
+
+
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
PyObject *
@@ -1906,34 +2040,11 @@ PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
int
PyObject_GenericSetDict(PyObject *obj, PyObject *value, void *context)
{
- PyObject **dictptr = _PyObject_GetDictPtr(obj);
- if (dictptr == NULL) {
- if (_PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_INLINE_VALUES) &&
- _PyObject_GetManagedDict(obj) == NULL
- ) {
- /* Was unable to convert to dict */
- PyErr_NoMemory();
- }
- else {
- PyErr_SetString(PyExc_AttributeError,
- "This object has no __dict__");
- }
- return -1;
- }
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "cannot delete __dict__");
return -1;
}
- if (!PyDict_Check(value)) {
- PyErr_Format(PyExc_TypeError,
- "__dict__ must be set to a dictionary, "
- "not a '%.200s'", Py_TYPE(value)->tp_name);
- return -1;
- }
- Py_BEGIN_CRITICAL_SECTION(obj);
- Py_XSETREF(*dictptr, Py_NewRef(value));
- Py_END_CRITICAL_SECTION();
- return 0;
+ return _PyObject_SetDict(obj, value);
}
@@ -1996,9 +2107,25 @@ _dir_locals(void)
PyObject *names;
PyObject *locals;
- locals = _PyEval_GetFrameLocals();
- if (locals == NULL)
+ if (_PyEval_GetFrame() != NULL) {
+ locals = _PyEval_GetFrameLocals();
+ }
+ else {
+ PyThreadState *tstate = _PyThreadState_GET();
+ locals = _PyEval_GetGlobalsFromRunningMain(tstate);
+ if (locals == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ locals = _PyEval_GetFrameLocals();
+ assert(_PyErr_Occurred(tstate));
+ }
+ }
+ else {
+ Py_INCREF(locals);
+ }
+ }
+ if (locals == NULL) {
return NULL;
+ }
names = PyMapping_Keys(locals);
Py_DECREF(locals);
@@ -2931,21 +3058,27 @@ finally:
/* Trashcan support. */
/* Add op to the gcstate->trash_delete_later list. Called when the current
- * call-stack depth gets large. op must be a currently untracked gc'ed
- * object, with refcount 0. Py_DECREF must already have been called on it.
+ * call-stack depth gets large. op must be a gc'ed object, with refcount 0.
+ * Py_DECREF must already have been called on it.
*/
void
_PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op)
{
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
+ PyTypeObject *tp = Py_TYPE(op);
+ assert(tp->tp_flags & Py_TPFLAGS_HAVE_GC);
+ int tracked = 0;
+ if (tp->tp_is_gc == NULL || tp->tp_is_gc(op)) {
+ tracked = _PyObject_GC_IS_TRACKED(op);
+ if (tracked) {
+ _PyObject_GC_UNTRACK(op);
+ }
+ }
+ uintptr_t tagged_ptr = ((uintptr_t)tstate->delete_later) | tracked;
#ifdef Py_GIL_DISABLED
- op->ob_tid = (uintptr_t)tstate->delete_later;
+ op->ob_tid = tagged_ptr;
#else
- /* Store the delete_later pointer in the refcnt field.
- * As this object may still be tracked by the GC,
- * it is important that we never store 0 (NULL). */
- uintptr_t refcnt = (uintptr_t)tstate->delete_later;
- *((uintptr_t*)op) = refcnt+1;
+ _Py_AS_GC(op)->_gc_next = tagged_ptr;
#endif
tstate->delete_later = op;
}
@@ -2960,17 +3093,17 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate)
destructor dealloc = Py_TYPE(op)->tp_dealloc;
#ifdef Py_GIL_DISABLED
- tstate->delete_later = (PyObject*) op->ob_tid;
+ uintptr_t tagged_ptr = op->ob_tid;
op->ob_tid = 0;
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED);
#else
- /* Get the delete_later pointer from the refcnt field.
- * See _PyTrash_thread_deposit_object(). */
- uintptr_t refcnt = *((uintptr_t*)op);
- tstate->delete_later = (PyObject *)(refcnt - 1);
- op->ob_refcnt = 0;
+ uintptr_t tagged_ptr = _Py_AS_GC(op)->_gc_next;
+ _Py_AS_GC(op)->_gc_next = 0;
#endif
-
+ tstate->delete_later = (PyObject *)(tagged_ptr & ~1);
+ if (tagged_ptr & 1) {
+ _PyObject_GC_TRACK(op);
+ }
/* Call the deallocator directly. This used to try to
* fool Py_DECREF into calling it indirectly, but
* Py_DECREF was already called on this object, and in
@@ -3044,10 +3177,11 @@ void
_Py_Dealloc(PyObject *op)
{
PyTypeObject *type = Py_TYPE(op);
+ unsigned long gc_flag = type->tp_flags & Py_TPFLAGS_HAVE_GC;
destructor dealloc = type->tp_dealloc;
PyThreadState *tstate = _PyThreadState_GET();
intptr_t margin = _Py_RecursionLimit_GetMargin(tstate);
- if (margin < 2) {
+ if (margin < 2 && gc_flag) {
_PyTrash_thread_deposit_object(tstate, (PyObject *)op);
return;
}
@@ -3093,7 +3227,7 @@ _Py_Dealloc(PyObject *op)
Py_XDECREF(old_exc);
Py_DECREF(type);
#endif
- if (tstate->delete_later && margin >= 4) {
+ if (tstate->delete_later && margin >= 4 && gc_flag) {
_PyTrash_thread_destroy_chain(tstate);
}
}
@@ -3249,3 +3383,11 @@ PyUnstable_IsImmortal(PyObject *op)
assert(op != NULL);
return _Py_IsImmortal(op);
}
+
+int
+PyUnstable_Object_IsUniquelyReferenced(PyObject *op)
+{
+ _Py_AssertHoldsTstate();
+ assert(op != NULL);
+ return _PyObject_IsUniquelyReferenced(op);
+}
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index b209808da90..deb7fd957e5 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -124,6 +124,33 @@ _PyMem_mi_page_is_safe_to_free(mi_page_t *page)
}
+#ifdef Py_GIL_DISABLED
+
+// If we are deferring collection of more than this amount of memory for
+// mimalloc pages, advance the write sequence. Advancing allows these
+// pages to be re-used in a different thread or for a different size class.
+#define QSBR_PAGE_MEM_LIMIT 4096*20
+
+// Return true if the global write sequence should be advanced for a mimalloc
+// page that is deferred from collection.
+static bool
+should_advance_qsbr_for_page(struct _qsbr_thread_state *qsbr, mi_page_t *page)
+{
+ size_t bsize = mi_page_block_size(page);
+ size_t page_size = page->capacity*bsize;
+ if (page_size > QSBR_PAGE_MEM_LIMIT) {
+ qsbr->deferred_page_memory = 0;
+ return true;
+ }
+ qsbr->deferred_page_memory += page_size;
+ if (qsbr->deferred_page_memory > QSBR_PAGE_MEM_LIMIT) {
+ qsbr->deferred_page_memory = 0;
+ return true;
+ }
+ return false;
+}
+#endif
+
static bool
_PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force)
{
@@ -139,7 +166,14 @@ _PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force)
_PyMem_mi_page_clear_qsbr(page);
page->retire_expire = 0;
- page->qsbr_goal = _Py_qsbr_deferred_advance(tstate->qsbr);
+
+ if (should_advance_qsbr_for_page(tstate->qsbr, page)) {
+ page->qsbr_goal = _Py_qsbr_advance(tstate->qsbr->shared);
+ }
+ else {
+ page->qsbr_goal = _Py_qsbr_shared_next(tstate->qsbr->shared);
+ }
+
llist_insert_tail(&tstate->mimalloc.page_list, &page->qsbr_node);
return false;
}
@@ -1141,8 +1175,44 @@ free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state)
}
}
+
+#ifdef Py_GIL_DISABLED
+
+// For deferred advance on free: the number of deferred items before advancing
+// the write sequence. This is based on WORK_ITEMS_PER_CHUNK. We ideally
+// want to process a chunk before it overflows.
+#define QSBR_DEFERRED_LIMIT 127
+
+// If the deferred memory exceeds 1 MiB, advance the write sequence. This
+// helps limit memory usage due to QSBR delaying frees too long.
+#define QSBR_FREE_MEM_LIMIT 1024*1024
+
+// Return true if the global write sequence should be advanced for a deferred
+// memory free.
+static bool
+should_advance_qsbr_for_free(struct _qsbr_thread_state *qsbr, size_t size)
+{
+ if (size > QSBR_FREE_MEM_LIMIT) {
+ qsbr->deferred_count = 0;
+ qsbr->deferred_memory = 0;
+ qsbr->should_process = true;
+ return true;
+ }
+ qsbr->deferred_count++;
+ qsbr->deferred_memory += size;
+ if (qsbr->deferred_count > QSBR_DEFERRED_LIMIT ||
+ qsbr->deferred_memory > QSBR_FREE_MEM_LIMIT) {
+ qsbr->deferred_count = 0;
+ qsbr->deferred_memory = 0;
+ qsbr->should_process = true;
+ return true;
+ }
+ return false;
+}
+#endif
+
static void
-free_delayed(uintptr_t ptr)
+free_delayed(uintptr_t ptr, size_t size)
{
#ifndef Py_GIL_DISABLED
free_work_item(ptr, NULL, NULL);
@@ -1200,23 +1270,32 @@ free_delayed(uintptr_t ptr)
}
assert(buf != NULL && buf->wr_idx < WORK_ITEMS_PER_CHUNK);
- uint64_t seq = _Py_qsbr_deferred_advance(tstate->qsbr);
+ uint64_t seq;
+ if (should_advance_qsbr_for_free(tstate->qsbr, size)) {
+ seq = _Py_qsbr_advance(tstate->qsbr->shared);
+ }
+ else {
+ seq = _Py_qsbr_shared_next(tstate->qsbr->shared);
+ }
buf->array[buf->wr_idx].ptr = ptr;
buf->array[buf->wr_idx].qsbr_goal = seq;
buf->wr_idx++;
if (buf->wr_idx == WORK_ITEMS_PER_CHUNK) {
+ // Normally the processing of delayed items is done from the eval
+ // breaker. Processing here is a safety measure to ensure too much
+ // work does not accumulate.
_PyMem_ProcessDelayed((PyThreadState *)tstate);
}
#endif
}
void
-_PyMem_FreeDelayed(void *ptr)
+_PyMem_FreeDelayed(void *ptr, size_t size)
{
assert(!((uintptr_t)ptr & 0x01));
if (ptr != NULL) {
- free_delayed((uintptr_t)ptr);
+ free_delayed((uintptr_t)ptr, size);
}
}
@@ -1226,7 +1305,25 @@ _PyObject_XDecRefDelayed(PyObject *ptr)
{
assert(!((uintptr_t)ptr & 0x01));
if (ptr != NULL) {
- free_delayed(((uintptr_t)ptr)|0x01);
+ // We use 0 as the size since we don't have an easy way to know the
+ // actual size. If we are freeing many objects, the write sequence
+ // will be advanced due to QSBR_DEFERRED_LIMIT.
+ free_delayed(((uintptr_t)ptr)|0x01, 0);
+ }
+}
+#endif
+
+#ifdef Py_GIL_DISABLED
+void
+_PyObject_XSetRefDelayed(PyObject **ptr, PyObject *value)
+{
+ PyObject *old = *ptr;
+ FT_ATOMIC_STORE_PTR_RELEASE(*ptr, value);
+ if (old == NULL) {
+ return;
+ }
+ if (!_Py_IsImmortal(old)) {
+ _PyObject_XDecRefDelayed(old);
}
}
#endif
@@ -1238,7 +1335,7 @@ work_queue_first(struct llist_node *head)
}
static void
-process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr,
+process_queue(struct llist_node *head, _PyThreadStateImpl *tstate,
bool keep_empty, delayed_dealloc_cb cb, void *state)
{
while (!llist_empty(head)) {
@@ -1246,7 +1343,7 @@ process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr,
if (buf->rd_idx < buf->wr_idx) {
struct _mem_work_item *item = &buf->array[buf->rd_idx];
- if (!_Py_qsbr_poll(qsbr, item->qsbr_goal)) {
+ if (!_Py_qsbr_poll(tstate->qsbr, item->qsbr_goal)) {
return;
}
@@ -1270,11 +1367,11 @@ process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr,
static void
process_interp_queue(struct _Py_mem_interp_free_queue *queue,
- struct _qsbr_thread_state *qsbr, delayed_dealloc_cb cb,
+ _PyThreadStateImpl *tstate, delayed_dealloc_cb cb,
void *state)
{
assert(PyMutex_IsLocked(&queue->mutex));
- process_queue(&queue->head, qsbr, false, cb, state);
+ process_queue(&queue->head, tstate, false, cb, state);
int more_work = !llist_empty(&queue->head);
_Py_atomic_store_int_relaxed(&queue->has_work, more_work);
@@ -1282,7 +1379,7 @@ process_interp_queue(struct _Py_mem_interp_free_queue *queue,
static void
maybe_process_interp_queue(struct _Py_mem_interp_free_queue *queue,
- struct _qsbr_thread_state *qsbr, delayed_dealloc_cb cb,
+ _PyThreadStateImpl *tstate, delayed_dealloc_cb cb,
void *state)
{
if (!_Py_atomic_load_int_relaxed(&queue->has_work)) {
@@ -1291,7 +1388,7 @@ maybe_process_interp_queue(struct _Py_mem_interp_free_queue *queue,
// Try to acquire the lock, but don't block if it's already held.
if (_PyMutex_LockTimed(&queue->mutex, 0, 0) == PY_LOCK_ACQUIRED) {
- process_interp_queue(queue, qsbr, cb, state);
+ process_interp_queue(queue, tstate, cb, state);
PyMutex_Unlock(&queue->mutex);
}
}
@@ -1302,11 +1399,13 @@ _PyMem_ProcessDelayed(PyThreadState *tstate)
PyInterpreterState *interp = tstate->interp;
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
+ tstate_impl->qsbr->should_process = false;
+
// Process thread-local work
- process_queue(&tstate_impl->mem_free_queue, tstate_impl->qsbr, true, NULL, NULL);
+ process_queue(&tstate_impl->mem_free_queue, tstate_impl, true, NULL, NULL);
// Process shared interpreter work
- maybe_process_interp_queue(&interp->mem_free_queue, tstate_impl->qsbr, NULL, NULL);
+ maybe_process_interp_queue(&interp->mem_free_queue, tstate_impl, NULL, NULL);
}
void
@@ -1316,10 +1415,10 @@ _PyMem_ProcessDelayedNoDealloc(PyThreadState *tstate, delayed_dealloc_cb cb, voi
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
// Process thread-local work
- process_queue(&tstate_impl->mem_free_queue, tstate_impl->qsbr, true, cb, state);
+ process_queue(&tstate_impl->mem_free_queue, tstate_impl, true, cb, state);
// Process shared interpreter work
- maybe_process_interp_queue(&interp->mem_free_queue, tstate_impl->qsbr, cb, state);
+ maybe_process_interp_queue(&interp->mem_free_queue, tstate_impl, cb, state);
}
void
@@ -1348,7 +1447,7 @@ _PyMem_AbandonDelayed(PyThreadState *tstate)
// Process the merged queue now (see gh-130794).
_PyThreadStateImpl *this_tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
- process_interp_queue(&interp->mem_free_queue, this_tstate->qsbr, NULL, NULL);
+ process_interp_queue(&interp->mem_free_queue, this_tstate, NULL, NULL);
PyMutex_Unlock(&interp->mem_free_queue.mutex);
diff --git a/Objects/odictobject.c b/Objects/odictobject.c
index 891f6197401..02fcbbaa0d4 100644
--- a/Objects/odictobject.c
+++ b/Objects/odictobject.c
@@ -473,6 +473,7 @@ later:
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
#include "pycore_tuple.h" // _PyTuple_Recycle()
#include <stddef.h> // offsetof()
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "clinic/odictobject.c.h"
@@ -1391,8 +1392,7 @@ odict_dealloc(PyObject *op)
PyObject_GC_UnTrack(self);
Py_XDECREF(self->od_inst_dict);
- if (self->od_weakreflist != NULL)
- PyObject_ClearWeakRefs((PyObject *)self);
+ FT_CLEAR_WEAKREFS(op, self->od_weakreflist);
_odict_clear_nodes(self);
PyDict_Type.tp_dealloc((PyObject *)self);
diff --git a/Objects/picklebufobject.c b/Objects/picklebufobject.c
index 3ce800de04c..50f17687bc4 100644
--- a/Objects/picklebufobject.c
+++ b/Objects/picklebufobject.c
@@ -1,6 +1,7 @@
/* PickleBuffer object implementation */
#include "Python.h"
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include <stddef.h>
typedef struct {
@@ -111,8 +112,7 @@ picklebuf_dealloc(PyObject *op)
{
PyPickleBufferObject *self = (PyPickleBufferObject*)op;
PyObject_GC_UnTrack(self);
- if (self->weakreflist != NULL)
- PyObject_ClearWeakRefs((PyObject *) self);
+ FT_CLEAR_WEAKREFS(op, self->weakreflist);
PyBuffer_Release(&self->view);
Py_TYPE(self)->tp_free((PyObject *) self);
}
diff --git a/Objects/setobject.c b/Objects/setobject.c
index 8aa6b0d1809..6e4fc5957ca 100644
--- a/Objects/setobject.c
+++ b/Objects/setobject.c
@@ -40,6 +40,7 @@
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_RELAXED()
#include "pycore_pyerrors.h" // _PyErr_SetKeyError()
#include "pycore_setobject.h" // _PySet_NextEntry() definition
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "stringlib/eq.h" // unicode_eq()
#include <stddef.h> // offsetof()
@@ -536,8 +537,7 @@ set_dealloc(PyObject *self)
/* bpo-31095: UnTrack is needed before calling any callbacks */
PyObject_GC_UnTrack(so);
- if (so->weakreflist != NULL)
- PyObject_ClearWeakRefs((PyObject *) so);
+ FT_CLEAR_WEAKREFS(self, so->weakreflist);
for (entry = so->table; used > 0; entry++) {
if (entry->key && entry->key != dummy) {
diff --git a/Objects/templateobject.c b/Objects/templateobject.c
index 7d356980b56..4293a311c44 100644
--- a/Objects/templateobject.c
+++ b/Objects/templateobject.c
@@ -23,6 +23,9 @@ templateiter_next(PyObject *op)
if (self->from_strings) {
item = PyIter_Next(self->stringsiter);
self->from_strings = 0;
+ if (item == NULL) {
+ return NULL;
+ }
if (PyUnicode_GET_LENGTH(item) == 0) {
Py_SETREF(item, PyIter_Next(self->interpolationsiter));
self->from_strings = 1;
@@ -444,6 +447,8 @@ template_reduce(PyObject *op, PyObject *Py_UNUSED(dummy))
static PyMethodDef template_methods[] = {
{"__reduce__", template_reduce, METH_NOARGS, NULL},
+ {"__class_getitem__", Py_GenericAlias,
+ METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
{NULL, NULL},
};
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index a7ab69fef4c..6e7471cb594 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -48,7 +48,7 @@ class object "PyObject *" "&PyBaseObject_Type"
& ((1 << MCACHE_SIZE_EXP) - 1))
#define MCACHE_HASH_METHOD(type, name) \
- MCACHE_HASH(FT_ATOMIC_LOAD_UINT32_RELAXED((type)->tp_version_tag), \
+ MCACHE_HASH(FT_ATOMIC_LOAD_UINT_RELAXED((type)->tp_version_tag), \
((Py_ssize_t)(name)) >> 3)
#define MCACHE_CACHEABLE_NAME(name) \
PyUnicode_CheckExact(name) && \
@@ -60,11 +60,19 @@ class object "PyObject *" "&PyBaseObject_Type"
#ifdef Py_GIL_DISABLED
-// There's a global lock for mutation of types. This avoids having to take
-// additional locks while doing various subclass processing which may result
-// in odd behaviors w.r.t. running with the GIL as the outer type lock could
-// be released and reacquired during a subclass update if there's contention
-// on the subclass lock.
+// There's a global lock for types that ensures that tp_version_tag and
+// _spec_cache are correctly updated if the type is modified. It also protects
+// tp_mro, tp_bases, and tp_base. This avoids having to take additional locks
+// while doing various subclass processing which may result in odd behaviors
+// w.r.t. running with the GIL as the outer type lock could be released and
+// reacquired during a subclass update if there's contention on the subclass
+// lock.
+//
+// Note that this lock does not protect updates of other type slots or the
+// tp_flags member. Instead, we either ensure those updates are done before
+// the type has been revealed to other threads or we only do those updates
+// while the stop-the-world mechanism is active. The slots and flags are read
+// in many places without holding a lock and without atomics.
#define TYPE_LOCK &PyInterpreterState_Get()->types.mutex
#define BEGIN_TYPE_LOCK() Py_BEGIN_CRITICAL_SECTION_MUT(TYPE_LOCK)
#define END_TYPE_LOCK() Py_END_CRITICAL_SECTION()
@@ -74,8 +82,100 @@ class object "PyObject *" "&PyBaseObject_Type"
#define END_TYPE_DICT_LOCK() Py_END_CRITICAL_SECTION2()
+#ifdef Py_DEBUG
+// Return true if the world is currently stopped.
+static bool
+types_world_is_stopped(void)
+{
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ return interp->stoptheworld.world_stopped;
+}
+#endif
+
+// Checks that the type has not yet been revealed (exposed) to other
+// threads. The _Py_TYPE_REVEALED_FLAG flag is set by type_new() and
+// PyType_FromMetaclass() to indicate that a newly initialized type might be
+// revealed. We only have ob_flags on 64-bit platforms.
+#if SIZEOF_VOID_P > 4
+#define TYPE_IS_REVEALED(tp) ((((PyObject *)(tp))->ob_flags & _Py_TYPE_REVEALED_FLAG) != 0)
+#else
+#define TYPE_IS_REVEALED(tp) 0
+#endif
+
+#ifdef Py_DEBUG
#define ASSERT_TYPE_LOCK_HELD() \
- _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(TYPE_LOCK)
+ if (!types_world_is_stopped()) { _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(TYPE_LOCK); }
+
+// Checks if we can safely update type slots or tp_flags.
+#define ASSERT_WORLD_STOPPED_OR_NEW_TYPE(tp) \
+ assert(!TYPE_IS_REVEALED(tp) || types_world_is_stopped())
+
+#define ASSERT_NEW_TYPE_OR_LOCKED(tp) \
+ if (TYPE_IS_REVEALED(tp)) { ASSERT_TYPE_LOCK_HELD(); }
+#else
+#define ASSERT_TYPE_LOCK_HELD()
+#define ASSERT_WORLD_STOPPED_OR_NEW_TYPE(tp)
+#define ASSERT_NEW_TYPE_OR_LOCKED(tp)
+#endif
+
+static void
+types_stop_world(void)
+{
+ assert(!types_world_is_stopped());
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ _PyEval_StopTheWorld(interp);
+ assert(types_world_is_stopped());
+}
+
+static void
+types_start_world(void)
+{
+ assert(types_world_is_stopped());
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ _PyEval_StartTheWorld(interp);
+ assert(!types_world_is_stopped());
+}
+
+// This is used to temporarily prevent the TYPE_LOCK from being suspended
+// when held by the topmost critical section.
+static void
+type_lock_prevent_release(void)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ uintptr_t *tagptr = &tstate->critical_section;
+ PyCriticalSection *c = (PyCriticalSection *)(*tagptr & ~_Py_CRITICAL_SECTION_MASK);
+ if (!(*tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
+ assert(c->_cs_mutex == TYPE_LOCK);
+ c->_cs_mutex = NULL;
+ }
+ else {
+ PyCriticalSection2 *c2 = (PyCriticalSection2 *)c;
+ if (c->_cs_mutex == TYPE_LOCK) {
+ c->_cs_mutex = c2->_cs_mutex2;
+ c2->_cs_mutex2 = NULL;
+ } else {
+ assert(c2->_cs_mutex2 == TYPE_LOCK);
+ c2->_cs_mutex2 = NULL;
+ }
+ }
+}
+
+static void
+type_lock_allow_release(void)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ uintptr_t *tagptr = &tstate->critical_section;
+ PyCriticalSection *c = (PyCriticalSection *)(*tagptr & ~_Py_CRITICAL_SECTION_MASK);
+ if (!(*tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
+ assert(c->_cs_mutex == NULL);
+ c->_cs_mutex = TYPE_LOCK;
+ }
+ else {
+ PyCriticalSection2 *c2 = (PyCriticalSection2 *)c;
+ assert(c2->_cs_mutex2 == NULL);
+ c2->_cs_mutex2 = TYPE_LOCK;
+ }
+}
#else
@@ -84,6 +184,12 @@ class object "PyObject *" "&PyBaseObject_Type"
#define BEGIN_TYPE_DICT_LOCK(d)
#define END_TYPE_DICT_LOCK()
#define ASSERT_TYPE_LOCK_HELD()
+#define TYPE_IS_REVEALED(tp) 0
+#define ASSERT_WORLD_STOPPED_OR_NEW_TYPE(tp)
+#define ASSERT_NEW_TYPE_OR_LOCKED(tp)
+#define types_world_is_stopped() 1
+#define types_stop_world()
+#define types_start_world()
#endif
@@ -106,6 +212,9 @@ slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
static int
slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value);
+static PyObject *
+slot_tp_call(PyObject *self, PyObject *args, PyObject *kwds);
+
static inline PyTypeObject *
type_from_ref(PyObject *ref)
{
@@ -346,21 +455,14 @@ _PyStaticType_GetBuiltins(void)
static void
type_set_flags(PyTypeObject *tp, unsigned long flags)
{
- if (tp->tp_flags & Py_TPFLAGS_READY) {
- // It's possible the type object has been exposed to other threads
- // if it's been marked ready. In that case, the type lock should be
- // held when flags are modified.
- ASSERT_TYPE_LOCK_HELD();
- }
- // Since PyType_HasFeature() reads the flags without holding the type
- // lock, we need an atomic store here.
- FT_ATOMIC_STORE_ULONG_RELAXED(tp->tp_flags, flags);
+ ASSERT_WORLD_STOPPED_OR_NEW_TYPE(tp);
+ tp->tp_flags = flags;
}
static void
type_set_flags_with_mask(PyTypeObject *tp, unsigned long mask, unsigned long flags)
{
- ASSERT_TYPE_LOCK_HELD();
+ ASSERT_WORLD_STOPPED_OR_NEW_TYPE(tp);
unsigned long new_flags = (tp->tp_flags & ~mask) | flags;
type_set_flags(tp, new_flags);
}
@@ -498,6 +600,7 @@ static inline void
set_tp_bases(PyTypeObject *self, PyObject *bases, int initial)
{
assert(PyTuple_Check(bases));
+ ASSERT_NEW_TYPE_OR_LOCKED(self);
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
// XXX tp_bases can probably be statically allocated for each
// static builtin type.
@@ -542,7 +645,7 @@ clear_tp_bases(PyTypeObject *self, int final)
static inline PyObject *
lookup_tp_mro(PyTypeObject *self)
{
- ASSERT_TYPE_LOCK_HELD();
+ ASSERT_NEW_TYPE_OR_LOCKED(self);
return self->tp_mro;
}
@@ -1027,7 +1130,6 @@ PyType_Unwatch(int watcher_id, PyObject* obj)
static void
set_version_unlocked(PyTypeObject *tp, unsigned int version)
{
- ASSERT_TYPE_LOCK_HELD();
assert(version == 0 || (tp->tp_versions_used != _Py_ATTR_CACHE_UNUSED));
#ifndef Py_GIL_DISABLED
PyInterpreterState *interp = _PyInterpreterState_GET();
@@ -1075,7 +1177,12 @@ type_modified_unlocked(PyTypeObject *type)
We don't assign new version tags eagerly, but only as
needed.
*/
- ASSERT_TYPE_LOCK_HELD();
+ ASSERT_NEW_TYPE_OR_LOCKED(type);
+#ifdef Py_GIL_DISABLED
+ // This function is re-entrant and it's not safe to call it
+ // with the world stopped.
+ assert(!types_world_is_stopped());
+#endif
if (type->tp_version_tag == 0) {
return;
}
@@ -1106,6 +1213,8 @@ type_modified_unlocked(PyTypeObject *type)
while (bits) {
assert(i < TYPE_MAX_WATCHERS);
if (bits & 1) {
+ // Note that PyErr_FormatUnraisable is potentially re-entrant
+ // and the watcher callback might be too.
PyType_WatchCallback cb = interp->type_watchers[i];
if (cb && (cb(type) < 0)) {
PyErr_FormatUnraisable(
@@ -1245,14 +1354,6 @@ _PyType_LookupByVersion(unsigned int version)
#endif
}
-unsigned int
-_PyType_GetVersionForCurrentState(PyTypeObject *tp)
-{
- return tp->tp_version_tag;
-}
-
-
-
#define MAX_VERSIONS_PER_CLASS 1000
#if _Py_ATTR_CACHE_UNUSED < MAX_VERSIONS_PER_CLASS
#error "_Py_ATTR_CACHE_UNUSED must be bigger than max"
@@ -1586,10 +1687,13 @@ type_set_abstractmethods(PyObject *tp, PyObject *value, void *Py_UNUSED(closure)
BEGIN_TYPE_LOCK();
type_modified_unlocked(type);
+ types_stop_world();
if (abstract)
type_add_flags(type, Py_TPFLAGS_IS_ABSTRACT);
else
type_clear_flags(type, Py_TPFLAGS_IS_ABSTRACT);
+ types_start_world();
+ ASSERT_TYPE_LOCK_HELD();
END_TYPE_LOCK();
return 0;
@@ -1624,15 +1728,15 @@ type_get_mro(PyObject *tp, void *Py_UNUSED(closure))
return mro;
}
-static PyTypeObject *best_base(PyObject *);
-static int mro_internal(PyTypeObject *, PyObject **);
+static PyTypeObject *find_best_base(PyObject *);
+static int mro_internal(PyTypeObject *, int, PyObject **);
static int type_is_subtype_base_chain(PyTypeObject *, PyTypeObject *);
static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, const char *);
static int add_subclass(PyTypeObject*, PyTypeObject*);
static int add_all_subclasses(PyTypeObject *type, PyObject *bases);
static void remove_subclass(PyTypeObject *, PyTypeObject *);
static void remove_all_subclasses(PyTypeObject *type, PyObject *bases);
-static void update_all_slots(PyTypeObject *);
+static int update_all_slots(PyTypeObject *);
typedef int (*update_callback)(PyTypeObject *, void *);
static int update_subclasses(PyTypeObject *type, PyObject *attr_name,
@@ -1640,13 +1744,15 @@ static int update_subclasses(PyTypeObject *type, PyObject *attr_name,
static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
update_callback callback, void *data);
+// Compute tp_mro for this type and all of its subclasses. This
+// is called after __bases__ is assigned to an existing type.
static int
mro_hierarchy(PyTypeObject *type, PyObject *temp)
{
ASSERT_TYPE_LOCK_HELD();
PyObject *old_mro;
- int res = mro_internal(type, &old_mro);
+ int res = mro_internal(type, 0, &old_mro);
if (res <= 0) {
/* error / reentrance */
return res;
@@ -1708,9 +1814,9 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
}
static int
-type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases)
+type_check_new_bases(PyTypeObject *type, PyObject *new_bases, PyTypeObject **best_base)
{
- // Check arguments
+ // Check arguments, this is re-entrant due to the PySys_Audit() call
if (!check_set_special_type_attr(type, new_bases, "__bases__")) {
return -1;
}
@@ -1759,20 +1865,29 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases)
}
// Compute the new MRO and the new base class
- PyTypeObject *new_base = best_base(new_bases);
- if (new_base == NULL)
+ *best_base = find_best_base(new_bases);
+ if (*best_base == NULL)
return -1;
- if (!compatible_for_assignment(type->tp_base, new_base, "__bases__")) {
+ if (!compatible_for_assignment(type->tp_base, *best_base, "__bases__")) {
return -1;
}
+ return 0;
+}
+
+static int
+type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *best_base)
+{
+ ASSERT_TYPE_LOCK_HELD();
+
+ Py_ssize_t n;
PyObject *old_bases = lookup_tp_bases(type);
assert(old_bases != NULL);
PyTypeObject *old_base = type->tp_base;
set_tp_bases(type, Py_NewRef(new_bases), 0);
- type->tp_base = (PyTypeObject *)Py_NewRef(new_base);
+ type->tp_base = (PyTypeObject *)Py_NewRef(best_base);
PyObject *temp = PyList_New(0);
if (temp == NULL) {
@@ -1796,7 +1911,11 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases)
add to all new_bases */
remove_all_subclasses(type, old_bases);
res = add_all_subclasses(type, new_bases);
- update_all_slots(type);
+ if (update_all_slots(type) < 0) {
+ goto bail;
+ }
+ /* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
+ type_modified_unlocked(type);
}
else {
res = 0;
@@ -1827,13 +1946,13 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases)
bail:
if (lookup_tp_bases(type) == new_bases) {
- assert(type->tp_base == new_base);
+ assert(type->tp_base == best_base);
set_tp_bases(type, old_bases, 0);
type->tp_base = old_base;
Py_DECREF(new_bases);
- Py_DECREF(new_base);
+ Py_DECREF(best_base);
}
else {
Py_DECREF(old_bases);
@@ -1848,9 +1967,13 @@ static int
type_set_bases(PyObject *tp, PyObject *new_bases, void *Py_UNUSED(closure))
{
PyTypeObject *type = PyTypeObject_CAST(tp);
+ PyTypeObject *best_base;
int res;
BEGIN_TYPE_LOCK();
- res = type_set_bases_unlocked(type, new_bases);
+ res = type_check_new_bases(type, new_bases, &best_base);
+ if (res == 0) {
+ res = type_set_bases_unlocked(type, new_bases, best_base);
+ }
END_TYPE_LOCK();
return res;
}
@@ -2065,19 +2188,46 @@ type_set_annotations(PyObject *tp, PyObject *value, void *Py_UNUSED(closure))
return -1;
}
- int result;
PyObject *dict = PyType_GetDict(type);
- if (value != NULL) {
- /* set */
- result = PyDict_SetItem(dict, &_Py_ID(__annotations_cache__), value);
- } else {
- /* delete */
- result = PyDict_Pop(dict, &_Py_ID(__annotations_cache__), NULL);
- if (result == 0) {
- PyErr_SetString(PyExc_AttributeError, "__annotations__");
+ int result = PyDict_ContainsString(dict, "__annotations__");
+ if (result < 0) {
+ Py_DECREF(dict);
+ return -1;
+ }
+ if (result) {
+ // If __annotations__ is currently in the dict, we update it,
+ if (value != NULL) {
+ result = PyDict_SetItem(dict, &_Py_ID(__annotations__), value);
+ } else {
+ result = PyDict_Pop(dict, &_Py_ID(__annotations__), NULL);
+ if (result == 0) {
+ // Somebody else just deleted it?
+ PyErr_SetString(PyExc_AttributeError, "__annotations__");
+ Py_DECREF(dict);
+ return -1;
+ }
+ }
+ if (result < 0) {
Py_DECREF(dict);
return -1;
}
+ // Also clear __annotations_cache__ just in case.
+ result = PyDict_Pop(dict, &_Py_ID(__annotations_cache__), NULL);
+ }
+ else {
+ // Else we update only __annotations_cache__.
+ if (value != NULL) {
+ /* set */
+ result = PyDict_SetItem(dict, &_Py_ID(__annotations_cache__), value);
+ } else {
+ /* delete */
+ result = PyDict_Pop(dict, &_Py_ID(__annotations_cache__), NULL);
+ if (result == 0) {
+ PyErr_SetString(PyExc_AttributeError, "__annotations__");
+ Py_DECREF(dict);
+ return -1;
+ }
+ }
}
if (result < 0) {
Py_DECREF(dict);
@@ -3051,6 +3201,7 @@ static PyObject *
class_name(PyObject *cls)
{
PyObject *name;
+ // Note that this is potentially re-entrant.
if (PyObject_GetOptionalAttr(cls, &_Py_ID(__name__), &name) == 0) {
name = PyObject_Repr(cls);
}
@@ -3387,9 +3538,13 @@ mro_invoke(PyTypeObject *type)
const int custom = !Py_IS_TYPE(type, &PyType_Type);
if (custom) {
+ // Custom mro() method on metaclass. This is potentially re-entrant.
+ // We are called either from type_ready() or from type_set_bases().
mro_result = call_method_noarg((PyObject *)type, &_Py_ID(mro));
}
else {
+ // In this case, the mro() method on the type object is being used and
+ // we know that these calls are not re-entrant.
mro_result = mro_implementation_unlocked(type);
}
if (mro_result == NULL)
@@ -3437,7 +3592,7 @@ mro_invoke(PyTypeObject *type)
- Returns -1 in case of an error.
*/
static int
-mro_internal_unlocked(PyTypeObject *type, int initial, PyObject **p_old_mro)
+mro_internal(PyTypeObject *type, int initial, PyObject **p_old_mro)
{
ASSERT_TYPE_LOCK_HELD();
@@ -3485,21 +3640,11 @@ mro_internal_unlocked(PyTypeObject *type, int initial, PyObject **p_old_mro)
return 1;
}
-static int
-mro_internal(PyTypeObject *type, PyObject **p_old_mro)
-{
- int res;
- BEGIN_TYPE_LOCK();
- res = mro_internal_unlocked(type, 0, p_old_mro);
- END_TYPE_LOCK();
- return res;
-}
-
/* Calculate the best base amongst multiple base classes.
This is the first one that's on the path to the "solid base". */
static PyTypeObject *
-best_base(PyObject *bases)
+find_best_base(PyObject *bases)
{
Py_ssize_t i, n;
PyTypeObject *base, *winner, *candidate;
@@ -3581,13 +3726,167 @@ solid_base(PyTypeObject *type)
}
}
+#ifdef Py_GIL_DISABLED
+
+// The structures and functions below are used in the free-threaded build
+// to safely make updates to type slots, on type_setattro() for a slot
+// or when __bases__ is re-assigned. Since the slots are read without atomic
+// operations and without locking, we can only safely update them while the
+// world is stopped. However, with the world stopped, we are very limited on
+// which APIs can be safely used. For example, calling _PyObject_HashFast()
+// or _PyDict_GetItemRef_KnownHash() are not safe and can potentially cause
+// deadlocks. Hashing can be re-entrant and _PyDict_GetItemRef_KnownHash can
+// acquire a lock if the dictionary is not owned by the current thread, to
+// mark it shared on reading.
+//
+// We do the slot updates in two steps. First, with TYPE_LOCK held, we lookup
+// the descriptor for each slot, for each subclass. We build a queue of
+// updates to perform but don't actually update the type structures. After we
+// are finished the lookups, we stop-the-world and apply all of the updates.
+// The apply_slot_updates() code is simple and easy to confirm that it is
+// safe.
+
+typedef struct {
+ PyTypeObject *type;
+ void **slot_ptr;
+ void *slot_value;
+} slot_update_item_t;
+
+// The number of slot updates performed is based on the number of changed
+// slots and the number of subclasses. It's possible there are many updates
+// required if there are many subclasses (potentially an unbounded amount).
+// Usually the number of slot updates is small, most often zero or one. When
+// running the unit tests, we don't exceed 20. The chunk size is set to
+// handle the common case with a single chunk and to not require too many
+// chunk allocations if there are many subclasses.
+#define SLOT_UPDATE_CHUNK_SIZE 30
+
+typedef struct _slot_update {
+ struct _slot_update *prev;
+ Py_ssize_t n;
+ slot_update_item_t updates[SLOT_UPDATE_CHUNK_SIZE];
+} slot_update_chunk_t;
+
+// a queue of updates to be performed
+typedef struct {
+ slot_update_chunk_t *head;
+} slot_update_t;
+
+static slot_update_chunk_t *
+slot_update_new_chunk(void)
+{
+ slot_update_chunk_t *chunk = PyMem_Malloc(sizeof(slot_update_chunk_t));
+ if (chunk == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ chunk->prev = NULL;
+ chunk->n = 0;
+ return chunk;
+}
+
+static void
+slot_update_free_chunks(slot_update_t *updates)
+{
+ slot_update_chunk_t *chunk = updates->head;
+ while (chunk != NULL) {
+ slot_update_chunk_t *prev = chunk->prev;
+ PyMem_Free(chunk);
+ chunk = prev;
+ }
+}
+
+static int
+queue_slot_update(slot_update_t *updates, PyTypeObject *type,
+ void **slot_ptr, void *slot_value)
+{
+ if (*slot_ptr == slot_value) {
+ return 0; // slot pointer not actually changed, don't queue update
+ }
+ if (updates->head == NULL || updates->head->n == SLOT_UPDATE_CHUNK_SIZE) {
+ slot_update_chunk_t *chunk = slot_update_new_chunk();
+ if (chunk == NULL) {
+ return -1; // out-of-memory
+ }
+ chunk->prev = updates->head;
+ updates->head = chunk;
+ }
+ slot_update_item_t *item = &updates->head->updates[updates->head->n];
+ item->type = type;
+ item->slot_ptr = slot_ptr;
+ item->slot_value = slot_value;
+ updates->head->n++;
+ assert(updates->head->n <= SLOT_UPDATE_CHUNK_SIZE);
+ return 0;
+}
+
+static void
+apply_slot_updates(slot_update_t *updates)
+{
+ assert(types_world_is_stopped());
+ slot_update_chunk_t *chunk = updates->head;
+ while (chunk != NULL) {
+ for (Py_ssize_t i = 0; i < chunk->n; i++) {
+ slot_update_item_t *item = &chunk->updates[i];
+ *(item->slot_ptr) = item->slot_value;
+ if (item->slot_value == slot_tp_call) {
+ /* A generic __call__ is incompatible with vectorcall */
+ type_clear_flags(item->type, Py_TPFLAGS_HAVE_VECTORCALL);
+ }
+ }
+ chunk = chunk->prev;
+ }
+}
+
+static void
+apply_type_slot_updates(slot_update_t *updates)
+{
+ // This must be done carefully to avoid data races and deadlocks. We
+ // have just updated the type __dict__, while holding TYPE_LOCK. We have
+ // collected all of the required type slot updates into the 'updates'
+ // queue. Note that those updates can apply to multiple types since
+ // subclasses might also be affected by the dict change.
+ //
+ // We need to prevent other threads from writing to the dict before we can
+ // finish updating the slots. The actual stores to the slots are done
+ // with the world stopped. If we block on the stop-the-world mutex then
+ // we could release TYPE_LOCK mutex and potentially allow other threads
+ // to update the dict. That's because TYPE_LOCK was acquired using a
+ // critical section.
+ //
+ // The type_lock_prevent_release() call prevents the TYPE_LOCK mutex from
+ // being released even if we block on the STM mutex. We need to take care
+ // that we do not deadlock because of that. It is safe because we always
+ // acquire locks in the same order: first the TYPE_LOCK mutex and then the
+ // STM mutex.
+ type_lock_prevent_release();
+ types_stop_world();
+ apply_slot_updates(updates);
+ types_start_world();
+ type_lock_allow_release();
+}
+
+#else
+
+// dummy definition, this parameter is only NULL in the default build
+typedef void slot_update_t;
+
+#endif
+
+/// data passed to update_slots_callback()
+typedef struct {
+ slot_update_t *queued_updates;
+ pytype_slotdef **defs;
+} update_callback_data_t;
+
static void object_dealloc(PyObject *);
static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *);
static int object_init(PyObject *, PyObject *, PyObject *);
-static int update_slot(PyTypeObject *, PyObject *);
+static int update_slot(PyTypeObject *, PyObject *, slot_update_t *update);
static void fixup_slot_dispatchers(PyTypeObject *);
static int type_new_set_names(PyTypeObject *);
static int type_new_init_subclass(PyTypeObject *, PyObject *);
+static bool has_slotdef(PyObject *);
/*
* Helpers for __dict__ descriptor. We don't want to expose the dicts
@@ -3649,10 +3948,35 @@ subtype_dict(PyObject *obj, void *context)
return PyObject_GenericGetDict(obj, context);
}
+int
+_PyObject_SetDict(PyObject *obj, PyObject *value)
+{
+ if (value != NULL && !PyDict_Check(value)) {
+ PyErr_Format(PyExc_TypeError,
+ "__dict__ must be set to a dictionary, "
+ "not a '%.200s'", Py_TYPE(value)->tp_name);
+ return -1;
+ }
+ if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
+ return _PyObject_SetManagedDict(obj, value);
+ }
+ PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
+ if (dictptr == NULL) {
+ PyErr_SetString(PyExc_AttributeError,
+ "This object has no __dict__");
+ return -1;
+ }
+ Py_BEGIN_CRITICAL_SECTION(obj);
+ // gh-133980: To prevent use-after-free from other threads that reference
+ // the __dict__
+ _PyObject_XSetRefDelayed(dictptr, Py_NewRef(value));
+ Py_END_CRITICAL_SECTION();
+ return 0;
+}
+
static int
subtype_setdict(PyObject *obj, PyObject *value, void *context)
{
- PyObject **dictptr;
PyTypeObject *base;
base = get_builtin_base_with_dict(Py_TYPE(obj));
@@ -3670,28 +3994,7 @@ subtype_setdict(PyObject *obj, PyObject *value, void *context)
}
return func(descr, obj, value);
}
- /* Almost like PyObject_GenericSetDict, but allow __dict__ to be deleted. */
- if (value != NULL && !PyDict_Check(value)) {
- PyErr_Format(PyExc_TypeError,
- "__dict__ must be set to a dictionary, "
- "not a '%.200s'", Py_TYPE(value)->tp_name);
- return -1;
- }
-
- if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
- return _PyObject_SetManagedDict(obj, value);
- }
- else {
- dictptr = _PyObject_ComputedDictPointer(obj);
- if (dictptr == NULL) {
- PyErr_SetString(PyExc_AttributeError,
- "This object has no __dict__");
- return -1;
- }
- Py_CLEAR(*dictptr);
- *dictptr = Py_XNewRef(value);
- }
- return 0;
+ return _PyObject_SetDict(obj, value);
}
static PyObject *
@@ -3785,7 +4088,7 @@ type_init(PyObject *cls, PyObject *args, PyObject *kwds)
unsigned long
PyType_GetFlags(PyTypeObject *type)
{
- return FT_ATOMIC_LOAD_ULONG_RELAXED(type->tp_flags);
+ return type->tp_flags;
}
@@ -4563,6 +4866,10 @@ type_new_impl(type_new_ctx *ctx)
}
assert(_PyType_CheckConsistency(type));
+#if defined(Py_GIL_DISABLED) && defined(Py_DEBUG) && SIZEOF_VOID_P > 4
+ // After this point, other threads can potentally use this type.
+ ((PyObject*)type)->ob_flags |= _Py_TYPE_REVEALED_FLAG;
+#endif
return (PyObject *)type;
@@ -4625,7 +4932,7 @@ type_new_get_bases(type_new_ctx *ctx, PyObject **type)
}
/* Calculate best base, and check that all bases are type objects */
- PyTypeObject *base = best_base(ctx->bases);
+ PyTypeObject *base = find_best_base(ctx->bases);
if (base == NULL) {
return -1;
}
@@ -5040,12 +5347,12 @@ PyType_FromMetaclass(
}
/* Calculate best base, and check that all bases are type objects */
- PyTypeObject *base = best_base(bases); // borrowed ref
+ PyTypeObject *base = find_best_base(bases); // borrowed ref
if (base == NULL) {
goto finally;
}
- // best_base should check Py_TPFLAGS_BASETYPE & raise a proper exception,
- // here we just check its work
+ // find_best_base() should check Py_TPFLAGS_BASETYPE & raise a proper
+ // exception, here we just check its work
assert(_PyType_HasFeature(base, Py_TPFLAGS_BASETYPE));
/* Calculate sizes */
@@ -5276,6 +5583,10 @@ PyType_FromMetaclass(
}
assert(_PyType_CheckConsistency(type));
+#if defined(Py_GIL_DISABLED) && defined(Py_DEBUG) && SIZEOF_VOID_P > 4
+ // After this point, other threads can potentally use this type.
+ ((PyObject*)type)->ob_flags |= _Py_TYPE_REVEALED_FLAG;
+#endif
finally:
if (PyErr_Occurred()) {
@@ -5571,8 +5882,6 @@ PyObject_GetItemData(PyObject *obj)
static PyObject *
find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
{
- ASSERT_TYPE_LOCK_HELD();
-
Py_hash_t hash = _PyObject_HashFast(name);
if (hash == -1) {
*error = -1;
@@ -5881,9 +6190,13 @@ _PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject *descriptor
void
_PyType_SetFlags(PyTypeObject *self, unsigned long mask, unsigned long flags)
{
- BEGIN_TYPE_LOCK();
- type_set_flags_with_mask(self, mask, flags);
- END_TYPE_LOCK();
+ unsigned long new_flags = (self->tp_flags & ~mask) | flags;
+ if (new_flags != self->tp_flags) {
+ types_stop_world();
+ // can't use new_flags here since they could be out-of-date
+ self->tp_flags = (self->tp_flags & ~mask) | flags;
+ types_start_world();
+ }
}
int
@@ -5930,9 +6243,9 @@ set_flags_recursive(PyTypeObject *self, unsigned long mask, unsigned long flags)
void
_PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long flags)
{
- BEGIN_TYPE_LOCK();
+ types_stop_world();
set_flags_recursive(self, mask, flags);
- END_TYPE_LOCK();
+ types_start_world();
}
/* This is similar to PyObject_GenericGetAttr(),
@@ -6046,6 +6359,8 @@ _Py_type_getattro(PyObject *tp, PyObject *name)
return _Py_type_getattro_impl(type, name, NULL);
}
+// Called by type_setattro(). Updates both the type dict and
+// the type versions.
static int
type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name,
PyObject *value, PyObject **old_value)
@@ -6075,10 +6390,30 @@ type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name,
return -1;
}
- if (is_dunder_name(name)) {
- return update_slot(type, name);
- }
+ return 0;
+}
+static int
+update_slot_after_setattr(PyTypeObject *type, PyObject *name)
+{
+#ifdef Py_GIL_DISABLED
+ // stack allocate one chunk since that's all we need
+ assert(SLOT_UPDATE_CHUNK_SIZE >= MAX_EQUIV);
+ slot_update_chunk_t chunk = {0};
+ slot_update_t queued_updates = {&chunk};
+
+ if (update_slot(type, name, &queued_updates) < 0) {
+ return -1;
+ }
+ if (queued_updates.head->n > 0) {
+ apply_type_slot_updates(&queued_updates);
+ ASSERT_TYPE_LOCK_HELD();
+ // should never allocate another chunk
+ assert(chunk.prev == NULL);
+ }
+#else
+ update_slot(type, name, NULL);
+#endif
return 0;
}
@@ -6136,7 +6471,9 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
PyObject *dict = type->tp_dict;
if (dict == NULL) {
- // We don't just do PyType_Ready because we could already be readying
+ // This is an unlikely case. PyType_Ready has not yet been done and
+ // we need to initialize tp_dict. We don't just do PyType_Ready
+ // because we could already be readying.
BEGIN_TYPE_LOCK();
dict = type->tp_dict;
if (dict == NULL) {
@@ -6152,6 +6489,12 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
BEGIN_TYPE_DICT_LOCK(dict);
res = type_update_dict(type, (PyDictObject *)dict, name, value, &old_value);
assert(_PyType_CheckConsistency(type));
+ if (res == 0) {
+ if (is_dunder_name(name) && has_slotdef(name)) {
+ // The name corresponds to a type slot.
+ res = update_slot_after_setattr(type, name);
+ }
+ }
END_TYPE_DICT_LOCK();
done:
@@ -7081,15 +7424,10 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
return -1;
}
-#ifdef Py_GIL_DISABLED
- PyInterpreterState *interp = _PyInterpreterState_GET();
- _PyEval_StopTheWorld(interp);
-#endif
+ types_stop_world();
PyTypeObject *oldto = Py_TYPE(self);
int res = object_set_class_world_stopped(self, newto);
-#ifdef Py_GIL_DISABLED
- _PyEval_StartTheWorld(interp);
-#endif
+ types_start_world();
if (res == 0) {
if (oldto->tp_flags & Py_TPFLAGS_HEAPTYPE) {
Py_DECREF(oldto);
@@ -8497,7 +8835,7 @@ type_ready_mro(PyTypeObject *type, int initial)
}
/* Calculate method resolution order */
- if (mro_internal_unlocked(type, initial, NULL) < 0) {
+ if (mro_internal(type, initial, NULL) < 0) {
return -1;
}
PyObject *mro = lookup_tp_mro(type);
@@ -9682,6 +10020,11 @@ tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds)
/* If staticbase is NULL now, it is a really weird type.
In the spirit of backwards compatibility (?), just shut up. */
if (staticbase && staticbase->tp_new != type->tp_new) {
+ if (staticbase->tp_new == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "cannot create '%s' instances", subtype->tp_name);
+ return NULL;
+ }
PyErr_Format(PyExc_TypeError,
"%s.__new__(%s) is not safe, use %s.__new__()",
type->tp_name,
@@ -11020,12 +11363,21 @@ resolve_slotdups(PyTypeObject *type, PyObject *name)
{
/* XXX Maybe this could be optimized more -- but is it worth it? */
+#ifdef Py_GIL_DISABLED
+ pytype_slotdef *ptrs[MAX_EQUIV];
+ pytype_slotdef **pp = ptrs;
+ /* Collect all slotdefs that match name into ptrs. */
+ for (pytype_slotdef *p = slotdefs; p->name_strobj; p++) {
+ if (p->name_strobj == name)
+ *pp++ = p;
+ }
+ *pp = NULL;
+#else
/* pname and ptrs act as a little cache */
PyInterpreterState *interp = _PyInterpreterState_GET();
#define pname _Py_INTERP_CACHED_OBJECT(interp, type_slots_pname)
#define ptrs _Py_INTERP_CACHED_OBJECT(interp, type_slots_ptrs)
pytype_slotdef *p, **pp;
- void **res, **ptr;
if (pname != name) {
/* Collect all slotdefs that match name into ptrs. */
@@ -11037,10 +11389,12 @@ resolve_slotdups(PyTypeObject *type, PyObject *name)
}
*pp = NULL;
}
+#endif
/* Look in all slots of the type matching the name. If exactly one of these
has a filled-in slot, return a pointer to that slot.
Otherwise, return NULL. */
+ void **res, **ptr;
res = NULL;
for (pp = ptrs; *pp; pp++) {
ptr = slotptr(type, (*pp)->offset);
@@ -11050,11 +11404,25 @@ resolve_slotdups(PyTypeObject *type, PyObject *name)
return NULL;
res = ptr;
}
- return res;
+#ifndef Py_GIL_DISABLED
#undef pname
#undef ptrs
+#endif
+ return res;
}
+// Return true if "name" corresponds to at least one slot definition. This is
+// a more accurate but more expensive test compared to is_dunder_name().
+static bool
+has_slotdef(PyObject *name)
+{
+ for (pytype_slotdef *p = slotdefs; p->name_strobj; p++) {
+ if (p->name_strobj == name) {
+ return true;
+ }
+ }
+ return false;
+}
/* Common code for update_slots_callback() and fixup_slot_dispatchers().
*
@@ -11107,13 +11475,22 @@ resolve_slotdups(PyTypeObject *type, PyObject *name)
* There are some further special cases for specific slots, like supporting
* __hash__ = None for tp_hash and special code for tp_new.
*
- * When done, return a pointer to the next slotdef with a different offset,
- * because that's convenient for fixup_slot_dispatchers(). This function never
- * sets an exception: if an internal error happens (unlikely), it's ignored. */
-static pytype_slotdef *
-update_one_slot(PyTypeObject *type, pytype_slotdef *p)
+ * When done, next_p is set to the next slotdef with a different offset,
+ * because that's convenient for fixup_slot_dispatchers().
+ *
+ * If the queued_updates pointer is provided, the actual updates to the slot
+ * pointers are queued, rather than being immediately performed. That argument
+ * is only used for the free-threaded build since those updates need to be
+ * done while the world is stopped.
+ *
+ * This function will only return an error if the queued_updates argument is
+ * provided and allocating memory for the queue fails. Other exceptions that
+ * occur internally are ignored, such as when looking up descriptors. */
+static int
+update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p,
+ slot_update_t *queued_updates)
{
- ASSERT_TYPE_LOCK_HELD();
+ ASSERT_NEW_TYPE_OR_LOCKED(type);
PyObject *descr;
PyWrapperDescrObject *d;
@@ -11136,7 +11513,10 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
do {
++p;
} while (p->offset == offset);
- return p;
+ if (next_p != NULL) {
+ *next_p = p;
+ }
+ return 0;
}
/* We may end up clearing live exceptions below, so make sure it's ours. */
assert(!PyErr_Occurred());
@@ -11219,16 +11599,41 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
}
if (p->function == slot_tp_call) {
/* A generic __call__ is incompatible with vectorcall */
- type_clear_flags(type, Py_TPFLAGS_HAVE_VECTORCALL);
+ if (queued_updates == NULL) {
+ type_clear_flags(type, Py_TPFLAGS_HAVE_VECTORCALL);
+ }
}
}
Py_DECREF(descr);
} while ((++p)->offset == offset);
- if (specific && !use_generic)
- *ptr = specific;
- else
- *ptr = generic;
- return p;
+
+ void *slot_value;
+ if (specific && !use_generic) {
+ slot_value = specific;
+ } else {
+ slot_value = generic;
+ }
+
+#ifdef Py_GIL_DISABLED
+ if (queued_updates != NULL) {
+ // queue the update to perform later, while world is stopped
+ if (queue_slot_update(queued_updates, type, ptr, slot_value) < 0) {
+ return -1;
+ }
+ } else {
+ // do the update to the type structure now
+ *ptr = slot_value;
+ }
+#else
+ // always do the update immediately
+ assert(queued_updates == NULL);
+ *ptr = slot_value;
+#endif
+
+ if (next_p != NULL) {
+ *next_p = p;
+ }
+ return 0;
}
/* In the type, update the slots whose slotdefs are gathered in the pp array.
@@ -11236,18 +11641,21 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
static int
update_slots_callback(PyTypeObject *type, void *data)
{
- ASSERT_TYPE_LOCK_HELD();
+ ASSERT_NEW_TYPE_OR_LOCKED(type);
- pytype_slotdef **pp = (pytype_slotdef **)data;
+ update_callback_data_t *update_data = (update_callback_data_t *)data;
+ pytype_slotdef **pp = update_data->defs;
for (; *pp; pp++) {
- update_one_slot(type, *pp);
+ if (update_one_slot(type, *pp, NULL, update_data->queued_updates) < 0) {
+ return -1;
+ }
}
return 0;
}
/* Update the slots after assignment to a class (type) attribute. */
static int
-update_slot(PyTypeObject *type, PyObject *name)
+update_slot(PyTypeObject *type, PyObject *name, slot_update_t *queued_updates)
{
pytype_slotdef *ptrs[MAX_EQUIV];
pytype_slotdef *p;
@@ -11278,8 +11686,12 @@ update_slot(PyTypeObject *type, PyObject *name)
}
if (ptrs[0] == NULL)
return 0; /* Not an attribute that affects any slots */
+
+ update_callback_data_t callback_data;
+ callback_data.defs = ptrs;
+ callback_data.queued_updates = queued_updates;
return update_subclasses(type, name,
- update_slots_callback, (void *)ptrs);
+ update_slots_callback, (void *)&callback_data);
}
/* Store the proper functions in the slot dispatches at class (type)
@@ -11288,35 +11700,56 @@ update_slot(PyTypeObject *type, PyObject *name)
static void
fixup_slot_dispatchers(PyTypeObject *type)
{
- // This lock isn't strictly necessary because the type has not been
- // exposed to anyone else yet, but update_ont_slot calls find_name_in_mro
- // where we'd like to assert that the type is locked.
- BEGIN_TYPE_LOCK();
-
assert(!PyErr_Occurred());
for (pytype_slotdef *p = slotdefs; p->name; ) {
- p = update_one_slot(type, p);
+ update_one_slot(type, p, &p, NULL);
}
-
- END_TYPE_LOCK();
}
-static void
+#ifdef Py_GIL_DISABLED
+
+// Called when __bases__ is re-assigned.
+static int
update_all_slots(PyTypeObject* type)
{
- pytype_slotdef *p;
-
- ASSERT_TYPE_LOCK_HELD();
+ // Note that update_slot() can fail due to out-of-memory when allocating
+ // the queue chunks to hold the updates. That's unlikely since the number
+ // of updates is normally small but we handle that case. update_slot()
+ // can fail internally for other reasons (a lookup fails) but those
+ // errors are suppressed.
+ slot_update_t queued_updates = {0};
+ for (pytype_slotdef *p = slotdefs; p->name; p++) {
+ if (update_slot(type, p->name_strobj, &queued_updates) < 0) {
+ if (queued_updates.head) {
+ slot_update_free_chunks(&queued_updates);
+ }
+ return -1;
+ }
+ }
+ if (queued_updates.head != NULL) {
+ apply_type_slot_updates(&queued_updates);
+ ASSERT_TYPE_LOCK_HELD();
+ slot_update_free_chunks(&queued_updates);
+ }
+ return 0;
+}
- /* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
- type_modified_unlocked(type);
+#else
+// Called when __bases__ is re-assigned.
+static int
+update_all_slots(PyTypeObject* type)
+{
+ pytype_slotdef *p;
for (p = slotdefs; p->name; p++) {
- /* update_slot returns int but can't actually fail */
- update_slot(type, p->name_strobj);
+ /* update_slot returns int but can't actually fail in this case*/
+ update_slot(type, p->name_strobj, NULL);
}
+ return 0;
}
+#endif
+
PyObject *
_PyType_GetSlotWrapperNames(void)
@@ -11586,7 +12019,10 @@ PyType_Freeze(PyTypeObject *type)
}
BEGIN_TYPE_LOCK();
+ types_stop_world();
type_add_flags(type, Py_TPFLAGS_IMMUTABLETYPE);
+ types_start_world();
+ ASSERT_TYPE_LOCK_HELD();
type_modified_unlocked(type);
END_TYPE_LOCK();
diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c
index 6c199a52aa0..cead6e69af5 100644
--- a/Objects/typevarobject.c
+++ b/Objects/typevarobject.c
@@ -192,7 +192,7 @@ constevaluator_call(PyObject *self, PyObject *args, PyObject *kwargs)
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(value); i++) {
PyObject *item = PyTuple_GET_ITEM(value, i);
if (i > 0) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
PyUnicodeWriter_Discard(writer);
return NULL;
}
@@ -273,7 +273,7 @@ _Py_typing_type_repr(PyUnicodeWriter *writer, PyObject *p)
}
if (p == (PyObject *)&_PyNone_Type) {
- return PyUnicodeWriter_WriteUTF8(writer, "None", 4);
+ return PyUnicodeWriter_WriteASCII(writer, "None", 4);
}
if ((rc = PyObject_HasAttrWithError(p, &_Py_ID(__origin__))) > 0 &&
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index eb3e1c48fd4..5c2308a0121 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -167,11 +167,7 @@ static inline void PyUnicode_SET_UTF8_LENGTH(PyObject *op, Py_ssize_t length)
#define _PyUnicode_HASH(op) \
(_PyASCIIObject_CAST(op)->hash)
-static inline Py_hash_t PyUnicode_HASH(PyObject *op)
-{
- assert(_PyUnicode_CHECK(op));
- return FT_ATOMIC_LOAD_SSIZE_RELAXED(_PyASCIIObject_CAST(op)->hash);
-}
+#define PyUnicode_HASH PyUnstable_Unicode_GET_CACHED_HASH
static inline void PyUnicode_SET_HASH(PyObject *op, Py_hash_t hash)
{
@@ -3730,7 +3726,7 @@ PyUnicode_Decode(const char *s,
return NULL;
}
-PyObject *
+PyAPI_FUNC(PyObject *)
PyUnicode_AsDecodedObject(PyObject *unicode,
const char *encoding,
const char *errors)
@@ -3740,12 +3736,6 @@ PyUnicode_AsDecodedObject(PyObject *unicode,
return NULL;
}
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "PyUnicode_AsDecodedObject() is deprecated "
- "and will be removed in 3.15; "
- "use PyCodec_Decode() to decode from str", 1) < 0)
- return NULL;
-
if (encoding == NULL)
encoding = PyUnicode_GetDefaultEncoding();
@@ -3753,7 +3743,7 @@ PyUnicode_AsDecodedObject(PyObject *unicode,
return PyCodec_Decode(unicode, encoding, errors);
}
-PyObject *
+PyAPI_FUNC(PyObject *)
PyUnicode_AsDecodedUnicode(PyObject *unicode,
const char *encoding,
const char *errors)
@@ -3765,12 +3755,6 @@ PyUnicode_AsDecodedUnicode(PyObject *unicode,
goto onError;
}
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "PyUnicode_AsDecodedUnicode() is deprecated "
- "and will be removed in 3.15; "
- "use PyCodec_Decode() to decode from str to str", 1) < 0)
- return NULL;
-
if (encoding == NULL)
encoding = PyUnicode_GetDefaultEncoding();
@@ -3793,7 +3777,7 @@ PyUnicode_AsDecodedUnicode(PyObject *unicode,
return NULL;
}
-PyObject *
+PyAPI_FUNC(PyObject *)
PyUnicode_AsEncodedObject(PyObject *unicode,
const char *encoding,
const char *errors)
@@ -3805,13 +3789,6 @@ PyUnicode_AsEncodedObject(PyObject *unicode,
goto onError;
}
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "PyUnicode_AsEncodedObject() is deprecated "
- "and will be removed in 3.15; "
- "use PyUnicode_AsEncodedString() to encode from str to bytes "
- "or PyCodec_Encode() for generic encoding", 1) < 0)
- return NULL;
-
if (encoding == NULL)
encoding = PyUnicode_GetDefaultEncoding();
@@ -4017,7 +3994,7 @@ PyUnicode_AsEncodedString(PyObject *unicode,
return NULL;
}
-PyObject *
+PyAPI_FUNC(PyObject *)
PyUnicode_AsEncodedUnicode(PyObject *unicode,
const char *encoding,
const char *errors)
@@ -4029,12 +4006,6 @@ PyUnicode_AsEncodedUnicode(PyObject *unicode,
goto onError;
}
- if (PyErr_WarnEx(PyExc_DeprecationWarning,
- "PyUnicode_AsEncodedUnicode() is deprecated "
- "and will be removed in 3.15; "
- "use PyCodec_Encode() to encode from str to str", 1) < 0)
- return NULL;
-
if (encoding == NULL)
encoding = PyUnicode_GetDefaultEncoding();
@@ -6621,13 +6592,15 @@ _PyUnicode_GetNameCAPI(void)
/* --- Unicode Escape Codec ----------------------------------------------- */
PyObject *
-_PyUnicode_DecodeUnicodeEscapeInternal(const char *s,
+_PyUnicode_DecodeUnicodeEscapeInternal2(const char *s,
Py_ssize_t size,
const char *errors,
Py_ssize_t *consumed,
- const char **first_invalid_escape)
+ int *first_invalid_escape_char,
+ const char **first_invalid_escape_ptr)
{
const char *starts = s;
+ const char *initial_starts = starts;
_PyUnicodeWriter writer;
const char *end;
PyObject *errorHandler = NULL;
@@ -6635,7 +6608,8 @@ _PyUnicode_DecodeUnicodeEscapeInternal(const char *s,
_PyUnicode_Name_CAPI *ucnhash_capi;
// so we can remember if we've seen an invalid escape char or not
- *first_invalid_escape = NULL;
+ *first_invalid_escape_char = -1;
+ *first_invalid_escape_ptr = NULL;
if (size == 0) {
if (consumed) {
@@ -6723,9 +6697,12 @@ _PyUnicode_DecodeUnicodeEscapeInternal(const char *s,
}
}
if (ch > 0377) {
- if (*first_invalid_escape == NULL) {
- *first_invalid_escape = s-3; /* Back up 3 chars, since we've
- already incremented s. */
+ if (*first_invalid_escape_char == -1) {
+ *first_invalid_escape_char = ch;
+ if (starts == initial_starts) {
+ /* Back up 3 chars, since we've already incremented s. */
+ *first_invalid_escape_ptr = s - 3;
+ }
}
}
WRITE_CHAR(ch);
@@ -6820,9 +6797,12 @@ _PyUnicode_DecodeUnicodeEscapeInternal(const char *s,
goto error;
default:
- if (*first_invalid_escape == NULL) {
- *first_invalid_escape = s-1; /* Back up one char, since we've
- already incremented s. */
+ if (*first_invalid_escape_char == -1) {
+ *first_invalid_escape_char = c;
+ if (starts == initial_starts) {
+ /* Back up one char, since we've already incremented s. */
+ *first_invalid_escape_ptr = s - 1;
+ }
}
WRITE_ASCII_CHAR('\\');
WRITE_CHAR(c);
@@ -6867,19 +6847,20 @@ _PyUnicode_DecodeUnicodeEscapeStateful(const char *s,
const char *errors,
Py_ssize_t *consumed)
{
- const char *first_invalid_escape;
- PyObject *result = _PyUnicode_DecodeUnicodeEscapeInternal(s, size, errors,
+ int first_invalid_escape_char;
+ const char *first_invalid_escape_ptr;
+ PyObject *result = _PyUnicode_DecodeUnicodeEscapeInternal2(s, size, errors,
consumed,
- &first_invalid_escape);
+ &first_invalid_escape_char,
+ &first_invalid_escape_ptr);
if (result == NULL)
return NULL;
- if (first_invalid_escape != NULL) {
- unsigned char c = *first_invalid_escape;
- if ('4' <= c && c <= '7') {
+ if (first_invalid_escape_char != -1) {
+ if (first_invalid_escape_char > 0xff) {
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
- "\"\\%.3s\" is an invalid octal escape sequence. "
+ "\"\\%o\" is an invalid octal escape sequence. "
"Such sequences will not work in the future. ",
- first_invalid_escape) < 0)
+ first_invalid_escape_char) < 0)
{
Py_DECREF(result);
return NULL;
@@ -6889,7 +6870,7 @@ _PyUnicode_DecodeUnicodeEscapeStateful(const char *s,
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"\"\\%c\" is an invalid escape sequence. "
"Such sequences will not work in the future. ",
- c) < 0)
+ first_invalid_escape_char) < 0)
{
Py_DECREF(result);
return NULL;
@@ -13944,7 +13925,12 @@ _PyUnicodeWriter_WriteStr(_PyUnicodeWriter *writer, PyObject *str)
int
PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj)
{
- if (Py_TYPE(obj) == &PyLong_Type) {
+ PyTypeObject *type = Py_TYPE(obj);
+ if (type == &PyUnicode_Type) {
+ return _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, obj);
+ }
+
+ if (type == &PyLong_Type) {
return _PyLong_FormatWriter((_PyUnicodeWriter*)writer, obj, 10, 0);
}
@@ -14093,6 +14079,20 @@ _PyUnicodeWriter_WriteASCIIString(_PyUnicodeWriter *writer,
return 0;
}
+
+int
+PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer,
+ const char *str,
+ Py_ssize_t size)
+{
+ assert(writer != NULL);
+ _Py_AssertHoldsTstate();
+
+ _PyUnicodeWriter *priv_writer = (_PyUnicodeWriter*)writer;
+ return _PyUnicodeWriter_WriteASCIIString(priv_writer, str, size);
+}
+
+
int
PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer,
const char *str,
diff --git a/Objects/unionobject.c b/Objects/unionobject.c
index 66435924b6c..2206ed80ef0 100644
--- a/Objects/unionobject.c
+++ b/Objects/unionobject.c
@@ -4,6 +4,7 @@
#include "pycore_typevarobject.h" // _PyTypeAlias_Type, _Py_typing_type_repr
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString
#include "pycore_unionobject.h"
+#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
typedef struct {
@@ -21,9 +22,7 @@ unionobject_dealloc(PyObject *self)
unionobject *alias = (unionobject *)self;
_PyObject_GC_UNTRACK(self);
- if (alias->weakreflist != NULL) {
- PyObject_ClearWeakRefs((PyObject *)alias);
- }
+ FT_CLEAR_WEAKREFS(self, alias->weakreflist);
Py_XDECREF(alias->args);
Py_XDECREF(alias->hashable_args);
@@ -290,7 +289,7 @@ union_repr(PyObject *self)
}
for (Py_ssize_t i = 0; i < len; i++) {
- if (i > 0 && PyUnicodeWriter_WriteUTF8(writer, " | ", 3) < 0) {
+ if (i > 0 && PyUnicodeWriter_WriteASCII(writer, " | ", 3) < 0) {
goto error;
}
PyObject *p = PyTuple_GET_ITEM(alias->args, i);
@@ -300,12 +299,12 @@ union_repr(PyObject *self)
}
#if 0
- PyUnicodeWriter_WriteUTF8(writer, "|args=", 6);
+ PyUnicodeWriter_WriteASCII(writer, "|args=", 6);
PyUnicodeWriter_WriteRepr(writer, alias->args);
- PyUnicodeWriter_WriteUTF8(writer, "|h=", 3);
+ PyUnicodeWriter_WriteASCII(writer, "|h=", 3);
PyUnicodeWriter_WriteRepr(writer, alias->hashable_args);
if (alias->unhashable_args) {
- PyUnicodeWriter_WriteUTF8(writer, "|u=", 3);
+ PyUnicodeWriter_WriteASCII(writer, "|u=", 3);
PyUnicodeWriter_WriteRepr(writer, alias->unhashable_args);
}
#endif
diff --git a/PC/_wmimodule.cpp b/PC/_wmimodule.cpp
index b6efb3e4a20..30d61c86587 100644
--- a/PC/_wmimodule.cpp
+++ b/PC/_wmimodule.cpp
@@ -57,11 +57,11 @@ _query_thread(LPVOID param)
IEnumWbemClassObject* enumerator = NULL;
HRESULT hr = S_OK;
BSTR bstrQuery = NULL;
- struct _query_data *data = (struct _query_data*)param;
+ _query_data data = *(struct _query_data*)param;
// gh-125315: Copy the query string first, so that if the main thread gives
// up on waiting we aren't left with a dangling pointer (and a likely crash)
- bstrQuery = SysAllocString(data->query);
+ bstrQuery = SysAllocString(data.query);
if (!bstrQuery) {
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
@@ -71,7 +71,7 @@ _query_thread(LPVOID param)
}
if (FAILED(hr)) {
- CloseHandle(data->writePipe);
+ CloseHandle(data.writePipe);
if (bstrQuery) {
SysFreeString(bstrQuery);
}
@@ -96,7 +96,7 @@ _query_thread(LPVOID param)
IID_IWbemLocator, (LPVOID *)&locator
);
}
- if (SUCCEEDED(hr) && !SetEvent(data->initEvent)) {
+ if (SUCCEEDED(hr) && !SetEvent(data.initEvent)) {
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr)) {
@@ -105,7 +105,7 @@ _query_thread(LPVOID param)
NULL, NULL, 0, NULL, 0, 0, &services
);
}
- if (SUCCEEDED(hr) && !SetEvent(data->connectEvent)) {
+ if (SUCCEEDED(hr) && !SetEvent(data.connectEvent)) {
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr)) {
@@ -143,7 +143,7 @@ _query_thread(LPVOID param)
if (FAILED(hr) || got != 1 || !value) {
continue;
}
- if (!startOfEnum && !WriteFile(data->writePipe, (LPVOID)L"\0", 2, &written, NULL)) {
+ if (!startOfEnum && !WriteFile(data.writePipe, (LPVOID)L"\0", 2, &written, NULL)) {
hr = HRESULT_FROM_WIN32(GetLastError());
break;
}
@@ -171,10 +171,10 @@ _query_thread(LPVOID param)
DWORD cbStr1, cbStr2;
cbStr1 = (DWORD)(wcslen(propName) * sizeof(propName[0]));
cbStr2 = (DWORD)(wcslen(propStr) * sizeof(propStr[0]));
- if (!WriteFile(data->writePipe, propName, cbStr1, &written, NULL) ||
- !WriteFile(data->writePipe, (LPVOID)L"=", 2, &written, NULL) ||
- !WriteFile(data->writePipe, propStr, cbStr2, &written, NULL) ||
- !WriteFile(data->writePipe, (LPVOID)L"\0", 2, &written, NULL)
+ if (!WriteFile(data.writePipe, propName, cbStr1, &written, NULL) ||
+ !WriteFile(data.writePipe, (LPVOID)L"=", 2, &written, NULL) ||
+ !WriteFile(data.writePipe, propStr, cbStr2, &written, NULL) ||
+ !WriteFile(data.writePipe, (LPVOID)L"\0", 2, &written, NULL)
) {
hr = HRESULT_FROM_WIN32(GetLastError());
}
@@ -200,7 +200,7 @@ _query_thread(LPVOID param)
locator->Release();
}
CoUninitialize();
- CloseHandle(data->writePipe);
+ CloseHandle(data.writePipe);
return (DWORD)hr;
}
diff --git a/PC/launcher.c b/PC/launcher.c
index 47fafbc3bf6..fed5e156b92 100644
--- a/PC/launcher.c
+++ b/PC/launcher.c
@@ -140,7 +140,7 @@ static wchar_t * get_env(wchar_t * key)
return buf;
}
-#if defined(_DEBUG)
+#if defined(Py_DEBUG)
/* Do not define EXECUTABLEPATH_VALUE in debug builds as it'll
never point to the debug build. */
#if defined(_WINDOWS)
@@ -1272,6 +1272,7 @@ static PYC_MAGIC magic_values[] = {
{ 3500, 3549, L"3.12" },
{ 3550, 3599, L"3.13" },
{ 3600, 3649, L"3.14" },
+ { 3650, 3699, L"3.15" },
{ 0 }
};
diff --git a/PC/layout/main.py b/PC/layout/main.py
index 7324a135133..8543e7c56e1 100644
--- a/PC/layout/main.py
+++ b/PC/layout/main.py
@@ -247,9 +247,15 @@ def get_layout(ns):
if ns.include_freethreaded:
yield from in_build("venvlaunchert.exe", "Lib/venv/scripts/nt/")
yield from in_build("venvwlaunchert.exe", "Lib/venv/scripts/nt/")
- else:
+ elif (VER_MAJOR, VER_MINOR) > (3, 12):
yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/")
yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/")
+ else:
+ # Older versions of venv expected the scripts to be named 'python'
+ # and they were renamed at this stage. We need to replicate that
+ # when packaging older versions.
+ yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python")
+ yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw")
if ns.include_tools:
@@ -652,15 +658,6 @@ def main():
ns.doc_build = (Path.cwd() / ns.doc_build).resolve()
if ns.include_cat and not ns.include_cat.is_absolute():
ns.include_cat = (Path.cwd() / ns.include_cat).resolve()
- if not ns.arch:
- # TODO: Calculate arch from files in ns.build instead
- if sys.winver.endswith("-arm64"):
- ns.arch = "arm64"
- elif sys.winver.endswith("-32"):
- ns.arch = "win32"
- else:
- ns.arch = "amd64"
-
if ns.zip and not ns.zip.is_absolute():
ns.zip = (Path.cwd() / ns.zip).resolve()
if ns.catalog and not ns.catalog.is_absolute():
@@ -668,6 +665,17 @@ def main():
configure_logger(ns)
+ if not ns.arch:
+ from .support.arch import calculate_from_build_dir
+ ns.arch = calculate_from_build_dir(ns.build)
+
+ expect = f"{VER_MAJOR}.{VER_MINOR}.{VER_MICRO}{VER_SUFFIX}"
+ actual = check_patchlevel_version(ns.source)
+ if actual and actual != expect:
+ log_error(f"Inferred version {expect} does not match {actual} from patchlevel.h. "
+ "You should set %PYTHONINCLUDE% or %PYTHON_HEXVERSION% before launching.")
+ return 5
+
log_info(
"""OPTIONS
Source: {ns.source}
diff --git a/PC/layout/support/arch.py b/PC/layout/support/arch.py
new file mode 100644
index 00000000000..daf4efbc7ab
--- /dev/null
+++ b/PC/layout/support/arch.py
@@ -0,0 +1,34 @@
+from struct import unpack
+from .constants import *
+from .logging import *
+
+def calculate_from_build_dir(root):
+ candidates = [
+ root / PYTHON_DLL_NAME,
+ root / FREETHREADED_PYTHON_DLL_NAME,
+ *root.glob("*.dll"),
+ *root.glob("*.pyd"),
+ # Check EXE last because it's easier to have cross-platform EXE
+ *root.glob("*.exe"),
+ ]
+
+ ARCHS = {
+ b"PE\0\0\x4c\x01": "win32",
+ b"PE\0\0\x64\x86": "amd64",
+ b"PE\0\0\x64\xAA": "arm64"
+ }
+
+ first_exc = None
+ for pe in candidates:
+ try:
+ # Read the PE header to grab the machine type
+ with open(pe, "rb") as f:
+ f.seek(0x3C)
+ offset = int.from_bytes(f.read(4), "little")
+ f.seek(offset)
+ arch = ARCHS[f.read(6)]
+ except (FileNotFoundError, PermissionError, LookupError) as ex:
+ log_debug("Failed to open {}: {}", pe, ex)
+ continue
+ log_info("Inferred architecture {} from {}", arch, pe)
+ return arch
diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py
index ae22aa16ebf..6b8c915e519 100644
--- a/PC/layout/support/constants.py
+++ b/PC/layout/support/constants.py
@@ -6,6 +6,8 @@ __author__ = "Steve Dower <steve.dower@python.org>"
__version__ = "3.8"
import os
+import pathlib
+import re
import struct
import sys
@@ -13,9 +15,15 @@ import sys
def _unpack_hexversion():
try:
hexversion = int(os.getenv("PYTHON_HEXVERSION"), 16)
+ return struct.pack(">i", hexversion)
except (TypeError, ValueError):
- hexversion = sys.hexversion
- return struct.pack(">i", hexversion)
+ pass
+ if os.getenv("PYTHONINCLUDE"):
+ try:
+ return _read_patchlevel_version(pathlib.Path(os.getenv("PYTHONINCLUDE")))
+ except OSError:
+ pass
+ return struct.pack(">i", sys.hexversion)
def _get_suffix(field4):
@@ -26,6 +34,39 @@ def _get_suffix(field4):
return ""
+def _read_patchlevel_version(sources):
+ if not sources.match("Include"):
+ sources /= "Include"
+ values = {}
+ with open(sources / "patchlevel.h", "r", encoding="utf-8") as f:
+ for line in f:
+ m = re.match(r'#\s*define\s+(PY_\S+?)\s+(\S+)', line.strip(), re.I)
+ if m and m.group(2):
+ v = m.group(2)
+ if v.startswith('"'):
+ v = v[1:-1]
+ else:
+ v = values.get(v, v)
+ if isinstance(v, str):
+ try:
+ v = int(v, 16 if v.startswith("0x") else 10)
+ except ValueError:
+ pass
+ values[m.group(1)] = v
+ return (
+ values["PY_MAJOR_VERSION"],
+ values["PY_MINOR_VERSION"],
+ values["PY_MICRO_VERSION"],
+ values["PY_RELEASE_LEVEL"] << 4 | values["PY_RELEASE_SERIAL"],
+ )
+
+
+def check_patchlevel_version(sources):
+ got = _read_patchlevel_version(sources)
+ if got != (VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4):
+ return f"{got[0]}.{got[1]}.{got[2]}{_get_suffix(got[3])}"
+
+
VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = _unpack_hexversion()
VER_SUFFIX = _get_suffix(VER_FIELD4)
VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4
diff --git a/PC/pyconfig.h.in b/PC/pyconfig.h
index 9e70303868e..0e8379387cd 100644
--- a/PC/pyconfig.h.in
+++ b/PC/pyconfig.h
@@ -94,12 +94,22 @@ WIN32 is still required for the locale module.
#endif
#endif /* Py_BUILD_CORE || Py_BUILD_CORE_BUILTIN || Py_BUILD_CORE_MODULE */
-/* Define to 1 if you want to disable the GIL */
-/* Uncomment the definition for free-threaded builds, or define it manually
- * when compiling extension modules. Note that we test with #ifdef, so
- * defining as 0 will still disable the GIL. */
-#ifndef Py_GIL_DISABLED
-/* #define Py_GIL_DISABLED 1 */
+/* _DEBUG implies Py_DEBUG */
+#ifdef _DEBUG
+# define Py_DEBUG 1
+#endif
+
+/* Define to 1 when compiling for experimental free-threaded builds */
+#ifdef Py_GIL_DISABLED
+/* We undefine if it was set to zero because all later checks are #ifdef.
+ * Note that non-Windows builds do not do this, and so every effort should
+ * be made to avoid defining the variable at all when not desired. However,
+ * sysconfig.get_config_var always returns a 1 or a 0, and so it seems likely
+ * that a build backend will define it with the value.
+ */
+#if Py_GIL_DISABLED == 0
+#undef Py_GIL_DISABLED
+#endif
#endif
/* Compiler specific defines */
@@ -319,21 +329,21 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
This is relevant when using build-system generator (e.g CMake) where
the linking is explicitly handled */
# if defined(Py_GIL_DISABLED)
-# if defined(_DEBUG)
-# pragma comment(lib,"python314t_d.lib")
+# if defined(Py_DEBUG)
+# pragma comment(lib,"python315t_d.lib")
# elif defined(Py_LIMITED_API)
# pragma comment(lib,"python3t.lib")
# else
-# pragma comment(lib,"python314t.lib")
-# endif /* _DEBUG */
+# pragma comment(lib,"python315t.lib")
+# endif /* Py_DEBUG */
# else /* Py_GIL_DISABLED */
-# if defined(_DEBUG)
-# pragma comment(lib,"python314_d.lib")
+# if defined(Py_DEBUG)
+# pragma comment(lib,"python315_d.lib")
# elif defined(Py_LIMITED_API)
# pragma comment(lib,"python3.lib")
# else
-# pragma comment(lib,"python314.lib")
-# endif /* _DEBUG */
+# pragma comment(lib,"python315.lib")
+# endif /* Py_DEBUG */
# endif /* Py_GIL_DISABLED */
# endif /* _MSC_VER && !Py_NO_LINK_LIB */
# endif /* Py_BUILD_CORE */
@@ -376,11 +386,6 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
# define ALIGNOF_MAX_ALIGN_T 8
#endif
-#ifdef _DEBUG
-# define Py_DEBUG
-#endif
-
-
#ifdef MS_WIN32
#define SIZEOF_SHORT 2
diff --git a/PC/python3dll.c b/PC/python3dll.c
index f0c578e11c6..8ec791f8280 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -595,7 +595,11 @@ EXPORT_FUNC(PySys_Audit)
EXPORT_FUNC(PySys_AuditTuple)
EXPORT_FUNC(PySys_FormatStderr)
EXPORT_FUNC(PySys_FormatStdout)
+EXPORT_FUNC(PySys_GetAttr)
+EXPORT_FUNC(PySys_GetAttrString)
EXPORT_FUNC(PySys_GetObject)
+EXPORT_FUNC(PySys_GetOptionalAttr)
+EXPORT_FUNC(PySys_GetOptionalAttrString)
EXPORT_FUNC(PySys_GetXOptions)
EXPORT_FUNC(PySys_HasWarnOptions)
EXPORT_FUNC(PySys_ResetWarnOptions)
diff --git a/PC/python_uwp.cpp b/PC/python_uwp.cpp
index b9c408a580c..8cdb8d722cd 100644
--- a/PC/python_uwp.cpp
+++ b/PC/python_uwp.cpp
@@ -19,13 +19,13 @@
#include <winrt\Windows.Storage.h>
#ifdef PYTHONW
-#ifdef _DEBUG
+#ifdef Py_DEBUG
const wchar_t *PROGNAME = L"pythonw_d.exe";
#else
const wchar_t *PROGNAME = L"pythonw.exe";
#endif
#else
-#ifdef _DEBUG
+#ifdef Py_DEBUG
const wchar_t *PROGNAME = L"python_d.exe";
#else
const wchar_t *PROGNAME = L"python.exe";
diff --git a/PC/python_ver_rc.h b/PC/python_ver_rc.h
index ee867fe4122..bb98144cd03 100644
--- a/PC/python_ver_rc.h
+++ b/PC/python_ver_rc.h
@@ -10,7 +10,7 @@
#define MS_WINDOWS
#include "modsupport.h"
#include "patchlevel.h"
-#ifdef _DEBUG
+#ifdef Py_DEBUG
# define PYTHON_DEBUG_EXT "_d"
#else
# define PYTHON_DEBUG_EXT
diff --git a/PC/winreg.c b/PC/winreg.c
index c0de5c1353a..d1a1c3d1c97 100644
--- a/PC/winreg.c
+++ b/PC/winreg.c
@@ -426,7 +426,9 @@ PyHKEY_Close(winreg_state *st, PyObject *ob_handle)
if (PyHKEY_Check(st, ob_handle)) {
((PyHKEYObject*)ob_handle)->hkey = 0;
}
+ Py_BEGIN_ALLOW_THREADS
rc = key ? RegCloseKey(key) : ERROR_SUCCESS;
+ Py_END_ALLOW_THREADS
if (rc != ERROR_SUCCESS)
PyErr_SetFromWindowsErrWithFunction(rc, "RegCloseKey");
return rc == ERROR_SUCCESS;
@@ -499,14 +501,21 @@ PyWinObject_CloseHKEY(winreg_state *st, PyObject *obHandle)
}
#if SIZEOF_LONG >= SIZEOF_HKEY
else if (PyLong_Check(obHandle)) {
- long rc = RegCloseKey((HKEY)PyLong_AsLong(obHandle));
+ long rc;
+ Py_BEGIN_ALLOW_THREADS
+ rc = RegCloseKey((HKEY)PyLong_AsLong(obHandle));
+ Py_END_ALLOW_THREADS
ok = (rc == ERROR_SUCCESS);
if (!ok)
PyErr_SetFromWindowsErrWithFunction(rc, "RegCloseKey");
}
#else
else if (PyLong_Check(obHandle)) {
- long rc = RegCloseKey((HKEY)PyLong_AsVoidPtr(obHandle));
+ long rc;
+ HKEY hkey = (HKEY)PyLong_AsVoidPtr(obHandle);
+ Py_BEGIN_ALLOW_THREADS
+ rc = RegCloseKey(hkey);
+ Py_END_ALLOW_THREADS
ok = (rc == ERROR_SUCCESS);
if (!ok)
PyErr_SetFromWindowsErrWithFunction(rc, "RegCloseKey");
@@ -924,7 +933,9 @@ winreg_CreateKey_impl(PyObject *module, HKEY key, const wchar_t *sub_key)
(Py_ssize_t)KEY_WRITE) < 0) {
return NULL;
}
+ Py_BEGIN_ALLOW_THREADS
rc = RegCreateKeyW(key, sub_key, &retKey);
+ Py_END_ALLOW_THREADS
if (rc != ERROR_SUCCESS) {
PyErr_SetFromWindowsErrWithFunction(rc, "CreateKey");
return NULL;
@@ -973,8 +984,10 @@ winreg_CreateKeyEx_impl(PyObject *module, HKEY key, const wchar_t *sub_key,
(Py_ssize_t)access) < 0) {
return NULL;
}
+ Py_BEGIN_ALLOW_THREADS
rc = RegCreateKeyExW(key, sub_key, reserved, NULL, 0,
access, NULL, &retKey, NULL);
+ Py_END_ALLOW_THREADS
if (rc != ERROR_SUCCESS) {
PyErr_SetFromWindowsErrWithFunction(rc, "CreateKeyEx");
return NULL;
@@ -1187,10 +1200,12 @@ winreg_EnumValue_impl(PyObject *module, HKEY key, int index)
(Py_ssize_t)key, index) < 0) {
return NULL;
}
- if ((rc = RegQueryInfoKeyW(key, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL,
- &retValueSize, &retDataSize, NULL, NULL))
- != ERROR_SUCCESS)
+
+ Py_BEGIN_ALLOW_THREADS
+ rc = RegQueryInfoKeyW(key, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &retValueSize, &retDataSize, NULL, NULL);
+ Py_END_ALLOW_THREADS
+ if (rc != ERROR_SUCCESS)
return PyErr_SetFromWindowsErrWithFunction(rc,
"RegQueryInfoKey");
++retValueSize; /* include null terminators */
@@ -1290,7 +1305,7 @@ winreg_ExpandEnvironmentStrings_impl(PyObject *module, const wchar_t *string)
return PyErr_SetFromWindowsErrWithFunction(retValueSize,
"ExpandEnvironmentStrings");
}
- o = PyUnicode_FromWideChar(retValue, wcslen(retValue));
+ o = PyUnicode_FromWideChar(retValue, -1);
PyMem_Free(retValue);
return o;
}
@@ -1477,9 +1492,11 @@ winreg_QueryInfoKey_impl(PyObject *module, HKEY key)
if (PySys_Audit("winreg.QueryInfoKey", "n", (Py_ssize_t)key) < 0) {
return NULL;
}
- if ((rc = RegQueryInfoKeyW(key, NULL, NULL, 0, &nSubKeys, NULL, NULL,
- &nValues, NULL, NULL, NULL, &ft))
- != ERROR_SUCCESS) {
+ Py_BEGIN_ALLOW_THREADS
+ rc = RegQueryInfoKeyW(key, NULL, NULL, 0, &nSubKeys, NULL, NULL,
+ &nValues, NULL, NULL, NULL, &ft);
+ Py_END_ALLOW_THREADS
+ if (rc != ERROR_SUCCESS) {
return PyErr_SetFromWindowsErrWithFunction(rc, "RegQueryInfoKey");
}
li.LowPart = ft.dwLowDateTime;
@@ -1587,7 +1604,9 @@ exit:
PyMem_Free(pbuf);
}
if (childKey != key) {
+ Py_BEGIN_ALLOW_THREADS
RegCloseKey(childKey);
+ Py_END_ALLOW_THREADS
}
return result;
}
@@ -1625,7 +1644,9 @@ winreg_QueryValueEx_impl(PyObject *module, HKEY key, const wchar_t *name)
(Py_ssize_t)key, NULL, name) < 0) {
return NULL;
}
+ Py_BEGIN_ALLOW_THREADS
rc = RegQueryValueExW(key, name, NULL, NULL, NULL, &bufSize);
+ Py_END_ALLOW_THREADS
if (rc == ERROR_MORE_DATA)
bufSize = 256;
else if (rc != ERROR_SUCCESS)
@@ -1637,8 +1658,10 @@ winreg_QueryValueEx_impl(PyObject *module, HKEY key, const wchar_t *name)
while (1) {
retSize = bufSize;
+ Py_BEGIN_ALLOW_THREADS
rc = RegQueryValueExW(key, name, NULL, &typ,
(BYTE *)retBuf, &retSize);
+ Py_END_ALLOW_THREADS
if (rc != ERROR_MORE_DATA)
break;
diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj
index 1f3f3170f3f..efff6a58d89 100644
--- a/PCbuild/_freeze_module.vcxproj
+++ b/PCbuild/_freeze_module.vcxproj
@@ -190,7 +190,7 @@
<ClCompile Include="..\Python\asdl.c" />
<ClCompile Include="..\Python\assemble.c" />
<ClCompile Include="..\Python\ast.c" />
- <ClCompile Include="..\Python\ast_opt.c" />
+ <ClCompile Include="..\Python\ast_preprocess.c" />
<ClCompile Include="..\Python\ast_unparse.c" />
<ClCompile Include="..\Python\bltinmodule.c" />
<ClCompile Include="..\Python\brc.c" />
@@ -276,7 +276,7 @@
<ClCompile Include="..\Python\uniqueid.c" />
</ItemGroup>
<ItemGroup>
- <ClInclude Include="..\PC\pyconfig.h.in" />
+ <ClInclude Include="..\PC\pyconfig.h" />
</ItemGroup>
<ItemGroup>
<!-- BEGIN frozen modules -->
@@ -436,31 +436,6 @@
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
- <!-- Direct copy from pythoncore.vcxproj, but this one is only used for our
- own build. All other extension modules will use the copy that pythoncore
- generates. -->
- <Target Name="_UpdatePyconfig" BeforeTargets="PrepareForBuild">
- <MakeDir Directories="$(IntDir)" Condition="!Exists($(IntDir))" />
- <ItemGroup>
- <PyConfigH Remove="@(PyConfigH)" />
- <PyConfigH Include="@(ClInclude)" Condition="'%(Filename)%(Extension)' == 'pyconfig.h.in'" />
- </ItemGroup>
- <Error Text="Did not find pyconfig.h" Condition="@(ClInclude) == ''" />
- <PropertyGroup>
- <PyConfigH>@(PyConfigH->'%(FullPath)', ';')</PyConfigH>
- <PyConfigHText>$([System.IO.File]::ReadAllText($(PyConfigH)))</PyConfigHText>
- <OldPyConfigH Condition="Exists('$(IntDir)pyconfig.h')">$([System.IO.File]::ReadAllText('$(IntDir)pyconfig.h'))</OldPyConfigH>
- </PropertyGroup>
- <PropertyGroup Condition="$(DisableGil) == 'true'">
- <PyConfigHText>$(PyConfigHText.Replace('/* #define Py_GIL_DISABLED 1 */', '#define Py_GIL_DISABLED 1'))</PyConfigHText>
- </PropertyGroup>
- <Message Text="Updating pyconfig.h" Condition="$(PyConfigHText.TrimEnd()) != $(OldPyConfigH.TrimEnd())" />
- <WriteLinesToFile File="$(IntDir)pyconfig.h"
- Lines="$(PyConfigHText)"
- Overwrite="true"
- Condition="$(PyConfigHText.TrimEnd()) != $(OldPyConfigH.TrimEnd())" />
- </Target>
-
<Target Name="_RebuildGetPath" AfterTargets="_RebuildFrozen" Condition="$(Configuration) != 'PGUpdate'">
<Exec Command='"$(TargetPath)" "%(GetPath.ModName)" "%(GetPath.FullPath)" "%(GetPath.IntFile)"' />
diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters
index 0a64de1d4f0..332d466b1f7 100644
--- a/PCbuild/_freeze_module.vcxproj.filters
+++ b/PCbuild/_freeze_module.vcxproj.filters
@@ -34,7 +34,7 @@
<ClCompile Include="..\Python\ast.c">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\Python\ast_opt.c">
+ <ClCompile Include="..\Python\ast_preprocess.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Python\ast_unparse.c">
diff --git a/PCbuild/_testexternalinspection.vcxproj b/PCbuild/_remote_debugging.vcxproj
index d5f347ecfec..c55f2908e03 100644
--- a/PCbuild/_testexternalinspection.vcxproj
+++ b/PCbuild/_remote_debugging.vcxproj
@@ -68,7 +68,7 @@
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{4D7C112F-3083-4D9E-9754-9341C14D9B39}</ProjectGuid>
- <RootNamespace>_testexternalinspection</RootNamespace>
+ <RootNamespace>_remote_debugging</RootNamespace>
<Keyword>Win32Proj</Keyword>
<SupportPGO>false</SupportPGO>
</PropertyGroup>
@@ -93,7 +93,7 @@
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
</PropertyGroup>
<ItemGroup>
- <ClCompile Include="..\Modules\_testexternalinspection.c" />
+ <ClCompile Include="..\Modules\_remote_debugging_module.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testexternalinspection.vcxproj.filters b/PCbuild/_remote_debugging.vcxproj.filters
index feb4343e5c2..ce4437f74e0 100644
--- a/PCbuild/_testexternalinspection.vcxproj.filters
+++ b/PCbuild/_remote_debugging.vcxproj.filters
@@ -9,7 +9,7 @@
</Filter>
</ItemGroup>
<ItemGroup>
- <ClCompile Include="..\Modules\_testexternalinspection.c" />
+ <ClCompile Include="..\Modules\_remote_debugging_module.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
diff --git a/PCbuild/_testclinic_limited.vcxproj b/PCbuild/_testclinic_limited.vcxproj
index 183a55080e8..95c205309b1 100644
--- a/PCbuild/_testclinic_limited.vcxproj
+++ b/PCbuild/_testclinic_limited.vcxproj
@@ -70,6 +70,7 @@
<ProjectGuid>{01FDF29A-40A1-46DF-84F5-85EBBD2A2410}</ProjectGuid>
<RootNamespace>_testclinic_limited</RootNamespace>
<Keyword>Win32Proj</Keyword>
+ <SupportPGO>false</SupportPGO>
</PropertyGroup>
<Import Project="python.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
diff --git a/PCbuild/_zstd.vcxproj b/PCbuild/_zstd.vcxproj
new file mode 100644
index 00000000000..6f91b8d05cc
--- /dev/null
+++ b/PCbuild/_zstd.vcxproj
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|ARM">
+ <Configuration>Debug</Configuration>
+ <Platform>ARM</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|ARM64">
+ <Configuration>Debug</Configuration>
+ <Platform>ARM64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|ARM">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>ARM</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|ARM64">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>ARM64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|Win32">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGInstrument|x64">
+ <Configuration>PGInstrument</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|ARM">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>ARM</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|ARM64">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>ARM64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|Win32">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="PGUpdate|x64">
+ <Configuration>PGUpdate</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|ARM">
+ <Configuration>Release</Configuration>
+ <Platform>ARM</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|ARM64">
+ <Configuration>Release</Configuration>
+ <Platform>ARM64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{07029b86-f3e9-443e-86fb-78aa6d47fed1}</ProjectGuid>
+ <RootNamespace>_zstd</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="python.props" />
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <CharacterSet>NotSet</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" />
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <PropertyGroup>
+ <TargetExt>$(PyStdlibPydExt)</TargetExt>
+ </PropertyGroup>
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="pyproject.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ </PropertyGroup>
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <PreprocessorDefinitions>WIN32;ZSTD_MULTITHREAD=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>$(zstdDir)lib\;$(zstdDir)lib\common;$(zstdDir)lib\compress;$(zstdDir)lib\decompress;$(zstdDir)lib\dictBuilder;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\Modules\_zstd\_zstdmodule.c" />
+ <ClCompile Include="..\Modules\_zstd\compressor.c" />
+ <ClCompile Include="..\Modules\_zstd\decompressor.c" />
+ <ClCompile Include="..\Modules\_zstd\zstddict.c" />
+ <ClCompile Include="$(zstdDir)lib\common\debug.c" />
+ <ClCompile Include="$(zstdDir)lib\common\entropy_common.c" />
+ <ClCompile Include="$(zstdDir)lib\common\error_private.c" />
+ <ClCompile Include="$(zstdDir)lib\common\fse_decompress.c" />
+ <ClCompile Include="$(zstdDir)lib\common\pool.c" />
+ <ClCompile Include="$(zstdDir)lib\common\threading.c" />
+ <ClCompile Include="$(zstdDir)lib\common\xxhash.c" />
+ <ClCompile Include="$(zstdDir)lib\common\zstd_common.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\fse_compress.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\hist.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\huf_compress.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress_literals.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress_sequences.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress_superblock.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_double_fast.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_fast.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_lazy.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_ldm.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_opt.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_preSplit.c" />
+ <ClCompile Include="$(zstdDir)lib\compress\zstdmt_compress.c" />
+ <ClCompile Include="$(zstdDir)lib\decompress\huf_decompress.c" />
+ <ClCompile Include="$(zstdDir)lib\decompress\zstd_ddict.c" />
+ <ClCompile Include="$(zstdDir)lib\decompress\zstd_decompress.c" />
+ <ClCompile Include="$(zstdDir)lib\decompress\zstd_decompress_block.c" />
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\cover.c" />
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\divsufsort.c" />
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\fastcover.c" />
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\zdict.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\Modules\_zstd\_zstdmodule.h" />
+ <ClInclude Include="..\Modules\_zstd\buffer.h" />
+ <ClInclude Include="..\Modules\_zstd\zstddict.h" />
+ <ClInclude Include="$(zstdDir)lib\common\bitstream.h" />
+ <ClInclude Include="$(zstdDir)lib\common\error_private.h" />
+ <ClInclude Include="$(zstdDir)lib\common\fse.h" />
+ <ClInclude Include="$(zstdDir)lib\common\huf.h" />
+ <ClInclude Include="$(zstdDir)lib\common\mem.h" />
+ <ClInclude Include="$(zstdDir)lib\common\pool.h" />
+ <ClInclude Include="$(zstdDir)lib\common\threading.h" />
+ <ClInclude Include="$(zstdDir)lib\common\xxhash.h" />
+ <ClInclude Include="$(zstdDir)lib\common\zstd_internal.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress_literals.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress_sequences.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress_superblock.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_cwksp.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_double_fast.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_fast.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_lazy.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_ldm.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_opt.h" />
+ <ClInclude Include="$(zstdDir)lib\compress\zstdmt_compress.h" />
+ <ClInclude Include="$(zstdDir)lib\decompress\zstd_ddict.h" />
+ <ClInclude Include="$(zstdDir)lib\zstd.h" />
+ <ClInclude Include="$(zstdDir)lib\zstd_errors.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\PC\python_nt.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="pythoncore.vcxproj">
+ <Project>{cf7ac3d1-e2df-41d2-bea6-1e2556cdea26}</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/PCbuild/_zstd.vcxproj.filters b/PCbuild/_zstd.vcxproj.filters
new file mode 100644
index 00000000000..eec666e5eaf
--- /dev/null
+++ b/PCbuild/_zstd.vcxproj.filters
@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\zstd">
+ <UniqueIdentifier>{971714e1-ff37-4240-87bf-a36f6afe764f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\zstd">
+ <UniqueIdentifier>{deb43fb6-fa77-4c93-a333-85785fe5f68d}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\Modules\_zstd\_zstdmodule.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Modules\_zstd\compressor.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Modules\_zstd\decompressor.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\Modules\_zstd\zstddict.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\debug.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\entropy_common.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\error_private.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\fse_decompress.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\pool.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\threading.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\xxhash.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\common\zstd_common.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\fse_compress.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\hist.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\huf_compress.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress_literals.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress_sequences.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_compress_superblock.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_double_fast.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_fast.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_lazy.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_ldm.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_opt.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstd_preSplit.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\compress\zstdmt_compress.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\decompress\huf_decompress.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\decompress\zstd_ddict.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\decompress\zstd_decompress.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\decompress\zstd_decompress_block.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\cover.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\divsufsort.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\fastcover.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ <ClCompile Include="$(zstdDir)lib\dictBuilder\zdict.c">
+ <Filter>Source Files\zstd</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\Modules\_zstd\_zstdmodule.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\Modules\_zstd\buffer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\Modules\_zstd\zstddict.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\zstd.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\zstd_errors.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\bitstream.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\error_private.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\fse.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\huf.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\mem.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\pool.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\threading.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\xxhash.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\common\zstd_internal.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress_literals.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress_sequences.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_compress_superblock.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_cwksp.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_double_fast.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_fast.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_lazy.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_ldm.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstd_opt.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\compress\zstdmt_compress.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ <ClInclude Include="$(zstdDir)lib\decompress\zstd_ddict.h">
+ <Filter>Header Files\zstd</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\PC\python_nt.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project>
diff --git a/PCbuild/build.bat b/PCbuild/build.bat
index 2f358991e48..60235704886 100644
--- a/PCbuild/build.bat
+++ b/PCbuild/build.bat
@@ -33,7 +33,7 @@ echo. -k Attempt to kill any running Pythons before building (usually done
echo. automatically by the pythoncore project)
echo. --pgo Build with Profile-Guided Optimization. This flag
echo. overrides -c and -d
-echo. --disable-gil Enable experimental support for running without the GIL.
+echo. --disable-gil Enable support for running without the GIL.
echo. --test-marker Enable the test marker within the build.
echo. --regen Regenerate all opcodes, grammar and tokens.
echo. --experimental-jit Enable the experimental just-in-time compiler.
diff --git a/PCbuild/get_external.py b/PCbuild/get_external.py
index 4ecc8925349..a78aa6a2304 100755
--- a/PCbuild/get_external.py
+++ b/PCbuild/get_external.py
@@ -5,8 +5,28 @@ import os
import pathlib
import sys
import time
+import urllib.error
+import urllib.request
import zipfile
-from urllib.request import urlretrieve
+
+
+def retrieve_with_retries(download_location, output_path, reporthook,
+ max_retries=7):
+ """Download a file with exponential backoff retry and save to disk."""
+ for attempt in range(max_retries + 1):
+ try:
+ resp = urllib.request.urlretrieve(
+ download_location,
+ output_path,
+ reporthook=reporthook,
+ )
+ except (urllib.error.URLError, ConnectionError) as ex:
+ if attempt == max_retries:
+ msg = f"Download from {download_location} failed."
+ raise OSError(msg) from ex
+ time.sleep(2.25**attempt)
+ else:
+ return resp
def fetch_zip(commit_hash, zip_dir, *, org='python', binary=False, verbose):
@@ -16,10 +36,10 @@ def fetch_zip(commit_hash, zip_dir, *, org='python', binary=False, verbose):
if verbose:
reporthook = print
zip_dir.mkdir(parents=True, exist_ok=True)
- filename, headers = urlretrieve(
+ filename, _headers = retrieve_with_retries(
url,
zip_dir / f'{commit_hash}.zip',
- reporthook=reporthook,
+ reporthook
)
return filename
diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat
index 49ace616793..e29054f5734 100644
--- a/PCbuild/get_externals.bat
+++ b/PCbuild/get_externals.bat
@@ -61,6 +61,7 @@ if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.15.
if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.15.0
set libraries=%libraries% xz-5.2.5
set libraries=%libraries% zlib-ng-2.2.4
+set libraries=%libraries% zstd-1.5.7
for %%e in (%libraries%) do (
if exist "%EXTERNALS_DIR%\%%e" (
diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj
index 1bf430e03de..7a5327bf016 100644
--- a/PCbuild/pcbuild.proj
+++ b/PCbuild/pcbuild.proj
@@ -66,10 +66,10 @@
<!-- pyshellext.dll -->
<Projects Include="pyshellext.vcxproj" />
<!-- Extension modules -->
- <ExtensionModules Include="_asyncio;_zoneinfo;_decimal;_elementtree;_multiprocessing;_overlapped;pyexpat;_queue;select;unicodedata;winsound;_uuid;_wmi" />
+ <ExtensionModules Include="_asyncio;_decimal;_elementtree;_multiprocessing;_overlapped;pyexpat;_queue;_remote_debugging;select;unicodedata;winsound;_uuid;_wmi;_zoneinfo" />
<ExtensionModules Include="_ctypes" Condition="$(IncludeCTypes)" />
<!-- Extension modules that require external sources -->
- <ExternalModules Include="_bz2;_lzma;_sqlite3" />
+ <ExternalModules Include="_bz2;_lzma;_sqlite3;_zstd" />
<!-- venv launchers -->
<Projects Include="venvlauncher.vcxproj;venvwlauncher.vcxproj" />
<!-- _ssl will build _socket as well, which may cause conflicts in parallel builds -->
@@ -79,7 +79,7 @@
<ExtensionModules Include="@(ExternalModules->'%(Identity)')" Condition="$(IncludeExternals)" />
<Projects Include="@(ExtensionModules->'%(Identity).vcxproj')" Condition="$(IncludeExtensions)" />
<!-- Test modules -->
- <TestModules Include="_ctypes_test;_testbuffer;_testcapi;_testlimitedcapi;_testexternalinspection;_testinternalcapi;_testembed;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testclinic;_testclinic_limited" />
+ <TestModules Include="_ctypes_test;_testbuffer;_testcapi;_testlimitedcapi;_testinternalcapi;_testembed;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testclinic;_testclinic_limited" />
<TestModules Include="xxlimited" Condition="'$(Configuration)' == 'Release'" />
<TestModules Include="xxlimited_35" Condition="'$(Configuration)' == 'Release'" />
<Projects Include="@(TestModules->'%(Identity).vcxproj')" Condition="$(IncludeTests)">
diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln
index 803bb149c90..7296ea75301 100644
--- a/PCbuild/pcbuild.sln
+++ b/PCbuild/pcbuild.sln
@@ -11,6 +11,7 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python", "python.vcxproj", "{B11D750F-CD1F-4A96-85CE-E69A5C5259F9}"
ProjectSection(ProjectDependencies) = postProject
{01FDF29A-40A1-46DF-84F5-85EBBD2A2410} = {01FDF29A-40A1-46DF-84F5-85EBBD2A2410}
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1} = {07029B86-F3E9-443E-86FB-78AA6D47FED1}
{0E9791DB-593A-465F-98BC-681011311617} = {0E9791DB-593A-465F-98BC-681011311617}
{0E9791DB-593A-465F-98BC-681011311618} = {0E9791DB-593A-465F-98BC-681011311618}
{12728250-16EC-4DC6-94D7-E21DD88947F8} = {12728250-16EC-4DC6-94D7-E21DD88947F8}
@@ -81,8 +82,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testclinic", "_testclinic.
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testinternalcapi", "_testinternalcapi.vcxproj", "{900342D7-516A-4469-B1AD-59A66E49A25F}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testexternalinspection", "_testexternalinspection.vcxproj", "{4D7C112F-3083-4D9E-9754-9341C14D9B39}"
-EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testimportmultiple", "_testimportmultiple.vcxproj", "{36D0C52C-DF4E-45D0-8BC7-E294C3ABC781}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_tkinter", "_tkinter.vcxproj", "{4946ECAC-2E69-4BF8-A90A-F5136F5094DF}"
@@ -165,6 +164,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testlimitedcapi", "_testli
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib-ng", "zlib-ng.vcxproj", "{FB91C8B2-6FBC-3A01-B644-1637111F902D}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_remote_debugging", "_remote_debugging.vcxproj", "{4D7C112F-3083-4D9E-9754-9341C14D9B39}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_zstd", "_zstd.vcxproj", "{07029B86-F3E9-443E-86FB-78AA6D47FED1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
@@ -1750,6 +1753,38 @@ Global
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|Win32.Build.0 = Release|Win32
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|x64.ActiveCfg = Release|x64
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|x64.Build.0 = Release|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|ARM.ActiveCfg = Debug|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|ARM.Build.0 = Debug|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|ARM64.Build.0 = Debug|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|Win32.ActiveCfg = Debug|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|Win32.Build.0 = Debug|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|x64.ActiveCfg = Debug|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Debug|x64.Build.0 = Debug|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|ARM.Build.0 = PGInstrument|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|Win32.Build.0 = PGInstrument|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|x64.ActiveCfg = PGInstrument|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGInstrument|x64.Build.0 = PGInstrument|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|ARM.Build.0 = PGUpdate|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|Win32.Build.0 = PGUpdate|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|x64.ActiveCfg = PGUpdate|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.PGUpdate|x64.Build.0 = PGUpdate|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|ARM.ActiveCfg = Release|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|ARM.Build.0 = Release|ARM
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|ARM64.ActiveCfg = Release|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|ARM64.Build.0 = Release|ARM64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|Win32.ActiveCfg = Release|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|Win32.Build.0 = Release|Win32
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|x64.ActiveCfg = Release|x64
+ {07029B86-F3E9-443E-86FB-78AA6D47FED1}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/PCbuild/pyproject.props b/PCbuild/pyproject.props
index 4e414dc913b..cf35e705f35 100644
--- a/PCbuild/pyproject.props
+++ b/PCbuild/pyproject.props
@@ -10,10 +10,9 @@
<Py_IntDir Condition="'$(Py_IntDir)' == ''">$(MSBuildThisFileDirectory)obj\</Py_IntDir>
<IntDir>$(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\$(ProjectName)\</IntDir>
<IntDir>$(IntDir.Replace(`\\`, `\`))</IntDir>
- <!-- pyconfig.h is updated by pythoncore.vcxproj, so it's always in pythoncore's IntDir -->
- <GeneratedPyConfigDir>$(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\pythoncore\</GeneratedPyConfigDir>
<GeneratedFrozenModulesDir>$(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)_frozen\</GeneratedFrozenModulesDir>
<GeneratedZlibNgDir>$(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\zlib-ng\</GeneratedZlibNgDir>
+ <GeneratedJitStencilsDir>$(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)_$(Configuration)</GeneratedJitStencilsDir>
<TargetName Condition="'$(TargetName)' == ''">$(ProjectName)</TargetName>
<TargetName>$(TargetName)$(PyDebugExt)</TargetName>
<GenerateManifest>false</GenerateManifest>
@@ -49,11 +48,12 @@
<_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64'">_WIN64;</_PlatformPreprocessorDefinition>
<_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64' and $(PlatformToolset) != 'ClangCL'">_M_X64;$(_PlatformPreprocessorDefinition)</_PlatformPreprocessorDefinition>
<_Py3NamePreprocessorDefinition>PY3_DLLNAME=L"$(Py3DllName)$(PyDebugExt)";</_Py3NamePreprocessorDefinition>
+ <_FreeThreadedPreprocessorDefinition Condition="$(DisableGil) == 'true'">Py_GIL_DISABLED=1;</_FreeThreadedPreprocessorDefinition>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
- <AdditionalIncludeDirectories>$(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)Include\internal\mimalloc;$(GeneratedPyConfigDir);$(PySourcePath)PC;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions>WIN32;$(_Py3NamePreprocessorDefinition);$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PyStatsPreprocessorDefinition)$(_PydPreprocessorDefinition)%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>$(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)Include\internal\mimalloc;$(PySourcePath)PC;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;$(_Py3NamePreprocessorDefinition)$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PyStatsPreprocessorDefinition)$(_PydPreprocessorDefinition)$(_FreeThreadedPreprocessorDefinition)%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(SupportPGO)' and ($(Configuration) == 'PGInstrument' or $(Configuration) == 'PGUpdate')">_Py_USING_PGO=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
@@ -96,19 +96,16 @@
<TargetMachine Condition="'$(Platform)' == 'x64'">MachineX64</TargetMachine>
<TargetMachine Condition="'$(Platform)'=='ARM'">MachineARM</TargetMachine>
<TargetMachine Condition="'$(Platform)'=='ARM64'">MachineARM64</TargetMachine>
- <ProfileGuidedDatabase Condition="$(SupportPGO)">$(OutDir)$(TargetName).pgd</ProfileGuidedDatabase>
- <LinkTimeCodeGeneration Condition="$(Configuration) == 'Release'">UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
- <LinkTimeCodeGeneration Condition="$(SupportPGO) and $(Configuration) == 'PGInstrument'">PGInstrument</LinkTimeCodeGeneration>
- <LinkTimeCodeGeneration Condition="$(SupportPGO) and $(Configuration) == 'PGUpdate'">PGUpdate</LinkTimeCodeGeneration>
+ <LinkTimeCodeGeneration Condition="$(Configuration) != 'Debug'">UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
<AdditionalDependencies>advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalOptions Condition="$(Configuration) != 'Debug'">/OPT:REF,NOICF %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="$(MSVCHasBrokenARM64Clamping) == 'true' and $(Platform) == 'ARM64'">-d2:-pattern-opt-disable:-932189325 %(AdditionalOptions)</AdditionalOptions>
+ <AdditionalOptions Condition="$(SupportPGO) and $(Configuration) == 'PGInstrument' and $(PlatformToolset) != 'ClangCL'">/GENPROFILE %(AdditionalOptions)</AdditionalOptions>
+ <AdditionalOptions Condition="$(SupportPGO) and $(Configuration) == 'PGUpdate' and $(PlatformToolset) != 'ClangCL'">/USEPROFILE %(AdditionalOptions)</AdditionalOptions>
</Link>
<Lib>
<LinkTimeCodeGeneration>false</LinkTimeCodeGeneration>
- <LinkTimeCodeGeneration Condition="$(Configuration) == 'Release'">true</LinkTimeCodeGeneration>
- <LinkTimeCodeGeneration Condition="$(SupportPGO) and $(Configuration) == 'PGInstrument'">true</LinkTimeCodeGeneration>
- <LinkTimeCodeGeneration Condition="$(SupportPGO) and $(Configuration) == 'PGUpdate'">true</LinkTimeCodeGeneration>
+ <LinkTimeCodeGeneration Condition="$(Configuration) != 'Debug'">true</LinkTimeCodeGeneration>
</Lib>
<ResourceCompile>
<AdditionalIncludeDirectories>$(PySourcePath)PC;$(PySourcePath)Include;$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
diff --git a/PCbuild/python.props b/PCbuild/python.props
index 7cb9ad8e65b..ddc7696d276 100644
--- a/PCbuild/python.props
+++ b/PCbuild/python.props
@@ -87,6 +87,7 @@
<nasmDir Condition="$(nasmDir) == ''">$(ExternalsDir)\nasm-2.11.06\</nasmDir>
<zlibDir Condition="$(zlibDir) == ''">$(ExternalsDir)\zlib-1.3.1\</zlibDir>
<zlibNgDir Condition="$(zlibNgDir) == ''">$(ExternalsDir)\zlib-ng-2.2.4\</zlibNgDir>
+ <zstdDir Condition="$(zstdDir) == ''">$(ExternalsDir)\zstd-1.5.7\</zstdDir>
</PropertyGroup>
<PropertyGroup>
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index d2aafce058d..b911c938563 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -102,6 +102,7 @@
<AdditionalOptions>/Zm200 %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>$(PySourcePath)Modules\_hacl;$(PySourcePath)Modules\_hacl\include;$(PySourcePath)Python;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="$(IncludeExternals)">$(zlibNgDir);$(GeneratedZlibNgDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <AdditionalIncludeDirectories Condition="'$(UseJIT)' == 'true'">$(GeneratedJitStencilsDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_USRDLL;Py_BUILD_CORE;Py_BUILD_CORE_BUILTIN;Py_ENABLE_SHARED;MS_DLL_ID="$(SysWinVer)";ZLIB_COMPAT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(IncludeExternals)">_Py_HAVE_ZLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(UseJIT)' == 'true'">_Py_JIT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -409,7 +410,7 @@
<ClInclude Include="..\Parser\string_parser.h" />
<ClInclude Include="..\Parser\pegen.h" />
<ClInclude Include="..\PC\errmap.h" />
- <ClInclude Include="..\PC\pyconfig.h.in" />
+ <ClInclude Include="..\PC\pyconfig.h" />
<ClInclude Include="..\Python\condvar.h" />
<ClInclude Include="..\Python\stdlib_module_names.h" />
<ClInclude Include="..\Python\thread_nt.h" />
@@ -418,8 +419,12 @@
<ClCompile Include="..\Modules\_abc.c" />
<ClCompile Include="..\Modules\_bisectmodule.c" />
<ClCompile Include="..\Modules\blake2module.c">
- <PreprocessorDefinitions Condition="'$(Platform)' == 'x64'">HACL_CAN_COMPILE_SIMD128;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <PreprocessorDefinitions Condition="'$(Platform)' == 'x64'">HACL_CAN_COMPILE_SIMD256;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions Condition="'$(Platform)' == 'x64'">
+ _Py_HACL_CAN_COMPILE_VEC128;%(PreprocessorDefinitions)
+ </PreprocessorDefinitions>
+ <PreprocessorDefinitions Condition="'$(Platform)' == 'x64'">
+ _Py_HACL_CAN_COMPILE_VEC256;%(PreprocessorDefinitions)
+ </PreprocessorDefinitions>
</ClCompile>
<ClCompile Include="..\Modules\_codecsmodule.c" />
<ClCompile Include="..\Modules\_collectionsmodule.c" />
@@ -580,7 +585,7 @@
<ClCompile Include="..\Python\asdl.c" />
<ClCompile Include="..\Python\assemble.c" />
<ClCompile Include="..\Python\ast.c" />
- <ClCompile Include="..\Python\ast_opt.c" />
+ <ClCompile Include="..\Python\ast_preprocess.c" />
<ClCompile Include="..\Python\ast_unparse.c" />
<ClCompile Include="..\Python\bltinmodule.c" />
<ClCompile Include="..\Python\bootstrap_hash.c" />
@@ -688,34 +693,6 @@
</ImportGroup>
<Target Name="_TriggerRegen" BeforeTargets="PrepareForBuild" DependsOnTargets="Regen" />
- <Target Name="_UpdatePyconfig" BeforeTargets="PrepareForBuild">
- <MakeDir Directories="$(IntDir)" Condition="!Exists($(IntDir))" />
- <ItemGroup>
- <PyConfigH Remove="@(PyConfigH)" />
- <PyConfigH Include="@(ClInclude)" Condition="'%(Filename)%(Extension)' == 'pyconfig.h.in'" />
- </ItemGroup>
- <Error Text="Did not find pyconfig.h" Condition="@(ClInclude) == ''" />
- <PropertyGroup>
- <PyConfigH>@(PyConfigH->'%(FullPath)', ';')</PyConfigH>
- <PyConfigHText>$([System.IO.File]::ReadAllText($(PyConfigH)))</PyConfigHText>
- <OldPyConfigH Condition="Exists('$(IntDir)pyconfig.h')">$([System.IO.File]::ReadAllText('$(IntDir)pyconfig.h'))</OldPyConfigH>
- </PropertyGroup>
- <PropertyGroup Condition="$(DisableGil) == 'true'">
- <PyConfigHText>$(PyConfigHText.Replace('/* #define Py_GIL_DISABLED 1 */', '#define Py_GIL_DISABLED 1'))</PyConfigHText>
- </PropertyGroup>
- <Message Text="Updating pyconfig.h" Condition="$(PyConfigHText.TrimEnd()) != $(OldPyConfigH.TrimEnd())" />
- <WriteLinesToFile File="$(IntDir)pyconfig.h"
- Lines="$(PyConfigHText)"
- Overwrite="true"
- Condition="$(PyConfigHText.TrimEnd()) != $(OldPyConfigH.TrimEnd())" />
- </Target>
- <Target Name="_CopyPyconfig" Inputs="$(IntDir)pyconfig.h" Outputs="$(OutDir)pyconfig.h" AfterTargets="Build" DependsOnTargets="_UpdatePyconfig">
- <Copy SourceFiles="$(IntDir)pyconfig.h" DestinationFolder="$(OutDir)" />
- </Target>
- <Target Name="_CleanPyconfig" AfterTargets="Clean">
- <Delete Files="$(IntDir)pyconfig.h;$(OutDir)pyconfig.h" />
- </Target>
-
<Target Name="_GetBuildInfo" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<GIT Condition="$(GIT) == ''">git</GIT>
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index 873f7d65a26..0e6d42cc959 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -1325,7 +1325,7 @@
<ClCompile Include="..\Python\ast.c">
<Filter>Python</Filter>
</ClCompile>
- <ClCompile Include="..\Python\ast_opt.c">
+ <ClCompile Include="..\Python\ast_preprocess.c">
<Filter>Python</Filter>
</ClCompile>
<ClCompile Include="..\Python\ast_unparse.c">
diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt
index 3bf215d907c..3ae3255d933 100644
--- a/PCbuild/readme.txt
+++ b/PCbuild/readme.txt
@@ -173,24 +173,27 @@ library which are implemented in C; each one builds a DLL (renamed to
* _asyncio
* _ctypes
* _ctypes_test
- * _zoneinfo
* _decimal
* _elementtree
* _hashlib
* _multiprocessing
* _overlapped
+ * _queue
+ * _remote_debugging
* _socket
* _testbuffer
* _testcapi
- * _testlimitedcapi
- * _testinternalcapi
* _testclinic
* _testclinic_limited
* _testconsole
* _testimportmultiple
+ * _testinternalcapi
+ * _testlimitedcapi
* _testmultiphase
* _testsinglephase
- * _tkinter
+ * _uuid
+ * _wmi
+ * _zoneinfo
* pyexpat
* select
* unicodedata
@@ -202,18 +205,22 @@ interpreter, but they do implement several major features. See the
"Getting External Sources" section below for additional information
about getting the source for building these libraries. The sub-projects
are:
+
_bz2
Python wrapper for version 1.0.8 of the libbzip2 compression library
Homepage:
http://www.bzip.org/
+
_lzma
- Python wrapper for version 5.2.2 of the liblzma compression library
+ Python wrapper for version 5.2.2 of the liblzma compression library,
+ which is itself built by liblzma.vcxproj.
Homepage:
https://tukaani.org/xz/
+
_ssl
Python wrapper for version 3.0.15 of the OpenSSL secure sockets
- library, which is downloaded from our binaries repository at
- https://github.com/python/cpython-bin-deps.
+ library, which is itself downloaded from our binaries repository at
+ https://github.com/python/cpython-bin-deps and built by openssl.vcxproj.
Homepage:
https://www.openssl.org/
@@ -233,6 +240,7 @@ _sqlite3
Wraps SQLite 3.49.1, which is itself built by sqlite3.vcxproj
Homepage:
https://www.sqlite.org/
+
_tkinter
Wraps version 8.6.15 of the Tk windowing system, which is downloaded
from our binaries repository at
@@ -245,13 +253,20 @@ _tkinter
PCbuild\prepare_tcltk.bat. This will retrieve the version of the
sources matched to the current commit from the Tcl and Tk branches
in our source repository at
- https://github.com/python/cpython-source-deps.
+ https://github.com/python/cpython-source-deps and build them via the
+ tcl.vcxproj and tk.vcxproj sub-projects.
The two projects install their respective components in a
directory alongside the source directories called "tcltk" on
Win32 and "tcltk64" on x64. They also copy the Tcl and Tk DLLs
into the current output directory, which should ensure that Tkinter
is able to load Tcl/Tk without having to change your PATH.
+
+_zstd
+ Python wrapper for version 1.5.7 of the zstd compression library
+ Homepage:
+ https://facebook.github.io/zstd/
+
zlib-ng
Compiles zlib-ng as a static library, which is later included by
pythoncore.vcxproj. This was generated using CMake against zlib-ng
@@ -262,6 +277,10 @@ zlib-ng
Sources for zlib-ng are imported unmodified into our source
repository at https://github.com/python/cpython-source-deps.
+_zstd
+ Python wrapper for version 1.5.7 of the Zstandard compression library
+ Homepage:
+ https://facebook.github.io/zstd/
Getting External Sources
diff --git a/PCbuild/regen.targets b/PCbuild/regen.targets
index e7822a126c6..742597f5cb5 100644
--- a/PCbuild/regen.targets
+++ b/PCbuild/regen.targets
@@ -29,12 +29,12 @@
<_KeywordSources Include="$(PySourcePath)Grammar\python.gram;$(PySourcePath)Grammar\Tokens" />
<_KeywordOutputs Include="$(PySourcePath)Lib\keyword.py" />
<!-- Taken from _Target._compute_digest in Tools\jit\_targets.py: -->
- <_JITSources Include="$(PySourcePath)Python\executor_cases.c.h;$(GeneratedPyConfigDir)pyconfig.h;$(PySourcePath)Tools\jit\**"/>
+ <_JITSources Include="$(PySourcePath)Python\executor_cases.c.h;$(PySourcePath)PC\pyconfig.h;$(PySourcePath)Tools\jit\**"/>
<!-- Need to explicitly enumerate these, since globbing doesn't work for missing outputs: -->
- <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils.h"/>
- <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils-aarch64-pc-windows-msvc.h" Condition="$(Platform) == 'ARM64'"/>
- <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils-i686-pc-windows-msvc.h" Condition="$(Platform) == 'Win32'"/>
- <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils-x86_64-pc-windows-msvc.h" Condition="$(Platform) == 'x64'"/>
+ <_JITOutputs Include="$(GeneratedJitStencilsDir)jit_stencils.h"/>
+ <_JITOutputs Include="$(GeneratedJitStencilsDir)jit_stencils-aarch64-pc-windows-msvc.h" Condition="$(Platform) == 'ARM64'"/>
+ <_JITOutputs Include="$(GeneratedJitStencilsDir)jit_stencils-i686-pc-windows-msvc.h" Condition="$(Platform) == 'Win32'"/>
+ <_JITOutputs Include="$(GeneratedJitStencilsDir)jit_stencils-x86_64-pc-windows-msvc.h" Condition="$(Platform) == 'x64'"/>
<_CasesSources Include="$(PySourcePath)Python\bytecodes.c;$(PySourcePath)Python\optimizer_bytecodes.c;"/>
<_CasesOutputs Include="$(PySourcePath)Python\generated_cases.c.h;$(PySourcePath)Include\opcode_ids.h;$(PySourcePath)Include\internal\pycore_uop_ids.h;$(PySourcePath)Python\opcode_targets.h;$(PySourcePath)Include\internal\pycore_opcode_metadata.h;$(PySourcePath)Include\internal\pycore_uop_metadata.h;$(PySourcePath)Python\optimizer_cases.c.h;$(PySourcePath)Lib\_opcode_metadata.py"/>
<_SbomSources Include="$(PySourcePath)PCbuild\get_externals.bat" />
@@ -116,7 +116,7 @@
<Target Name="_RegenJIT"
Condition="'$(UseJIT)' == 'true'"
- DependsOnTargets="_UpdatePyconfig;FindPythonForBuild"
+ DependsOnTargets="FindPythonForBuild"
Inputs="@(_JITSources)"
Outputs="@(_JITOutputs)">
<PropertyGroup>
@@ -125,8 +125,7 @@
<JITArgs Condition="$(Platform) == 'x64'">x86_64-pc-windows-msvc</JITArgs>
<JITArgs Condition="$(Configuration) == 'Debug'">$(JITArgs) --debug</JITArgs>
</PropertyGroup>
- <Exec Command='$(PythonForBuild) "$(PySourcePath)Tools\jit\build.py" $(JITArgs)'
- WorkingDirectory="$(GeneratedPyConfigDir)"/>
+ <Exec Command='$(PythonForBuild) "$(PySourcePath)Tools\jit\build.py" $(JITArgs) --output-dir "$(GeneratedJitStencilsDir)" --pyconfig-dir "$(PySourcePath)PC"'/>
</Target>
<Target Name="_CleanJIT" AfterTargets="Clean">
<Delete Files="@(_JITOutputs)"/>
@@ -157,7 +156,8 @@
<_LicenseSources Include="$(PySourcePath)LICENSE;
$(PySourcePath)PC\crtlicense.txt;
$(bz2Dir)LICENSE;
- $(libffiDir)LICENSE;" />
+ $(libffiDir)LICENSE;
+ $(zstdDir)\LICENSE;" />
<_LicenseSources Include="$(opensslOutDir)LICENSE.txt" Condition="Exists('$(opensslOutDir)LICENSE.txt')" />
<_LicenseSources Include="$(opensslOutDir)LICENSE" Condition="!Exists('$(opensslOutDir)LICENSE.txt')" />
<_LicenseSources Include="$(tcltkDir)tcllicense.terms;
diff --git a/PCbuild/rt.bat b/PCbuild/rt.bat
index c436215780f..f1e06073934 100644
--- a/PCbuild/rt.bat
+++ b/PCbuild/rt.bat
@@ -42,7 +42,7 @@ if "%~1"=="-O" (set dashO=-O) & shift & goto CheckOpts
if "%~1"=="-q" (set qmode=yes) & shift & goto CheckOpts
if "%~1"=="-d" (set suffix=_d) & shift & goto CheckOpts
rem HACK: Need some way to infer the version number in this script
-if "%~1"=="--disable-gil" (set pyname=python3.14t) & shift & goto CheckOpts
+if "%~1"=="--disable-gil" (set pyname=python3.15t) & shift & goto CheckOpts
if "%~1"=="-win32" (set prefix=%pcbuild%win32) & shift & goto CheckOpts
if "%~1"=="-x64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts
if "%~1"=="-amd64" (set prefix=%pcbuild%amd64) & shift & goto CheckOpts
diff --git a/Parser/Python.asdl b/Parser/Python.asdl
index db61d8b1f66..96f3914b029 100644
--- a/Parser/Python.asdl
+++ b/Parser/Python.asdl
@@ -63,7 +63,7 @@ module Python
| UnaryOp(unaryop op, expr operand)
| Lambda(arguments args, expr body)
| IfExp(expr test, expr body, expr orelse)
- | Dict(expr* keys, expr* values)
+ | Dict(expr?* keys, expr* values)
| Set(expr* elts)
| ListComp(expr elt, comprehension* generators)
| SetComp(expr elt, comprehension* generators)
diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c
index 3bcc0870882..0d362bf7a91 100644
--- a/Parser/action_helpers.c
+++ b/Parser/action_helpers.c
@@ -965,7 +965,7 @@ _PyPegen_check_fstring_conversion(Parser *p, Token* conv_token, expr_ty conv)
if (conv_token->lineno != conv->lineno || conv_token->end_col_offset != conv->col_offset) {
return RAISE_SYNTAX_ERROR_KNOWN_RANGE(
conv_token, conv,
- "%c-string: conversion type must come right after the exclamanation mark",
+ "%c-string: conversion type must come right after the exclamation mark",
TOK_GET_STRING_PREFIX(p->tok)
);
}
diff --git a/Parser/asdl.py b/Parser/asdl.py
index e3e6c34d2a9..f762ed8110d 100644
--- a/Parser/asdl.py
+++ b/Parser/asdl.py
@@ -12,7 +12,7 @@
# type ::= product | sum
# product ::= fields ["attributes" fields]
# fields ::= "(" { field, "," } field ")"
-# field ::= TypeId ["?" | "*"] [Id]
+# field ::= TypeId { "?" | "*" } [Id]
# sum ::= constructor { "|" constructor } ["attributes" fields]
# constructor ::= ConstructorId [fields]
#
@@ -20,6 +20,7 @@
# http://asdl.sourceforge.net/
#-------------------------------------------------------------------------------
from collections import namedtuple
+import enum
import re
__all__ = [
@@ -64,30 +65,43 @@ class Constructor(AST):
def __repr__(self):
return 'Constructor({0.name}, {0.fields})'.format(self)
+class Quantifier(enum.Enum):
+ OPTIONAL = enum.auto()
+ SEQUENCE = enum.auto()
+
class Field(AST):
- def __init__(self, type, name=None, seq=False, opt=False):
+ def __init__(self, type, name=None, quantifiers=None):
self.type = type
self.name = name
- self.seq = seq
- self.opt = opt
+ self.seq = False
+ self.opt = False
+ self.quantifiers = quantifiers or []
+ if len(self.quantifiers) > 0:
+ self.seq = self.quantifiers[-1] is Quantifier.SEQUENCE
+ self.opt = self.quantifiers[-1] is Quantifier.OPTIONAL
def __str__(self):
- if self.seq:
- extra = "*"
- elif self.opt:
- extra = "?"
- else:
- extra = ""
+ extra = ""
+ for mod in self.quantifiers:
+ if mod is Quantifier.SEQUENCE:
+ extra += "*"
+ elif mod is Quantifier.OPTIONAL:
+ extra += "?"
return "{}{} {}".format(self.type, extra, self.name)
def __repr__(self):
- if self.seq:
- extra = ", seq=True"
- elif self.opt:
- extra = ", opt=True"
+ if self.quantifiers:
+ texts = []
+ for mod in self.quantifiers:
+ if mod is Quantifier.SEQUENCE:
+ texts.append("SEQUENCE")
+ elif mod is Quantifier.OPTIONAL:
+ texts.append("OPTIONAL")
+ extra = ", quantifiers=[{}]".format(", ".join(texts))
else:
extra = ""
+
if self.name is None:
return 'Field({0.type}{1})'.format(self, extra)
else:
@@ -314,10 +328,10 @@ class ASDLParser:
self._match(TokenKind.LParen)
while self.cur_token.kind == TokenKind.TypeId:
typename = self._advance()
- is_seq, is_opt = self._parse_optional_field_quantifier()
+ quantifiers = self._parse_optional_field_quantifier()
id = (self._advance() if self.cur_token.kind in self._id_kinds
else None)
- fields.append(Field(typename, id, seq=is_seq, opt=is_opt))
+ fields.append(Field(typename, id, quantifiers=quantifiers))
if self.cur_token.kind == TokenKind.RParen:
break
elif self.cur_token.kind == TokenKind.Comma:
@@ -339,14 +353,14 @@ class ASDLParser:
return None
def _parse_optional_field_quantifier(self):
- is_seq, is_opt = False, False
- if self.cur_token.kind == TokenKind.Asterisk:
- is_seq = True
- self._advance()
- elif self.cur_token.kind == TokenKind.Question:
- is_opt = True
+ quantifiers = []
+ while self.cur_token.kind in (TokenKind.Asterisk, TokenKind.Question):
+ if self.cur_token.kind == TokenKind.Asterisk:
+ quantifiers.append(Quantifier.SEQUENCE)
+ elif self.cur_token.kind == TokenKind.Question:
+ quantifiers.append(Quantifier.OPTIONAL)
self._advance()
- return is_seq, is_opt
+ return quantifiers
def _advance(self):
""" Return the value of the current token and read the next one into
diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py
index 09e014534fb..dba20226c32 100755
--- a/Parser/asdl_c.py
+++ b/Parser/asdl_c.py
@@ -1244,6 +1244,32 @@ ast_type_replace_check(PyObject *self,
Py_DECREF(unused);
}
}
+
+ // Discard fields from 'expecting' that default to None
+ PyObject *field_types = NULL;
+ if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self),
+ &_Py_ID(_field_types),
+ &field_types) < 0)
+ {
+ Py_DECREF(expecting);
+ return -1;
+ }
+ if (field_types != NULL) {
+ Py_ssize_t pos = 0;
+ PyObject *field_name, *field_type;
+ while (PyDict_Next(field_types, &pos, &field_name, &field_type)) {
+ if (_PyUnion_Check(field_type)) {
+ // optional field
+ if (PySet_Discard(expecting, field_name) < 0) {
+ Py_DECREF(expecting);
+ Py_DECREF(field_types);
+ return -1;
+ }
+ }
+ }
+ Py_DECREF(field_types);
+ }
+
// Now 'expecting' contains the fields or attributes
// that would not be filled inside ast_type_replace().
Py_ssize_t m = PySet_GET_SIZE(expecting);
@@ -1486,7 +1512,7 @@ ast_repr_list(PyObject *list, int depth)
for (Py_ssize_t i = 0; i < Py_MIN(length, 2); i++) {
if (i > 0) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
goto error;
}
}
@@ -1510,7 +1536,7 @@ ast_repr_list(PyObject *list, int depth)
}
if (i == 0 && length > 2) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ...", 5) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ...", 5) < 0) {
goto error;
}
}
@@ -1614,7 +1640,7 @@ ast_repr_max_depth(AST_object *self, int depth)
}
if (i > 0) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
Py_DECREF(name);
Py_DECREF(value_repr);
goto error;
diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c
index 4d10bccf0a5..0a078dd5941 100644
--- a/Parser/lexer/lexer.c
+++ b/Parser/lexer/lexer.c
@@ -1421,7 +1421,8 @@ f_string_middle:
return MAKE_TOKEN(
_PyTokenizer_syntaxerror(
tok,
- "f-string: newlines are not allowed in format specifiers for single quoted f-strings"
+ "%c-string: newlines are not allowed in format specifiers for single quoted %c-strings",
+ TOK_GET_STRING_PREFIX(tok), TOK_GET_STRING_PREFIX(tok)
)
);
}
diff --git a/Parser/parser.c b/Parser/parser.c
index 509fac7df6e..58fea894a79 100644
--- a/Parser/parser.c
+++ b/Parser/parser.c
@@ -14,66 +14,66 @@
# define MAXSTACK 4000
# endif
#else
-# define MAXSTACK 4000
+# define MAXSTACK 6000
#endif
static const int n_keyword_lists = 9;
static KeywordToken *reserved_keywords[] = {
(KeywordToken[]) {{NULL, -1}},
(KeywordToken[]) {{NULL, -1}},
(KeywordToken[]) {
- {"if", 682},
- {"as", 680},
- {"in", 695},
- {"or", 588},
- {"is", 596},
+ {"if", 687},
+ {"as", 685},
+ {"in", 700},
+ {"or", 589},
+ {"is", 597},
{NULL, -1},
},
(KeywordToken[]) {
- {"del", 625},
- {"def", 699},
- {"for", 694},
- {"try", 656},
- {"and", 589},
- {"not", 703},
+ {"del", 630},
+ {"def", 704},
+ {"for", 699},
+ {"try", 661},
+ {"and", 590},
+ {"not", 708},
{NULL, -1},
},
(KeywordToken[]) {
- {"from", 633},
- {"pass", 526},
- {"with", 647},
- {"elif", 687},
- {"else", 686},
- {"None", 623},
- {"True", 622},
+ {"from", 638},
+ {"pass", 527},
+ {"with", 652},
+ {"elif", 692},
+ {"else", 691},
+ {"None", 624},
+ {"True", 623},
{NULL, -1},
},
(KeywordToken[]) {
- {"raise", 525},
- {"yield", 587},
- {"break", 527},
- {"async", 698},
- {"class", 701},
- {"while", 689},
- {"False", 624},
- {"await", 597},
+ {"raise", 628},
+ {"yield", 588},
+ {"break", 528},
+ {"async", 703},
+ {"class", 706},
+ {"while", 694},
+ {"False", 625},
+ {"await", 598},
{NULL, -1},
},
(KeywordToken[]) {
{"return", 522},
- {"import", 634},
- {"assert", 532},
- {"global", 529},
- {"except", 677},
- {"lambda", 621},
+ {"import", 639},
+ {"assert", 533},
+ {"global", 530},
+ {"except", 682},
+ {"lambda", 622},
{NULL, -1},
},
(KeywordToken[]) {
- {"finally", 673},
+ {"finally", 678},
{NULL, -1},
},
(KeywordToken[]) {
- {"continue", 528},
- {"nonlocal", 530},
+ {"continue", 529},
+ {"nonlocal", 531},
{NULL, -1},
},
};
@@ -298,235 +298,235 @@ static char *soft_keywords[] = {
#define invalid_named_expression_type 1211
#define invalid_assignment_type 1212
#define invalid_ann_assign_target_type 1213
-#define invalid_del_stmt_type 1214
-#define invalid_block_type 1215
-#define invalid_comprehension_type 1216
-#define invalid_dict_comprehension_type 1217
-#define invalid_parameters_type 1218
-#define invalid_default_type 1219
-#define invalid_star_etc_type 1220
-#define invalid_kwds_type 1221
-#define invalid_parameters_helper_type 1222
-#define invalid_lambda_parameters_type 1223
-#define invalid_lambda_parameters_helper_type 1224
-#define invalid_lambda_star_etc_type 1225
-#define invalid_lambda_kwds_type 1226
-#define invalid_double_type_comments_type 1227
-#define invalid_with_item_type 1228
-#define invalid_for_if_clause_type 1229
-#define invalid_for_target_type 1230
-#define invalid_group_type 1231
-#define invalid_import_type 1232
-#define invalid_dotted_as_name_type 1233
-#define invalid_import_from_as_name_type 1234
-#define invalid_import_from_targets_type 1235
-#define invalid_with_stmt_type 1236
-#define invalid_with_stmt_indent_type 1237
-#define invalid_try_stmt_type 1238
-#define invalid_except_stmt_type 1239
-#define invalid_except_star_stmt_type 1240
-#define invalid_finally_stmt_type 1241
-#define invalid_except_stmt_indent_type 1242
-#define invalid_except_star_stmt_indent_type 1243
-#define invalid_match_stmt_type 1244
-#define invalid_case_block_type 1245
-#define invalid_as_pattern_type 1246
-#define invalid_class_pattern_type 1247
-#define invalid_class_argument_pattern_type 1248
-#define invalid_if_stmt_type 1249
-#define invalid_elif_stmt_type 1250
-#define invalid_else_stmt_type 1251
-#define invalid_while_stmt_type 1252
-#define invalid_for_stmt_type 1253
-#define invalid_def_raw_type 1254
-#define invalid_class_def_raw_type 1255
-#define invalid_double_starred_kvpairs_type 1256
-#define invalid_kvpair_type 1257
-#define invalid_starred_expression_unpacking_type 1258
-#define invalid_starred_expression_type 1259
-#define invalid_fstring_replacement_field_type 1260
-#define invalid_fstring_conversion_character_type 1261
-#define invalid_tstring_replacement_field_type 1262
-#define invalid_tstring_conversion_character_type 1263
-#define invalid_arithmetic_type 1264
-#define invalid_factor_type 1265
-#define invalid_type_params_type 1266
-#define _loop0_1_type 1267
-#define _loop1_2_type 1268
-#define _loop0_3_type 1269
-#define _gather_4_type 1270
-#define _tmp_5_type 1271
-#define _tmp_6_type 1272
-#define _tmp_7_type 1273
-#define _tmp_8_type 1274
-#define _tmp_9_type 1275
-#define _tmp_10_type 1276
-#define _tmp_11_type 1277
-#define _loop1_12_type 1278
-#define _tmp_13_type 1279
-#define _loop0_14_type 1280
-#define _gather_15_type 1281
-#define _tmp_16_type 1282
-#define _tmp_17_type 1283
-#define _loop0_18_type 1284
-#define _loop1_19_type 1285
-#define _loop0_20_type 1286
-#define _gather_21_type 1287
-#define _tmp_22_type 1288
-#define _loop0_23_type 1289
-#define _gather_24_type 1290
-#define _loop1_25_type 1291
-#define _tmp_26_type 1292
-#define _tmp_27_type 1293
-#define _loop0_28_type 1294
-#define _loop0_29_type 1295
-#define _loop1_30_type 1296
-#define _loop1_31_type 1297
-#define _loop0_32_type 1298
-#define _loop1_33_type 1299
-#define _loop0_34_type 1300
-#define _gather_35_type 1301
-#define _tmp_36_type 1302
-#define _loop1_37_type 1303
-#define _loop1_38_type 1304
-#define _loop1_39_type 1305
-#define _loop0_40_type 1306
-#define _gather_41_type 1307
-#define _tmp_42_type 1308
-#define _tmp_43_type 1309
-#define _tmp_44_type 1310
-#define _loop0_45_type 1311
-#define _gather_46_type 1312
-#define _loop0_47_type 1313
-#define _gather_48_type 1314
-#define _tmp_49_type 1315
-#define _loop0_50_type 1316
-#define _gather_51_type 1317
-#define _loop0_52_type 1318
-#define _gather_53_type 1319
-#define _loop0_54_type 1320
-#define _gather_55_type 1321
-#define _loop1_56_type 1322
-#define _loop1_57_type 1323
-#define _loop0_58_type 1324
-#define _gather_59_type 1325
-#define _loop1_60_type 1326
-#define _loop1_61_type 1327
-#define _loop1_62_type 1328
-#define _tmp_63_type 1329
-#define _loop0_64_type 1330
-#define _gather_65_type 1331
-#define _tmp_66_type 1332
-#define _tmp_67_type 1333
-#define _tmp_68_type 1334
-#define _tmp_69_type 1335
-#define _tmp_70_type 1336
-#define _loop0_71_type 1337
-#define _loop0_72_type 1338
-#define _loop1_73_type 1339
-#define _loop1_74_type 1340
-#define _loop0_75_type 1341
-#define _loop1_76_type 1342
-#define _loop0_77_type 1343
-#define _loop0_78_type 1344
-#define _loop0_79_type 1345
-#define _loop0_80_type 1346
-#define _loop1_81_type 1347
-#define _tmp_82_type 1348
-#define _loop0_83_type 1349
-#define _gather_84_type 1350
-#define _loop1_85_type 1351
-#define _loop0_86_type 1352
-#define _tmp_87_type 1353
-#define _loop0_88_type 1354
-#define _gather_89_type 1355
-#define _tmp_90_type 1356
-#define _loop0_91_type 1357
-#define _gather_92_type 1358
-#define _loop0_93_type 1359
-#define _gather_94_type 1360
-#define _loop0_95_type 1361
-#define _loop0_96_type 1362
-#define _gather_97_type 1363
-#define _loop1_98_type 1364
-#define _tmp_99_type 1365
-#define _loop0_100_type 1366
-#define _gather_101_type 1367
-#define _loop0_102_type 1368
-#define _gather_103_type 1369
-#define _tmp_104_type 1370
-#define _tmp_105_type 1371
-#define _loop0_106_type 1372
-#define _gather_107_type 1373
-#define _tmp_108_type 1374
-#define _tmp_109_type 1375
-#define _tmp_110_type 1376
-#define _tmp_111_type 1377
-#define _tmp_112_type 1378
-#define _loop1_113_type 1379
-#define _tmp_114_type 1380
-#define _tmp_115_type 1381
-#define _tmp_116_type 1382
-#define _tmp_117_type 1383
-#define _tmp_118_type 1384
-#define _loop0_119_type 1385
-#define _loop0_120_type 1386
-#define _tmp_121_type 1387
-#define _tmp_122_type 1388
-#define _tmp_123_type 1389
-#define _tmp_124_type 1390
-#define _tmp_125_type 1391
-#define _tmp_126_type 1392
-#define _tmp_127_type 1393
-#define _tmp_128_type 1394
-#define _tmp_129_type 1395
-#define _loop0_130_type 1396
-#define _gather_131_type 1397
-#define _tmp_132_type 1398
-#define _tmp_133_type 1399
-#define _tmp_134_type 1400
-#define _tmp_135_type 1401
-#define _loop0_136_type 1402
-#define _gather_137_type 1403
-#define _tmp_138_type 1404
-#define _loop0_139_type 1405
-#define _gather_140_type 1406
-#define _loop0_141_type 1407
-#define _gather_142_type 1408
-#define _tmp_143_type 1409
-#define _loop0_144_type 1410
-#define _tmp_145_type 1411
-#define _tmp_146_type 1412
-#define _tmp_147_type 1413
-#define _tmp_148_type 1414
-#define _tmp_149_type 1415
-#define _tmp_150_type 1416
-#define _tmp_151_type 1417
-#define _tmp_152_type 1418
-#define _tmp_153_type 1419
-#define _tmp_154_type 1420
-#define _tmp_155_type 1421
-#define _tmp_156_type 1422
-#define _tmp_157_type 1423
-#define _tmp_158_type 1424
-#define _tmp_159_type 1425
-#define _tmp_160_type 1426
-#define _tmp_161_type 1427
-#define _tmp_162_type 1428
-#define _tmp_163_type 1429
-#define _tmp_164_type 1430
-#define _tmp_165_type 1431
-#define _tmp_166_type 1432
-#define _tmp_167_type 1433
-#define _tmp_168_type 1434
-#define _tmp_169_type 1435
-#define _tmp_170_type 1436
-#define _loop0_171_type 1437
-#define _tmp_172_type 1438
-#define _tmp_173_type 1439
-#define _tmp_174_type 1440
-#define _tmp_175_type 1441
-#define _tmp_176_type 1442
+#define invalid_raise_stmt_type 1214
+#define invalid_del_stmt_type 1215
+#define invalid_block_type 1216
+#define invalid_comprehension_type 1217
+#define invalid_dict_comprehension_type 1218
+#define invalid_parameters_type 1219
+#define invalid_default_type 1220
+#define invalid_star_etc_type 1221
+#define invalid_kwds_type 1222
+#define invalid_parameters_helper_type 1223
+#define invalid_lambda_parameters_type 1224
+#define invalid_lambda_parameters_helper_type 1225
+#define invalid_lambda_star_etc_type 1226
+#define invalid_lambda_kwds_type 1227
+#define invalid_double_type_comments_type 1228
+#define invalid_with_item_type 1229
+#define invalid_for_if_clause_type 1230
+#define invalid_for_target_type 1231
+#define invalid_group_type 1232
+#define invalid_import_type 1233
+#define invalid_dotted_as_name_type 1234
+#define invalid_import_from_as_name_type 1235
+#define invalid_import_from_targets_type 1236
+#define invalid_with_stmt_type 1237
+#define invalid_with_stmt_indent_type 1238
+#define invalid_try_stmt_type 1239
+#define invalid_except_stmt_type 1240
+#define invalid_except_star_stmt_type 1241
+#define invalid_finally_stmt_type 1242
+#define invalid_except_stmt_indent_type 1243
+#define invalid_except_star_stmt_indent_type 1244
+#define invalid_match_stmt_type 1245
+#define invalid_case_block_type 1246
+#define invalid_as_pattern_type 1247
+#define invalid_class_pattern_type 1248
+#define invalid_class_argument_pattern_type 1249
+#define invalid_if_stmt_type 1250
+#define invalid_elif_stmt_type 1251
+#define invalid_else_stmt_type 1252
+#define invalid_while_stmt_type 1253
+#define invalid_for_stmt_type 1254
+#define invalid_def_raw_type 1255
+#define invalid_class_def_raw_type 1256
+#define invalid_double_starred_kvpairs_type 1257
+#define invalid_kvpair_type 1258
+#define invalid_starred_expression_unpacking_type 1259
+#define invalid_starred_expression_type 1260
+#define invalid_fstring_replacement_field_type 1261
+#define invalid_fstring_conversion_character_type 1262
+#define invalid_tstring_replacement_field_type 1263
+#define invalid_tstring_conversion_character_type 1264
+#define invalid_arithmetic_type 1265
+#define invalid_factor_type 1266
+#define invalid_type_params_type 1267
+#define _loop0_1_type 1268
+#define _loop1_2_type 1269
+#define _loop0_3_type 1270
+#define _gather_4_type 1271
+#define _tmp_5_type 1272
+#define _tmp_6_type 1273
+#define _tmp_7_type 1274
+#define _tmp_8_type 1275
+#define _tmp_9_type 1276
+#define _tmp_10_type 1277
+#define _tmp_11_type 1278
+#define _loop1_12_type 1279
+#define _loop0_13_type 1280
+#define _gather_14_type 1281
+#define _tmp_15_type 1282
+#define _tmp_16_type 1283
+#define _loop0_17_type 1284
+#define _loop1_18_type 1285
+#define _loop0_19_type 1286
+#define _gather_20_type 1287
+#define _tmp_21_type 1288
+#define _loop0_22_type 1289
+#define _gather_23_type 1290
+#define _loop1_24_type 1291
+#define _tmp_25_type 1292
+#define _tmp_26_type 1293
+#define _loop0_27_type 1294
+#define _loop0_28_type 1295
+#define _loop1_29_type 1296
+#define _loop1_30_type 1297
+#define _loop0_31_type 1298
+#define _loop1_32_type 1299
+#define _loop0_33_type 1300
+#define _gather_34_type 1301
+#define _tmp_35_type 1302
+#define _loop1_36_type 1303
+#define _loop1_37_type 1304
+#define _loop1_38_type 1305
+#define _loop0_39_type 1306
+#define _gather_40_type 1307
+#define _tmp_41_type 1308
+#define _tmp_42_type 1309
+#define _tmp_43_type 1310
+#define _loop0_44_type 1311
+#define _gather_45_type 1312
+#define _loop0_46_type 1313
+#define _gather_47_type 1314
+#define _tmp_48_type 1315
+#define _loop0_49_type 1316
+#define _gather_50_type 1317
+#define _loop0_51_type 1318
+#define _gather_52_type 1319
+#define _loop0_53_type 1320
+#define _gather_54_type 1321
+#define _loop1_55_type 1322
+#define _loop1_56_type 1323
+#define _loop0_57_type 1324
+#define _gather_58_type 1325
+#define _loop1_59_type 1326
+#define _loop1_60_type 1327
+#define _loop1_61_type 1328
+#define _tmp_62_type 1329
+#define _loop0_63_type 1330
+#define _gather_64_type 1331
+#define _tmp_65_type 1332
+#define _tmp_66_type 1333
+#define _tmp_67_type 1334
+#define _tmp_68_type 1335
+#define _tmp_69_type 1336
+#define _loop0_70_type 1337
+#define _loop0_71_type 1338
+#define _loop1_72_type 1339
+#define _loop1_73_type 1340
+#define _loop0_74_type 1341
+#define _loop1_75_type 1342
+#define _loop0_76_type 1343
+#define _loop0_77_type 1344
+#define _loop0_78_type 1345
+#define _loop0_79_type 1346
+#define _loop1_80_type 1347
+#define _tmp_81_type 1348
+#define _loop0_82_type 1349
+#define _gather_83_type 1350
+#define _loop1_84_type 1351
+#define _loop0_85_type 1352
+#define _tmp_86_type 1353
+#define _loop0_87_type 1354
+#define _gather_88_type 1355
+#define _tmp_89_type 1356
+#define _loop0_90_type 1357
+#define _gather_91_type 1358
+#define _loop0_92_type 1359
+#define _gather_93_type 1360
+#define _loop0_94_type 1361
+#define _loop0_95_type 1362
+#define _gather_96_type 1363
+#define _loop1_97_type 1364
+#define _tmp_98_type 1365
+#define _loop0_99_type 1366
+#define _gather_100_type 1367
+#define _loop0_101_type 1368
+#define _gather_102_type 1369
+#define _tmp_103_type 1370
+#define _tmp_104_type 1371
+#define _loop0_105_type 1372
+#define _gather_106_type 1373
+#define _tmp_107_type 1374
+#define _tmp_108_type 1375
+#define _tmp_109_type 1376
+#define _tmp_110_type 1377
+#define _tmp_111_type 1378
+#define _loop1_112_type 1379
+#define _tmp_113_type 1380
+#define _tmp_114_type 1381
+#define _tmp_115_type 1382
+#define _tmp_116_type 1383
+#define _tmp_117_type 1384
+#define _loop0_118_type 1385
+#define _loop0_119_type 1386
+#define _tmp_120_type 1387
+#define _tmp_121_type 1388
+#define _tmp_122_type 1389
+#define _tmp_123_type 1390
+#define _tmp_124_type 1391
+#define _tmp_125_type 1392
+#define _tmp_126_type 1393
+#define _tmp_127_type 1394
+#define _tmp_128_type 1395
+#define _loop0_129_type 1396
+#define _gather_130_type 1397
+#define _tmp_131_type 1398
+#define _tmp_132_type 1399
+#define _tmp_133_type 1400
+#define _tmp_134_type 1401
+#define _loop0_135_type 1402
+#define _gather_136_type 1403
+#define _tmp_137_type 1404
+#define _loop0_138_type 1405
+#define _gather_139_type 1406
+#define _loop0_140_type 1407
+#define _gather_141_type 1408
+#define _tmp_142_type 1409
+#define _loop0_143_type 1410
+#define _tmp_144_type 1411
+#define _tmp_145_type 1412
+#define _tmp_146_type 1413
+#define _tmp_147_type 1414
+#define _tmp_148_type 1415
+#define _tmp_149_type 1416
+#define _tmp_150_type 1417
+#define _tmp_151_type 1418
+#define _tmp_152_type 1419
+#define _tmp_153_type 1420
+#define _tmp_154_type 1421
+#define _tmp_155_type 1422
+#define _tmp_156_type 1423
+#define _tmp_157_type 1424
+#define _tmp_158_type 1425
+#define _tmp_159_type 1426
+#define _tmp_160_type 1427
+#define _tmp_161_type 1428
+#define _tmp_162_type 1429
+#define _tmp_163_type 1430
+#define _tmp_164_type 1431
+#define _tmp_165_type 1432
+#define _tmp_166_type 1433
+#define _tmp_167_type 1434
+#define _tmp_168_type 1435
+#define _tmp_169_type 1436
+#define _loop0_170_type 1437
+#define _tmp_171_type 1438
+#define _tmp_172_type 1439
+#define _tmp_173_type 1440
+#define _tmp_174_type 1441
+#define _tmp_175_type 1442
static mod_ty file_rule(Parser *p);
static mod_ty interactive_rule(Parser *p);
@@ -742,6 +742,7 @@ static void *invalid_expression_rule(Parser *p);
static void *invalid_named_expression_rule(Parser *p);
static void *invalid_assignment_rule(Parser *p);
static expr_ty invalid_ann_assign_target_rule(Parser *p);
+static void *invalid_raise_stmt_rule(Parser *p);
static void *invalid_del_stmt_rule(Parser *p);
static void *invalid_block_rule(Parser *p);
static void *invalid_comprehension_rule(Parser *p);
@@ -807,114 +808,114 @@ static void *_tmp_9_rule(Parser *p);
static void *_tmp_10_rule(Parser *p);
static void *_tmp_11_rule(Parser *p);
static asdl_seq *_loop1_12_rule(Parser *p);
-static void *_tmp_13_rule(Parser *p);
-static asdl_seq *_loop0_14_rule(Parser *p);
-static asdl_seq *_gather_15_rule(Parser *p);
+static asdl_seq *_loop0_13_rule(Parser *p);
+static asdl_seq *_gather_14_rule(Parser *p);
+static void *_tmp_15_rule(Parser *p);
static void *_tmp_16_rule(Parser *p);
-static void *_tmp_17_rule(Parser *p);
-static asdl_seq *_loop0_18_rule(Parser *p);
-static asdl_seq *_loop1_19_rule(Parser *p);
-static asdl_seq *_loop0_20_rule(Parser *p);
-static asdl_seq *_gather_21_rule(Parser *p);
-static void *_tmp_22_rule(Parser *p);
-static asdl_seq *_loop0_23_rule(Parser *p);
-static asdl_seq *_gather_24_rule(Parser *p);
-static asdl_seq *_loop1_25_rule(Parser *p);
+static asdl_seq *_loop0_17_rule(Parser *p);
+static asdl_seq *_loop1_18_rule(Parser *p);
+static asdl_seq *_loop0_19_rule(Parser *p);
+static asdl_seq *_gather_20_rule(Parser *p);
+static void *_tmp_21_rule(Parser *p);
+static asdl_seq *_loop0_22_rule(Parser *p);
+static asdl_seq *_gather_23_rule(Parser *p);
+static asdl_seq *_loop1_24_rule(Parser *p);
+static void *_tmp_25_rule(Parser *p);
static void *_tmp_26_rule(Parser *p);
-static void *_tmp_27_rule(Parser *p);
+static asdl_seq *_loop0_27_rule(Parser *p);
static asdl_seq *_loop0_28_rule(Parser *p);
-static asdl_seq *_loop0_29_rule(Parser *p);
+static asdl_seq *_loop1_29_rule(Parser *p);
static asdl_seq *_loop1_30_rule(Parser *p);
-static asdl_seq *_loop1_31_rule(Parser *p);
-static asdl_seq *_loop0_32_rule(Parser *p);
-static asdl_seq *_loop1_33_rule(Parser *p);
-static asdl_seq *_loop0_34_rule(Parser *p);
-static asdl_seq *_gather_35_rule(Parser *p);
-static void *_tmp_36_rule(Parser *p);
+static asdl_seq *_loop0_31_rule(Parser *p);
+static asdl_seq *_loop1_32_rule(Parser *p);
+static asdl_seq *_loop0_33_rule(Parser *p);
+static asdl_seq *_gather_34_rule(Parser *p);
+static void *_tmp_35_rule(Parser *p);
+static asdl_seq *_loop1_36_rule(Parser *p);
static asdl_seq *_loop1_37_rule(Parser *p);
static asdl_seq *_loop1_38_rule(Parser *p);
-static asdl_seq *_loop1_39_rule(Parser *p);
-static asdl_seq *_loop0_40_rule(Parser *p);
-static asdl_seq *_gather_41_rule(Parser *p);
+static asdl_seq *_loop0_39_rule(Parser *p);
+static asdl_seq *_gather_40_rule(Parser *p);
+static void *_tmp_41_rule(Parser *p);
static void *_tmp_42_rule(Parser *p);
static void *_tmp_43_rule(Parser *p);
-static void *_tmp_44_rule(Parser *p);
-static asdl_seq *_loop0_45_rule(Parser *p);
-static asdl_seq *_gather_46_rule(Parser *p);
-static asdl_seq *_loop0_47_rule(Parser *p);
-static asdl_seq *_gather_48_rule(Parser *p);
-static void *_tmp_49_rule(Parser *p);
-static asdl_seq *_loop0_50_rule(Parser *p);
-static asdl_seq *_gather_51_rule(Parser *p);
-static asdl_seq *_loop0_52_rule(Parser *p);
-static asdl_seq *_gather_53_rule(Parser *p);
-static asdl_seq *_loop0_54_rule(Parser *p);
-static asdl_seq *_gather_55_rule(Parser *p);
+static asdl_seq *_loop0_44_rule(Parser *p);
+static asdl_seq *_gather_45_rule(Parser *p);
+static asdl_seq *_loop0_46_rule(Parser *p);
+static asdl_seq *_gather_47_rule(Parser *p);
+static void *_tmp_48_rule(Parser *p);
+static asdl_seq *_loop0_49_rule(Parser *p);
+static asdl_seq *_gather_50_rule(Parser *p);
+static asdl_seq *_loop0_51_rule(Parser *p);
+static asdl_seq *_gather_52_rule(Parser *p);
+static asdl_seq *_loop0_53_rule(Parser *p);
+static asdl_seq *_gather_54_rule(Parser *p);
+static asdl_seq *_loop1_55_rule(Parser *p);
static asdl_seq *_loop1_56_rule(Parser *p);
-static asdl_seq *_loop1_57_rule(Parser *p);
-static asdl_seq *_loop0_58_rule(Parser *p);
-static asdl_seq *_gather_59_rule(Parser *p);
+static asdl_seq *_loop0_57_rule(Parser *p);
+static asdl_seq *_gather_58_rule(Parser *p);
+static asdl_seq *_loop1_59_rule(Parser *p);
static asdl_seq *_loop1_60_rule(Parser *p);
static asdl_seq *_loop1_61_rule(Parser *p);
-static asdl_seq *_loop1_62_rule(Parser *p);
-static void *_tmp_63_rule(Parser *p);
-static asdl_seq *_loop0_64_rule(Parser *p);
-static asdl_seq *_gather_65_rule(Parser *p);
+static void *_tmp_62_rule(Parser *p);
+static asdl_seq *_loop0_63_rule(Parser *p);
+static asdl_seq *_gather_64_rule(Parser *p);
+static void *_tmp_65_rule(Parser *p);
static void *_tmp_66_rule(Parser *p);
static void *_tmp_67_rule(Parser *p);
static void *_tmp_68_rule(Parser *p);
static void *_tmp_69_rule(Parser *p);
-static void *_tmp_70_rule(Parser *p);
+static asdl_seq *_loop0_70_rule(Parser *p);
static asdl_seq *_loop0_71_rule(Parser *p);
-static asdl_seq *_loop0_72_rule(Parser *p);
+static asdl_seq *_loop1_72_rule(Parser *p);
static asdl_seq *_loop1_73_rule(Parser *p);
-static asdl_seq *_loop1_74_rule(Parser *p);
-static asdl_seq *_loop0_75_rule(Parser *p);
-static asdl_seq *_loop1_76_rule(Parser *p);
+static asdl_seq *_loop0_74_rule(Parser *p);
+static asdl_seq *_loop1_75_rule(Parser *p);
+static asdl_seq *_loop0_76_rule(Parser *p);
static asdl_seq *_loop0_77_rule(Parser *p);
static asdl_seq *_loop0_78_rule(Parser *p);
static asdl_seq *_loop0_79_rule(Parser *p);
-static asdl_seq *_loop0_80_rule(Parser *p);
-static asdl_seq *_loop1_81_rule(Parser *p);
-static void *_tmp_82_rule(Parser *p);
-static asdl_seq *_loop0_83_rule(Parser *p);
-static asdl_seq *_gather_84_rule(Parser *p);
-static asdl_seq *_loop1_85_rule(Parser *p);
-static asdl_seq *_loop0_86_rule(Parser *p);
-static void *_tmp_87_rule(Parser *p);
-static asdl_seq *_loop0_88_rule(Parser *p);
-static asdl_seq *_gather_89_rule(Parser *p);
-static void *_tmp_90_rule(Parser *p);
-static asdl_seq *_loop0_91_rule(Parser *p);
-static asdl_seq *_gather_92_rule(Parser *p);
-static asdl_seq *_loop0_93_rule(Parser *p);
-static asdl_seq *_gather_94_rule(Parser *p);
+static asdl_seq *_loop1_80_rule(Parser *p);
+static void *_tmp_81_rule(Parser *p);
+static asdl_seq *_loop0_82_rule(Parser *p);
+static asdl_seq *_gather_83_rule(Parser *p);
+static asdl_seq *_loop1_84_rule(Parser *p);
+static asdl_seq *_loop0_85_rule(Parser *p);
+static void *_tmp_86_rule(Parser *p);
+static asdl_seq *_loop0_87_rule(Parser *p);
+static asdl_seq *_gather_88_rule(Parser *p);
+static void *_tmp_89_rule(Parser *p);
+static asdl_seq *_loop0_90_rule(Parser *p);
+static asdl_seq *_gather_91_rule(Parser *p);
+static asdl_seq *_loop0_92_rule(Parser *p);
+static asdl_seq *_gather_93_rule(Parser *p);
+static asdl_seq *_loop0_94_rule(Parser *p);
static asdl_seq *_loop0_95_rule(Parser *p);
-static asdl_seq *_loop0_96_rule(Parser *p);
-static asdl_seq *_gather_97_rule(Parser *p);
-static asdl_seq *_loop1_98_rule(Parser *p);
-static void *_tmp_99_rule(Parser *p);
-static asdl_seq *_loop0_100_rule(Parser *p);
-static asdl_seq *_gather_101_rule(Parser *p);
-static asdl_seq *_loop0_102_rule(Parser *p);
-static asdl_seq *_gather_103_rule(Parser *p);
+static asdl_seq *_gather_96_rule(Parser *p);
+static asdl_seq *_loop1_97_rule(Parser *p);
+static void *_tmp_98_rule(Parser *p);
+static asdl_seq *_loop0_99_rule(Parser *p);
+static asdl_seq *_gather_100_rule(Parser *p);
+static asdl_seq *_loop0_101_rule(Parser *p);
+static asdl_seq *_gather_102_rule(Parser *p);
+static void *_tmp_103_rule(Parser *p);
static void *_tmp_104_rule(Parser *p);
-static void *_tmp_105_rule(Parser *p);
-static asdl_seq *_loop0_106_rule(Parser *p);
-static asdl_seq *_gather_107_rule(Parser *p);
+static asdl_seq *_loop0_105_rule(Parser *p);
+static asdl_seq *_gather_106_rule(Parser *p);
+static void *_tmp_107_rule(Parser *p);
static void *_tmp_108_rule(Parser *p);
static void *_tmp_109_rule(Parser *p);
static void *_tmp_110_rule(Parser *p);
static void *_tmp_111_rule(Parser *p);
-static void *_tmp_112_rule(Parser *p);
-static asdl_seq *_loop1_113_rule(Parser *p);
+static asdl_seq *_loop1_112_rule(Parser *p);
+static void *_tmp_113_rule(Parser *p);
static void *_tmp_114_rule(Parser *p);
static void *_tmp_115_rule(Parser *p);
static void *_tmp_116_rule(Parser *p);
static void *_tmp_117_rule(Parser *p);
-static void *_tmp_118_rule(Parser *p);
+static asdl_seq *_loop0_118_rule(Parser *p);
static asdl_seq *_loop0_119_rule(Parser *p);
-static asdl_seq *_loop0_120_rule(Parser *p);
+static void *_tmp_120_rule(Parser *p);
static void *_tmp_121_rule(Parser *p);
static void *_tmp_122_rule(Parser *p);
static void *_tmp_123_rule(Parser *p);
@@ -923,22 +924,22 @@ static void *_tmp_125_rule(Parser *p);
static void *_tmp_126_rule(Parser *p);
static void *_tmp_127_rule(Parser *p);
static void *_tmp_128_rule(Parser *p);
-static void *_tmp_129_rule(Parser *p);
-static asdl_seq *_loop0_130_rule(Parser *p);
-static asdl_seq *_gather_131_rule(Parser *p);
+static asdl_seq *_loop0_129_rule(Parser *p);
+static asdl_seq *_gather_130_rule(Parser *p);
+static void *_tmp_131_rule(Parser *p);
static void *_tmp_132_rule(Parser *p);
static void *_tmp_133_rule(Parser *p);
static void *_tmp_134_rule(Parser *p);
-static void *_tmp_135_rule(Parser *p);
-static asdl_seq *_loop0_136_rule(Parser *p);
-static asdl_seq *_gather_137_rule(Parser *p);
-static void *_tmp_138_rule(Parser *p);
-static asdl_seq *_loop0_139_rule(Parser *p);
-static asdl_seq *_gather_140_rule(Parser *p);
-static asdl_seq *_loop0_141_rule(Parser *p);
-static asdl_seq *_gather_142_rule(Parser *p);
-static void *_tmp_143_rule(Parser *p);
-static asdl_seq *_loop0_144_rule(Parser *p);
+static asdl_seq *_loop0_135_rule(Parser *p);
+static asdl_seq *_gather_136_rule(Parser *p);
+static void *_tmp_137_rule(Parser *p);
+static asdl_seq *_loop0_138_rule(Parser *p);
+static asdl_seq *_gather_139_rule(Parser *p);
+static asdl_seq *_loop0_140_rule(Parser *p);
+static asdl_seq *_gather_141_rule(Parser *p);
+static void *_tmp_142_rule(Parser *p);
+static asdl_seq *_loop0_143_rule(Parser *p);
+static void *_tmp_144_rule(Parser *p);
static void *_tmp_145_rule(Parser *p);
static void *_tmp_146_rule(Parser *p);
static void *_tmp_147_rule(Parser *p);
@@ -964,13 +965,12 @@ static void *_tmp_166_rule(Parser *p);
static void *_tmp_167_rule(Parser *p);
static void *_tmp_168_rule(Parser *p);
static void *_tmp_169_rule(Parser *p);
-static void *_tmp_170_rule(Parser *p);
-static asdl_seq *_loop0_171_rule(Parser *p);
+static asdl_seq *_loop0_170_rule(Parser *p);
+static void *_tmp_171_rule(Parser *p);
static void *_tmp_172_rule(Parser *p);
static void *_tmp_173_rule(Parser *p);
static void *_tmp_174_rule(Parser *p);
static void *_tmp_175_rule(Parser *p);
-static void *_tmp_176_rule(Parser *p);
// file: statements? $
@@ -1677,7 +1677,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&('import' | 'from') import_stmt"));
stmt_ty import_stmt_var;
if (
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_5_rule, p)
+ _PyPegen_lookahead(1, _tmp_5_rule, p)
&&
(import_stmt_var = import_stmt_rule(p)) // import_stmt
)
@@ -1698,7 +1698,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'raise' raise_stmt"));
stmt_ty raise_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 525) // token='raise'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 628) // token='raise'
&&
(raise_stmt_var = raise_stmt_rule(p)) // raise_stmt
)
@@ -1719,7 +1719,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'pass' pass_stmt"));
stmt_ty pass_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 526) // token='pass'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 527) // token='pass'
&&
(pass_stmt_var = pass_stmt_rule(p)) // pass_stmt
)
@@ -1740,7 +1740,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'del' del_stmt"));
stmt_ty del_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 625) // token='del'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 630) // token='del'
&&
(del_stmt_var = del_stmt_rule(p)) // del_stmt
)
@@ -1761,7 +1761,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'yield' yield_stmt"));
stmt_ty yield_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 587) // token='yield'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 588) // token='yield'
&&
(yield_stmt_var = yield_stmt_rule(p)) // yield_stmt
)
@@ -1782,7 +1782,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'assert' assert_stmt"));
stmt_ty assert_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 532) // token='assert'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 533) // token='assert'
&&
(assert_stmt_var = assert_stmt_rule(p)) // assert_stmt
)
@@ -1803,7 +1803,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'break' break_stmt"));
stmt_ty break_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 527) // token='break'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 528) // token='break'
&&
(break_stmt_var = break_stmt_rule(p)) // break_stmt
)
@@ -1824,7 +1824,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'continue' continue_stmt"));
stmt_ty continue_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 528) // token='continue'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 529) // token='continue'
&&
(continue_stmt_var = continue_stmt_rule(p)) // continue_stmt
)
@@ -1845,7 +1845,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'global' global_stmt"));
stmt_ty global_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 529) // token='global'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 530) // token='global'
&&
(global_stmt_var = global_stmt_rule(p)) // global_stmt
)
@@ -1866,7 +1866,7 @@ simple_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> simple_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'nonlocal' nonlocal_stmt"));
stmt_ty nonlocal_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 530) // token='nonlocal'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 531) // token='nonlocal'
&&
(nonlocal_stmt_var = nonlocal_stmt_rule(p)) // nonlocal_stmt
)
@@ -1915,7 +1915,7 @@ compound_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> compound_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&('def' | '@' | 'async') function_def"));
stmt_ty function_def_var;
if (
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_6_rule, p)
+ _PyPegen_lookahead(1, _tmp_6_rule, p)
&&
(function_def_var = function_def_rule(p)) // function_def
)
@@ -1936,7 +1936,7 @@ compound_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> compound_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'if' if_stmt"));
stmt_ty if_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 682) // token='if'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 687) // token='if'
&&
(if_stmt_var = if_stmt_rule(p)) // if_stmt
)
@@ -1957,7 +1957,7 @@ compound_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> compound_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&('class' | '@') class_def"));
stmt_ty class_def_var;
if (
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_7_rule, p)
+ _PyPegen_lookahead(1, _tmp_7_rule, p)
&&
(class_def_var = class_def_rule(p)) // class_def
)
@@ -1978,7 +1978,7 @@ compound_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> compound_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&('with' | 'async') with_stmt"));
stmt_ty with_stmt_var;
if (
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_8_rule, p)
+ _PyPegen_lookahead(1, _tmp_8_rule, p)
&&
(with_stmt_var = with_stmt_rule(p)) // with_stmt
)
@@ -1999,7 +1999,7 @@ compound_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> compound_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&('for' | 'async') for_stmt"));
stmt_ty for_stmt_var;
if (
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_9_rule, p)
+ _PyPegen_lookahead(1, _tmp_9_rule, p)
&&
(for_stmt_var = for_stmt_rule(p)) // for_stmt
)
@@ -2020,7 +2020,7 @@ compound_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> compound_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'try' try_stmt"));
stmt_ty try_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 656) // token='try'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 661) // token='try'
&&
(try_stmt_var = try_stmt_rule(p)) // try_stmt
)
@@ -2041,7 +2041,7 @@ compound_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> compound_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'while' while_stmt"));
stmt_ty while_stmt_var;
if (
- _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 689) // token='while'
+ _PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 694) // token='while'
&&
(while_stmt_var = while_stmt_rule(p)) // while_stmt
)
@@ -2767,7 +2767,11 @@ return_stmt_rule(Parser *p)
return _res;
}
-// raise_stmt: 'raise' expression ['from' expression] | 'raise'
+// raise_stmt:
+// | 'raise' expression 'from' expression
+// | invalid_raise_stmt
+// | 'raise' expression
+// | 'raise'
static stmt_ty
raise_stmt_rule(Parser *p)
{
@@ -2789,24 +2793,27 @@ raise_stmt_rule(Parser *p)
UNUSED(_start_lineno); // Only used by EXTRA macro
int _start_col_offset = p->tokens[_mark]->col_offset;
UNUSED(_start_col_offset); // Only used by EXTRA macro
- { // 'raise' expression ['from' expression]
+ { // 'raise' expression 'from' expression
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> raise_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'raise' expression ['from' expression]"));
+ D(fprintf(stderr, "%*c> raise_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'raise' expression 'from' expression"));
Token * _keyword;
+ Token * _keyword_1;
expr_ty a;
- void *b;
+ expr_ty b;
if (
- (_keyword = _PyPegen_expect_token(p, 525)) // token='raise'
+ (_keyword = _PyPegen_expect_token(p, 628)) // token='raise'
&&
(a = expression_rule(p)) // expression
&&
- (b = _tmp_13_rule(p), !p->error_indicator) // ['from' expression]
+ (_keyword_1 = _PyPegen_expect_token(p, 638)) // token='from'
+ &&
+ (b = expression_rule(p)) // expression
)
{
- D(fprintf(stderr, "%*c+ raise_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'raise' expression ['from' expression]"));
+ D(fprintf(stderr, "%*c+ raise_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'raise' expression 'from' expression"));
Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
if (_token == NULL) {
p->level--;
@@ -2826,7 +2833,62 @@ raise_stmt_rule(Parser *p)
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s raise_stmt[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'raise' expression ['from' expression]"));
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'raise' expression 'from' expression"));
+ }
+ if (p->call_invalid_rules) { // invalid_raise_stmt
+ if (p->error_indicator) {
+ p->level--;
+ return NULL;
+ }
+ D(fprintf(stderr, "%*c> raise_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "invalid_raise_stmt"));
+ void *invalid_raise_stmt_var;
+ if (
+ (invalid_raise_stmt_var = invalid_raise_stmt_rule(p)) // invalid_raise_stmt
+ )
+ {
+ D(fprintf(stderr, "%*c+ raise_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "invalid_raise_stmt"));
+ _res = invalid_raise_stmt_var;
+ goto done;
+ }
+ p->mark = _mark;
+ D(fprintf(stderr, "%*c%s raise_stmt[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "invalid_raise_stmt"));
+ }
+ { // 'raise' expression
+ if (p->error_indicator) {
+ p->level--;
+ return NULL;
+ }
+ D(fprintf(stderr, "%*c> raise_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'raise' expression"));
+ Token * _keyword;
+ expr_ty a;
+ if (
+ (_keyword = _PyPegen_expect_token(p, 628)) // token='raise'
+ &&
+ (a = expression_rule(p)) // expression
+ )
+ {
+ D(fprintf(stderr, "%*c+ raise_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'raise' expression"));
+ Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
+ if (_token == NULL) {
+ p->level--;
+ return NULL;
+ }
+ int _end_lineno = _token->end_lineno;
+ UNUSED(_end_lineno); // Only used by EXTRA macro
+ int _end_col_offset = _token->end_col_offset;
+ UNUSED(_end_col_offset); // Only used by EXTRA macro
+ _res = _PyAST_Raise ( a , NULL , EXTRA );
+ if (_res == NULL && PyErr_Occurred()) {
+ p->error_indicator = 1;
+ p->level--;
+ return NULL;
+ }
+ goto done;
+ }
+ p->mark = _mark;
+ D(fprintf(stderr, "%*c%s raise_stmt[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'raise' expression"));
}
{ // 'raise'
if (p->error_indicator) {
@@ -2836,7 +2898,7 @@ raise_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> raise_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'raise'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 525)) // token='raise'
+ (_keyword = _PyPegen_expect_token(p, 628)) // token='raise'
)
{
D(fprintf(stderr, "%*c+ raise_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'raise'"));
@@ -2897,7 +2959,7 @@ pass_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> pass_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'pass'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 526)) // token='pass'
+ (_keyword = _PyPegen_expect_token(p, 527)) // token='pass'
)
{
D(fprintf(stderr, "%*c+ pass_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'pass'"));
@@ -2958,7 +3020,7 @@ break_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> break_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'break'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 527)) // token='break'
+ (_keyword = _PyPegen_expect_token(p, 528)) // token='break'
)
{
D(fprintf(stderr, "%*c+ break_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'break'"));
@@ -3019,7 +3081,7 @@ continue_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c> continue_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'continue'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 528)) // token='continue'
+ (_keyword = _PyPegen_expect_token(p, 529)) // token='continue'
)
{
D(fprintf(stderr, "%*c+ continue_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'continue'"));
@@ -3081,9 +3143,9 @@ global_stmt_rule(Parser *p)
Token * _keyword;
asdl_expr_seq* a;
if (
- (_keyword = _PyPegen_expect_token(p, 529)) // token='global'
+ (_keyword = _PyPegen_expect_token(p, 530)) // token='global'
&&
- (a = (asdl_expr_seq*)_gather_15_rule(p)) // ','.NAME+
+ (a = (asdl_expr_seq*)_gather_14_rule(p)) // ','.NAME+
)
{
D(fprintf(stderr, "%*c+ global_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'global' ','.NAME+"));
@@ -3145,9 +3207,9 @@ nonlocal_stmt_rule(Parser *p)
Token * _keyword;
asdl_expr_seq* a;
if (
- (_keyword = _PyPegen_expect_token(p, 530)) // token='nonlocal'
+ (_keyword = _PyPegen_expect_token(p, 531)) // token='nonlocal'
&&
- (a = (asdl_expr_seq*)_gather_15_rule(p)) // ','.NAME+
+ (a = (asdl_expr_seq*)_gather_14_rule(p)) // ','.NAME+
)
{
D(fprintf(stderr, "%*c+ nonlocal_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'nonlocal' ','.NAME+"));
@@ -3209,11 +3271,11 @@ del_stmt_rule(Parser *p)
Token * _keyword;
asdl_expr_seq* a;
if (
- (_keyword = _PyPegen_expect_token(p, 625)) // token='del'
+ (_keyword = _PyPegen_expect_token(p, 630)) // token='del'
&&
(a = del_targets_rule(p)) // del_targets
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_16_rule, p)
+ _PyPegen_lookahead(1, _tmp_15_rule, p)
)
{
D(fprintf(stderr, "%*c+ del_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'del' del_targets &(';' | NEWLINE)"));
@@ -3356,11 +3418,11 @@ assert_stmt_rule(Parser *p)
expr_ty a;
void *b;
if (
- (_keyword = _PyPegen_expect_token(p, 532)) // token='assert'
+ (_keyword = _PyPegen_expect_token(p, 533)) // token='assert'
&&
(a = expression_rule(p)) // expression
&&
- (b = _tmp_17_rule(p), !p->error_indicator) // [',' expression]
+ (b = _tmp_16_rule(p), !p->error_indicator) // [',' expression]
)
{
D(fprintf(stderr, "%*c+ assert_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'assert' expression [',' expression]"));
@@ -3498,7 +3560,7 @@ import_name_rule(Parser *p)
Token * _keyword;
asdl_alias_seq* a;
if (
- (_keyword = _PyPegen_expect_token(p, 634)) // token='import'
+ (_keyword = _PyPegen_expect_token(p, 639)) // token='import'
&&
(a = dotted_as_names_rule(p)) // dotted_as_names
)
@@ -3567,13 +3629,13 @@ import_from_rule(Parser *p)
expr_ty b;
asdl_alias_seq* c;
if (
- (_keyword = _PyPegen_expect_token(p, 633)) // token='from'
+ (_keyword = _PyPegen_expect_token(p, 638)) // token='from'
&&
- (a = _loop0_18_rule(p)) // (('.' | '...'))*
+ (a = _loop0_17_rule(p)) // (('.' | '...'))*
&&
(b = dotted_name_rule(p)) // dotted_name
&&
- (_keyword_1 = _PyPegen_expect_token(p, 634)) // token='import'
+ (_keyword_1 = _PyPegen_expect_token(p, 639)) // token='import'
&&
(c = import_from_targets_rule(p)) // import_from_targets
)
@@ -3611,11 +3673,11 @@ import_from_rule(Parser *p)
asdl_seq * a;
asdl_alias_seq* b;
if (
- (_keyword = _PyPegen_expect_token(p, 633)) // token='from'
+ (_keyword = _PyPegen_expect_token(p, 638)) // token='from'
&&
- (a = _loop1_19_rule(p)) // (('.' | '...'))+
+ (a = _loop1_18_rule(p)) // (('.' | '...'))+
&&
- (_keyword_1 = _PyPegen_expect_token(p, 634)) // token='import'
+ (_keyword_1 = _PyPegen_expect_token(p, 639)) // token='import'
&&
(b = import_from_targets_rule(p)) // import_from_targets
)
@@ -3808,7 +3870,7 @@ import_from_as_names_rule(Parser *p)
D(fprintf(stderr, "%*c> import_from_as_names[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.import_from_as_name+"));
asdl_alias_seq* a;
if (
- (a = (asdl_alias_seq*)_gather_21_rule(p)) // ','.import_from_as_name+
+ (a = (asdl_alias_seq*)_gather_20_rule(p)) // ','.import_from_as_name+
)
{
D(fprintf(stderr, "%*c+ import_from_as_names[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.import_from_as_name+"));
@@ -3882,7 +3944,7 @@ import_from_as_name_rule(Parser *p)
if (
(a = _PyPegen_name_token(p)) // NAME
&&
- (b = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (b = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
)
{
D(fprintf(stderr, "%*c+ import_from_as_name[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME ['as' NAME]"));
@@ -3934,7 +3996,7 @@ dotted_as_names_rule(Parser *p)
D(fprintf(stderr, "%*c> dotted_as_names[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.dotted_as_name+"));
asdl_alias_seq* a;
if (
- (a = (asdl_alias_seq*)_gather_24_rule(p)) // ','.dotted_as_name+
+ (a = (asdl_alias_seq*)_gather_23_rule(p)) // ','.dotted_as_name+
)
{
D(fprintf(stderr, "%*c+ dotted_as_names[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.dotted_as_name+"));
@@ -4008,7 +4070,7 @@ dotted_as_name_rule(Parser *p)
if (
(a = dotted_name_rule(p)) // dotted_name
&&
- (b = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (b = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
)
{
D(fprintf(stderr, "%*c+ dotted_as_name[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dotted_name ['as' NAME]"));
@@ -4259,7 +4321,7 @@ decorators_rule(Parser *p)
D(fprintf(stderr, "%*c> decorators[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(('@' named_expression NEWLINE))+"));
asdl_expr_seq* a;
if (
- (a = (asdl_expr_seq*)_loop1_25_rule(p)) // (('@' named_expression NEWLINE))+
+ (a = (asdl_expr_seq*)_loop1_24_rule(p)) // (('@' named_expression NEWLINE))+
)
{
D(fprintf(stderr, "%*c+ decorators[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(('@' named_expression NEWLINE))+"));
@@ -4402,13 +4464,13 @@ class_def_raw_rule(Parser *p)
asdl_stmt_seq* c;
void *t;
if (
- (_keyword = _PyPegen_expect_token(p, 701)) // token='class'
+ (_keyword = _PyPegen_expect_token(p, 706)) // token='class'
&&
(a = _PyPegen_name_token(p)) // NAME
&&
(t = type_params_rule(p), !p->error_indicator) // type_params?
&&
- (b = _tmp_26_rule(p), !p->error_indicator) // ['(' arguments? ')']
+ (b = _tmp_25_rule(p), !p->error_indicator) // ['(' arguments? ')']
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -4569,7 +4631,7 @@ function_def_raw_rule(Parser *p)
void *t;
void *tc;
if (
- (_keyword = _PyPegen_expect_token(p, 699)) // token='def'
+ (_keyword = _PyPegen_expect_token(p, 704)) // token='def'
&&
(n = _PyPegen_name_token(p)) // NAME
&&
@@ -4581,7 +4643,7 @@ function_def_raw_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
&&
- (a = _tmp_27_rule(p), !p->error_indicator) // ['->' expression]
+ (a = _tmp_26_rule(p), !p->error_indicator) // ['->' expression]
&&
(_literal_2 = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -4630,9 +4692,9 @@ function_def_raw_rule(Parser *p)
void *t;
void *tc;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 699)) // token='def'
+ (_keyword_1 = _PyPegen_expect_token(p, 704)) // token='def'
&&
(n = _PyPegen_name_token(p)) // NAME
&&
@@ -4644,7 +4706,7 @@ function_def_raw_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
&&
- (a = _tmp_27_rule(p), !p->error_indicator) // ['->' expression]
+ (a = _tmp_26_rule(p), !p->error_indicator) // ['->' expression]
&&
(_literal_2 = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -4769,9 +4831,9 @@ parameters_rule(Parser *p)
if (
(a = slash_no_default_rule(p)) // slash_no_default
&&
- (b = (asdl_arg_seq*)_loop0_28_rule(p)) // param_no_default*
+ (b = (asdl_arg_seq*)_loop0_27_rule(p)) // param_no_default*
&&
- (c = _loop0_29_rule(p)) // param_with_default*
+ (c = _loop0_28_rule(p)) // param_with_default*
&&
(d = star_etc_rule(p), !p->error_indicator) // star_etc?
)
@@ -4801,7 +4863,7 @@ parameters_rule(Parser *p)
if (
(a = slash_with_default_rule(p)) // slash_with_default
&&
- (b = _loop0_29_rule(p)) // param_with_default*
+ (b = _loop0_28_rule(p)) // param_with_default*
&&
(c = star_etc_rule(p), !p->error_indicator) // star_etc?
)
@@ -4829,9 +4891,9 @@ parameters_rule(Parser *p)
asdl_seq * b;
void *c;
if (
- (a = (asdl_arg_seq*)_loop1_30_rule(p)) // param_no_default+
+ (a = (asdl_arg_seq*)_loop1_29_rule(p)) // param_no_default+
&&
- (b = _loop0_29_rule(p)) // param_with_default*
+ (b = _loop0_28_rule(p)) // param_with_default*
&&
(c = star_etc_rule(p), !p->error_indicator) // star_etc?
)
@@ -4858,7 +4920,7 @@ parameters_rule(Parser *p)
asdl_seq * a;
void *b;
if (
- (a = _loop1_31_rule(p)) // param_with_default+
+ (a = _loop1_30_rule(p)) // param_with_default+
&&
(b = star_etc_rule(p), !p->error_indicator) // star_etc?
)
@@ -4929,7 +4991,7 @@ slash_no_default_rule(Parser *p)
Token * _literal_1;
asdl_arg_seq* a;
if (
- (a = (asdl_arg_seq*)_loop1_30_rule(p)) // param_no_default+
+ (a = (asdl_arg_seq*)_loop1_29_rule(p)) // param_no_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -4958,7 +5020,7 @@ slash_no_default_rule(Parser *p)
Token * _literal;
asdl_arg_seq* a;
if (
- (a = (asdl_arg_seq*)_loop1_30_rule(p)) // param_no_default+
+ (a = (asdl_arg_seq*)_loop1_29_rule(p)) // param_no_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -5010,9 +5072,9 @@ slash_with_default_rule(Parser *p)
asdl_seq * a;
asdl_seq * b;
if (
- (a = _loop0_28_rule(p)) // param_no_default*
+ (a = _loop0_27_rule(p)) // param_no_default*
&&
- (b = _loop1_31_rule(p)) // param_with_default+
+ (b = _loop1_30_rule(p)) // param_with_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -5042,9 +5104,9 @@ slash_with_default_rule(Parser *p)
asdl_seq * a;
asdl_seq * b;
if (
- (a = _loop0_28_rule(p)) // param_no_default*
+ (a = _loop0_27_rule(p)) // param_no_default*
&&
- (b = _loop1_31_rule(p)) // param_with_default+
+ (b = _loop1_30_rule(p)) // param_with_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -5122,7 +5184,7 @@ star_etc_rule(Parser *p)
&&
(a = param_no_default_rule(p)) // param_no_default
&&
- (b = _loop0_32_rule(p)) // param_maybe_default*
+ (b = _loop0_31_rule(p)) // param_maybe_default*
&&
(c = kwds_rule(p), !p->error_indicator) // kwds?
)
@@ -5155,7 +5217,7 @@ star_etc_rule(Parser *p)
&&
(a = param_no_default_star_annotation_rule(p)) // param_no_default_star_annotation
&&
- (b = _loop0_32_rule(p)) // param_maybe_default*
+ (b = _loop0_31_rule(p)) // param_maybe_default*
&&
(c = kwds_rule(p), !p->error_indicator) // kwds?
)
@@ -5188,7 +5250,7 @@ star_etc_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 12)) // token=','
&&
- (b = _loop1_33_rule(p)) // param_maybe_default+
+ (b = _loop1_32_rule(p)) // param_maybe_default+
&&
(c = kwds_rule(p), !p->error_indicator) // kwds?
)
@@ -5970,7 +6032,7 @@ if_stmt_rule(Parser *p)
asdl_stmt_seq* b;
stmt_ty c;
if (
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(a = named_expression_rule(p)) // named_expression
&&
@@ -6015,7 +6077,7 @@ if_stmt_rule(Parser *p)
asdl_stmt_seq* b;
void *c;
if (
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(a = named_expression_rule(p)) // named_expression
&&
@@ -6110,7 +6172,7 @@ elif_stmt_rule(Parser *p)
asdl_stmt_seq* b;
stmt_ty c;
if (
- (_keyword = _PyPegen_expect_token(p, 687)) // token='elif'
+ (_keyword = _PyPegen_expect_token(p, 692)) // token='elif'
&&
(a = named_expression_rule(p)) // named_expression
&&
@@ -6155,7 +6217,7 @@ elif_stmt_rule(Parser *p)
asdl_stmt_seq* b;
void *c;
if (
- (_keyword = _PyPegen_expect_token(p, 687)) // token='elif'
+ (_keyword = _PyPegen_expect_token(p, 692)) // token='elif'
&&
(a = named_expression_rule(p)) // named_expression
&&
@@ -6236,7 +6298,7 @@ else_block_rule(Parser *p)
Token * _literal;
asdl_stmt_seq* b;
if (
- (_keyword = _PyPegen_expect_token(p, 686)) // token='else'
+ (_keyword = _PyPegen_expect_token(p, 691)) // token='else'
&&
(_literal = _PyPegen_expect_forced_token(p, 11, ":")) // forced_token=':'
&&
@@ -6315,7 +6377,7 @@ while_stmt_rule(Parser *p)
asdl_stmt_seq* b;
void *c;
if (
- (_keyword = _PyPegen_expect_token(p, 689)) // token='while'
+ (_keyword = _PyPegen_expect_token(p, 694)) // token='while'
&&
(a = named_expression_rule(p)) // named_expression
&&
@@ -6415,11 +6477,11 @@ for_stmt_rule(Parser *p)
expr_ty t;
void *tc;
if (
- (_keyword = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword = _PyPegen_expect_token(p, 699)) // token='for'
&&
(t = star_targets_rule(p)) // star_targets
&&
- (_keyword_1 = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword_1 = _PyPegen_expect_token(p, 700)) // token='in'
&&
(_cut_var = 1)
&&
@@ -6477,13 +6539,13 @@ for_stmt_rule(Parser *p)
expr_ty t;
void *tc;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword_1 = _PyPegen_expect_token(p, 699)) // token='for'
&&
(t = star_targets_rule(p)) // star_targets
&&
- (_keyword_2 = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword_2 = _PyPegen_expect_token(p, 700)) // token='in'
&&
(_cut_var = 1)
&&
@@ -6612,11 +6674,11 @@ with_stmt_rule(Parser *p)
asdl_stmt_seq* b;
void *tc;
if (
- (_keyword = _PyPegen_expect_token(p, 647)) // token='with'
+ (_keyword = _PyPegen_expect_token(p, 652)) // token='with'
&&
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
- (a = (asdl_withitem_seq*)_gather_35_rule(p)) // ','.with_item+
+ (a = (asdl_withitem_seq*)_gather_34_rule(p)) // ','.with_item+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
&&
@@ -6663,9 +6725,9 @@ with_stmt_rule(Parser *p)
asdl_stmt_seq* b;
void *tc;
if (
- (_keyword = _PyPegen_expect_token(p, 647)) // token='with'
+ (_keyword = _PyPegen_expect_token(p, 652)) // token='with'
&&
- (a = (asdl_withitem_seq*)_gather_35_rule(p)) // ','.with_item+
+ (a = (asdl_withitem_seq*)_gather_34_rule(p)) // ','.with_item+
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -6712,13 +6774,13 @@ with_stmt_rule(Parser *p)
asdl_withitem_seq* a;
asdl_stmt_seq* b;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 647)) // token='with'
+ (_keyword_1 = _PyPegen_expect_token(p, 652)) // token='with'
&&
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
- (a = (asdl_withitem_seq*)_gather_35_rule(p)) // ','.with_item+
+ (a = (asdl_withitem_seq*)_gather_34_rule(p)) // ','.with_item+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
&&
@@ -6764,11 +6826,11 @@ with_stmt_rule(Parser *p)
asdl_stmt_seq* b;
void *tc;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 647)) // token='with'
+ (_keyword_1 = _PyPegen_expect_token(p, 652)) // token='with'
&&
- (a = (asdl_withitem_seq*)_gather_35_rule(p)) // ','.with_item+
+ (a = (asdl_withitem_seq*)_gather_34_rule(p)) // ','.with_item+
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -6852,11 +6914,11 @@ with_item_rule(Parser *p)
if (
(e = expression_rule(p)) // expression
&&
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
(t = star_target_rule(p)) // star_target
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_36_rule, p)
+ _PyPegen_lookahead(1, _tmp_35_rule, p)
)
{
D(fprintf(stderr, "%*c+ with_item[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression 'as' star_target &(',' | ')' | ':')"));
@@ -6977,7 +7039,7 @@ try_stmt_rule(Parser *p)
asdl_stmt_seq* b;
asdl_stmt_seq* f;
if (
- (_keyword = _PyPegen_expect_token(p, 656)) // token='try'
+ (_keyword = _PyPegen_expect_token(p, 661)) // token='try'
&&
(_literal = _PyPegen_expect_forced_token(p, 11, ":")) // forced_token=':'
&&
@@ -7021,13 +7083,13 @@ try_stmt_rule(Parser *p)
asdl_excepthandler_seq* ex;
void *f;
if (
- (_keyword = _PyPegen_expect_token(p, 656)) // token='try'
+ (_keyword = _PyPegen_expect_token(p, 661)) // token='try'
&&
(_literal = _PyPegen_expect_forced_token(p, 11, ":")) // forced_token=':'
&&
(b = block_rule(p)) // block
&&
- (ex = (asdl_excepthandler_seq*)_loop1_37_rule(p)) // except_block+
+ (ex = (asdl_excepthandler_seq*)_loop1_36_rule(p)) // except_block+
&&
(el = else_block_rule(p), !p->error_indicator) // else_block?
&&
@@ -7069,13 +7131,13 @@ try_stmt_rule(Parser *p)
asdl_excepthandler_seq* ex;
void *f;
if (
- (_keyword = _PyPegen_expect_token(p, 656)) // token='try'
+ (_keyword = _PyPegen_expect_token(p, 661)) // token='try'
&&
(_literal = _PyPegen_expect_forced_token(p, 11, ":")) // forced_token=':'
&&
(b = block_rule(p)) // block
&&
- (ex = (asdl_excepthandler_seq*)_loop1_38_rule(p)) // except_star_block+
+ (ex = (asdl_excepthandler_seq*)_loop1_37_rule(p)) // except_star_block+
&&
(el = else_block_rule(p), !p->error_indicator) // else_block?
&&
@@ -7168,7 +7230,7 @@ except_block_rule(Parser *p)
asdl_stmt_seq* b;
expr_ty e;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(e = expression_rule(p)) // expression
&&
@@ -7212,11 +7274,11 @@ except_block_rule(Parser *p)
expr_ty e;
expr_ty t;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(e = expression_rule(p)) // expression
&&
- (_keyword_1 = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword_1 = _PyPegen_expect_token(p, 685)) // token='as'
&&
(t = _PyPegen_name_token(p)) // NAME
&&
@@ -7258,7 +7320,7 @@ except_block_rule(Parser *p)
asdl_stmt_seq* b;
expr_ty e;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(e = expressions_rule(p)) // expressions
&&
@@ -7299,7 +7361,7 @@ except_block_rule(Parser *p)
Token * _literal;
asdl_stmt_seq* b;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -7411,7 +7473,7 @@ except_star_block_rule(Parser *p)
asdl_stmt_seq* b;
expr_ty e;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
@@ -7458,13 +7520,13 @@ except_star_block_rule(Parser *p)
expr_ty e;
expr_ty t;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
(e = expression_rule(p)) // expression
&&
- (_keyword_1 = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword_1 = _PyPegen_expect_token(p, 685)) // token='as'
&&
(t = _PyPegen_name_token(p)) // NAME
&&
@@ -7507,7 +7569,7 @@ except_star_block_rule(Parser *p)
asdl_stmt_seq* b;
expr_ty e;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
@@ -7607,7 +7669,7 @@ finally_block_rule(Parser *p)
Token * _literal;
asdl_stmt_seq* a;
if (
- (_keyword = _PyPegen_expect_token(p, 673)) // token='finally'
+ (_keyword = _PyPegen_expect_token(p, 678)) // token='finally'
&&
(_literal = _PyPegen_expect_forced_token(p, 11, ":")) // forced_token=':'
&&
@@ -7681,7 +7743,7 @@ match_stmt_rule(Parser *p)
&&
(indent_var = _PyPegen_expect_token(p, INDENT)) // token='INDENT'
&&
- (cases = (asdl_match_case_seq*)_loop1_39_rule(p)) // case_block+
+ (cases = (asdl_match_case_seq*)_loop1_38_rule(p)) // case_block+
&&
(dedent_var = _PyPegen_expect_token(p, DEDENT)) // token='DEDENT'
)
@@ -7915,7 +7977,7 @@ guard_rule(Parser *p)
Token * _keyword;
expr_ty guard;
if (
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(guard = named_expression_rule(p)) // named_expression
)
@@ -8110,7 +8172,7 @@ as_pattern_rule(Parser *p)
if (
(pattern = or_pattern_rule(p)) // or_pattern
&&
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
(target = pattern_capture_target_rule(p)) // pattern_capture_target
)
@@ -8192,7 +8254,7 @@ or_pattern_rule(Parser *p)
D(fprintf(stderr, "%*c> or_pattern[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'|'.closed_pattern+"));
asdl_pattern_seq* patterns;
if (
- (patterns = (asdl_pattern_seq*)_gather_41_rule(p)) // '|'.closed_pattern+
+ (patterns = (asdl_pattern_seq*)_gather_40_rule(p)) // '|'.closed_pattern+
)
{
D(fprintf(stderr, "%*c+ or_pattern[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'|'.closed_pattern+"));
@@ -8445,7 +8507,7 @@ literal_pattern_rule(Parser *p)
if (
(value = signed_number_rule(p)) // signed_number
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_42_rule, p)
+ _PyPegen_lookahead(0, _tmp_41_rule, p)
)
{
D(fprintf(stderr, "%*c+ literal_pattern[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "signed_number !('+' | '-')"));
@@ -8544,7 +8606,7 @@ literal_pattern_rule(Parser *p)
D(fprintf(stderr, "%*c> literal_pattern[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 623)) // token='None'
+ (_keyword = _PyPegen_expect_token(p, 624)) // token='None'
)
{
D(fprintf(stderr, "%*c+ literal_pattern[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'"));
@@ -8577,7 +8639,7 @@ literal_pattern_rule(Parser *p)
D(fprintf(stderr, "%*c> literal_pattern[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 622)) // token='True'
+ (_keyword = _PyPegen_expect_token(p, 623)) // token='True'
)
{
D(fprintf(stderr, "%*c+ literal_pattern[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'"));
@@ -8610,7 +8672,7 @@ literal_pattern_rule(Parser *p)
D(fprintf(stderr, "%*c> literal_pattern[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 624)) // token='False'
+ (_keyword = _PyPegen_expect_token(p, 625)) // token='False'
)
{
D(fprintf(stderr, "%*c+ literal_pattern[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'"));
@@ -8679,7 +8741,7 @@ literal_expr_rule(Parser *p)
if (
(signed_number_var = signed_number_rule(p)) // signed_number
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_42_rule, p)
+ _PyPegen_lookahead(0, _tmp_41_rule, p)
)
{
D(fprintf(stderr, "%*c+ literal_expr[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "signed_number !('+' | '-')"));
@@ -8717,7 +8779,7 @@ literal_expr_rule(Parser *p)
D(fprintf(stderr, "%*c> literal_expr[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&(STRING | FSTRING_START | TSTRING_START) strings"));
expr_ty strings_var;
if (
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_43_rule, p)
+ _PyPegen_lookahead(1, _tmp_42_rule, p)
&&
(strings_var = strings_rule(p)) // strings
)
@@ -8738,7 +8800,7 @@ literal_expr_rule(Parser *p)
D(fprintf(stderr, "%*c> literal_expr[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 623)) // token='None'
+ (_keyword = _PyPegen_expect_token(p, 624)) // token='None'
)
{
D(fprintf(stderr, "%*c+ literal_expr[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'"));
@@ -8771,7 +8833,7 @@ literal_expr_rule(Parser *p)
D(fprintf(stderr, "%*c> literal_expr[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 622)) // token='True'
+ (_keyword = _PyPegen_expect_token(p, 623)) // token='True'
)
{
D(fprintf(stderr, "%*c+ literal_expr[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'"));
@@ -8804,7 +8866,7 @@ literal_expr_rule(Parser *p)
D(fprintf(stderr, "%*c> literal_expr[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 624)) // token='False'
+ (_keyword = _PyPegen_expect_token(p, 625)) // token='False'
)
{
D(fprintf(stderr, "%*c+ literal_expr[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'"));
@@ -9281,7 +9343,7 @@ pattern_capture_target_rule(Parser *p)
&&
(name = _PyPegen_name_token(p)) // NAME
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_44_rule, p)
+ _PyPegen_lookahead(0, _tmp_43_rule, p)
)
{
D(fprintf(stderr, "%*c+ pattern_capture_target[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "!\"_\" NAME !('.' | '(' | '=')"));
@@ -9396,7 +9458,7 @@ value_pattern_rule(Parser *p)
if (
(attr = attr_rule(p)) // attr
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_44_rule, p)
+ _PyPegen_lookahead(0, _tmp_43_rule, p)
)
{
D(fprintf(stderr, "%*c+ value_pattern[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "attr !('.' | '(' | '=')"));
@@ -9815,7 +9877,7 @@ maybe_sequence_pattern_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
asdl_seq * patterns;
if (
- (patterns = _gather_46_rule(p)) // ','.maybe_star_pattern+
+ (patterns = _gather_45_rule(p)) // ','.maybe_star_pattern+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -10223,13 +10285,13 @@ items_pattern_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> items_pattern[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.key_value_pattern+"));
- asdl_seq * _gather_48_var;
+ asdl_seq * _gather_47_var;
if (
- (_gather_48_var = _gather_48_rule(p)) // ','.key_value_pattern+
+ (_gather_47_var = _gather_47_rule(p)) // ','.key_value_pattern+
)
{
D(fprintf(stderr, "%*c+ items_pattern[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.key_value_pattern+"));
- _res = _gather_48_var;
+ _res = _gather_47_var;
goto done;
}
p->mark = _mark;
@@ -10265,7 +10327,7 @@ key_value_pattern_rule(Parser *p)
void *key;
pattern_ty pattern;
if (
- (key = _tmp_49_rule(p)) // literal_expr | attr
+ (key = _tmp_48_rule(p)) // literal_expr | attr
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -10593,7 +10655,7 @@ positional_patterns_rule(Parser *p)
D(fprintf(stderr, "%*c> positional_patterns[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.pattern+"));
asdl_pattern_seq* args;
if (
- (args = (asdl_pattern_seq*)_gather_51_rule(p)) // ','.pattern+
+ (args = (asdl_pattern_seq*)_gather_50_rule(p)) // ','.pattern+
)
{
D(fprintf(stderr, "%*c+ positional_patterns[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.pattern+"));
@@ -10634,13 +10696,13 @@ keyword_patterns_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> keyword_patterns[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.keyword_pattern+"));
- asdl_seq * _gather_53_var;
+ asdl_seq * _gather_52_var;
if (
- (_gather_53_var = _gather_53_rule(p)) // ','.keyword_pattern+
+ (_gather_52_var = _gather_52_rule(p)) // ','.keyword_pattern+
)
{
D(fprintf(stderr, "%*c+ keyword_patterns[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.keyword_pattern+"));
- _res = _gather_53_var;
+ _res = _gather_52_var;
goto done;
}
p->mark = _mark;
@@ -10866,7 +10928,7 @@ type_param_seq_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
asdl_type_param_seq* a;
if (
- (a = (asdl_type_param_seq*)_gather_55_rule(p)) // ','.type_param+
+ (a = (asdl_type_param_seq*)_gather_54_rule(p)) // ','.type_param+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -11236,7 +11298,7 @@ expressions_rule(Parser *p)
if (
(a = expression_rule(p)) // expression
&&
- (b = _loop1_56_rule(p)) // ((',' expression))+
+ (b = _loop1_55_rule(p)) // ((',' expression))+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -11407,11 +11469,11 @@ expression_rule(Parser *p)
if (
(a = disjunction_rule(p)) // disjunction
&&
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(b = disjunction_rule(p)) // disjunction
&&
- (_keyword_1 = _PyPegen_expect_token(p, 686)) // token='else'
+ (_keyword_1 = _PyPegen_expect_token(p, 691)) // token='else'
&&
(c = expression_rule(p)) // expression
)
@@ -11515,9 +11577,9 @@ yield_expr_rule(Parser *p)
Token * _keyword_1;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 587)) // token='yield'
+ (_keyword = _PyPegen_expect_token(p, 588)) // token='yield'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 633)) // token='from'
+ (_keyword_1 = _PyPegen_expect_token(p, 638)) // token='from'
&&
(a = expression_rule(p)) // expression
)
@@ -11553,7 +11615,7 @@ yield_expr_rule(Parser *p)
Token * _keyword;
void *a;
if (
- (_keyword = _PyPegen_expect_token(p, 587)) // token='yield'
+ (_keyword = _PyPegen_expect_token(p, 588)) // token='yield'
&&
(a = star_expressions_rule(p), !p->error_indicator) // star_expressions?
)
@@ -11624,7 +11686,7 @@ star_expressions_rule(Parser *p)
if (
(a = star_expression_rule(p)) // star_expression
&&
- (b = _loop1_57_rule(p)) // ((',' star_expression))+
+ (b = _loop1_56_rule(p)) // ((',' star_expression))+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -11823,7 +11885,7 @@ star_named_expressions_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
asdl_expr_seq* a;
if (
- (a = (asdl_expr_seq*)_gather_59_rule(p)) // ','.star_named_expression+
+ (a = (asdl_expr_seq*)_gather_58_rule(p)) // ','.star_named_expression+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -12119,7 +12181,7 @@ disjunction_rule(Parser *p)
if (
(a = conjunction_rule(p)) // conjunction
&&
- (b = _loop1_60_rule(p)) // (('or' conjunction))+
+ (b = _loop1_59_rule(p)) // (('or' conjunction))+
)
{
D(fprintf(stderr, "%*c+ disjunction[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "conjunction (('or' conjunction))+"));
@@ -12207,7 +12269,7 @@ conjunction_rule(Parser *p)
if (
(a = inversion_rule(p)) // inversion
&&
- (b = _loop1_61_rule(p)) // (('and' inversion))+
+ (b = _loop1_60_rule(p)) // (('and' inversion))+
)
{
D(fprintf(stderr, "%*c+ conjunction[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "inversion (('and' inversion))+"));
@@ -12293,7 +12355,7 @@ inversion_rule(Parser *p)
Token * _keyword;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 703)) // token='not'
+ (_keyword = _PyPegen_expect_token(p, 708)) // token='not'
&&
(a = inversion_rule(p)) // inversion
)
@@ -12379,7 +12441,7 @@ comparison_rule(Parser *p)
if (
(a = bitwise_or_rule(p)) // bitwise_or
&&
- (b = _loop1_62_rule(p)) // compare_op_bitwise_or_pair+
+ (b = _loop1_61_rule(p)) // compare_op_bitwise_or_pair+
)
{
D(fprintf(stderr, "%*c+ comparison[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "bitwise_or compare_op_bitwise_or_pair+"));
@@ -12713,10 +12775,10 @@ noteq_bitwise_or_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> noteq_bitwise_or[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('!=') bitwise_or"));
- void *_tmp_63_var;
+ void *_tmp_62_var;
expr_ty a;
if (
- (_tmp_63_var = _tmp_63_rule(p)) // '!='
+ (_tmp_62_var = _tmp_62_rule(p)) // '!='
&&
(a = bitwise_or_rule(p)) // bitwise_or
)
@@ -12947,9 +13009,9 @@ notin_bitwise_or_rule(Parser *p)
Token * _keyword_1;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 703)) // token='not'
+ (_keyword = _PyPegen_expect_token(p, 708)) // token='not'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword_1 = _PyPegen_expect_token(p, 700)) // token='in'
&&
(a = bitwise_or_rule(p)) // bitwise_or
)
@@ -12995,7 +13057,7 @@ in_bitwise_or_rule(Parser *p)
Token * _keyword;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword = _PyPegen_expect_token(p, 700)) // token='in'
&&
(a = bitwise_or_rule(p)) // bitwise_or
)
@@ -13042,9 +13104,9 @@ isnot_bitwise_or_rule(Parser *p)
Token * _keyword_1;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 596)) // token='is'
+ (_keyword = _PyPegen_expect_token(p, 597)) // token='is'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 703)) // token='not'
+ (_keyword_1 = _PyPegen_expect_token(p, 708)) // token='not'
&&
(a = bitwise_or_rule(p)) // bitwise_or
)
@@ -13090,7 +13152,7 @@ is_bitwise_or_rule(Parser *p)
Token * _keyword;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 596)) // token='is'
+ (_keyword = _PyPegen_expect_token(p, 597)) // token='is'
&&
(a = bitwise_or_rule(p)) // bitwise_or
)
@@ -14406,7 +14468,7 @@ await_primary_rule(Parser *p)
Token * _keyword;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 597)) // token='await'
+ (_keyword = _PyPegen_expect_token(p, 598)) // token='await'
&&
(a = primary_rule(p)) // primary
)
@@ -14764,7 +14826,7 @@ slices_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
asdl_expr_seq* a;
if (
- (a = (asdl_expr_seq*)_gather_65_rule(p)) // ','.(slice | starred_expression)+
+ (a = (asdl_expr_seq*)_gather_64_rule(p)) // ','.(slice | starred_expression)+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -14836,7 +14898,7 @@ slice_rule(Parser *p)
&&
(b = expression_rule(p), !p->error_indicator) // expression?
&&
- (c = _tmp_66_rule(p), !p->error_indicator) // [':' expression?]
+ (c = _tmp_65_rule(p), !p->error_indicator) // [':' expression?]
)
{
D(fprintf(stderr, "%*c+ slice[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression? ':' expression? [':' expression?]"));
@@ -14950,7 +15012,7 @@ atom_rule(Parser *p)
D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 622)) // token='True'
+ (_keyword = _PyPegen_expect_token(p, 623)) // token='True'
)
{
D(fprintf(stderr, "%*c+ atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'"));
@@ -14983,7 +15045,7 @@ atom_rule(Parser *p)
D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 624)) // token='False'
+ (_keyword = _PyPegen_expect_token(p, 625)) // token='False'
)
{
D(fprintf(stderr, "%*c+ atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'"));
@@ -15016,7 +15078,7 @@ atom_rule(Parser *p)
D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 623)) // token='None'
+ (_keyword = _PyPegen_expect_token(p, 624)) // token='None'
)
{
D(fprintf(stderr, "%*c+ atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'"));
@@ -15049,7 +15111,7 @@ atom_rule(Parser *p)
D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&(STRING | FSTRING_START | TSTRING_START) strings"));
expr_ty strings_var;
if (
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_43_rule, p)
+ _PyPegen_lookahead(1, _tmp_42_rule, p)
&&
(strings_var = strings_rule(p)) // strings
)
@@ -15087,15 +15149,15 @@ atom_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'(' (tuple | group | genexp)"));
- void *_tmp_67_var;
+ void *_tmp_66_var;
if (
_PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 7) // token='('
&&
- (_tmp_67_var = _tmp_67_rule(p)) // tuple | group | genexp
+ (_tmp_66_var = _tmp_66_rule(p)) // tuple | group | genexp
)
{
D(fprintf(stderr, "%*c+ atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "&'(' (tuple | group | genexp)"));
- _res = _tmp_67_var;
+ _res = _tmp_66_var;
goto done;
}
p->mark = _mark;
@@ -15108,15 +15170,15 @@ atom_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'[' (list | listcomp)"));
- void *_tmp_68_var;
+ void *_tmp_67_var;
if (
_PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 9) // token='['
&&
- (_tmp_68_var = _tmp_68_rule(p)) // list | listcomp
+ (_tmp_67_var = _tmp_67_rule(p)) // list | listcomp
)
{
D(fprintf(stderr, "%*c+ atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "&'[' (list | listcomp)"));
- _res = _tmp_68_var;
+ _res = _tmp_67_var;
goto done;
}
p->mark = _mark;
@@ -15129,15 +15191,15 @@ atom_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "&'{' (dict | set | dictcomp | setcomp)"));
- void *_tmp_69_var;
+ void *_tmp_68_var;
if (
_PyPegen_lookahead_with_int(1, _PyPegen_expect_token, p, 25) // token='{'
&&
- (_tmp_69_var = _tmp_69_rule(p)) // dict | set | dictcomp | setcomp
+ (_tmp_68_var = _tmp_68_rule(p)) // dict | set | dictcomp | setcomp
)
{
D(fprintf(stderr, "%*c+ atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "&'{' (dict | set | dictcomp | setcomp)"));
- _res = _tmp_69_var;
+ _res = _tmp_68_var;
goto done;
}
p->mark = _mark;
@@ -15208,7 +15270,7 @@ group_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
- (a = _tmp_70_rule(p)) // yield_expr | named_expression
+ (a = _tmp_69_rule(p)) // yield_expr | named_expression
&&
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
)
@@ -15284,7 +15346,7 @@ lambdef_rule(Parser *p)
void *a;
expr_ty b;
if (
- (_keyword = _PyPegen_expect_token(p, 621)) // token='lambda'
+ (_keyword = _PyPegen_expect_token(p, 622)) // token='lambda'
&&
(a = lambda_params_rule(p), !p->error_indicator) // lambda_params?
&&
@@ -15409,9 +15471,9 @@ lambda_parameters_rule(Parser *p)
if (
(a = lambda_slash_no_default_rule(p)) // lambda_slash_no_default
&&
- (b = (asdl_arg_seq*)_loop0_71_rule(p)) // lambda_param_no_default*
+ (b = (asdl_arg_seq*)_loop0_70_rule(p)) // lambda_param_no_default*
&&
- (c = _loop0_72_rule(p)) // lambda_param_with_default*
+ (c = _loop0_71_rule(p)) // lambda_param_with_default*
&&
(d = lambda_star_etc_rule(p), !p->error_indicator) // lambda_star_etc?
)
@@ -15441,7 +15503,7 @@ lambda_parameters_rule(Parser *p)
if (
(a = lambda_slash_with_default_rule(p)) // lambda_slash_with_default
&&
- (b = _loop0_72_rule(p)) // lambda_param_with_default*
+ (b = _loop0_71_rule(p)) // lambda_param_with_default*
&&
(c = lambda_star_etc_rule(p), !p->error_indicator) // lambda_star_etc?
)
@@ -15469,9 +15531,9 @@ lambda_parameters_rule(Parser *p)
asdl_seq * b;
void *c;
if (
- (a = (asdl_arg_seq*)_loop1_73_rule(p)) // lambda_param_no_default+
+ (a = (asdl_arg_seq*)_loop1_72_rule(p)) // lambda_param_no_default+
&&
- (b = _loop0_72_rule(p)) // lambda_param_with_default*
+ (b = _loop0_71_rule(p)) // lambda_param_with_default*
&&
(c = lambda_star_etc_rule(p), !p->error_indicator) // lambda_star_etc?
)
@@ -15498,7 +15560,7 @@ lambda_parameters_rule(Parser *p)
asdl_seq * a;
void *b;
if (
- (a = _loop1_74_rule(p)) // lambda_param_with_default+
+ (a = _loop1_73_rule(p)) // lambda_param_with_default+
&&
(b = lambda_star_etc_rule(p), !p->error_indicator) // lambda_star_etc?
)
@@ -15571,7 +15633,7 @@ lambda_slash_no_default_rule(Parser *p)
Token * _literal_1;
asdl_arg_seq* a;
if (
- (a = (asdl_arg_seq*)_loop1_73_rule(p)) // lambda_param_no_default+
+ (a = (asdl_arg_seq*)_loop1_72_rule(p)) // lambda_param_no_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -15600,7 +15662,7 @@ lambda_slash_no_default_rule(Parser *p)
Token * _literal;
asdl_arg_seq* a;
if (
- (a = (asdl_arg_seq*)_loop1_73_rule(p)) // lambda_param_no_default+
+ (a = (asdl_arg_seq*)_loop1_72_rule(p)) // lambda_param_no_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -15652,9 +15714,9 @@ lambda_slash_with_default_rule(Parser *p)
asdl_seq * a;
asdl_seq * b;
if (
- (a = _loop0_71_rule(p)) // lambda_param_no_default*
+ (a = _loop0_70_rule(p)) // lambda_param_no_default*
&&
- (b = _loop1_74_rule(p)) // lambda_param_with_default+
+ (b = _loop1_73_rule(p)) // lambda_param_with_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -15684,9 +15746,9 @@ lambda_slash_with_default_rule(Parser *p)
asdl_seq * a;
asdl_seq * b;
if (
- (a = _loop0_71_rule(p)) // lambda_param_no_default*
+ (a = _loop0_70_rule(p)) // lambda_param_no_default*
&&
- (b = _loop1_74_rule(p)) // lambda_param_with_default+
+ (b = _loop1_73_rule(p)) // lambda_param_with_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -15763,7 +15825,7 @@ lambda_star_etc_rule(Parser *p)
&&
(a = lambda_param_no_default_rule(p)) // lambda_param_no_default
&&
- (b = _loop0_75_rule(p)) // lambda_param_maybe_default*
+ (b = _loop0_74_rule(p)) // lambda_param_maybe_default*
&&
(c = lambda_kwds_rule(p), !p->error_indicator) // lambda_kwds?
)
@@ -15796,7 +15858,7 @@ lambda_star_etc_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 12)) // token=','
&&
- (b = _loop1_76_rule(p)) // lambda_param_maybe_default+
+ (b = _loop1_75_rule(p)) // lambda_param_maybe_default+
&&
(c = lambda_kwds_rule(p), !p->error_indicator) // lambda_kwds?
)
@@ -16436,7 +16498,7 @@ fstring_full_format_spec_rule(Parser *p)
if (
(colon = _PyPegen_expect_token(p, 11)) // token=':'
&&
- (spec = _loop0_77_rule(p)) // fstring_format_spec*
+ (spec = _loop0_76_rule(p)) // fstring_format_spec*
)
{
D(fprintf(stderr, "%*c+ fstring_full_format_spec[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':' fstring_format_spec*"));
@@ -16554,7 +16616,7 @@ fstring_rule(Parser *p)
if (
(a = _PyPegen_expect_token(p, FSTRING_START)) // token='FSTRING_START'
&&
- (b = _loop0_78_rule(p)) // fstring_middle*
+ (b = _loop0_77_rule(p)) // fstring_middle*
&&
(c = _PyPegen_expect_token(p, FSTRING_END)) // token='FSTRING_END'
)
@@ -16770,7 +16832,7 @@ tstring_full_format_spec_rule(Parser *p)
if (
(colon = _PyPegen_expect_token(p, 11)) // token=':'
&&
- (spec = _loop0_79_rule(p)) // tstring_format_spec*
+ (spec = _loop0_78_rule(p)) // tstring_format_spec*
)
{
D(fprintf(stderr, "%*c+ tstring_full_format_spec[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':' tstring_format_spec*"));
@@ -16989,7 +17051,7 @@ tstring_rule(Parser *p)
if (
(a = _PyPegen_expect_token(p, TSTRING_START)) // token='TSTRING_START'
&&
- (b = _loop0_80_rule(p)) // tstring_middle*
+ (b = _loop0_79_rule(p)) // tstring_middle*
&&
(c = _PyPegen_expect_token(p, TSTRING_END)) // token='TSTRING_END'
)
@@ -17091,7 +17153,7 @@ strings_rule(Parser *p)
D(fprintf(stderr, "%*c> strings[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((fstring | string | tstring))+"));
asdl_expr_seq* a;
if (
- (a = (asdl_expr_seq*)_loop1_81_rule(p)) // ((fstring | string | tstring))+
+ (a = (asdl_expr_seq*)_loop1_80_rule(p)) // ((fstring | string | tstring))+
)
{
D(fprintf(stderr, "%*c+ strings[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "((fstring | string | tstring))+"));
@@ -17224,7 +17286,7 @@ tuple_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
- (a = _tmp_82_rule(p), !p->error_indicator) // [star_named_expression ',' star_named_expressions?]
+ (a = _tmp_81_rule(p), !p->error_indicator) // [star_named_expression ',' star_named_expressions?]
&&
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
)
@@ -17439,7 +17501,7 @@ double_starred_kvpairs_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
asdl_seq * a;
if (
- (a = _gather_84_rule(p)) // ','.double_starred_kvpair+
+ (a = _gather_83_rule(p)) // ','.double_starred_kvpair+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -17598,7 +17660,7 @@ for_if_clauses_rule(Parser *p)
D(fprintf(stderr, "%*c> for_if_clauses[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "for_if_clause+"));
asdl_comprehension_seq* a;
if (
- (a = (asdl_comprehension_seq*)_loop1_85_rule(p)) // for_if_clause+
+ (a = (asdl_comprehension_seq*)_loop1_84_rule(p)) // for_if_clause+
)
{
D(fprintf(stderr, "%*c+ for_if_clauses[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "for_if_clause+"));
@@ -17651,19 +17713,19 @@ for_if_clause_rule(Parser *p)
expr_ty b;
asdl_expr_seq* c;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
&&
- (_keyword_1 = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword_1 = _PyPegen_expect_token(p, 699)) // token='for'
&&
(a = star_targets_rule(p)) // star_targets
&&
- (_keyword_2 = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword_2 = _PyPegen_expect_token(p, 700)) // token='in'
&&
(_cut_var = 1)
&&
(b = disjunction_rule(p)) // disjunction
&&
- (c = (asdl_expr_seq*)_loop0_86_rule(p)) // (('if' disjunction))*
+ (c = (asdl_expr_seq*)_loop0_85_rule(p)) // (('if' disjunction))*
)
{
D(fprintf(stderr, "%*c+ for_if_clause[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'async' 'for' star_targets 'in' ~ disjunction (('if' disjunction))*"));
@@ -17696,17 +17758,17 @@ for_if_clause_rule(Parser *p)
expr_ty b;
asdl_expr_seq* c;
if (
- (_keyword = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword = _PyPegen_expect_token(p, 699)) // token='for'
&&
(a = star_targets_rule(p)) // star_targets
&&
- (_keyword_1 = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword_1 = _PyPegen_expect_token(p, 700)) // token='in'
&&
(_cut_var = 1)
&&
(b = disjunction_rule(p)) // disjunction
&&
- (c = (asdl_expr_seq*)_loop0_86_rule(p)) // (('if' disjunction))*
+ (c = (asdl_expr_seq*)_loop0_85_rule(p)) // (('if' disjunction))*
)
{
D(fprintf(stderr, "%*c+ for_if_clause[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'for' star_targets 'in' ~ disjunction (('if' disjunction))*"));
@@ -17985,7 +18047,7 @@ genexp_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
- (a = _tmp_87_rule(p)) // assignment_expression | expression !':='
+ (a = _tmp_86_rule(p)) // assignment_expression | expression !':='
&&
(b = for_if_clauses_rule(p)) // for_if_clauses
&&
@@ -18234,9 +18296,9 @@ args_rule(Parser *p)
asdl_expr_seq* a;
void *b;
if (
- (a = (asdl_expr_seq*)_gather_89_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+
+ (a = (asdl_expr_seq*)_gather_88_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+
&&
- (b = _tmp_90_rule(p), !p->error_indicator) // [',' kwargs]
+ (b = _tmp_89_rule(p), !p->error_indicator) // [',' kwargs]
)
{
D(fprintf(stderr, "%*c+ args[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ [',' kwargs]"));
@@ -18326,11 +18388,11 @@ kwargs_rule(Parser *p)
asdl_seq * a;
asdl_seq * b;
if (
- (a = _gather_92_rule(p)) // ','.kwarg_or_starred+
+ (a = _gather_91_rule(p)) // ','.kwarg_or_starred+
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (b = _gather_94_rule(p)) // ','.kwarg_or_double_starred+
+ (b = _gather_93_rule(p)) // ','.kwarg_or_double_starred+
)
{
D(fprintf(stderr, "%*c+ kwargs[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.kwarg_or_starred+ ',' ','.kwarg_or_double_starred+"));
@@ -18352,13 +18414,13 @@ kwargs_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> kwargs[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.kwarg_or_starred+"));
- asdl_seq * _gather_92_var;
+ asdl_seq * _gather_91_var;
if (
- (_gather_92_var = _gather_92_rule(p)) // ','.kwarg_or_starred+
+ (_gather_91_var = _gather_91_rule(p)) // ','.kwarg_or_starred+
)
{
D(fprintf(stderr, "%*c+ kwargs[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.kwarg_or_starred+"));
- _res = _gather_92_var;
+ _res = _gather_91_var;
goto done;
}
p->mark = _mark;
@@ -18371,13 +18433,13 @@ kwargs_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> kwargs[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.kwarg_or_double_starred+"));
- asdl_seq * _gather_94_var;
+ asdl_seq * _gather_93_var;
if (
- (_gather_94_var = _gather_94_rule(p)) // ','.kwarg_or_double_starred+
+ (_gather_93_var = _gather_93_rule(p)) // ','.kwarg_or_double_starred+
)
{
D(fprintf(stderr, "%*c+ kwargs[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.kwarg_or_double_starred+"));
- _res = _gather_94_var;
+ _res = _gather_93_var;
goto done;
}
p->mark = _mark;
@@ -18788,7 +18850,7 @@ star_targets_rule(Parser *p)
if (
(a = star_target_rule(p)) // star_target
&&
- (b = _loop0_95_rule(p)) // ((',' star_target))*
+ (b = _loop0_94_rule(p)) // ((',' star_target))*
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -18844,7 +18906,7 @@ star_targets_list_seq_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
asdl_expr_seq* a;
if (
- (a = (asdl_expr_seq*)_gather_97_rule(p)) // ','.star_target+
+ (a = (asdl_expr_seq*)_gather_96_rule(p)) // ','.star_target+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -18894,7 +18956,7 @@ star_targets_tuple_seq_rule(Parser *p)
if (
(a = star_target_rule(p)) // star_target
&&
- (b = _loop1_98_rule(p)) // ((',' star_target))+
+ (b = _loop1_97_rule(p)) // ((',' star_target))+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -18982,7 +19044,7 @@ star_target_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (a = _tmp_99_rule(p)) // !'*' star_target
+ (a = _tmp_98_rule(p)) // !'*' star_target
)
{
D(fprintf(stderr, "%*c+ star_target[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (!'*' star_target)"));
@@ -19078,7 +19140,7 @@ target_with_star_atom_rule(Parser *p)
&&
(b = _PyPegen_name_token(p)) // NAME
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(0, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ target_with_star_atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '.' NAME !t_lookahead"));
@@ -19122,7 +19184,7 @@ target_with_star_atom_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 10)) // token=']'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(0, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ target_with_star_atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '[' slices ']' !t_lookahead"));
@@ -19469,7 +19531,7 @@ single_subscript_attribute_target_rule(Parser *p)
&&
(b = _PyPegen_name_token(p)) // NAME
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(0, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ single_subscript_attribute_target[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '.' NAME !t_lookahead"));
@@ -19513,7 +19575,7 @@ single_subscript_attribute_target_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 10)) // token=']'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(0, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ single_subscript_attribute_target[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '[' slices ']' !t_lookahead"));
@@ -19623,7 +19685,7 @@ t_primary_raw(Parser *p)
&&
(b = _PyPegen_name_token(p)) // NAME
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(1, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ t_primary[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '.' NAME &t_lookahead"));
@@ -19667,7 +19729,7 @@ t_primary_raw(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 10)) // token=']'
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(1, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ t_primary[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '[' slices ']' &t_lookahead"));
@@ -19705,7 +19767,7 @@ t_primary_raw(Parser *p)
&&
(b = genexp_rule(p)) // genexp
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(1, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ t_primary[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary genexp &t_lookahead"));
@@ -19749,7 +19811,7 @@ t_primary_raw(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(1, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ t_primary[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '(' arguments? ')' &t_lookahead"));
@@ -19784,7 +19846,7 @@ t_primary_raw(Parser *p)
if (
(a = atom_rule(p)) // atom
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(1, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ t_primary[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "atom &t_lookahead"));
@@ -19905,7 +19967,7 @@ del_targets_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
asdl_expr_seq* a;
if (
- (a = (asdl_expr_seq*)_gather_101_rule(p)) // ','.del_target+
+ (a = (asdl_expr_seq*)_gather_100_rule(p)) // ','.del_target+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
@@ -19974,7 +20036,7 @@ del_target_rule(Parser *p)
&&
(b = _PyPegen_name_token(p)) // NAME
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(0, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ del_target[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '.' NAME !t_lookahead"));
@@ -20018,7 +20080,7 @@ del_target_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 10)) // token=']'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) t_lookahead_rule, p)
+ _PyPegen_lookahead(0, t_lookahead_rule, p)
)
{
D(fprintf(stderr, "%*c+ del_target[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '[' slices ']' !t_lookahead"));
@@ -20263,7 +20325,7 @@ type_expressions_rule(Parser *p)
expr_ty b;
expr_ty c;
if (
- (a = _gather_103_rule(p)) // ','.expression+
+ (a = _gather_102_rule(p)) // ','.expression+
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
@@ -20302,7 +20364,7 @@ type_expressions_rule(Parser *p)
asdl_seq * a;
expr_ty b;
if (
- (a = _gather_103_rule(p)) // ','.expression+
+ (a = _gather_102_rule(p)) // ','.expression+
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
@@ -20335,7 +20397,7 @@ type_expressions_rule(Parser *p)
asdl_seq * a;
expr_ty b;
if (
- (a = _gather_103_rule(p)) // ','.expression+
+ (a = _gather_102_rule(p)) // ','.expression+
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
@@ -20455,7 +20517,7 @@ type_expressions_rule(Parser *p)
D(fprintf(stderr, "%*c> type_expressions[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.expression+"));
asdl_expr_seq* a;
if (
- (a = (asdl_expr_seq*)_gather_103_rule(p)) // ','.expression+
+ (a = (asdl_expr_seq*)_gather_102_rule(p)) // ','.expression+
)
{
D(fprintf(stderr, "%*c+ type_expressions[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.expression+"));
@@ -20506,7 +20568,7 @@ func_type_comment_rule(Parser *p)
&&
(t = _PyPegen_expect_token(p, TYPE_COMMENT)) // token='TYPE_COMMENT'
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_104_rule, p)
+ _PyPegen_lookahead(1, _tmp_103_rule, p)
)
{
D(fprintf(stderr, "%*c+ func_type_comment[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE TYPE_COMMENT &(NEWLINE INDENT)"));
@@ -20592,15 +20654,15 @@ invalid_arguments_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_arguments[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' ','.(starred_expression !'=')+"));
- asdl_seq * _gather_107_var;
- void *_tmp_105_var;
+ asdl_seq * _gather_106_var;
+ void *_tmp_104_var;
Token * a;
if (
- (_tmp_105_var = _tmp_105_rule(p)) // (','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs
+ (_tmp_104_var = _tmp_104_rule(p)) // (','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs
&&
(a = _PyPegen_expect_token(p, 12)) // token=','
&&
- (_gather_107_var = _gather_107_rule(p)) // ','.(starred_expression !'=')+
+ (_gather_106_var = _gather_106_rule(p)) // ','.(starred_expression !'=')+
)
{
D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "((','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs) | kwargs) ',' ','.(starred_expression !'=')+"));
@@ -20634,7 +20696,7 @@ invalid_arguments_rule(Parser *p)
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (_opt_var = _tmp_108_rule(p), !p->error_indicator) // [args | expression for_if_clauses]
+ (_opt_var = _tmp_107_rule(p), !p->error_indicator) // [args | expression for_if_clauses]
)
{
D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses ',' [args | expression for_if_clauses]"));
@@ -20694,13 +20756,13 @@ invalid_arguments_rule(Parser *p)
expr_ty a;
Token * b;
if (
- (_opt_var = _tmp_109_rule(p), !p->error_indicator) // [(args ',')]
+ (_opt_var = _tmp_108_rule(p), !p->error_indicator) // [(args ',')]
&&
(a = _PyPegen_name_token(p)) // NAME
&&
(b = _PyPegen_expect_token(p, 22)) // token='='
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_110_rule, p)
+ _PyPegen_lookahead(1, _tmp_109_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "[(args ',')] NAME '=' &(',' | ')')"));
@@ -20838,7 +20900,7 @@ invalid_kwarg_rule(Parser *p)
Token* a;
Token * b;
if (
- (a = (Token*)_tmp_111_rule(p)) // 'True' | 'False' | 'None'
+ (a = (Token*)_tmp_110_rule(p)) // 'True' | 'False' | 'None'
&&
(b = _PyPegen_expect_token(p, 22)) // token='='
)
@@ -20898,7 +20960,7 @@ invalid_kwarg_rule(Parser *p)
expr_ty a;
Token * b;
if (
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_112_rule, p)
+ _PyPegen_lookahead(0, _tmp_111_rule, p)
&&
(a = expression_rule(p)) // expression
&&
@@ -21001,11 +21063,11 @@ expression_without_invalid_rule(Parser *p)
if (
(a = disjunction_rule(p)) // disjunction
&&
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(b = disjunction_rule(p)) // disjunction
&&
- (_keyword_1 = _PyPegen_expect_token(p, 686)) // token='else'
+ (_keyword_1 = _PyPegen_expect_token(p, 691)) // token='else'
&&
(c = expression_rule(p)) // expression
)
@@ -21246,7 +21308,7 @@ invalid_expression_rule(Parser *p)
if (
(string_var = _PyPegen_string_token(p)) // STRING
&&
- (a = _loop1_113_rule(p)) // ((!STRING expression_without_invalid))+
+ (a = _loop1_112_rule(p)) // ((!STRING expression_without_invalid))+
&&
(string_var_1 = _PyPegen_string_token(p)) // STRING
)
@@ -21273,7 +21335,7 @@ invalid_expression_rule(Parser *p)
expr_ty a;
expr_ty b;
if (
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_114_rule, p)
+ _PyPegen_lookahead(0, _tmp_113_rule, p)
&&
(a = disjunction_rule(p)) // disjunction
&&
@@ -21305,11 +21367,11 @@ invalid_expression_rule(Parser *p)
if (
(a = disjunction_rule(p)) // disjunction
&&
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(b = disjunction_rule(p)) // disjunction
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_115_rule, p)
+ _PyPegen_lookahead(0, _tmp_114_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "disjunction 'if' disjunction !('else' | ':')"));
@@ -21338,13 +21400,13 @@ invalid_expression_rule(Parser *p)
if (
(a = disjunction_rule(p)) // disjunction
&&
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(b = disjunction_rule(p)) // disjunction
&&
- (_keyword_1 = _PyPegen_expect_token(p, 686)) // token='else'
+ (_keyword_1 = _PyPegen_expect_token(p, 691)) // token='else'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) expression_rule, p)
+ _PyPegen_lookahead_for_expr(0, expression_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "disjunction 'if' disjunction 'else' !expression"));
@@ -21372,13 +21434,13 @@ invalid_expression_rule(Parser *p)
expr_ty b;
stmt_ty c;
if (
- (a = (stmt_ty)_tmp_116_rule(p)) // pass_stmt | break_stmt | continue_stmt
+ (a = (stmt_ty)_tmp_115_rule(p)) // pass_stmt | break_stmt | continue_stmt
&&
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(b = disjunction_rule(p)) // disjunction
&&
- (_keyword_1 = _PyPegen_expect_token(p, 686)) // token='else'
+ (_keyword_1 = _PyPegen_expect_token(p, 691)) // token='else'
&&
(c = simple_stmt_rule(p)) // simple_stmt
)
@@ -21407,7 +21469,7 @@ invalid_expression_rule(Parser *p)
Token * a;
Token * b;
if (
- (a = _PyPegen_expect_token(p, 621)) // token='lambda'
+ (a = _PyPegen_expect_token(p, 622)) // token='lambda'
&&
(_opt_var = lambda_params_rule(p), !p->error_indicator) // lambda_params?
&&
@@ -21440,7 +21502,7 @@ invalid_expression_rule(Parser *p)
Token * a;
Token * b;
if (
- (a = _PyPegen_expect_token(p, 621)) // token='lambda'
+ (a = _PyPegen_expect_token(p, 622)) // token='lambda'
&&
(_opt_var = lambda_params_rule(p), !p->error_indicator) // lambda_params?
&&
@@ -21534,7 +21596,7 @@ invalid_named_expression_rule(Parser *p)
&&
(b = bitwise_or_rule(p)) // bitwise_or
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_117_rule, p)
+ _PyPegen_lookahead(0, _tmp_116_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_named_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME '=' bitwise_or !('=' | ':=')"));
@@ -21560,7 +21622,7 @@ invalid_named_expression_rule(Parser *p)
Token * b;
expr_ty bitwise_or_var;
if (
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_118_rule, p)
+ _PyPegen_lookahead(0, _tmp_117_rule, p)
&&
(a = bitwise_or_rule(p)) // bitwise_or
&&
@@ -21568,7 +21630,7 @@ invalid_named_expression_rule(Parser *p)
&&
(bitwise_or_var = bitwise_or_rule(p)) // bitwise_or
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_117_rule, p)
+ _PyPegen_lookahead(0, _tmp_116_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_named_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "!(list | tuple | genexp | 'True' | 'None' | 'False') bitwise_or '=' bitwise_or !('=' | ':=')"));
@@ -21648,7 +21710,7 @@ invalid_assignment_rule(Parser *p)
D(fprintf(stderr, "%*c> invalid_assignment[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expression ',' star_named_expressions* ':' expression"));
Token * _literal;
Token * _literal_1;
- asdl_seq * _loop0_119_var;
+ asdl_seq * _loop0_118_var;
expr_ty a;
expr_ty expression_var;
if (
@@ -21656,7 +21718,7 @@ invalid_assignment_rule(Parser *p)
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (_loop0_119_var = _loop0_119_rule(p)) // star_named_expressions*
+ (_loop0_118_var = _loop0_118_rule(p)) // star_named_expressions*
&&
(_literal_1 = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -21713,10 +21775,10 @@ invalid_assignment_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_assignment[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((star_targets '='))* star_expressions '='"));
Token * _literal;
- asdl_seq * _loop0_120_var;
+ asdl_seq * _loop0_119_var;
expr_ty a;
if (
- (_loop0_120_var = _loop0_120_rule(p)) // ((star_targets '='))*
+ (_loop0_119_var = _loop0_119_rule(p)) // ((star_targets '='))*
&&
(a = star_expressions_rule(p)) // star_expressions
&&
@@ -21743,10 +21805,10 @@ invalid_assignment_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_assignment[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "((star_targets '='))* yield_expr '='"));
Token * _literal;
- asdl_seq * _loop0_120_var;
+ asdl_seq * _loop0_119_var;
expr_ty a;
if (
- (_loop0_120_var = _loop0_120_rule(p)) // ((star_targets '='))*
+ (_loop0_119_var = _loop0_119_rule(p)) // ((star_targets '='))*
&&
(a = yield_expr_rule(p)) // yield_expr
&&
@@ -21889,6 +21951,82 @@ invalid_ann_assign_target_rule(Parser *p)
return _res;
}
+// invalid_raise_stmt: 'raise' 'from' | 'raise' expression 'from'
+static void *
+invalid_raise_stmt_rule(Parser *p)
+{
+ if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
+ _Pypegen_stack_overflow(p);
+ }
+ if (p->error_indicator) {
+ p->level--;
+ return NULL;
+ }
+ void * _res = NULL;
+ int _mark = p->mark;
+ { // 'raise' 'from'
+ if (p->error_indicator) {
+ p->level--;
+ return NULL;
+ }
+ D(fprintf(stderr, "%*c> invalid_raise_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'raise' 'from'"));
+ Token * a;
+ Token * b;
+ if (
+ (a = _PyPegen_expect_token(p, 628)) // token='raise'
+ &&
+ (b = _PyPegen_expect_token(p, 638)) // token='from'
+ )
+ {
+ D(fprintf(stderr, "%*c+ invalid_raise_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'raise' 'from'"));
+ _res = RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , b , "did you forget an expression between 'raise' and 'from'?" );
+ if (_res == NULL && PyErr_Occurred()) {
+ p->error_indicator = 1;
+ p->level--;
+ return NULL;
+ }
+ goto done;
+ }
+ p->mark = _mark;
+ D(fprintf(stderr, "%*c%s invalid_raise_stmt[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'raise' 'from'"));
+ }
+ { // 'raise' expression 'from'
+ if (p->error_indicator) {
+ p->level--;
+ return NULL;
+ }
+ D(fprintf(stderr, "%*c> invalid_raise_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'raise' expression 'from'"));
+ Token * _keyword;
+ Token * a;
+ expr_ty expression_var;
+ if (
+ (_keyword = _PyPegen_expect_token(p, 628)) // token='raise'
+ &&
+ (expression_var = expression_rule(p)) // expression
+ &&
+ (a = _PyPegen_expect_token(p, 638)) // token='from'
+ )
+ {
+ D(fprintf(stderr, "%*c+ invalid_raise_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'raise' expression 'from'"));
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "did you forget an expression after 'from'?" );
+ if (_res == NULL && PyErr_Occurred()) {
+ p->error_indicator = 1;
+ p->level--;
+ return NULL;
+ }
+ goto done;
+ }
+ p->mark = _mark;
+ D(fprintf(stderr, "%*c%s invalid_raise_stmt[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'raise' expression 'from'"));
+ }
+ _res = NULL;
+ done:
+ p->level--;
+ return _res;
+}
+
// invalid_del_stmt: 'del' star_expressions
static void *
invalid_del_stmt_rule(Parser *p)
@@ -21911,7 +22049,7 @@ invalid_del_stmt_rule(Parser *p)
Token * _keyword;
expr_ty a;
if (
- (_keyword = _PyPegen_expect_token(p, 625)) // token='del'
+ (_keyword = _PyPegen_expect_token(p, 630)) // token='del'
&&
(a = star_expressions_rule(p)) // star_expressions
)
@@ -22002,11 +22140,11 @@ invalid_comprehension_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_comprehension[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('[' | '(' | '{') starred_expression for_if_clauses"));
- void *_tmp_121_var;
+ void *_tmp_120_var;
expr_ty a;
asdl_comprehension_seq* for_if_clauses_var;
if (
- (_tmp_121_var = _tmp_121_rule(p)) // '[' | '(' | '{'
+ (_tmp_120_var = _tmp_120_rule(p)) // '[' | '(' | '{'
&&
(a = starred_expression_rule(p)) // starred_expression
&&
@@ -22033,12 +22171,12 @@ invalid_comprehension_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_comprehension[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('[' | '{') star_named_expression ',' star_named_expressions for_if_clauses"));
Token * _literal;
- void *_tmp_122_var;
+ void *_tmp_121_var;
expr_ty a;
asdl_expr_seq* b;
asdl_comprehension_seq* for_if_clauses_var;
if (
- (_tmp_122_var = _tmp_122_rule(p)) // '[' | '{'
+ (_tmp_121_var = _tmp_121_rule(p)) // '[' | '{'
&&
(a = star_named_expression_rule(p)) // star_named_expression
&&
@@ -22068,12 +22206,12 @@ invalid_comprehension_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_comprehension[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('[' | '{') star_named_expression ',' for_if_clauses"));
- void *_tmp_122_var;
+ void *_tmp_121_var;
expr_ty a;
Token * b;
asdl_comprehension_seq* for_if_clauses_var;
if (
- (_tmp_122_var = _tmp_122_rule(p)) // '[' | '{'
+ (_tmp_121_var = _tmp_121_rule(p)) // '[' | '{'
&&
(a = star_named_expression_rule(p)) // star_named_expression
&&
@@ -22190,7 +22328,7 @@ invalid_parameters_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_parameters[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"/\" ','"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "at least one argument must precede /" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "at least one parameter must precede /" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22208,13 +22346,13 @@ invalid_parameters_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(slash_no_default | slash_with_default) param_maybe_default* '/'"));
- asdl_seq * _loop0_32_var;
- void *_tmp_123_var;
+ asdl_seq * _loop0_31_var;
+ void *_tmp_122_var;
Token * a;
if (
- (_tmp_123_var = _tmp_123_rule(p)) // slash_no_default | slash_with_default
+ (_tmp_122_var = _tmp_122_rule(p)) // slash_no_default | slash_with_default
&&
- (_loop0_32_var = _loop0_32_rule(p)) // param_maybe_default*
+ (_loop0_31_var = _loop0_31_rule(p)) // param_maybe_default*
&&
(a = _PyPegen_expect_token(p, 17)) // token='/'
)
@@ -22238,7 +22376,7 @@ invalid_parameters_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_no_default? param_no_default* invalid_parameters_helper param_no_default"));
- asdl_seq * _loop0_28_var;
+ asdl_seq * _loop0_27_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
arg_ty a;
@@ -22246,7 +22384,7 @@ invalid_parameters_rule(Parser *p)
if (
(_opt_var = slash_no_default_rule(p), !p->error_indicator) // slash_no_default?
&&
- (_loop0_28_var = _loop0_28_rule(p)) // param_no_default*
+ (_loop0_27_var = _loop0_27_rule(p)) // param_no_default*
&&
(invalid_parameters_helper_var = invalid_parameters_helper_rule(p)) // invalid_parameters_helper
&&
@@ -22272,18 +22410,18 @@ invalid_parameters_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default* '(' param_no_default+ ','? ')'"));
- asdl_seq * _loop0_28_var;
- asdl_seq * _loop1_30_var;
+ asdl_seq * _loop0_27_var;
+ asdl_seq * _loop1_29_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
Token * a;
Token * b;
if (
- (_loop0_28_var = _loop0_28_rule(p)) // param_no_default*
+ (_loop0_27_var = _loop0_27_rule(p)) // param_no_default*
&&
(a = _PyPegen_expect_token(p, 7)) // token='('
&&
- (_loop1_30_var = _loop1_30_rule(p)) // param_no_default+
+ (_loop1_29_var = _loop1_29_rule(p)) // param_no_default+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
&&
@@ -22310,22 +22448,22 @@ invalid_parameters_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "[(slash_no_default | slash_with_default)] param_maybe_default* '*' (',' | param_no_default) param_maybe_default* '/'"));
Token * _literal;
- asdl_seq * _loop0_32_var;
- asdl_seq * _loop0_32_var_1;
+ asdl_seq * _loop0_31_var;
+ asdl_seq * _loop0_31_var_1;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
- void *_tmp_124_var;
+ void *_tmp_123_var;
Token * a;
if (
- (_opt_var = _tmp_123_rule(p), !p->error_indicator) // [(slash_no_default | slash_with_default)]
+ (_opt_var = _tmp_122_rule(p), !p->error_indicator) // [(slash_no_default | slash_with_default)]
&&
- (_loop0_32_var = _loop0_32_rule(p)) // param_maybe_default*
+ (_loop0_31_var = _loop0_31_rule(p)) // param_maybe_default*
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_124_var = _tmp_124_rule(p)) // ',' | param_no_default
+ (_tmp_123_var = _tmp_123_rule(p)) // ',' | param_no_default
&&
- (_loop0_32_var_1 = _loop0_32_rule(p)) // param_maybe_default*
+ (_loop0_31_var_1 = _loop0_31_rule(p)) // param_maybe_default*
&&
(a = _PyPegen_expect_token(p, 17)) // token='/'
)
@@ -22350,10 +22488,10 @@ invalid_parameters_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default+ '/' '*'"));
Token * _literal;
- asdl_seq * _loop1_33_var;
+ asdl_seq * _loop1_32_var;
Token * a;
if (
- (_loop1_33_var = _loop1_33_rule(p)) // param_maybe_default+
+ (_loop1_32_var = _loop1_32_rule(p)) // param_maybe_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -22402,7 +22540,7 @@ invalid_default_rule(Parser *p)
if (
(a = _PyPegen_expect_token(p, 22)) // token='='
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_125_rule, p)
+ _PyPegen_lookahead(1, _tmp_124_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_default[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'=' &(')' | ',')"));
@@ -22447,16 +22585,16 @@ invalid_star_etc_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (')' | ',' (')' | '**'))"));
- void *_tmp_126_var;
+ void *_tmp_125_var;
Token * a;
if (
(a = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_126_var = _tmp_126_rule(p)) // ')' | ',' (')' | '**')
+ (_tmp_125_var = _tmp_125_rule(p)) // ')' | ',' (')' | '**')
)
{
D(fprintf(stderr, "%*c+ invalid_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (')' | ',' (')' | '**'))"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "named arguments must follow bare *" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "named parameters must follow bare *" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22516,7 +22654,7 @@ invalid_star_etc_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' param '='"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-positional argument cannot have default value" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-positional parameter cannot have default value" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22535,24 +22673,24 @@ invalid_star_etc_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (param_no_default | ',') param_maybe_default* '*' (param_no_default | ',')"));
Token * _literal;
- asdl_seq * _loop0_32_var;
- void *_tmp_127_var;
- void *_tmp_127_var_1;
+ asdl_seq * _loop0_31_var;
+ void *_tmp_126_var;
+ void *_tmp_126_var_1;
Token * a;
if (
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_127_var = _tmp_127_rule(p)) // param_no_default | ','
+ (_tmp_126_var = _tmp_126_rule(p)) // param_no_default | ','
&&
- (_loop0_32_var = _loop0_32_rule(p)) // param_maybe_default*
+ (_loop0_31_var = _loop0_31_rule(p)) // param_maybe_default*
&&
(a = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_127_var_1 = _tmp_127_rule(p)) // param_no_default | ','
+ (_tmp_126_var_1 = _tmp_126_rule(p)) // param_no_default | ','
)
{
D(fprintf(stderr, "%*c+ invalid_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (param_no_default | ',') param_maybe_default* '*' (param_no_default | ',')"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "* argument may appear only once" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "* may appear only once" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22601,7 +22739,7 @@ invalid_kwds_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' param '='"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-keyword argument cannot have default value" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-keyword parameter cannot have default value" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22634,7 +22772,7 @@ invalid_kwds_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' param ',' param"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "arguments cannot follow var-keyword argument" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "parameters cannot follow var-keyword parameter" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22663,11 +22801,11 @@ invalid_kwds_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 12)) // token=','
&&
- (a = (Token*)_tmp_128_rule(p)) // '*' | '**' | '/'
+ (a = (Token*)_tmp_127_rule(p)) // '*' | '**' | '/'
)
{
D(fprintf(stderr, "%*c+ invalid_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' param ',' ('*' | '**' | '/')"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "arguments cannot follow var-keyword argument" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "parameters cannot follow var-keyword parameter" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22728,13 +22866,13 @@ invalid_parameters_helper_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_parameters_helper[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default+"));
- asdl_seq * _loop1_31_var;
+ asdl_seq * _loop1_30_var;
if (
- (_loop1_31_var = _loop1_31_rule(p)) // param_with_default+
+ (_loop1_30_var = _loop1_30_rule(p)) // param_with_default+
)
{
D(fprintf(stderr, "%*c+ invalid_parameters_helper[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_with_default+"));
- _res = _loop1_31_var;
+ _res = _loop1_30_var;
goto done;
}
p->mark = _mark;
@@ -22781,7 +22919,7 @@ invalid_lambda_parameters_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_parameters[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"/\" ','"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "at least one argument must precede /" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "at least one parameter must precede /" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -22799,13 +22937,13 @@ invalid_lambda_parameters_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(lambda_slash_no_default | lambda_slash_with_default) lambda_param_maybe_default* '/'"));
- asdl_seq * _loop0_75_var;
- void *_tmp_129_var;
+ asdl_seq * _loop0_74_var;
+ void *_tmp_128_var;
Token * a;
if (
- (_tmp_129_var = _tmp_129_rule(p)) // lambda_slash_no_default | lambda_slash_with_default
+ (_tmp_128_var = _tmp_128_rule(p)) // lambda_slash_no_default | lambda_slash_with_default
&&
- (_loop0_75_var = _loop0_75_rule(p)) // lambda_param_maybe_default*
+ (_loop0_74_var = _loop0_74_rule(p)) // lambda_param_maybe_default*
&&
(a = _PyPegen_expect_token(p, 17)) // token='/'
)
@@ -22829,7 +22967,7 @@ invalid_lambda_parameters_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default? lambda_param_no_default* invalid_lambda_parameters_helper lambda_param_no_default"));
- asdl_seq * _loop0_71_var;
+ asdl_seq * _loop0_70_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
arg_ty a;
@@ -22837,7 +22975,7 @@ invalid_lambda_parameters_rule(Parser *p)
if (
(_opt_var = lambda_slash_no_default_rule(p), !p->error_indicator) // lambda_slash_no_default?
&&
- (_loop0_71_var = _loop0_71_rule(p)) // lambda_param_no_default*
+ (_loop0_70_var = _loop0_70_rule(p)) // lambda_param_no_default*
&&
(invalid_lambda_parameters_helper_var = invalid_lambda_parameters_helper_rule(p)) // invalid_lambda_parameters_helper
&&
@@ -22863,18 +23001,18 @@ invalid_lambda_parameters_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default* '(' ','.lambda_param+ ','? ')'"));
- asdl_seq * _gather_131_var;
- asdl_seq * _loop0_71_var;
+ asdl_seq * _gather_130_var;
+ asdl_seq * _loop0_70_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
Token * a;
Token * b;
if (
- (_loop0_71_var = _loop0_71_rule(p)) // lambda_param_no_default*
+ (_loop0_70_var = _loop0_70_rule(p)) // lambda_param_no_default*
&&
(a = _PyPegen_expect_token(p, 7)) // token='('
&&
- (_gather_131_var = _gather_131_rule(p)) // ','.lambda_param+
+ (_gather_130_var = _gather_130_rule(p)) // ','.lambda_param+
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
&&
@@ -22901,22 +23039,22 @@ invalid_lambda_parameters_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "[(lambda_slash_no_default | lambda_slash_with_default)] lambda_param_maybe_default* '*' (',' | lambda_param_no_default) lambda_param_maybe_default* '/'"));
Token * _literal;
- asdl_seq * _loop0_75_var;
- asdl_seq * _loop0_75_var_1;
+ asdl_seq * _loop0_74_var;
+ asdl_seq * _loop0_74_var_1;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
- void *_tmp_132_var;
+ void *_tmp_131_var;
Token * a;
if (
- (_opt_var = _tmp_129_rule(p), !p->error_indicator) // [(lambda_slash_no_default | lambda_slash_with_default)]
+ (_opt_var = _tmp_128_rule(p), !p->error_indicator) // [(lambda_slash_no_default | lambda_slash_with_default)]
&&
- (_loop0_75_var = _loop0_75_rule(p)) // lambda_param_maybe_default*
+ (_loop0_74_var = _loop0_74_rule(p)) // lambda_param_maybe_default*
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_132_var = _tmp_132_rule(p)) // ',' | lambda_param_no_default
+ (_tmp_131_var = _tmp_131_rule(p)) // ',' | lambda_param_no_default
&&
- (_loop0_75_var_1 = _loop0_75_rule(p)) // lambda_param_maybe_default*
+ (_loop0_74_var_1 = _loop0_74_rule(p)) // lambda_param_maybe_default*
&&
(a = _PyPegen_expect_token(p, 17)) // token='/'
)
@@ -22941,10 +23079,10 @@ invalid_lambda_parameters_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_lambda_parameters[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default+ '/' '*'"));
Token * _literal;
- asdl_seq * _loop1_76_var;
+ asdl_seq * _loop1_75_var;
Token * a;
if (
- (_loop1_76_var = _loop1_76_rule(p)) // lambda_param_maybe_default+
+ (_loop1_75_var = _loop1_75_rule(p)) // lambda_param_maybe_default+
&&
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
&&
@@ -23015,13 +23153,13 @@ invalid_lambda_parameters_helper_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_lambda_parameters_helper[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default+"));
- asdl_seq * _loop1_74_var;
+ asdl_seq * _loop1_73_var;
if (
- (_loop1_74_var = _loop1_74_rule(p)) // lambda_param_with_default+
+ (_loop1_73_var = _loop1_73_rule(p)) // lambda_param_with_default+
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_parameters_helper[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default+"));
- _res = _loop1_74_var;
+ _res = _loop1_73_var;
goto done;
}
p->mark = _mark;
@@ -23057,15 +23195,15 @@ invalid_lambda_star_etc_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_lambda_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (':' | ',' (':' | '**'))"));
Token * _literal;
- void *_tmp_133_var;
+ void *_tmp_132_var;
if (
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_133_var = _tmp_133_rule(p)) // ':' | ',' (':' | '**')
+ (_tmp_132_var = _tmp_132_rule(p)) // ':' | ',' (':' | '**')
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (':' | ',' (':' | '**'))"));
- _res = RAISE_SYNTAX_ERROR ( "named arguments must follow bare *" );
+ _res = RAISE_SYNTAX_ERROR ( "named parameters must follow bare *" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -23095,7 +23233,7 @@ invalid_lambda_star_etc_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' lambda_param '='"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-positional argument cannot have default value" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-positional parameter cannot have default value" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -23114,24 +23252,24 @@ invalid_lambda_star_etc_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_lambda_star_etc[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*' (lambda_param_no_default | ',') lambda_param_maybe_default* '*' (lambda_param_no_default | ',')"));
Token * _literal;
- asdl_seq * _loop0_75_var;
- void *_tmp_134_var;
- void *_tmp_134_var_1;
+ asdl_seq * _loop0_74_var;
+ void *_tmp_133_var;
+ void *_tmp_133_var_1;
Token * a;
if (
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_134_var = _tmp_134_rule(p)) // lambda_param_no_default | ','
+ (_tmp_133_var = _tmp_133_rule(p)) // lambda_param_no_default | ','
&&
- (_loop0_75_var = _loop0_75_rule(p)) // lambda_param_maybe_default*
+ (_loop0_74_var = _loop0_74_rule(p)) // lambda_param_maybe_default*
&&
(a = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_134_var_1 = _tmp_134_rule(p)) // lambda_param_no_default | ','
+ (_tmp_133_var_1 = _tmp_133_rule(p)) // lambda_param_no_default | ','
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_star_etc[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*' (lambda_param_no_default | ',') lambda_param_maybe_default* '*' (lambda_param_no_default | ',')"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "* argument may appear only once" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "* may appear only once" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -23183,7 +23321,7 @@ invalid_lambda_kwds_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' lambda_param '='"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-keyword argument cannot have default value" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "var-keyword parameter cannot have default value" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -23216,7 +23354,7 @@ invalid_lambda_kwds_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' lambda_param ',' lambda_param"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "arguments cannot follow var-keyword argument" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "parameters cannot follow var-keyword parameter" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -23245,11 +23383,11 @@ invalid_lambda_kwds_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 12)) // token=','
&&
- (a = (Token*)_tmp_128_rule(p)) // '*' | '**' | '/'
+ (a = (Token*)_tmp_127_rule(p)) // '*' | '**' | '/'
)
{
D(fprintf(stderr, "%*c+ invalid_lambda_kwds[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**' lambda_param ',' ('*' | '**' | '/')"));
- _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "arguments cannot follow var-keyword argument" );
+ _res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "parameters cannot follow var-keyword parameter" );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -23347,11 +23485,11 @@ invalid_with_item_rule(Parser *p)
if (
(expression_var = expression_rule(p)) // expression
&&
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
(a = expression_rule(p)) // expression
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_36_rule, p)
+ _PyPegen_lookahead(1, _tmp_35_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_with_item[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression 'as' expression &(',' | ')' | ':')"));
@@ -23395,15 +23533,15 @@ invalid_for_if_clause_rule(Parser *p)
Token * _keyword;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
- void *_tmp_135_var;
+ void *_tmp_134_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (_keyword = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword = _PyPegen_expect_token(p, 699)) // token='for'
&&
- (_tmp_135_var = _tmp_135_rule(p)) // bitwise_or ((',' bitwise_or))* ','?
+ (_tmp_134_var = _tmp_134_rule(p)) // bitwise_or ((',' bitwise_or))* ','?
&&
- _PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 695) // token='in'
+ _PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 700) // token='in'
)
{
D(fprintf(stderr, "%*c+ invalid_for_if_clause[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'async'? 'for' (bitwise_or ((',' bitwise_or))* ','?) !'in'"));
@@ -23449,9 +23587,9 @@ invalid_for_target_rule(Parser *p)
UNUSED(_opt_var); // Silence compiler warnings
expr_ty a;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (_keyword = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword = _PyPegen_expect_token(p, 699)) // token='for'
&&
(a = star_expressions_rule(p)) // star_expressions
)
@@ -23576,16 +23714,16 @@ invalid_import_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_import[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'import' ','.dotted_name+ 'from' dotted_name"));
- asdl_seq * _gather_137_var;
+ asdl_seq * _gather_136_var;
Token * _keyword;
Token * a;
expr_ty dotted_name_var;
if (
- (a = _PyPegen_expect_token(p, 634)) // token='import'
+ (a = _PyPegen_expect_token(p, 639)) // token='import'
&&
- (_gather_137_var = _gather_137_rule(p)) // ','.dotted_name+
+ (_gather_136_var = _gather_136_rule(p)) // ','.dotted_name+
&&
- (_keyword = _PyPegen_expect_token(p, 633)) // token='from'
+ (_keyword = _PyPegen_expect_token(p, 638)) // token='from'
&&
(dotted_name_var = dotted_name_rule(p)) // dotted_name
)
@@ -23612,7 +23750,7 @@ invalid_import_rule(Parser *p)
Token * _keyword;
Token * token;
if (
- (_keyword = _PyPegen_expect_token(p, 634)) // token='import'
+ (_keyword = _PyPegen_expect_token(p, 639)) // token='import'
&&
(token = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
@@ -23661,9 +23799,9 @@ invalid_dotted_as_name_rule(Parser *p)
if (
(dotted_name_var = dotted_name_rule(p)) // dotted_name
&&
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_138_rule, p)
+ _PyPegen_lookahead(0, _tmp_137_rule, p)
&&
(a = expression_rule(p)) // expression
)
@@ -23712,9 +23850,9 @@ invalid_import_from_as_name_rule(Parser *p)
if (
(name_var = _PyPegen_name_token(p)) // NAME
&&
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_138_rule, p)
+ _PyPegen_lookahead(0, _tmp_137_rule, p)
&&
(a = expression_rule(p)) // expression
)
@@ -23832,17 +23970,17 @@ invalid_with_stmt_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_with_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'async'? 'with' ','.(expression ['as' star_target])+ NEWLINE"));
- asdl_seq * _gather_140_var;
+ asdl_seq * _gather_139_var;
Token * _keyword;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
Token * newline_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (_keyword = _PyPegen_expect_token(p, 647)) // token='with'
+ (_keyword = _PyPegen_expect_token(p, 652)) // token='with'
&&
- (_gather_140_var = _gather_140_rule(p)) // ','.(expression ['as' star_target])+
+ (_gather_139_var = _gather_139_rule(p)) // ','.(expression ['as' star_target])+
&&
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
@@ -23866,7 +24004,7 @@ invalid_with_stmt_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_with_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'async'? 'with' '(' ','.(expressions ['as' star_target])+ ','? ')' NEWLINE"));
- asdl_seq * _gather_142_var;
+ asdl_seq * _gather_141_var;
Token * _keyword;
Token * _literal;
Token * _literal_1;
@@ -23876,13 +24014,13 @@ invalid_with_stmt_rule(Parser *p)
UNUSED(_opt_var_1); // Silence compiler warnings
Token * newline_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (_keyword = _PyPegen_expect_token(p, 647)) // token='with'
+ (_keyword = _PyPegen_expect_token(p, 652)) // token='with'
&&
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
- (_gather_142_var = _gather_142_rule(p)) // ','.(expressions ['as' star_target])+
+ (_gather_141_var = _gather_141_rule(p)) // ','.(expressions ['as' star_target])+
&&
(_opt_var_1 = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
&&
@@ -23931,18 +24069,18 @@ invalid_with_stmt_indent_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_with_stmt_indent[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'async'? 'with' ','.(expression ['as' star_target])+ ':' NEWLINE !INDENT"));
- asdl_seq * _gather_140_var;
+ asdl_seq * _gather_139_var;
Token * _literal;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
Token * a;
Token * newline_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (a = _PyPegen_expect_token(p, 647)) // token='with'
+ (a = _PyPegen_expect_token(p, 652)) // token='with'
&&
- (_gather_140_var = _gather_140_rule(p)) // ','.(expression ['as' star_target])+
+ (_gather_139_var = _gather_139_rule(p)) // ','.(expression ['as' star_target])+
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -23970,7 +24108,7 @@ invalid_with_stmt_indent_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_with_stmt_indent[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'async'? 'with' '(' ','.(expressions ['as' star_target])+ ','? ')' ':' NEWLINE !INDENT"));
- asdl_seq * _gather_142_var;
+ asdl_seq * _gather_141_var;
Token * _literal;
Token * _literal_1;
Token * _literal_2;
@@ -23981,13 +24119,13 @@ invalid_with_stmt_indent_rule(Parser *p)
Token * a;
Token * newline_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (a = _PyPegen_expect_token(p, 647)) // token='with'
+ (a = _PyPegen_expect_token(p, 652)) // token='with'
&&
(_literal = _PyPegen_expect_token(p, 7)) // token='('
&&
- (_gather_142_var = _gather_142_rule(p)) // ','.(expressions ['as' star_target])+
+ (_gather_141_var = _gather_141_rule(p)) // ','.(expressions ['as' star_target])+
&&
(_opt_var_1 = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
&&
@@ -24046,7 +24184,7 @@ invalid_try_stmt_rule(Parser *p)
Token * a;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 656)) // token='try'
+ (a = _PyPegen_expect_token(p, 661)) // token='try'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -24078,13 +24216,13 @@ invalid_try_stmt_rule(Parser *p)
Token * _literal;
asdl_stmt_seq* block_var;
if (
- (_keyword = _PyPegen_expect_token(p, 656)) // token='try'
+ (_keyword = _PyPegen_expect_token(p, 661)) // token='try'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
(block_var = block_rule(p)) // block
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_143_rule, p)
+ _PyPegen_lookahead(0, _tmp_142_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_try_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'try' ':' block !('except' | 'finally')"));
@@ -24109,29 +24247,29 @@ invalid_try_stmt_rule(Parser *p)
Token * _keyword;
Token * _literal;
Token * _literal_1;
- asdl_seq * _loop0_144_var;
- asdl_seq * _loop1_37_var;
+ asdl_seq * _loop0_143_var;
+ asdl_seq * _loop1_36_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
Token * a;
Token * b;
expr_ty expression_var;
if (
- (_keyword = _PyPegen_expect_token(p, 656)) // token='try'
+ (_keyword = _PyPegen_expect_token(p, 661)) // token='try'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
- (_loop0_144_var = _loop0_144_rule(p)) // block*
+ (_loop0_143_var = _loop0_143_rule(p)) // block*
&&
- (_loop1_37_var = _loop1_37_rule(p)) // except_block+
+ (_loop1_36_var = _loop1_36_rule(p)) // except_block+
&&
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(b = _PyPegen_expect_token(p, 16)) // token='*'
&&
(expression_var = expression_rule(p)) // expression
&&
- (_opt_var = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (_opt_var = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
&&
(_literal_1 = _PyPegen_expect_token(p, 11)) // token=':'
)
@@ -24158,23 +24296,23 @@ invalid_try_stmt_rule(Parser *p)
Token * _keyword;
Token * _literal;
Token * _literal_1;
- asdl_seq * _loop0_144_var;
- asdl_seq * _loop1_38_var;
+ asdl_seq * _loop0_143_var;
+ asdl_seq * _loop1_37_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
Token * a;
if (
- (_keyword = _PyPegen_expect_token(p, 656)) // token='try'
+ (_keyword = _PyPegen_expect_token(p, 661)) // token='try'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
- (_loop0_144_var = _loop0_144_rule(p)) // block*
+ (_loop0_143_var = _loop0_143_rule(p)) // block*
&&
- (_loop1_38_var = _loop1_38_rule(p)) // except_star_block+
+ (_loop1_37_var = _loop1_37_rule(p)) // except_star_block+
&&
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
- (_opt_var = _tmp_145_rule(p), !p->error_indicator) // [expression ['as' NAME]]
+ (_opt_var = _tmp_144_rule(p), !p->error_indicator) // [expression ['as' NAME]]
&&
(_literal_1 = _PyPegen_expect_token(p, 11)) // token=':'
)
@@ -24202,7 +24340,7 @@ invalid_try_stmt_rule(Parser *p)
// | 'except' expression ',' expressions 'as' NAME ':'
// | 'except' expression ['as' NAME] NEWLINE
// | 'except' NEWLINE
-// | 'except' expression 'as' expression
+// | 'except' expression 'as' expression ':' block
static void *
invalid_except_stmt_rule(Parser *p)
{
@@ -24229,7 +24367,7 @@ invalid_except_stmt_rule(Parser *p)
expr_ty expressions_var;
expr_ty name_var;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(a = expression_rule(p)) // expression
&&
@@ -24237,7 +24375,7 @@ invalid_except_stmt_rule(Parser *p)
&&
(expressions_var = expressions_rule(p)) // expressions
&&
- (_keyword_1 = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword_1 = _PyPegen_expect_token(p, 685)) // token='as'
&&
(name_var = _PyPegen_name_token(p)) // NAME
&&
@@ -24269,11 +24407,11 @@ invalid_except_stmt_rule(Parser *p)
expr_ty expression_var;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(expression_var = expression_rule(p)) // expression
&&
- (_opt_var = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (_opt_var = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
&&
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
@@ -24300,7 +24438,7 @@ invalid_except_stmt_rule(Parser *p)
Token * a;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
@@ -24318,27 +24456,33 @@ invalid_except_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c%s invalid_except_stmt[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except' NEWLINE"));
}
- { // 'except' expression 'as' expression
+ { // 'except' expression 'as' expression ':' block
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> invalid_except_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except' expression 'as' expression"));
+ D(fprintf(stderr, "%*c> invalid_except_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except' expression 'as' expression ':' block"));
Token * _keyword;
Token * _keyword_1;
+ Token * _literal;
expr_ty a;
+ asdl_stmt_seq* block_var;
expr_ty expression_var;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(expression_var = expression_rule(p)) // expression
&&
- (_keyword_1 = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword_1 = _PyPegen_expect_token(p, 685)) // token='as'
&&
(a = expression_rule(p)) // expression
+ &&
+ (_literal = _PyPegen_expect_token(p, 11)) // token=':'
+ &&
+ (block_var = block_rule(p)) // block
)
{
- D(fprintf(stderr, "%*c+ invalid_except_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' expression 'as' expression"));
+ D(fprintf(stderr, "%*c+ invalid_except_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' expression 'as' expression ':' block"));
_res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "cannot use except statement with %s" , _PyPegen_get_expr_name ( a ) );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -24349,7 +24493,7 @@ invalid_except_stmt_rule(Parser *p)
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s invalid_except_stmt[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except' expression 'as' expression"));
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except' expression 'as' expression ':' block"));
}
_res = NULL;
done:
@@ -24361,7 +24505,7 @@ invalid_except_stmt_rule(Parser *p)
// | 'except' '*' expression ',' expressions 'as' NAME ':'
// | 'except' '*' expression ['as' NAME] NEWLINE
// | 'except' '*' (NEWLINE | ':')
-// | 'except' '*' expression 'as' expression
+// | 'except' '*' expression 'as' expression ':' block
static void *
invalid_except_star_stmt_rule(Parser *p)
{
@@ -24389,7 +24533,7 @@ invalid_except_star_stmt_rule(Parser *p)
expr_ty expressions_var;
expr_ty name_var;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
@@ -24399,7 +24543,7 @@ invalid_except_star_stmt_rule(Parser *p)
&&
(expressions_var = expressions_rule(p)) // expressions
&&
- (_keyword_1 = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword_1 = _PyPegen_expect_token(p, 685)) // token='as'
&&
(name_var = _PyPegen_name_token(p)) // NAME
&&
@@ -24432,13 +24576,13 @@ invalid_except_star_stmt_rule(Parser *p)
expr_ty expression_var;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
(expression_var = expression_rule(p)) // expression
&&
- (_opt_var = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (_opt_var = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
&&
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
@@ -24463,14 +24607,14 @@ invalid_except_star_stmt_rule(Parser *p)
}
D(fprintf(stderr, "%*c> invalid_except_star_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except' '*' (NEWLINE | ':')"));
Token * _literal;
- void *_tmp_146_var;
+ void *_tmp_145_var;
Token * a;
if (
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
- (_tmp_146_var = _tmp_146_rule(p)) // NEWLINE | ':'
+ (_tmp_145_var = _tmp_145_rule(p)) // NEWLINE | ':'
)
{
D(fprintf(stderr, "%*c+ invalid_except_star_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' '*' (NEWLINE | ':')"));
@@ -24486,30 +24630,36 @@ invalid_except_star_stmt_rule(Parser *p)
D(fprintf(stderr, "%*c%s invalid_except_star_stmt[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except' '*' (NEWLINE | ':')"));
}
- { // 'except' '*' expression 'as' expression
+ { // 'except' '*' expression 'as' expression ':' block
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> invalid_except_star_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except' '*' expression 'as' expression"));
+ D(fprintf(stderr, "%*c> invalid_except_star_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except' '*' expression 'as' expression ':' block"));
Token * _keyword;
Token * _keyword_1;
Token * _literal;
+ Token * _literal_1;
expr_ty a;
+ asdl_stmt_seq* block_var;
expr_ty expression_var;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
(expression_var = expression_rule(p)) // expression
&&
- (_keyword_1 = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword_1 = _PyPegen_expect_token(p, 685)) // token='as'
&&
(a = expression_rule(p)) // expression
+ &&
+ (_literal_1 = _PyPegen_expect_token(p, 11)) // token=':'
+ &&
+ (block_var = block_rule(p)) // block
)
{
- D(fprintf(stderr, "%*c+ invalid_except_star_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' '*' expression 'as' expression"));
+ D(fprintf(stderr, "%*c+ invalid_except_star_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' '*' expression 'as' expression ':' block"));
_res = RAISE_SYNTAX_ERROR_KNOWN_LOCATION ( a , "cannot use except* statement with %s" , _PyPegen_get_expr_name ( a ) );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -24520,7 +24670,7 @@ invalid_except_star_stmt_rule(Parser *p)
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s invalid_except_star_stmt[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except' '*' expression 'as' expression"));
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except' '*' expression 'as' expression ':' block"));
}
_res = NULL;
done:
@@ -24551,7 +24701,7 @@ invalid_finally_stmt_rule(Parser *p)
Token * a;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 673)) // token='finally'
+ (a = _PyPegen_expect_token(p, 678)) // token='finally'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -24607,11 +24757,11 @@ invalid_except_stmt_indent_rule(Parser *p)
expr_ty expression_var;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(expression_var = expression_rule(p)) // expression
&&
- (_opt_var = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (_opt_var = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -24643,7 +24793,7 @@ invalid_except_stmt_indent_rule(Parser *p)
Token * a;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -24699,13 +24849,13 @@ invalid_except_star_stmt_indent_rule(Parser *p)
expr_ty expression_var;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 677)) // token='except'
+ (a = _PyPegen_expect_token(p, 682)) // token='except'
&&
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
&&
(expression_var = expression_rule(p)) // expression
&&
- (_opt_var = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (_opt_var = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
&&
(_literal_1 = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -24938,7 +25088,7 @@ invalid_as_pattern_rule(Parser *p)
if (
(or_pattern_var = or_pattern_rule(p)) // or_pattern
&&
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
(a = _PyPegen_expect_soft_keyword(p, "_")) // soft_keyword='"_"'
)
@@ -24968,7 +25118,7 @@ invalid_as_pattern_rule(Parser *p)
if (
(or_pattern_var = or_pattern_rule(p)) // or_pattern
&&
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
(a = expression_rule(p)) // expression
)
@@ -25067,7 +25217,7 @@ invalid_class_argument_pattern_rule(Parser *p)
asdl_pattern_seq* a;
asdl_seq* keyword_patterns_var;
if (
- (_opt_var = _tmp_147_rule(p), !p->error_indicator) // [positional_patterns ',']
+ (_opt_var = _tmp_146_rule(p), !p->error_indicator) // [positional_patterns ',']
&&
(keyword_patterns_var = keyword_patterns_rule(p)) // keyword_patterns
&&
@@ -25120,7 +25270,7 @@ invalid_if_stmt_rule(Parser *p)
expr_ty named_expression_var;
Token * newline_var;
if (
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(named_expression_var = named_expression_rule(p)) // named_expression
&&
@@ -25151,7 +25301,7 @@ invalid_if_stmt_rule(Parser *p)
expr_ty a_1;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 682)) // token='if'
+ (a = _PyPegen_expect_token(p, 687)) // token='if'
&&
(a_1 = named_expression_rule(p)) // named_expression
&&
@@ -25206,7 +25356,7 @@ invalid_elif_stmt_rule(Parser *p)
expr_ty named_expression_var;
Token * newline_var;
if (
- (_keyword = _PyPegen_expect_token(p, 687)) // token='elif'
+ (_keyword = _PyPegen_expect_token(p, 692)) // token='elif'
&&
(named_expression_var = named_expression_rule(p)) // named_expression
&&
@@ -25237,7 +25387,7 @@ invalid_elif_stmt_rule(Parser *p)
expr_ty named_expression_var;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 687)) // token='elif'
+ (a = _PyPegen_expect_token(p, 692)) // token='elif'
&&
(named_expression_var = named_expression_rule(p)) // named_expression
&&
@@ -25290,7 +25440,7 @@ invalid_else_stmt_rule(Parser *p)
Token * a;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 686)) // token='else'
+ (a = _PyPegen_expect_token(p, 691)) // token='else'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -25323,13 +25473,13 @@ invalid_else_stmt_rule(Parser *p)
Token * _literal;
asdl_stmt_seq* block_var;
if (
- (_keyword = _PyPegen_expect_token(p, 686)) // token='else'
+ (_keyword = _PyPegen_expect_token(p, 691)) // token='else'
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
(block_var = block_rule(p)) // block
&&
- (_keyword_1 = _PyPegen_expect_token(p, 687)) // token='elif'
+ (_keyword_1 = _PyPegen_expect_token(p, 692)) // token='elif'
)
{
D(fprintf(stderr, "%*c+ invalid_else_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'else' ':' block 'elif'"));
@@ -25376,7 +25526,7 @@ invalid_while_stmt_rule(Parser *p)
expr_ty named_expression_var;
Token * newline_var;
if (
- (_keyword = _PyPegen_expect_token(p, 689)) // token='while'
+ (_keyword = _PyPegen_expect_token(p, 694)) // token='while'
&&
(named_expression_var = named_expression_rule(p)) // named_expression
&&
@@ -25407,7 +25557,7 @@ invalid_while_stmt_rule(Parser *p)
expr_ty named_expression_var;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 689)) // token='while'
+ (a = _PyPegen_expect_token(p, 694)) // token='while'
&&
(named_expression_var = named_expression_rule(p)) // named_expression
&&
@@ -25466,13 +25616,13 @@ invalid_for_stmt_rule(Parser *p)
expr_ty star_expressions_var;
expr_ty star_targets_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (_keyword = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword = _PyPegen_expect_token(p, 699)) // token='for'
&&
(star_targets_var = star_targets_rule(p)) // star_targets
&&
- (_keyword_1 = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword_1 = _PyPegen_expect_token(p, 700)) // token='in'
&&
(star_expressions_var = star_expressions_rule(p)) // star_expressions
&&
@@ -25507,13 +25657,13 @@ invalid_for_stmt_rule(Parser *p)
expr_ty star_expressions_var;
expr_ty star_targets_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (a = _PyPegen_expect_token(p, 694)) // token='for'
+ (a = _PyPegen_expect_token(p, 699)) // token='for'
&&
(star_targets_var = star_targets_rule(p)) // star_targets
&&
- (_keyword = _PyPegen_expect_token(p, 695)) // token='in'
+ (_keyword = _PyPegen_expect_token(p, 700)) // token='in'
&&
(star_expressions_var = star_expressions_rule(p)) // star_expressions
&&
@@ -25579,9 +25729,9 @@ invalid_def_raw_rule(Parser *p)
expr_ty name_var;
Token * newline_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (a = _PyPegen_expect_token(p, 699)) // token='def'
+ (a = _PyPegen_expect_token(p, 704)) // token='def'
&&
(name_var = _PyPegen_name_token(p)) // NAME
&&
@@ -25593,7 +25743,7 @@ invalid_def_raw_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
&&
- (_opt_var_3 = _tmp_27_rule(p), !p->error_indicator) // ['->' expression]
+ (_opt_var_3 = _tmp_26_rule(p), !p->error_indicator) // ['->' expression]
&&
(_literal_2 = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -25638,9 +25788,9 @@ invalid_def_raw_rule(Parser *p)
asdl_stmt_seq* block_var;
expr_ty name_var;
if (
- (_opt_var = _PyPegen_expect_token(p, 698), !p->error_indicator) // 'async'?
+ (_opt_var = _PyPegen_expect_token(p, 703), !p->error_indicator) // 'async'?
&&
- (_keyword = _PyPegen_expect_token(p, 699)) // token='def'
+ (_keyword = _PyPegen_expect_token(p, 704)) // token='def'
&&
(name_var = _PyPegen_name_token(p)) // NAME
&&
@@ -25652,7 +25802,7 @@ invalid_def_raw_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
&&
- (_opt_var_3 = _tmp_27_rule(p), !p->error_indicator) // ['->' expression]
+ (_opt_var_3 = _tmp_26_rule(p), !p->error_indicator) // ['->' expression]
&&
(_literal_2 = _PyPegen_expect_forced_token(p, 11, ":")) // forced_token=':'
&&
@@ -25704,13 +25854,13 @@ invalid_class_def_raw_rule(Parser *p)
expr_ty name_var;
Token * newline_var;
if (
- (_keyword = _PyPegen_expect_token(p, 701)) // token='class'
+ (_keyword = _PyPegen_expect_token(p, 706)) // token='class'
&&
(name_var = _PyPegen_name_token(p)) // NAME
&&
(_opt_var = type_params_rule(p), !p->error_indicator) // type_params?
&&
- (_opt_var_1 = _tmp_26_rule(p), !p->error_indicator) // ['(' arguments? ')']
+ (_opt_var_1 = _tmp_25_rule(p), !p->error_indicator) // ['(' arguments? ')']
&&
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
@@ -25743,13 +25893,13 @@ invalid_class_def_raw_rule(Parser *p)
expr_ty name_var;
Token * newline_var;
if (
- (a = _PyPegen_expect_token(p, 701)) // token='class'
+ (a = _PyPegen_expect_token(p, 706)) // token='class'
&&
(name_var = _PyPegen_name_token(p)) // NAME
&&
(_opt_var = type_params_rule(p), !p->error_indicator) // type_params?
&&
- (_opt_var_1 = _tmp_26_rule(p), !p->error_indicator) // ['(' arguments? ')']
+ (_opt_var_1 = _tmp_25_rule(p), !p->error_indicator) // ['(' arguments? ')']
&&
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
&&
@@ -25799,11 +25949,11 @@ invalid_double_starred_kvpairs_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_double_starred_kvpairs[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.double_starred_kvpair+ ',' invalid_kvpair"));
- asdl_seq * _gather_84_var;
+ asdl_seq * _gather_83_var;
Token * _literal;
void *invalid_kvpair_var;
if (
- (_gather_84_var = _gather_84_rule(p)) // ','.double_starred_kvpair+
+ (_gather_83_var = _gather_83_rule(p)) // ','.double_starred_kvpair+
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
@@ -25811,7 +25961,7 @@ invalid_double_starred_kvpairs_rule(Parser *p)
)
{
D(fprintf(stderr, "%*c+ invalid_double_starred_kvpairs[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.double_starred_kvpair+ ',' invalid_kvpair"));
- _res = _PyPegen_dummy_name(p, _gather_84_var, _literal, invalid_kvpair_var);
+ _res = _PyPegen_dummy_name(p, _gather_83_var, _literal, invalid_kvpair_var);
goto done;
}
p->mark = _mark;
@@ -25864,7 +26014,7 @@ invalid_double_starred_kvpairs_rule(Parser *p)
&&
(a = _PyPegen_expect_token(p, 11)) // token=':'
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_148_rule, p)
+ _PyPegen_lookahead(1, _tmp_147_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_double_starred_kvpairs[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ':' &('}' | ',')"));
@@ -25974,7 +26124,7 @@ invalid_kvpair_rule(Parser *p)
&&
(a = _PyPegen_expect_token(p, 11)) // token=':'
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_148_rule, p)
+ _PyPegen_lookahead(1, _tmp_147_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_kvpair[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ':' &('}' | ',')"));
@@ -26233,7 +26383,7 @@ invalid_fstring_replacement_field_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 25)) // token='{'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) annotated_rhs_rule, p)
+ _PyPegen_lookahead_for_expr(0, annotated_rhs_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_fstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' !annotated_rhs"));
@@ -26262,7 +26412,7 @@ invalid_fstring_replacement_field_rule(Parser *p)
&&
(annotated_rhs_var = annotated_rhs_rule(p)) // annotated_rhs
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_149_rule, p)
+ _PyPegen_lookahead(0, _tmp_148_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_fstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs !('=' | '!' | ':' | '}')"));
@@ -26294,7 +26444,7 @@ invalid_fstring_replacement_field_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 22)) // token='='
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_150_rule, p)
+ _PyPegen_lookahead(0, _tmp_149_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_fstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs '=' !('!' | ':' | '}')"));
@@ -26358,9 +26508,9 @@ invalid_fstring_replacement_field_rule(Parser *p)
&&
(_opt_var = _PyPegen_expect_token(p, 22), !p->error_indicator) // '='?
&&
- (_opt_var_1 = _tmp_151_rule(p), !p->error_indicator) // ['!' NAME]
+ (_opt_var_1 = _tmp_150_rule(p), !p->error_indicator) // ['!' NAME]
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_152_rule, p)
+ _PyPegen_lookahead(0, _tmp_151_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_fstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs '='? ['!' NAME] !(':' | '}')"));
@@ -26384,7 +26534,7 @@ invalid_fstring_replacement_field_rule(Parser *p)
D(fprintf(stderr, "%*c> invalid_fstring_replacement_field[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs '='? ['!' NAME] ':' fstring_format_spec* !'}'"));
Token * _literal;
Token * _literal_1;
- asdl_seq * _loop0_77_var;
+ asdl_seq * _loop0_76_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
void *_opt_var_1;
@@ -26397,11 +26547,11 @@ invalid_fstring_replacement_field_rule(Parser *p)
&&
(_opt_var = _PyPegen_expect_token(p, 22), !p->error_indicator) // '='?
&&
- (_opt_var_1 = _tmp_151_rule(p), !p->error_indicator) // ['!' NAME]
+ (_opt_var_1 = _tmp_150_rule(p), !p->error_indicator) // ['!' NAME]
&&
(_literal_1 = _PyPegen_expect_token(p, 11)) // token=':'
&&
- (_loop0_77_var = _loop0_77_rule(p)) // fstring_format_spec*
+ (_loop0_76_var = _loop0_76_rule(p)) // fstring_format_spec*
&&
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 26) // token='}'
)
@@ -26438,7 +26588,7 @@ invalid_fstring_replacement_field_rule(Parser *p)
&&
(_opt_var = _PyPegen_expect_token(p, 22), !p->error_indicator) // '='?
&&
- (_opt_var_1 = _tmp_151_rule(p), !p->error_indicator) // ['!' NAME]
+ (_opt_var_1 = _tmp_150_rule(p), !p->error_indicator) // ['!' NAME]
&&
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 26) // token='}'
)
@@ -26485,7 +26635,7 @@ invalid_fstring_conversion_character_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 54)) // token='!'
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_152_rule, p)
+ _PyPegen_lookahead(1, _tmp_151_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_fstring_conversion_character[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!' &(':' | '}')"));
@@ -26511,7 +26661,7 @@ invalid_fstring_conversion_character_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 54)) // token='!'
&&
- _PyPegen_lookahead_with_name(0, _PyPegen_name_token, p)
+ _PyPegen_lookahead_for_expr(0, _PyPegen_name_token, p)
)
{
D(fprintf(stderr, "%*c+ invalid_fstring_conversion_character[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!' !NAME"));
@@ -26675,7 +26825,7 @@ invalid_tstring_replacement_field_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 25)) // token='{'
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) annotated_rhs_rule, p)
+ _PyPegen_lookahead_for_expr(0, annotated_rhs_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_tstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' !annotated_rhs"));
@@ -26704,7 +26854,7 @@ invalid_tstring_replacement_field_rule(Parser *p)
&&
(annotated_rhs_var = annotated_rhs_rule(p)) // annotated_rhs
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_149_rule, p)
+ _PyPegen_lookahead(0, _tmp_148_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_tstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs !('=' | '!' | ':' | '}')"));
@@ -26736,7 +26886,7 @@ invalid_tstring_replacement_field_rule(Parser *p)
&&
(_literal_1 = _PyPegen_expect_token(p, 22)) // token='='
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_150_rule, p)
+ _PyPegen_lookahead(0, _tmp_149_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_tstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs '=' !('!' | ':' | '}')"));
@@ -26800,9 +26950,9 @@ invalid_tstring_replacement_field_rule(Parser *p)
&&
(_opt_var = _PyPegen_expect_token(p, 22), !p->error_indicator) // '='?
&&
- (_opt_var_1 = _tmp_151_rule(p), !p->error_indicator) // ['!' NAME]
+ (_opt_var_1 = _tmp_150_rule(p), !p->error_indicator) // ['!' NAME]
&&
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _tmp_152_rule, p)
+ _PyPegen_lookahead(0, _tmp_151_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_tstring_replacement_field[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs '='? ['!' NAME] !(':' | '}')"));
@@ -26826,7 +26976,7 @@ invalid_tstring_replacement_field_rule(Parser *p)
D(fprintf(stderr, "%*c> invalid_tstring_replacement_field[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{' annotated_rhs '='? ['!' NAME] ':' fstring_format_spec* !'}'"));
Token * _literal;
Token * _literal_1;
- asdl_seq * _loop0_77_var;
+ asdl_seq * _loop0_76_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
void *_opt_var_1;
@@ -26839,11 +26989,11 @@ invalid_tstring_replacement_field_rule(Parser *p)
&&
(_opt_var = _PyPegen_expect_token(p, 22), !p->error_indicator) // '='?
&&
- (_opt_var_1 = _tmp_151_rule(p), !p->error_indicator) // ['!' NAME]
+ (_opt_var_1 = _tmp_150_rule(p), !p->error_indicator) // ['!' NAME]
&&
(_literal_1 = _PyPegen_expect_token(p, 11)) // token=':'
&&
- (_loop0_77_var = _loop0_77_rule(p)) // fstring_format_spec*
+ (_loop0_76_var = _loop0_76_rule(p)) // fstring_format_spec*
&&
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 26) // token='}'
)
@@ -26880,7 +27030,7 @@ invalid_tstring_replacement_field_rule(Parser *p)
&&
(_opt_var = _PyPegen_expect_token(p, 22), !p->error_indicator) // '='?
&&
- (_opt_var_1 = _tmp_151_rule(p), !p->error_indicator) // ['!' NAME]
+ (_opt_var_1 = _tmp_150_rule(p), !p->error_indicator) // ['!' NAME]
&&
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 26) // token='}'
)
@@ -26927,7 +27077,7 @@ invalid_tstring_conversion_character_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 54)) // token='!'
&&
- _PyPegen_lookahead(1, (void *(*)(Parser *)) _tmp_152_rule, p)
+ _PyPegen_lookahead(1, _tmp_151_rule, p)
)
{
D(fprintf(stderr, "%*c+ invalid_tstring_conversion_character[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!' &(':' | '}')"));
@@ -26953,7 +27103,7 @@ invalid_tstring_conversion_character_rule(Parser *p)
if (
(_literal = _PyPegen_expect_token(p, 54)) // token='!'
&&
- _PyPegen_lookahead_with_name(0, _PyPegen_name_token, p)
+ _PyPegen_lookahead_for_expr(0, _PyPegen_name_token, p)
)
{
D(fprintf(stderr, "%*c+ invalid_tstring_conversion_character[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!' !NAME"));
@@ -26994,16 +27144,16 @@ invalid_arithmetic_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_arithmetic[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "sum ('+' | '-' | '*' | '/' | '%' | '//' | '@') 'not' inversion"));
- void *_tmp_153_var;
+ void *_tmp_152_var;
Token * a;
expr_ty b;
expr_ty sum_var;
if (
(sum_var = sum_rule(p)) // sum
&&
- (_tmp_153_var = _tmp_153_rule(p)) // '+' | '-' | '*' | '/' | '%' | '//' | '@'
+ (_tmp_152_var = _tmp_152_rule(p)) // '+' | '-' | '*' | '/' | '%' | '//' | '@'
&&
- (a = _PyPegen_expect_token(p, 703)) // token='not'
+ (a = _PyPegen_expect_token(p, 708)) // token='not'
&&
(b = inversion_rule(p)) // inversion
)
@@ -27046,13 +27196,13 @@ invalid_factor_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> invalid_factor[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('+' | '-' | '~') 'not' factor"));
- void *_tmp_154_var;
+ void *_tmp_153_var;
Token * a;
expr_ty b;
if (
- (_tmp_154_var = _tmp_154_rule(p)) // '+' | '-' | '~'
+ (_tmp_153_var = _tmp_153_rule(p)) // '+' | '-' | '~'
&&
- (a = _PyPegen_expect_token(p, 703)) // token='not'
+ (a = _PyPegen_expect_token(p, 708)) // token='not'
&&
(b = factor_rule(p)) // factor
)
@@ -27399,7 +27549,7 @@ _tmp_5_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_5[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'import'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 634)) // token='import'
+ (_keyword = _PyPegen_expect_token(p, 639)) // token='import'
)
{
D(fprintf(stderr, "%*c+ _tmp_5[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'import'"));
@@ -27418,7 +27568,7 @@ _tmp_5_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_5[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'from'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 633)) // token='from'
+ (_keyword = _PyPegen_expect_token(p, 638)) // token='from'
)
{
D(fprintf(stderr, "%*c+ _tmp_5[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'from'"));
@@ -27456,7 +27606,7 @@ _tmp_6_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_6[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'def'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 699)) // token='def'
+ (_keyword = _PyPegen_expect_token(p, 704)) // token='def'
)
{
D(fprintf(stderr, "%*c+ _tmp_6[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'def'"));
@@ -27494,7 +27644,7 @@ _tmp_6_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_6[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'async'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
)
{
D(fprintf(stderr, "%*c+ _tmp_6[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'async'"));
@@ -27532,7 +27682,7 @@ _tmp_7_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_7[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'class'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 701)) // token='class'
+ (_keyword = _PyPegen_expect_token(p, 706)) // token='class'
)
{
D(fprintf(stderr, "%*c+ _tmp_7[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'class'"));
@@ -27589,7 +27739,7 @@ _tmp_8_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_8[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'with'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 647)) // token='with'
+ (_keyword = _PyPegen_expect_token(p, 652)) // token='with'
)
{
D(fprintf(stderr, "%*c+ _tmp_8[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'with'"));
@@ -27608,7 +27758,7 @@ _tmp_8_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_8[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'async'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
)
{
D(fprintf(stderr, "%*c+ _tmp_8[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'async'"));
@@ -27646,7 +27796,7 @@ _tmp_9_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_9[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'for'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 694)) // token='for'
+ (_keyword = _PyPegen_expect_token(p, 699)) // token='for'
)
{
D(fprintf(stderr, "%*c+ _tmp_9[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'for'"));
@@ -27665,7 +27815,7 @@ _tmp_9_rule(Parser *p)
D(fprintf(stderr, "%*c> _tmp_9[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'async'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 698)) // token='async'
+ (_keyword = _PyPegen_expect_token(p, 703)) // token='async'
)
{
D(fprintf(stderr, "%*c+ _tmp_9[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'async'"));
@@ -27824,12 +27974,12 @@ _loop1_12_rule(Parser *p)
return NULL;
}
D(fprintf(stderr, "%*c> _loop1_12[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')"));
- void *_tmp_155_var;
+ void *_tmp_154_var;
while (
- (_tmp_155_var = _tmp_155_rule(p)) // star_targets '='
+ (_tmp_154_var = _tmp_154_rule(p)) // star_targets '='
)
{
- _res = _tmp_155_var;
+ _res = _tmp_154_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -27868,55 +28018,9 @@ _loop1_12_rule(Parser *p)
return _seq;
}
-// _tmp_13: 'from' expression
-static void *
-_tmp_13_rule(Parser *p)
-{
- if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
- _Pypegen_stack_overflow(p);
- }
- if (p->error_indicator) {
- p->level--;
- return NULL;
- }
- void * _res = NULL;
- int _mark = p->mark;
- { // 'from' expression
- if (p->error_indicator) {
- p->level--;
- return NULL;
- }
- D(fprintf(stderr, "%*c> _tmp_13[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'from' expression"));
- Token * _keyword;
- expr_ty z;
- if (
- (_keyword = _PyPegen_expect_token(p, 633)) // token='from'
- &&
- (z = expression_rule(p)) // expression
- )
- {
- D(fprintf(stderr, "%*c+ _tmp_13[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'from' expression"));
- _res = z;
- if (_res == NULL && PyErr_Occurred()) {
- p->error_indicator = 1;
- p->level--;
- return NULL;
- }
- goto done;
- }
- p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_13[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'from' expression"));
- }
- _res = NULL;
- done:
- p->level--;
- return _res;
-}
-
-// _loop0_14: ',' NAME
+// _loop0_13: ',' NAME
static asdl_seq *
-_loop0_14_rule(Parser *p)
+_loop0_13_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -27941,7 +28045,7 @@ _loop0_14_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_14[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' NAME"));
+ D(fprintf(stderr, "%*c> _loop0_13[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' NAME"));
Token * _literal;
expr_ty elem;
while (
@@ -27973,7 +28077,7 @@ _loop0_14_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_14[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_13[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' NAME"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -27990,9 +28094,9 @@ _loop0_14_rule(Parser *p)
return _seq;
}
-// _gather_15: NAME _loop0_14
+// _gather_14: NAME _loop0_13
static asdl_seq *
-_gather_15_rule(Parser *p)
+_gather_14_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28003,27 +28107,27 @@ _gather_15_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // NAME _loop0_14
+ { // NAME _loop0_13
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_15[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME _loop0_14"));
+ D(fprintf(stderr, "%*c> _gather_14[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME _loop0_13"));
expr_ty elem;
asdl_seq * seq;
if (
(elem = _PyPegen_name_token(p)) // NAME
&&
- (seq = _loop0_14_rule(p)) // _loop0_14
+ (seq = _loop0_13_rule(p)) // _loop0_13
)
{
- D(fprintf(stderr, "%*c+ _gather_15[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME _loop0_14"));
+ D(fprintf(stderr, "%*c+ _gather_14[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME _loop0_13"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_15[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME _loop0_14"));
+ D(fprintf(stderr, "%*c%s _gather_14[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME _loop0_13"));
}
_res = NULL;
done:
@@ -28031,9 +28135,9 @@ _gather_15_rule(Parser *p)
return _res;
}
-// _tmp_16: ';' | NEWLINE
+// _tmp_15: ';' | NEWLINE
static void *
-_tmp_16_rule(Parser *p)
+_tmp_15_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28049,18 +28153,18 @@ _tmp_16_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_16[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "';'"));
+ D(fprintf(stderr, "%*c> _tmp_15[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "';'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 13)) // token=';'
)
{
- D(fprintf(stderr, "%*c+ _tmp_16[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "';'"));
+ D(fprintf(stderr, "%*c+ _tmp_15[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "';'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_16[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_15[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "';'"));
}
{ // NEWLINE
@@ -28068,18 +28172,18 @@ _tmp_16_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_16[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
+ D(fprintf(stderr, "%*c> _tmp_15[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
Token * newline_var;
if (
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
{
- D(fprintf(stderr, "%*c+ _tmp_16[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
+ D(fprintf(stderr, "%*c+ _tmp_15[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
_res = newline_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_16[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_15[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NEWLINE"));
}
_res = NULL;
@@ -28088,9 +28192,9 @@ _tmp_16_rule(Parser *p)
return _res;
}
-// _tmp_17: ',' expression
+// _tmp_16: ',' expression
static void *
-_tmp_17_rule(Parser *p)
+_tmp_16_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28106,7 +28210,7 @@ _tmp_17_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_17[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' expression"));
+ D(fprintf(stderr, "%*c> _tmp_16[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' expression"));
Token * _literal;
expr_ty z;
if (
@@ -28115,7 +28219,7 @@ _tmp_17_rule(Parser *p)
(z = expression_rule(p)) // expression
)
{
- D(fprintf(stderr, "%*c+ _tmp_17[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' expression"));
+ D(fprintf(stderr, "%*c+ _tmp_16[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' expression"));
_res = z;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -28125,7 +28229,7 @@ _tmp_17_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_17[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_16[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' expression"));
}
_res = NULL;
@@ -28134,9 +28238,9 @@ _tmp_17_rule(Parser *p)
return _res;
}
-// _loop0_18: ('.' | '...')
+// _loop0_17: ('.' | '...')
static asdl_seq *
-_loop0_18_rule(Parser *p)
+_loop0_17_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28161,13 +28265,13 @@ _loop0_18_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_18[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('.' | '...')"));
- void *_tmp_156_var;
+ D(fprintf(stderr, "%*c> _loop0_17[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('.' | '...')"));
+ void *_tmp_155_var;
while (
- (_tmp_156_var = _tmp_156_rule(p)) // '.' | '...'
+ (_tmp_155_var = _tmp_155_rule(p)) // '.' | '...'
)
{
- _res = _tmp_156_var;
+ _res = _tmp_155_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -28184,7 +28288,7 @@ _loop0_18_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_18[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_17[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "('.' | '...')"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -28201,9 +28305,9 @@ _loop0_18_rule(Parser *p)
return _seq;
}
-// _loop1_19: ('.' | '...')
+// _loop1_18: ('.' | '...')
static asdl_seq *
-_loop1_19_rule(Parser *p)
+_loop1_18_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28228,13 +28332,13 @@ _loop1_19_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_19[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('.' | '...')"));
- void *_tmp_156_var;
+ D(fprintf(stderr, "%*c> _loop1_18[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('.' | '...')"));
+ void *_tmp_155_var;
while (
- (_tmp_156_var = _tmp_156_rule(p)) // '.' | '...'
+ (_tmp_155_var = _tmp_155_rule(p)) // '.' | '...'
)
{
- _res = _tmp_156_var;
+ _res = _tmp_155_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -28251,7 +28355,7 @@ _loop1_19_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_19[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_18[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "('.' | '...')"));
}
if (_n == 0 || p->error_indicator) {
@@ -28273,9 +28377,9 @@ _loop1_19_rule(Parser *p)
return _seq;
}
-// _loop0_20: ',' import_from_as_name
+// _loop0_19: ',' import_from_as_name
static asdl_seq *
-_loop0_20_rule(Parser *p)
+_loop0_19_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28300,7 +28404,7 @@ _loop0_20_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_20[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' import_from_as_name"));
+ D(fprintf(stderr, "%*c> _loop0_19[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' import_from_as_name"));
Token * _literal;
alias_ty elem;
while (
@@ -28332,7 +28436,7 @@ _loop0_20_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_20[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_19[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' import_from_as_name"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -28349,9 +28453,9 @@ _loop0_20_rule(Parser *p)
return _seq;
}
-// _gather_21: import_from_as_name _loop0_20
+// _gather_20: import_from_as_name _loop0_19
static asdl_seq *
-_gather_21_rule(Parser *p)
+_gather_20_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28362,27 +28466,27 @@ _gather_21_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // import_from_as_name _loop0_20
+ { // import_from_as_name _loop0_19
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_21[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "import_from_as_name _loop0_20"));
+ D(fprintf(stderr, "%*c> _gather_20[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "import_from_as_name _loop0_19"));
alias_ty elem;
asdl_seq * seq;
if (
(elem = import_from_as_name_rule(p)) // import_from_as_name
&&
- (seq = _loop0_20_rule(p)) // _loop0_20
+ (seq = _loop0_19_rule(p)) // _loop0_19
)
{
- D(fprintf(stderr, "%*c+ _gather_21[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "import_from_as_name _loop0_20"));
+ D(fprintf(stderr, "%*c+ _gather_20[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "import_from_as_name _loop0_19"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_21[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "import_from_as_name _loop0_20"));
+ D(fprintf(stderr, "%*c%s _gather_20[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "import_from_as_name _loop0_19"));
}
_res = NULL;
done:
@@ -28390,9 +28494,9 @@ _gather_21_rule(Parser *p)
return _res;
}
-// _tmp_22: 'as' NAME
+// _tmp_21: 'as' NAME
static void *
-_tmp_22_rule(Parser *p)
+_tmp_21_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28408,16 +28512,16 @@ _tmp_22_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_22[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME"));
+ D(fprintf(stderr, "%*c> _tmp_21[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' NAME"));
Token * _keyword;
expr_ty z;
if (
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
(z = _PyPegen_name_token(p)) // NAME
)
{
- D(fprintf(stderr, "%*c+ _tmp_22[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME"));
+ D(fprintf(stderr, "%*c+ _tmp_21[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' NAME"));
_res = z;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -28427,7 +28531,7 @@ _tmp_22_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_22[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_21[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' NAME"));
}
_res = NULL;
@@ -28436,9 +28540,9 @@ _tmp_22_rule(Parser *p)
return _res;
}
-// _loop0_23: ',' dotted_as_name
+// _loop0_22: ',' dotted_as_name
static asdl_seq *
-_loop0_23_rule(Parser *p)
+_loop0_22_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28463,7 +28567,7 @@ _loop0_23_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_23[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' dotted_as_name"));
+ D(fprintf(stderr, "%*c> _loop0_22[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' dotted_as_name"));
Token * _literal;
alias_ty elem;
while (
@@ -28495,7 +28599,7 @@ _loop0_23_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_23[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_22[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' dotted_as_name"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -28512,9 +28616,9 @@ _loop0_23_rule(Parser *p)
return _seq;
}
-// _gather_24: dotted_as_name _loop0_23
+// _gather_23: dotted_as_name _loop0_22
static asdl_seq *
-_gather_24_rule(Parser *p)
+_gather_23_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28525,27 +28629,27 @@ _gather_24_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // dotted_as_name _loop0_23
+ { // dotted_as_name _loop0_22
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_24[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dotted_as_name _loop0_23"));
+ D(fprintf(stderr, "%*c> _gather_23[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dotted_as_name _loop0_22"));
alias_ty elem;
asdl_seq * seq;
if (
(elem = dotted_as_name_rule(p)) // dotted_as_name
&&
- (seq = _loop0_23_rule(p)) // _loop0_23
+ (seq = _loop0_22_rule(p)) // _loop0_22
)
{
- D(fprintf(stderr, "%*c+ _gather_24[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dotted_as_name _loop0_23"));
+ D(fprintf(stderr, "%*c+ _gather_23[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dotted_as_name _loop0_22"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_24[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "dotted_as_name _loop0_23"));
+ D(fprintf(stderr, "%*c%s _gather_23[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "dotted_as_name _loop0_22"));
}
_res = NULL;
done:
@@ -28553,9 +28657,9 @@ _gather_24_rule(Parser *p)
return _res;
}
-// _loop1_25: ('@' named_expression NEWLINE)
+// _loop1_24: ('@' named_expression NEWLINE)
static asdl_seq *
-_loop1_25_rule(Parser *p)
+_loop1_24_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28580,13 +28684,13 @@ _loop1_25_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_25[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('@' named_expression NEWLINE)"));
- void *_tmp_157_var;
+ D(fprintf(stderr, "%*c> _loop1_24[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('@' named_expression NEWLINE)"));
+ void *_tmp_156_var;
while (
- (_tmp_157_var = _tmp_157_rule(p)) // '@' named_expression NEWLINE
+ (_tmp_156_var = _tmp_156_rule(p)) // '@' named_expression NEWLINE
)
{
- _res = _tmp_157_var;
+ _res = _tmp_156_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -28603,7 +28707,7 @@ _loop1_25_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_25[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_24[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "('@' named_expression NEWLINE)"));
}
if (_n == 0 || p->error_indicator) {
@@ -28625,9 +28729,9 @@ _loop1_25_rule(Parser *p)
return _seq;
}
-// _tmp_26: '(' arguments? ')'
+// _tmp_25: '(' arguments? ')'
static void *
-_tmp_26_rule(Parser *p)
+_tmp_25_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28643,7 +28747,7 @@ _tmp_26_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_26[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'"));
+ D(fprintf(stderr, "%*c> _tmp_25[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'"));
Token * _literal;
Token * _literal_1;
void *z;
@@ -28655,7 +28759,7 @@ _tmp_26_rule(Parser *p)
(_literal_1 = _PyPegen_expect_token(p, 8)) // token=')'
)
{
- D(fprintf(stderr, "%*c+ _tmp_26[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'"));
+ D(fprintf(stderr, "%*c+ _tmp_25[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'(' arguments? ')'"));
_res = z;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -28665,7 +28769,7 @@ _tmp_26_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_26[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_25[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'(' arguments? ')'"));
}
_res = NULL;
@@ -28674,9 +28778,9 @@ _tmp_26_rule(Parser *p)
return _res;
}
-// _tmp_27: '->' expression
+// _tmp_26: '->' expression
static void *
-_tmp_27_rule(Parser *p)
+_tmp_26_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28692,7 +28796,7 @@ _tmp_27_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_27[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'->' expression"));
+ D(fprintf(stderr, "%*c> _tmp_26[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'->' expression"));
Token * _literal;
expr_ty z;
if (
@@ -28701,7 +28805,7 @@ _tmp_27_rule(Parser *p)
(z = expression_rule(p)) // expression
)
{
- D(fprintf(stderr, "%*c+ _tmp_27[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'->' expression"));
+ D(fprintf(stderr, "%*c+ _tmp_26[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'->' expression"));
_res = z;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -28711,7 +28815,7 @@ _tmp_27_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_27[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_26[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'->' expression"));
}
_res = NULL;
@@ -28720,9 +28824,9 @@ _tmp_27_rule(Parser *p)
return _res;
}
-// _loop0_28: param_no_default
+// _loop0_27: param_no_default
static asdl_seq *
-_loop0_28_rule(Parser *p)
+_loop0_27_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28747,7 +28851,7 @@ _loop0_28_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_28[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
+ D(fprintf(stderr, "%*c> _loop0_27[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
arg_ty param_no_default_var;
while (
(param_no_default_var = param_no_default_rule(p)) // param_no_default
@@ -28770,7 +28874,7 @@ _loop0_28_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_28[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_27[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -28787,9 +28891,9 @@ _loop0_28_rule(Parser *p)
return _seq;
}
-// _loop0_29: param_with_default
+// _loop0_28: param_with_default
static asdl_seq *
-_loop0_29_rule(Parser *p)
+_loop0_28_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28814,7 +28918,7 @@ _loop0_29_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_29[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default"));
+ D(fprintf(stderr, "%*c> _loop0_28[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default"));
NameDefaultPair* param_with_default_var;
while (
(param_with_default_var = param_with_default_rule(p)) // param_with_default
@@ -28837,7 +28941,7 @@ _loop0_29_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_29[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_28[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_with_default"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -28854,9 +28958,9 @@ _loop0_29_rule(Parser *p)
return _seq;
}
-// _loop1_30: param_no_default
+// _loop1_29: param_no_default
static asdl_seq *
-_loop1_30_rule(Parser *p)
+_loop1_29_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28881,7 +28985,7 @@ _loop1_30_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_30[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
+ D(fprintf(stderr, "%*c> _loop1_29[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
arg_ty param_no_default_var;
while (
(param_no_default_var = param_no_default_rule(p)) // param_no_default
@@ -28904,7 +29008,7 @@ _loop1_30_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_30[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_29[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default"));
}
if (_n == 0 || p->error_indicator) {
@@ -28926,9 +29030,9 @@ _loop1_30_rule(Parser *p)
return _seq;
}
-// _loop1_31: param_with_default
+// _loop1_30: param_with_default
static asdl_seq *
-_loop1_31_rule(Parser *p)
+_loop1_30_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -28953,7 +29057,7 @@ _loop1_31_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_31[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default"));
+ D(fprintf(stderr, "%*c> _loop1_30[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_with_default"));
NameDefaultPair* param_with_default_var;
while (
(param_with_default_var = param_with_default_rule(p)) // param_with_default
@@ -28976,7 +29080,7 @@ _loop1_31_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_31[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_30[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_with_default"));
}
if (_n == 0 || p->error_indicator) {
@@ -28998,9 +29102,9 @@ _loop1_31_rule(Parser *p)
return _seq;
}
-// _loop0_32: param_maybe_default
+// _loop0_31: param_maybe_default
static asdl_seq *
-_loop0_32_rule(Parser *p)
+_loop0_31_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29025,7 +29129,7 @@ _loop0_32_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_32[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default"));
+ D(fprintf(stderr, "%*c> _loop0_31[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default"));
NameDefaultPair* param_maybe_default_var;
while (
(param_maybe_default_var = param_maybe_default_rule(p)) // param_maybe_default
@@ -29048,7 +29152,7 @@ _loop0_32_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_32[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_31[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_maybe_default"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -29065,9 +29169,9 @@ _loop0_32_rule(Parser *p)
return _seq;
}
-// _loop1_33: param_maybe_default
+// _loop1_32: param_maybe_default
static asdl_seq *
-_loop1_33_rule(Parser *p)
+_loop1_32_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29092,7 +29196,7 @@ _loop1_33_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_33[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default"));
+ D(fprintf(stderr, "%*c> _loop1_32[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_maybe_default"));
NameDefaultPair* param_maybe_default_var;
while (
(param_maybe_default_var = param_maybe_default_rule(p)) // param_maybe_default
@@ -29115,7 +29219,7 @@ _loop1_33_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_33[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_32[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_maybe_default"));
}
if (_n == 0 || p->error_indicator) {
@@ -29137,9 +29241,9 @@ _loop1_33_rule(Parser *p)
return _seq;
}
-// _loop0_34: ',' with_item
+// _loop0_33: ',' with_item
static asdl_seq *
-_loop0_34_rule(Parser *p)
+_loop0_33_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29164,7 +29268,7 @@ _loop0_34_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_34[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' with_item"));
+ D(fprintf(stderr, "%*c> _loop0_33[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' with_item"));
Token * _literal;
withitem_ty elem;
while (
@@ -29196,7 +29300,7 @@ _loop0_34_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_34[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_33[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' with_item"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -29213,9 +29317,9 @@ _loop0_34_rule(Parser *p)
return _seq;
}
-// _gather_35: with_item _loop0_34
+// _gather_34: with_item _loop0_33
static asdl_seq *
-_gather_35_rule(Parser *p)
+_gather_34_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29226,27 +29330,27 @@ _gather_35_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // with_item _loop0_34
+ { // with_item _loop0_33
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_35[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "with_item _loop0_34"));
+ D(fprintf(stderr, "%*c> _gather_34[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "with_item _loop0_33"));
withitem_ty elem;
asdl_seq * seq;
if (
(elem = with_item_rule(p)) // with_item
&&
- (seq = _loop0_34_rule(p)) // _loop0_34
+ (seq = _loop0_33_rule(p)) // _loop0_33
)
{
- D(fprintf(stderr, "%*c+ _gather_35[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "with_item _loop0_34"));
+ D(fprintf(stderr, "%*c+ _gather_34[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "with_item _loop0_33"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_35[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "with_item _loop0_34"));
+ D(fprintf(stderr, "%*c%s _gather_34[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "with_item _loop0_33"));
}
_res = NULL;
done:
@@ -29254,9 +29358,9 @@ _gather_35_rule(Parser *p)
return _res;
}
-// _tmp_36: ',' | ')' | ':'
+// _tmp_35: ',' | ')' | ':'
static void *
-_tmp_36_rule(Parser *p)
+_tmp_35_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29272,18 +29376,18 @@ _tmp_36_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_36[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_35[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_36[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_35[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_36[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_35[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
{ // ')'
@@ -29291,18 +29395,18 @@ _tmp_36_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_36[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c> _tmp_35[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 8)) // token=')'
)
{
- D(fprintf(stderr, "%*c+ _tmp_36[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c+ _tmp_35[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_36[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_35[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'"));
}
{ // ':'
@@ -29310,18 +29414,18 @@ _tmp_36_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_36[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_35[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_36[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_35[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_36[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_35[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
_res = NULL;
@@ -29330,9 +29434,9 @@ _tmp_36_rule(Parser *p)
return _res;
}
-// _loop1_37: except_block
+// _loop1_36: except_block
static asdl_seq *
-_loop1_37_rule(Parser *p)
+_loop1_36_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29357,7 +29461,7 @@ _loop1_37_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_37[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_block"));
+ D(fprintf(stderr, "%*c> _loop1_36[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_block"));
excepthandler_ty except_block_var;
while (
(except_block_var = except_block_rule(p)) // except_block
@@ -29380,7 +29484,7 @@ _loop1_37_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_37[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_36[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "except_block"));
}
if (_n == 0 || p->error_indicator) {
@@ -29402,9 +29506,9 @@ _loop1_37_rule(Parser *p)
return _seq;
}
-// _loop1_38: except_star_block
+// _loop1_37: except_star_block
static asdl_seq *
-_loop1_38_rule(Parser *p)
+_loop1_37_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29429,7 +29533,7 @@ _loop1_38_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_38[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_star_block"));
+ D(fprintf(stderr, "%*c> _loop1_37[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "except_star_block"));
excepthandler_ty except_star_block_var;
while (
(except_star_block_var = except_star_block_rule(p)) // except_star_block
@@ -29452,7 +29556,7 @@ _loop1_38_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_38[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_37[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "except_star_block"));
}
if (_n == 0 || p->error_indicator) {
@@ -29474,9 +29578,9 @@ _loop1_38_rule(Parser *p)
return _seq;
}
-// _loop1_39: case_block
+// _loop1_38: case_block
static asdl_seq *
-_loop1_39_rule(Parser *p)
+_loop1_38_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29501,7 +29605,7 @@ _loop1_39_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_39[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "case_block"));
+ D(fprintf(stderr, "%*c> _loop1_38[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "case_block"));
match_case_ty case_block_var;
while (
(case_block_var = case_block_rule(p)) // case_block
@@ -29524,7 +29628,7 @@ _loop1_39_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_39[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_38[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "case_block"));
}
if (_n == 0 || p->error_indicator) {
@@ -29546,9 +29650,9 @@ _loop1_39_rule(Parser *p)
return _seq;
}
-// _loop0_40: '|' closed_pattern
+// _loop0_39: '|' closed_pattern
static asdl_seq *
-_loop0_40_rule(Parser *p)
+_loop0_39_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29573,7 +29677,7 @@ _loop0_40_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_40[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'|' closed_pattern"));
+ D(fprintf(stderr, "%*c> _loop0_39[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'|' closed_pattern"));
Token * _literal;
pattern_ty elem;
while (
@@ -29605,7 +29709,7 @@ _loop0_40_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_40[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_39[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'|' closed_pattern"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -29622,9 +29726,9 @@ _loop0_40_rule(Parser *p)
return _seq;
}
-// _gather_41: closed_pattern _loop0_40
+// _gather_40: closed_pattern _loop0_39
static asdl_seq *
-_gather_41_rule(Parser *p)
+_gather_40_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29635,27 +29739,27 @@ _gather_41_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // closed_pattern _loop0_40
+ { // closed_pattern _loop0_39
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_41[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "closed_pattern _loop0_40"));
+ D(fprintf(stderr, "%*c> _gather_40[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "closed_pattern _loop0_39"));
pattern_ty elem;
asdl_seq * seq;
if (
(elem = closed_pattern_rule(p)) // closed_pattern
&&
- (seq = _loop0_40_rule(p)) // _loop0_40
+ (seq = _loop0_39_rule(p)) // _loop0_39
)
{
- D(fprintf(stderr, "%*c+ _gather_41[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "closed_pattern _loop0_40"));
+ D(fprintf(stderr, "%*c+ _gather_40[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "closed_pattern _loop0_39"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_41[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "closed_pattern _loop0_40"));
+ D(fprintf(stderr, "%*c%s _gather_40[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "closed_pattern _loop0_39"));
}
_res = NULL;
done:
@@ -29663,9 +29767,9 @@ _gather_41_rule(Parser *p)
return _res;
}
-// _tmp_42: '+' | '-'
+// _tmp_41: '+' | '-'
static void *
-_tmp_42_rule(Parser *p)
+_tmp_41_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29681,18 +29785,18 @@ _tmp_42_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_42[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+'"));
+ D(fprintf(stderr, "%*c> _tmp_41[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 14)) // token='+'
)
{
- D(fprintf(stderr, "%*c+ _tmp_42[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+'"));
+ D(fprintf(stderr, "%*c+ _tmp_41[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_42[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_41[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'+'"));
}
{ // '-'
@@ -29700,18 +29804,18 @@ _tmp_42_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_42[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'-'"));
+ D(fprintf(stderr, "%*c> _tmp_41[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'-'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 15)) // token='-'
)
{
- D(fprintf(stderr, "%*c+ _tmp_42[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'-'"));
+ D(fprintf(stderr, "%*c+ _tmp_41[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'-'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_42[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_41[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'-'"));
}
_res = NULL;
@@ -29720,9 +29824,9 @@ _tmp_42_rule(Parser *p)
return _res;
}
-// _tmp_43: STRING | FSTRING_START | TSTRING_START
+// _tmp_42: STRING | FSTRING_START | TSTRING_START
static void *
-_tmp_43_rule(Parser *p)
+_tmp_42_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29738,18 +29842,18 @@ _tmp_43_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_43[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "STRING"));
+ D(fprintf(stderr, "%*c> _tmp_42[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "STRING"));
expr_ty string_var;
if (
(string_var = _PyPegen_string_token(p)) // STRING
)
{
- D(fprintf(stderr, "%*c+ _tmp_43[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "STRING"));
+ D(fprintf(stderr, "%*c+ _tmp_42[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "STRING"));
_res = string_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_43[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_42[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "STRING"));
}
{ // FSTRING_START
@@ -29757,18 +29861,18 @@ _tmp_43_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_43[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "FSTRING_START"));
+ D(fprintf(stderr, "%*c> _tmp_42[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "FSTRING_START"));
Token * fstring_start_var;
if (
(fstring_start_var = _PyPegen_expect_token(p, FSTRING_START)) // token='FSTRING_START'
)
{
- D(fprintf(stderr, "%*c+ _tmp_43[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "FSTRING_START"));
+ D(fprintf(stderr, "%*c+ _tmp_42[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "FSTRING_START"));
_res = fstring_start_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_43[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_42[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "FSTRING_START"));
}
{ // TSTRING_START
@@ -29776,18 +29880,18 @@ _tmp_43_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_43[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "TSTRING_START"));
+ D(fprintf(stderr, "%*c> _tmp_42[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "TSTRING_START"));
Token * tstring_start_var;
if (
(tstring_start_var = _PyPegen_expect_token(p, TSTRING_START)) // token='TSTRING_START'
)
{
- D(fprintf(stderr, "%*c+ _tmp_43[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "TSTRING_START"));
+ D(fprintf(stderr, "%*c+ _tmp_42[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "TSTRING_START"));
_res = tstring_start_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_43[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_42[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "TSTRING_START"));
}
_res = NULL;
@@ -29796,9 +29900,9 @@ _tmp_43_rule(Parser *p)
return _res;
}
-// _tmp_44: '.' | '(' | '='
+// _tmp_43: '.' | '(' | '='
static void *
-_tmp_44_rule(Parser *p)
+_tmp_43_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29814,18 +29918,18 @@ _tmp_44_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_44[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'"));
+ D(fprintf(stderr, "%*c> _tmp_43[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 23)) // token='.'
)
{
- D(fprintf(stderr, "%*c+ _tmp_44[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'"));
+ D(fprintf(stderr, "%*c+ _tmp_43[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_44[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_43[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'.'"));
}
{ // '('
@@ -29833,18 +29937,18 @@ _tmp_44_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_44[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'('"));
+ D(fprintf(stderr, "%*c> _tmp_43[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'('"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 7)) // token='('
)
{
- D(fprintf(stderr, "%*c+ _tmp_44[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'('"));
+ D(fprintf(stderr, "%*c+ _tmp_43[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'('"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_44[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_43[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'('"));
}
{ // '='
@@ -29852,18 +29956,18 @@ _tmp_44_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_44[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='"));
+ D(fprintf(stderr, "%*c> _tmp_43[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 22)) // token='='
)
{
- D(fprintf(stderr, "%*c+ _tmp_44[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='"));
+ D(fprintf(stderr, "%*c+ _tmp_43[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_44[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_43[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'='"));
}
_res = NULL;
@@ -29872,9 +29976,9 @@ _tmp_44_rule(Parser *p)
return _res;
}
-// _loop0_45: ',' maybe_star_pattern
+// _loop0_44: ',' maybe_star_pattern
static asdl_seq *
-_loop0_45_rule(Parser *p)
+_loop0_44_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29899,7 +30003,7 @@ _loop0_45_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_45[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' maybe_star_pattern"));
+ D(fprintf(stderr, "%*c> _loop0_44[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' maybe_star_pattern"));
Token * _literal;
pattern_ty elem;
while (
@@ -29931,7 +30035,7 @@ _loop0_45_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_45[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_44[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' maybe_star_pattern"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -29948,9 +30052,9 @@ _loop0_45_rule(Parser *p)
return _seq;
}
-// _gather_46: maybe_star_pattern _loop0_45
+// _gather_45: maybe_star_pattern _loop0_44
static asdl_seq *
-_gather_46_rule(Parser *p)
+_gather_45_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -29961,27 +30065,27 @@ _gather_46_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // maybe_star_pattern _loop0_45
+ { // maybe_star_pattern _loop0_44
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_46[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "maybe_star_pattern _loop0_45"));
+ D(fprintf(stderr, "%*c> _gather_45[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "maybe_star_pattern _loop0_44"));
pattern_ty elem;
asdl_seq * seq;
if (
(elem = maybe_star_pattern_rule(p)) // maybe_star_pattern
&&
- (seq = _loop0_45_rule(p)) // _loop0_45
+ (seq = _loop0_44_rule(p)) // _loop0_44
)
{
- D(fprintf(stderr, "%*c+ _gather_46[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "maybe_star_pattern _loop0_45"));
+ D(fprintf(stderr, "%*c+ _gather_45[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "maybe_star_pattern _loop0_44"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_46[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "maybe_star_pattern _loop0_45"));
+ D(fprintf(stderr, "%*c%s _gather_45[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "maybe_star_pattern _loop0_44"));
}
_res = NULL;
done:
@@ -29989,9 +30093,9 @@ _gather_46_rule(Parser *p)
return _res;
}
-// _loop0_47: ',' key_value_pattern
+// _loop0_46: ',' key_value_pattern
static asdl_seq *
-_loop0_47_rule(Parser *p)
+_loop0_46_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30016,7 +30120,7 @@ _loop0_47_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_47[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' key_value_pattern"));
+ D(fprintf(stderr, "%*c> _loop0_46[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' key_value_pattern"));
Token * _literal;
KeyPatternPair* elem;
while (
@@ -30048,7 +30152,7 @@ _loop0_47_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_47[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_46[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' key_value_pattern"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -30065,9 +30169,9 @@ _loop0_47_rule(Parser *p)
return _seq;
}
-// _gather_48: key_value_pattern _loop0_47
+// _gather_47: key_value_pattern _loop0_46
static asdl_seq *
-_gather_48_rule(Parser *p)
+_gather_47_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30078,27 +30182,27 @@ _gather_48_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // key_value_pattern _loop0_47
+ { // key_value_pattern _loop0_46
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_48[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "key_value_pattern _loop0_47"));
+ D(fprintf(stderr, "%*c> _gather_47[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "key_value_pattern _loop0_46"));
KeyPatternPair* elem;
asdl_seq * seq;
if (
(elem = key_value_pattern_rule(p)) // key_value_pattern
&&
- (seq = _loop0_47_rule(p)) // _loop0_47
+ (seq = _loop0_46_rule(p)) // _loop0_46
)
{
- D(fprintf(stderr, "%*c+ _gather_48[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "key_value_pattern _loop0_47"));
+ D(fprintf(stderr, "%*c+ _gather_47[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "key_value_pattern _loop0_46"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_48[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "key_value_pattern _loop0_47"));
+ D(fprintf(stderr, "%*c%s _gather_47[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "key_value_pattern _loop0_46"));
}
_res = NULL;
done:
@@ -30106,9 +30210,9 @@ _gather_48_rule(Parser *p)
return _res;
}
-// _tmp_49: literal_expr | attr
+// _tmp_48: literal_expr | attr
static void *
-_tmp_49_rule(Parser *p)
+_tmp_48_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30124,18 +30228,18 @@ _tmp_49_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_49[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "literal_expr"));
+ D(fprintf(stderr, "%*c> _tmp_48[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "literal_expr"));
expr_ty literal_expr_var;
if (
(literal_expr_var = literal_expr_rule(p)) // literal_expr
)
{
- D(fprintf(stderr, "%*c+ _tmp_49[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "literal_expr"));
+ D(fprintf(stderr, "%*c+ _tmp_48[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "literal_expr"));
_res = literal_expr_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_49[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_48[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "literal_expr"));
}
{ // attr
@@ -30143,18 +30247,18 @@ _tmp_49_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_49[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "attr"));
+ D(fprintf(stderr, "%*c> _tmp_48[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "attr"));
expr_ty attr_var;
if (
(attr_var = attr_rule(p)) // attr
)
{
- D(fprintf(stderr, "%*c+ _tmp_49[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "attr"));
+ D(fprintf(stderr, "%*c+ _tmp_48[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "attr"));
_res = attr_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_49[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_48[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "attr"));
}
_res = NULL;
@@ -30163,9 +30267,9 @@ _tmp_49_rule(Parser *p)
return _res;
}
-// _loop0_50: ',' pattern
+// _loop0_49: ',' pattern
static asdl_seq *
-_loop0_50_rule(Parser *p)
+_loop0_49_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30190,7 +30294,7 @@ _loop0_50_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_50[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' pattern"));
+ D(fprintf(stderr, "%*c> _loop0_49[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' pattern"));
Token * _literal;
pattern_ty elem;
while (
@@ -30222,7 +30326,7 @@ _loop0_50_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_50[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_49[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' pattern"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -30239,9 +30343,9 @@ _loop0_50_rule(Parser *p)
return _seq;
}
-// _gather_51: pattern _loop0_50
+// _gather_50: pattern _loop0_49
static asdl_seq *
-_gather_51_rule(Parser *p)
+_gather_50_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30252,27 +30356,27 @@ _gather_51_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // pattern _loop0_50
+ { // pattern _loop0_49
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_51[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "pattern _loop0_50"));
+ D(fprintf(stderr, "%*c> _gather_50[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "pattern _loop0_49"));
pattern_ty elem;
asdl_seq * seq;
if (
(elem = pattern_rule(p)) // pattern
&&
- (seq = _loop0_50_rule(p)) // _loop0_50
+ (seq = _loop0_49_rule(p)) // _loop0_49
)
{
- D(fprintf(stderr, "%*c+ _gather_51[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "pattern _loop0_50"));
+ D(fprintf(stderr, "%*c+ _gather_50[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "pattern _loop0_49"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_51[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "pattern _loop0_50"));
+ D(fprintf(stderr, "%*c%s _gather_50[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "pattern _loop0_49"));
}
_res = NULL;
done:
@@ -30280,9 +30384,9 @@ _gather_51_rule(Parser *p)
return _res;
}
-// _loop0_52: ',' keyword_pattern
+// _loop0_51: ',' keyword_pattern
static asdl_seq *
-_loop0_52_rule(Parser *p)
+_loop0_51_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30307,7 +30411,7 @@ _loop0_52_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_52[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' keyword_pattern"));
+ D(fprintf(stderr, "%*c> _loop0_51[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' keyword_pattern"));
Token * _literal;
KeyPatternPair* elem;
while (
@@ -30339,7 +30443,7 @@ _loop0_52_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_52[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_51[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' keyword_pattern"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -30356,9 +30460,9 @@ _loop0_52_rule(Parser *p)
return _seq;
}
-// _gather_53: keyword_pattern _loop0_52
+// _gather_52: keyword_pattern _loop0_51
static asdl_seq *
-_gather_53_rule(Parser *p)
+_gather_52_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30369,27 +30473,27 @@ _gather_53_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // keyword_pattern _loop0_52
+ { // keyword_pattern _loop0_51
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_53[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "keyword_pattern _loop0_52"));
+ D(fprintf(stderr, "%*c> _gather_52[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "keyword_pattern _loop0_51"));
KeyPatternPair* elem;
asdl_seq * seq;
if (
(elem = keyword_pattern_rule(p)) // keyword_pattern
&&
- (seq = _loop0_52_rule(p)) // _loop0_52
+ (seq = _loop0_51_rule(p)) // _loop0_51
)
{
- D(fprintf(stderr, "%*c+ _gather_53[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "keyword_pattern _loop0_52"));
+ D(fprintf(stderr, "%*c+ _gather_52[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "keyword_pattern _loop0_51"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_53[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "keyword_pattern _loop0_52"));
+ D(fprintf(stderr, "%*c%s _gather_52[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "keyword_pattern _loop0_51"));
}
_res = NULL;
done:
@@ -30397,9 +30501,9 @@ _gather_53_rule(Parser *p)
return _res;
}
-// _loop0_54: ',' type_param
+// _loop0_53: ',' type_param
static asdl_seq *
-_loop0_54_rule(Parser *p)
+_loop0_53_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30424,7 +30528,7 @@ _loop0_54_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_54[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' type_param"));
+ D(fprintf(stderr, "%*c> _loop0_53[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' type_param"));
Token * _literal;
type_param_ty elem;
while (
@@ -30456,7 +30560,7 @@ _loop0_54_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_54[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_53[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' type_param"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -30473,9 +30577,9 @@ _loop0_54_rule(Parser *p)
return _seq;
}
-// _gather_55: type_param _loop0_54
+// _gather_54: type_param _loop0_53
static asdl_seq *
-_gather_55_rule(Parser *p)
+_gather_54_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30486,27 +30590,27 @@ _gather_55_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // type_param _loop0_54
+ { // type_param _loop0_53
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_55[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "type_param _loop0_54"));
+ D(fprintf(stderr, "%*c> _gather_54[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "type_param _loop0_53"));
type_param_ty elem;
asdl_seq * seq;
if (
(elem = type_param_rule(p)) // type_param
&&
- (seq = _loop0_54_rule(p)) // _loop0_54
+ (seq = _loop0_53_rule(p)) // _loop0_53
)
{
- D(fprintf(stderr, "%*c+ _gather_55[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "type_param _loop0_54"));
+ D(fprintf(stderr, "%*c+ _gather_54[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "type_param _loop0_53"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_55[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "type_param _loop0_54"));
+ D(fprintf(stderr, "%*c%s _gather_54[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "type_param _loop0_53"));
}
_res = NULL;
done:
@@ -30514,9 +30618,9 @@ _gather_55_rule(Parser *p)
return _res;
}
-// _loop1_56: (',' expression)
+// _loop1_55: (',' expression)
static asdl_seq *
-_loop1_56_rule(Parser *p)
+_loop1_55_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30541,13 +30645,13 @@ _loop1_56_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_56[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' expression)"));
- void *_tmp_17_var;
+ D(fprintf(stderr, "%*c> _loop1_55[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' expression)"));
+ void *_tmp_16_var;
while (
- (_tmp_17_var = _tmp_17_rule(p)) // ',' expression
+ (_tmp_16_var = _tmp_16_rule(p)) // ',' expression
)
{
- _res = _tmp_17_var;
+ _res = _tmp_16_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -30564,7 +30668,7 @@ _loop1_56_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_56[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_55[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(',' expression)"));
}
if (_n == 0 || p->error_indicator) {
@@ -30586,9 +30690,9 @@ _loop1_56_rule(Parser *p)
return _seq;
}
-// _loop1_57: (',' star_expression)
+// _loop1_56: (',' star_expression)
static asdl_seq *
-_loop1_57_rule(Parser *p)
+_loop1_56_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30613,13 +30717,13 @@ _loop1_57_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_57[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_expression)"));
- void *_tmp_158_var;
+ D(fprintf(stderr, "%*c> _loop1_56[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_expression)"));
+ void *_tmp_157_var;
while (
- (_tmp_158_var = _tmp_158_rule(p)) // ',' star_expression
+ (_tmp_157_var = _tmp_157_rule(p)) // ',' star_expression
)
{
- _res = _tmp_158_var;
+ _res = _tmp_157_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -30636,7 +30740,7 @@ _loop1_57_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_57[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_56[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(',' star_expression)"));
}
if (_n == 0 || p->error_indicator) {
@@ -30658,9 +30762,9 @@ _loop1_57_rule(Parser *p)
return _seq;
}
-// _loop0_58: ',' star_named_expression
+// _loop0_57: ',' star_named_expression
static asdl_seq *
-_loop0_58_rule(Parser *p)
+_loop0_57_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30685,7 +30789,7 @@ _loop0_58_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_58[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_named_expression"));
+ D(fprintf(stderr, "%*c> _loop0_57[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_named_expression"));
Token * _literal;
expr_ty elem;
while (
@@ -30717,7 +30821,7 @@ _loop0_58_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_58[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_57[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' star_named_expression"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -30734,9 +30838,9 @@ _loop0_58_rule(Parser *p)
return _seq;
}
-// _gather_59: star_named_expression _loop0_58
+// _gather_58: star_named_expression _loop0_57
static asdl_seq *
-_gather_59_rule(Parser *p)
+_gather_58_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30747,27 +30851,27 @@ _gather_59_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // star_named_expression _loop0_58
+ { // star_named_expression _loop0_57
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_59[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expression _loop0_58"));
+ D(fprintf(stderr, "%*c> _gather_58[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expression _loop0_57"));
expr_ty elem;
asdl_seq * seq;
if (
(elem = star_named_expression_rule(p)) // star_named_expression
&&
- (seq = _loop0_58_rule(p)) // _loop0_58
+ (seq = _loop0_57_rule(p)) // _loop0_57
)
{
- D(fprintf(stderr, "%*c+ _gather_59[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_named_expression _loop0_58"));
+ D(fprintf(stderr, "%*c+ _gather_58[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_named_expression _loop0_57"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_59[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_named_expression _loop0_58"));
+ D(fprintf(stderr, "%*c%s _gather_58[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_named_expression _loop0_57"));
}
_res = NULL;
done:
@@ -30775,9 +30879,9 @@ _gather_59_rule(Parser *p)
return _res;
}
-// _loop1_60: ('or' conjunction)
+// _loop1_59: ('or' conjunction)
static asdl_seq *
-_loop1_60_rule(Parser *p)
+_loop1_59_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30802,13 +30906,13 @@ _loop1_60_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_60[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('or' conjunction)"));
- void *_tmp_159_var;
+ D(fprintf(stderr, "%*c> _loop1_59[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('or' conjunction)"));
+ void *_tmp_158_var;
while (
- (_tmp_159_var = _tmp_159_rule(p)) // 'or' conjunction
+ (_tmp_158_var = _tmp_158_rule(p)) // 'or' conjunction
)
{
- _res = _tmp_159_var;
+ _res = _tmp_158_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -30825,7 +30929,7 @@ _loop1_60_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_60[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_59[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "('or' conjunction)"));
}
if (_n == 0 || p->error_indicator) {
@@ -30847,9 +30951,9 @@ _loop1_60_rule(Parser *p)
return _seq;
}
-// _loop1_61: ('and' inversion)
+// _loop1_60: ('and' inversion)
static asdl_seq *
-_loop1_61_rule(Parser *p)
+_loop1_60_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30874,13 +30978,13 @@ _loop1_61_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_61[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('and' inversion)"));
- void *_tmp_160_var;
+ D(fprintf(stderr, "%*c> _loop1_60[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('and' inversion)"));
+ void *_tmp_159_var;
while (
- (_tmp_160_var = _tmp_160_rule(p)) // 'and' inversion
+ (_tmp_159_var = _tmp_159_rule(p)) // 'and' inversion
)
{
- _res = _tmp_160_var;
+ _res = _tmp_159_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -30897,7 +31001,7 @@ _loop1_61_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_61[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_60[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "('and' inversion)"));
}
if (_n == 0 || p->error_indicator) {
@@ -30919,9 +31023,9 @@ _loop1_61_rule(Parser *p)
return _seq;
}
-// _loop1_62: compare_op_bitwise_or_pair
+// _loop1_61: compare_op_bitwise_or_pair
static asdl_seq *
-_loop1_62_rule(Parser *p)
+_loop1_61_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -30946,7 +31050,7 @@ _loop1_62_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_62[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "compare_op_bitwise_or_pair"));
+ D(fprintf(stderr, "%*c> _loop1_61[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "compare_op_bitwise_or_pair"));
CmpopExprPair* compare_op_bitwise_or_pair_var;
while (
(compare_op_bitwise_or_pair_var = compare_op_bitwise_or_pair_rule(p)) // compare_op_bitwise_or_pair
@@ -30969,7 +31073,7 @@ _loop1_62_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_62[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_61[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "compare_op_bitwise_or_pair"));
}
if (_n == 0 || p->error_indicator) {
@@ -30991,9 +31095,9 @@ _loop1_62_rule(Parser *p)
return _seq;
}
-// _tmp_63: '!='
+// _tmp_62: '!='
static void *
-_tmp_63_rule(Parser *p)
+_tmp_62_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31009,13 +31113,13 @@ _tmp_63_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_63[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!='"));
+ D(fprintf(stderr, "%*c> _tmp_62[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!='"));
Token * tok;
if (
(tok = _PyPegen_expect_token(p, 28)) // token='!='
)
{
- D(fprintf(stderr, "%*c+ _tmp_63[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!='"));
+ D(fprintf(stderr, "%*c+ _tmp_62[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!='"));
_res = _PyPegen_check_barry_as_flufl ( p , tok ) ? NULL : tok;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -31025,7 +31129,7 @@ _tmp_63_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_63[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_62[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'!='"));
}
_res = NULL;
@@ -31034,9 +31138,9 @@ _tmp_63_rule(Parser *p)
return _res;
}
-// _loop0_64: ',' (slice | starred_expression)
+// _loop0_63: ',' (slice | starred_expression)
static asdl_seq *
-_loop0_64_rule(Parser *p)
+_loop0_63_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31061,13 +31165,13 @@ _loop0_64_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_64[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (slice | starred_expression)"));
+ D(fprintf(stderr, "%*c> _loop0_63[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (slice | starred_expression)"));
Token * _literal;
void *elem;
while (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (elem = _tmp_161_rule(p)) // slice | starred_expression
+ (elem = _tmp_160_rule(p)) // slice | starred_expression
)
{
_res = elem;
@@ -31093,7 +31197,7 @@ _loop0_64_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_64[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_63[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (slice | starred_expression)"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -31110,9 +31214,9 @@ _loop0_64_rule(Parser *p)
return _seq;
}
-// _gather_65: (slice | starred_expression) _loop0_64
+// _gather_64: (slice | starred_expression) _loop0_63
static asdl_seq *
-_gather_65_rule(Parser *p)
+_gather_64_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31123,27 +31227,27 @@ _gather_65_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // (slice | starred_expression) _loop0_64
+ { // (slice | starred_expression) _loop0_63
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_65[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(slice | starred_expression) _loop0_64"));
+ D(fprintf(stderr, "%*c> _gather_64[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(slice | starred_expression) _loop0_63"));
void *elem;
asdl_seq * seq;
if (
- (elem = _tmp_161_rule(p)) // slice | starred_expression
+ (elem = _tmp_160_rule(p)) // slice | starred_expression
&&
- (seq = _loop0_64_rule(p)) // _loop0_64
+ (seq = _loop0_63_rule(p)) // _loop0_63
)
{
- D(fprintf(stderr, "%*c+ _gather_65[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(slice | starred_expression) _loop0_64"));
+ D(fprintf(stderr, "%*c+ _gather_64[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(slice | starred_expression) _loop0_63"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_65[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(slice | starred_expression) _loop0_64"));
+ D(fprintf(stderr, "%*c%s _gather_64[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(slice | starred_expression) _loop0_63"));
}
_res = NULL;
done:
@@ -31151,9 +31255,9 @@ _gather_65_rule(Parser *p)
return _res;
}
-// _tmp_66: ':' expression?
+// _tmp_65: ':' expression?
static void *
-_tmp_66_rule(Parser *p)
+_tmp_65_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31169,7 +31273,7 @@ _tmp_66_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_66[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':' expression?"));
+ D(fprintf(stderr, "%*c> _tmp_65[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':' expression?"));
Token * _literal;
void *d;
if (
@@ -31178,7 +31282,7 @@ _tmp_66_rule(Parser *p)
(d = expression_rule(p), !p->error_indicator) // expression?
)
{
- D(fprintf(stderr, "%*c+ _tmp_66[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':' expression?"));
+ D(fprintf(stderr, "%*c+ _tmp_65[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':' expression?"));
_res = d;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -31188,7 +31292,7 @@ _tmp_66_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_66[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_65[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':' expression?"));
}
_res = NULL;
@@ -31197,9 +31301,9 @@ _tmp_66_rule(Parser *p)
return _res;
}
-// _tmp_67: tuple | group | genexp
+// _tmp_66: tuple | group | genexp
static void *
-_tmp_67_rule(Parser *p)
+_tmp_66_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31215,18 +31319,18 @@ _tmp_67_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_67[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tuple"));
+ D(fprintf(stderr, "%*c> _tmp_66[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tuple"));
expr_ty tuple_var;
if (
(tuple_var = tuple_rule(p)) // tuple
)
{
- D(fprintf(stderr, "%*c+ _tmp_67[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tuple"));
+ D(fprintf(stderr, "%*c+ _tmp_66[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tuple"));
_res = tuple_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_67[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_66[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "tuple"));
}
{ // group
@@ -31234,18 +31338,18 @@ _tmp_67_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_67[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "group"));
+ D(fprintf(stderr, "%*c> _tmp_66[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "group"));
expr_ty group_var;
if (
(group_var = group_rule(p)) // group
)
{
- D(fprintf(stderr, "%*c+ _tmp_67[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "group"));
+ D(fprintf(stderr, "%*c+ _tmp_66[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "group"));
_res = group_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_67[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_66[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "group"));
}
{ // genexp
@@ -31253,18 +31357,18 @@ _tmp_67_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_67[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "genexp"));
+ D(fprintf(stderr, "%*c> _tmp_66[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "genexp"));
expr_ty genexp_var;
if (
(genexp_var = genexp_rule(p)) // genexp
)
{
- D(fprintf(stderr, "%*c+ _tmp_67[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "genexp"));
+ D(fprintf(stderr, "%*c+ _tmp_66[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "genexp"));
_res = genexp_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_67[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_66[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "genexp"));
}
_res = NULL;
@@ -31273,9 +31377,9 @@ _tmp_67_rule(Parser *p)
return _res;
}
-// _tmp_68: list | listcomp
+// _tmp_67: list | listcomp
static void *
-_tmp_68_rule(Parser *p)
+_tmp_67_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31291,18 +31395,18 @@ _tmp_68_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_68[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "list"));
+ D(fprintf(stderr, "%*c> _tmp_67[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "list"));
expr_ty list_var;
if (
(list_var = list_rule(p)) // list
)
{
- D(fprintf(stderr, "%*c+ _tmp_68[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "list"));
+ D(fprintf(stderr, "%*c+ _tmp_67[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "list"));
_res = list_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_68[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_67[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "list"));
}
{ // listcomp
@@ -31310,18 +31414,18 @@ _tmp_68_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_68[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "listcomp"));
+ D(fprintf(stderr, "%*c> _tmp_67[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "listcomp"));
expr_ty listcomp_var;
if (
(listcomp_var = listcomp_rule(p)) // listcomp
)
{
- D(fprintf(stderr, "%*c+ _tmp_68[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "listcomp"));
+ D(fprintf(stderr, "%*c+ _tmp_67[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "listcomp"));
_res = listcomp_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_68[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_67[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "listcomp"));
}
_res = NULL;
@@ -31330,9 +31434,9 @@ _tmp_68_rule(Parser *p)
return _res;
}
-// _tmp_69: dict | set | dictcomp | setcomp
+// _tmp_68: dict | set | dictcomp | setcomp
static void *
-_tmp_69_rule(Parser *p)
+_tmp_68_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31348,18 +31452,18 @@ _tmp_69_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_69[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dict"));
+ D(fprintf(stderr, "%*c> _tmp_68[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dict"));
expr_ty dict_var;
if (
(dict_var = dict_rule(p)) // dict
)
{
- D(fprintf(stderr, "%*c+ _tmp_69[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dict"));
+ D(fprintf(stderr, "%*c+ _tmp_68[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dict"));
_res = dict_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_69[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_68[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "dict"));
}
{ // set
@@ -31367,18 +31471,18 @@ _tmp_69_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_69[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "set"));
+ D(fprintf(stderr, "%*c> _tmp_68[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "set"));
expr_ty set_var;
if (
(set_var = set_rule(p)) // set
)
{
- D(fprintf(stderr, "%*c+ _tmp_69[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "set"));
+ D(fprintf(stderr, "%*c+ _tmp_68[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "set"));
_res = set_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_69[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_68[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "set"));
}
{ // dictcomp
@@ -31386,18 +31490,18 @@ _tmp_69_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_69[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dictcomp"));
+ D(fprintf(stderr, "%*c> _tmp_68[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dictcomp"));
expr_ty dictcomp_var;
if (
(dictcomp_var = dictcomp_rule(p)) // dictcomp
)
{
- D(fprintf(stderr, "%*c+ _tmp_69[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dictcomp"));
+ D(fprintf(stderr, "%*c+ _tmp_68[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dictcomp"));
_res = dictcomp_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_69[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_68[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "dictcomp"));
}
{ // setcomp
@@ -31405,18 +31509,18 @@ _tmp_69_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_69[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "setcomp"));
+ D(fprintf(stderr, "%*c> _tmp_68[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "setcomp"));
expr_ty setcomp_var;
if (
(setcomp_var = setcomp_rule(p)) // setcomp
)
{
- D(fprintf(stderr, "%*c+ _tmp_69[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "setcomp"));
+ D(fprintf(stderr, "%*c+ _tmp_68[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "setcomp"));
_res = setcomp_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_69[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_68[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "setcomp"));
}
_res = NULL;
@@ -31425,9 +31529,9 @@ _tmp_69_rule(Parser *p)
return _res;
}
-// _tmp_70: yield_expr | named_expression
+// _tmp_69: yield_expr | named_expression
static void *
-_tmp_70_rule(Parser *p)
+_tmp_69_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31443,18 +31547,18 @@ _tmp_70_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_70[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "yield_expr"));
+ D(fprintf(stderr, "%*c> _tmp_69[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "yield_expr"));
expr_ty yield_expr_var;
if (
(yield_expr_var = yield_expr_rule(p)) // yield_expr
)
{
- D(fprintf(stderr, "%*c+ _tmp_70[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "yield_expr"));
+ D(fprintf(stderr, "%*c+ _tmp_69[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "yield_expr"));
_res = yield_expr_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_70[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_69[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "yield_expr"));
}
{ // named_expression
@@ -31462,18 +31566,18 @@ _tmp_70_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_70[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "named_expression"));
+ D(fprintf(stderr, "%*c> _tmp_69[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "named_expression"));
expr_ty named_expression_var;
if (
(named_expression_var = named_expression_rule(p)) // named_expression
)
{
- D(fprintf(stderr, "%*c+ _tmp_70[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "named_expression"));
+ D(fprintf(stderr, "%*c+ _tmp_69[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "named_expression"));
_res = named_expression_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_70[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_69[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "named_expression"));
}
_res = NULL;
@@ -31482,9 +31586,9 @@ _tmp_70_rule(Parser *p)
return _res;
}
-// _loop0_71: lambda_param_no_default
+// _loop0_70: lambda_param_no_default
static asdl_seq *
-_loop0_71_rule(Parser *p)
+_loop0_70_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31509,7 +31613,7 @@ _loop0_71_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_71[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
+ D(fprintf(stderr, "%*c> _loop0_70[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
arg_ty lambda_param_no_default_var;
while (
(lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default
@@ -31532,7 +31636,7 @@ _loop0_71_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_71[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_70[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -31549,9 +31653,9 @@ _loop0_71_rule(Parser *p)
return _seq;
}
-// _loop0_72: lambda_param_with_default
+// _loop0_71: lambda_param_with_default
static asdl_seq *
-_loop0_72_rule(Parser *p)
+_loop0_71_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31576,7 +31680,7 @@ _loop0_72_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_72[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default"));
+ D(fprintf(stderr, "%*c> _loop0_71[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default"));
NameDefaultPair* lambda_param_with_default_var;
while (
(lambda_param_with_default_var = lambda_param_with_default_rule(p)) // lambda_param_with_default
@@ -31599,7 +31703,7 @@ _loop0_72_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_72[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_71[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_with_default"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -31616,9 +31720,9 @@ _loop0_72_rule(Parser *p)
return _seq;
}
-// _loop1_73: lambda_param_no_default
+// _loop1_72: lambda_param_no_default
static asdl_seq *
-_loop1_73_rule(Parser *p)
+_loop1_72_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31643,7 +31747,7 @@ _loop1_73_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_73[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
+ D(fprintf(stderr, "%*c> _loop1_72[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
arg_ty lambda_param_no_default_var;
while (
(lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default
@@ -31666,7 +31770,7 @@ _loop1_73_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_73[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_72[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default"));
}
if (_n == 0 || p->error_indicator) {
@@ -31688,9 +31792,9 @@ _loop1_73_rule(Parser *p)
return _seq;
}
-// _loop1_74: lambda_param_with_default
+// _loop1_73: lambda_param_with_default
static asdl_seq *
-_loop1_74_rule(Parser *p)
+_loop1_73_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31715,7 +31819,7 @@ _loop1_74_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_74[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default"));
+ D(fprintf(stderr, "%*c> _loop1_73[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_with_default"));
NameDefaultPair* lambda_param_with_default_var;
while (
(lambda_param_with_default_var = lambda_param_with_default_rule(p)) // lambda_param_with_default
@@ -31738,7 +31842,7 @@ _loop1_74_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_74[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_73[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_with_default"));
}
if (_n == 0 || p->error_indicator) {
@@ -31760,9 +31864,9 @@ _loop1_74_rule(Parser *p)
return _seq;
}
-// _loop0_75: lambda_param_maybe_default
+// _loop0_74: lambda_param_maybe_default
static asdl_seq *
-_loop0_75_rule(Parser *p)
+_loop0_74_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31787,7 +31891,7 @@ _loop0_75_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_75[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default"));
+ D(fprintf(stderr, "%*c> _loop0_74[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default"));
NameDefaultPair* lambda_param_maybe_default_var;
while (
(lambda_param_maybe_default_var = lambda_param_maybe_default_rule(p)) // lambda_param_maybe_default
@@ -31810,7 +31914,7 @@ _loop0_75_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_75[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_74[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_maybe_default"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -31827,9 +31931,9 @@ _loop0_75_rule(Parser *p)
return _seq;
}
-// _loop1_76: lambda_param_maybe_default
+// _loop1_75: lambda_param_maybe_default
static asdl_seq *
-_loop1_76_rule(Parser *p)
+_loop1_75_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31854,7 +31958,7 @@ _loop1_76_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_76[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default"));
+ D(fprintf(stderr, "%*c> _loop1_75[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_maybe_default"));
NameDefaultPair* lambda_param_maybe_default_var;
while (
(lambda_param_maybe_default_var = lambda_param_maybe_default_rule(p)) // lambda_param_maybe_default
@@ -31877,7 +31981,7 @@ _loop1_76_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_76[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_75[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_maybe_default"));
}
if (_n == 0 || p->error_indicator) {
@@ -31899,9 +32003,9 @@ _loop1_76_rule(Parser *p)
return _seq;
}
-// _loop0_77: fstring_format_spec
+// _loop0_76: fstring_format_spec
static asdl_seq *
-_loop0_77_rule(Parser *p)
+_loop0_76_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31926,7 +32030,7 @@ _loop0_77_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_77[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "fstring_format_spec"));
+ D(fprintf(stderr, "%*c> _loop0_76[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "fstring_format_spec"));
expr_ty fstring_format_spec_var;
while (
(fstring_format_spec_var = fstring_format_spec_rule(p)) // fstring_format_spec
@@ -31949,7 +32053,7 @@ _loop0_77_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_77[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_76[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "fstring_format_spec"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -31966,9 +32070,9 @@ _loop0_77_rule(Parser *p)
return _seq;
}
-// _loop0_78: fstring_middle
+// _loop0_77: fstring_middle
static asdl_seq *
-_loop0_78_rule(Parser *p)
+_loop0_77_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -31993,7 +32097,7 @@ _loop0_78_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_78[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "fstring_middle"));
+ D(fprintf(stderr, "%*c> _loop0_77[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "fstring_middle"));
expr_ty fstring_middle_var;
while (
(fstring_middle_var = fstring_middle_rule(p)) // fstring_middle
@@ -32016,7 +32120,7 @@ _loop0_78_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_78[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_77[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "fstring_middle"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32033,9 +32137,9 @@ _loop0_78_rule(Parser *p)
return _seq;
}
-// _loop0_79: tstring_format_spec
+// _loop0_78: tstring_format_spec
static asdl_seq *
-_loop0_79_rule(Parser *p)
+_loop0_78_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32060,7 +32164,7 @@ _loop0_79_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_79[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tstring_format_spec"));
+ D(fprintf(stderr, "%*c> _loop0_78[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tstring_format_spec"));
expr_ty tstring_format_spec_var;
while (
(tstring_format_spec_var = tstring_format_spec_rule(p)) // tstring_format_spec
@@ -32083,7 +32187,7 @@ _loop0_79_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_79[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_78[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "tstring_format_spec"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32100,9 +32204,9 @@ _loop0_79_rule(Parser *p)
return _seq;
}
-// _loop0_80: tstring_middle
+// _loop0_79: tstring_middle
static asdl_seq *
-_loop0_80_rule(Parser *p)
+_loop0_79_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32127,7 +32231,7 @@ _loop0_80_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_80[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tstring_middle"));
+ D(fprintf(stderr, "%*c> _loop0_79[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tstring_middle"));
expr_ty tstring_middle_var;
while (
(tstring_middle_var = tstring_middle_rule(p)) // tstring_middle
@@ -32150,7 +32254,7 @@ _loop0_80_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_80[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_79[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "tstring_middle"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32167,9 +32271,9 @@ _loop0_80_rule(Parser *p)
return _seq;
}
-// _loop1_81: (fstring | string | tstring)
+// _loop1_80: (fstring | string | tstring)
static asdl_seq *
-_loop1_81_rule(Parser *p)
+_loop1_80_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32194,13 +32298,13 @@ _loop1_81_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_81[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(fstring | string | tstring)"));
- void *_tmp_162_var;
+ D(fprintf(stderr, "%*c> _loop1_80[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(fstring | string | tstring)"));
+ void *_tmp_161_var;
while (
- (_tmp_162_var = _tmp_162_rule(p)) // fstring | string | tstring
+ (_tmp_161_var = _tmp_161_rule(p)) // fstring | string | tstring
)
{
- _res = _tmp_162_var;
+ _res = _tmp_161_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -32217,7 +32321,7 @@ _loop1_81_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_81[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_80[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(fstring | string | tstring)"));
}
if (_n == 0 || p->error_indicator) {
@@ -32239,9 +32343,9 @@ _loop1_81_rule(Parser *p)
return _seq;
}
-// _tmp_82: star_named_expression ',' star_named_expressions?
+// _tmp_81: star_named_expression ',' star_named_expressions?
static void *
-_tmp_82_rule(Parser *p)
+_tmp_81_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32257,7 +32361,7 @@ _tmp_82_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_82[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expression ',' star_named_expressions?"));
+ D(fprintf(stderr, "%*c> _tmp_81[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expression ',' star_named_expressions?"));
Token * _literal;
expr_ty y;
void *z;
@@ -32269,7 +32373,7 @@ _tmp_82_rule(Parser *p)
(z = star_named_expressions_rule(p), !p->error_indicator) // star_named_expressions?
)
{
- D(fprintf(stderr, "%*c+ _tmp_82[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_named_expression ',' star_named_expressions?"));
+ D(fprintf(stderr, "%*c+ _tmp_81[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_named_expression ',' star_named_expressions?"));
_res = _PyPegen_seq_insert_in_front ( p , y , z );
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -32279,7 +32383,7 @@ _tmp_82_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_82[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_81[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_named_expression ',' star_named_expressions?"));
}
_res = NULL;
@@ -32288,9 +32392,9 @@ _tmp_82_rule(Parser *p)
return _res;
}
-// _loop0_83: ',' double_starred_kvpair
+// _loop0_82: ',' double_starred_kvpair
static asdl_seq *
-_loop0_83_rule(Parser *p)
+_loop0_82_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32315,7 +32419,7 @@ _loop0_83_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_83[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' double_starred_kvpair"));
+ D(fprintf(stderr, "%*c> _loop0_82[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' double_starred_kvpair"));
Token * _literal;
KeyValuePair* elem;
while (
@@ -32347,7 +32451,7 @@ _loop0_83_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_83[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_82[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' double_starred_kvpair"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32364,9 +32468,9 @@ _loop0_83_rule(Parser *p)
return _seq;
}
-// _gather_84: double_starred_kvpair _loop0_83
+// _gather_83: double_starred_kvpair _loop0_82
static asdl_seq *
-_gather_84_rule(Parser *p)
+_gather_83_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32377,27 +32481,27 @@ _gather_84_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // double_starred_kvpair _loop0_83
+ { // double_starred_kvpair _loop0_82
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_84[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_83"));
+ D(fprintf(stderr, "%*c> _gather_83[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_82"));
KeyValuePair* elem;
asdl_seq * seq;
if (
(elem = double_starred_kvpair_rule(p)) // double_starred_kvpair
&&
- (seq = _loop0_83_rule(p)) // _loop0_83
+ (seq = _loop0_82_rule(p)) // _loop0_82
)
{
- D(fprintf(stderr, "%*c+ _gather_84[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_83"));
+ D(fprintf(stderr, "%*c+ _gather_83[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "double_starred_kvpair _loop0_82"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_84[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "double_starred_kvpair _loop0_83"));
+ D(fprintf(stderr, "%*c%s _gather_83[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "double_starred_kvpair _loop0_82"));
}
_res = NULL;
done:
@@ -32405,9 +32509,9 @@ _gather_84_rule(Parser *p)
return _res;
}
-// _loop1_85: for_if_clause
+// _loop1_84: for_if_clause
static asdl_seq *
-_loop1_85_rule(Parser *p)
+_loop1_84_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32432,7 +32536,7 @@ _loop1_85_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_85[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "for_if_clause"));
+ D(fprintf(stderr, "%*c> _loop1_84[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "for_if_clause"));
comprehension_ty for_if_clause_var;
while (
(for_if_clause_var = for_if_clause_rule(p)) // for_if_clause
@@ -32455,7 +32559,7 @@ _loop1_85_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_85[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_84[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "for_if_clause"));
}
if (_n == 0 || p->error_indicator) {
@@ -32477,9 +32581,9 @@ _loop1_85_rule(Parser *p)
return _seq;
}
-// _loop0_86: ('if' disjunction)
+// _loop0_85: ('if' disjunction)
static asdl_seq *
-_loop0_86_rule(Parser *p)
+_loop0_85_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32504,13 +32608,13 @@ _loop0_86_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_86[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('if' disjunction)"));
- void *_tmp_163_var;
+ D(fprintf(stderr, "%*c> _loop0_85[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "('if' disjunction)"));
+ void *_tmp_162_var;
while (
- (_tmp_163_var = _tmp_163_rule(p)) // 'if' disjunction
+ (_tmp_162_var = _tmp_162_rule(p)) // 'if' disjunction
)
{
- _res = _tmp_163_var;
+ _res = _tmp_162_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -32527,7 +32631,7 @@ _loop0_86_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_86[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_85[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "('if' disjunction)"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32544,9 +32648,9 @@ _loop0_86_rule(Parser *p)
return _seq;
}
-// _tmp_87: assignment_expression | expression !':='
+// _tmp_86: assignment_expression | expression !':='
static void *
-_tmp_87_rule(Parser *p)
+_tmp_86_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32562,18 +32666,18 @@ _tmp_87_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_87[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "assignment_expression"));
+ D(fprintf(stderr, "%*c> _tmp_86[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "assignment_expression"));
expr_ty assignment_expression_var;
if (
(assignment_expression_var = assignment_expression_rule(p)) // assignment_expression
)
{
- D(fprintf(stderr, "%*c+ _tmp_87[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "assignment_expression"));
+ D(fprintf(stderr, "%*c+ _tmp_86[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "assignment_expression"));
_res = assignment_expression_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_87[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_86[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "assignment_expression"));
}
{ // expression !':='
@@ -32581,7 +32685,7 @@ _tmp_87_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_87[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression !':='"));
+ D(fprintf(stderr, "%*c> _tmp_86[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression !':='"));
expr_ty expression_var;
if (
(expression_var = expression_rule(p)) // expression
@@ -32589,12 +32693,12 @@ _tmp_87_rule(Parser *p)
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 53) // token=':='
)
{
- D(fprintf(stderr, "%*c+ _tmp_87[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression !':='"));
+ D(fprintf(stderr, "%*c+ _tmp_86[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression !':='"));
_res = expression_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_87[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_86[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression !':='"));
}
_res = NULL;
@@ -32603,9 +32707,9 @@ _tmp_87_rule(Parser *p)
return _res;
}
-// _loop0_88: ',' (starred_expression | (assignment_expression | expression !':=') !'=')
+// _loop0_87: ',' (starred_expression | (assignment_expression | expression !':=') !'=')
static asdl_seq *
-_loop0_88_rule(Parser *p)
+_loop0_87_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32630,13 +32734,13 @@ _loop0_88_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_88[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (starred_expression | (assignment_expression | expression !':=') !'=')"));
+ D(fprintf(stderr, "%*c> _loop0_87[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (starred_expression | (assignment_expression | expression !':=') !'=')"));
Token * _literal;
void *elem;
while (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (elem = _tmp_164_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'='
+ (elem = _tmp_163_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'='
)
{
_res = elem;
@@ -32662,7 +32766,7 @@ _loop0_88_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_88[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_87[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (starred_expression | (assignment_expression | expression !':=') !'=')"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32679,10 +32783,10 @@ _loop0_88_rule(Parser *p)
return _seq;
}
-// _gather_89:
-// | (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_88
+// _gather_88:
+// | (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_87
static asdl_seq *
-_gather_89_rule(Parser *p)
+_gather_88_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32693,27 +32797,27 @@ _gather_89_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_88
+ { // (starred_expression | (assignment_expression | expression !':=') !'=') _loop0_87
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_89[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_88"));
+ D(fprintf(stderr, "%*c> _gather_88[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_87"));
void *elem;
asdl_seq * seq;
if (
- (elem = _tmp_164_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'='
+ (elem = _tmp_163_rule(p)) // starred_expression | (assignment_expression | expression !':=') !'='
&&
- (seq = _loop0_88_rule(p)) // _loop0_88
+ (seq = _loop0_87_rule(p)) // _loop0_87
)
{
- D(fprintf(stderr, "%*c+ _gather_89[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_88"));
+ D(fprintf(stderr, "%*c+ _gather_88[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_87"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_89[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_88"));
+ D(fprintf(stderr, "%*c%s _gather_88[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(starred_expression | (assignment_expression | expression !':=') !'=') _loop0_87"));
}
_res = NULL;
done:
@@ -32721,9 +32825,9 @@ _gather_89_rule(Parser *p)
return _res;
}
-// _tmp_90: ',' kwargs
+// _tmp_89: ',' kwargs
static void *
-_tmp_90_rule(Parser *p)
+_tmp_89_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32739,7 +32843,7 @@ _tmp_90_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_90[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' kwargs"));
+ D(fprintf(stderr, "%*c> _tmp_89[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' kwargs"));
Token * _literal;
asdl_seq* k;
if (
@@ -32748,7 +32852,7 @@ _tmp_90_rule(Parser *p)
(k = kwargs_rule(p)) // kwargs
)
{
- D(fprintf(stderr, "%*c+ _tmp_90[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' kwargs"));
+ D(fprintf(stderr, "%*c+ _tmp_89[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' kwargs"));
_res = k;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -32758,7 +32862,7 @@ _tmp_90_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_90[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_89[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' kwargs"));
}
_res = NULL;
@@ -32767,9 +32871,9 @@ _tmp_90_rule(Parser *p)
return _res;
}
-// _loop0_91: ',' kwarg_or_starred
+// _loop0_90: ',' kwarg_or_starred
static asdl_seq *
-_loop0_91_rule(Parser *p)
+_loop0_90_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32794,7 +32898,7 @@ _loop0_91_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_91[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' kwarg_or_starred"));
+ D(fprintf(stderr, "%*c> _loop0_90[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' kwarg_or_starred"));
Token * _literal;
KeywordOrStarred* elem;
while (
@@ -32826,7 +32930,7 @@ _loop0_91_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_91[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_90[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' kwarg_or_starred"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32843,9 +32947,9 @@ _loop0_91_rule(Parser *p)
return _seq;
}
-// _gather_92: kwarg_or_starred _loop0_91
+// _gather_91: kwarg_or_starred _loop0_90
static asdl_seq *
-_gather_92_rule(Parser *p)
+_gather_91_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32856,27 +32960,27 @@ _gather_92_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // kwarg_or_starred _loop0_91
+ { // kwarg_or_starred _loop0_90
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_92[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "kwarg_or_starred _loop0_91"));
+ D(fprintf(stderr, "%*c> _gather_91[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "kwarg_or_starred _loop0_90"));
KeywordOrStarred* elem;
asdl_seq * seq;
if (
(elem = kwarg_or_starred_rule(p)) // kwarg_or_starred
&&
- (seq = _loop0_91_rule(p)) // _loop0_91
+ (seq = _loop0_90_rule(p)) // _loop0_90
)
{
- D(fprintf(stderr, "%*c+ _gather_92[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "kwarg_or_starred _loop0_91"));
+ D(fprintf(stderr, "%*c+ _gather_91[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "kwarg_or_starred _loop0_90"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_92[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "kwarg_or_starred _loop0_91"));
+ D(fprintf(stderr, "%*c%s _gather_91[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "kwarg_or_starred _loop0_90"));
}
_res = NULL;
done:
@@ -32884,9 +32988,9 @@ _gather_92_rule(Parser *p)
return _res;
}
-// _loop0_93: ',' kwarg_or_double_starred
+// _loop0_92: ',' kwarg_or_double_starred
static asdl_seq *
-_loop0_93_rule(Parser *p)
+_loop0_92_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32911,7 +33015,7 @@ _loop0_93_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_93[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' kwarg_or_double_starred"));
+ D(fprintf(stderr, "%*c> _loop0_92[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' kwarg_or_double_starred"));
Token * _literal;
KeywordOrStarred* elem;
while (
@@ -32943,7 +33047,7 @@ _loop0_93_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_93[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_92[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' kwarg_or_double_starred"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -32960,9 +33064,9 @@ _loop0_93_rule(Parser *p)
return _seq;
}
-// _gather_94: kwarg_or_double_starred _loop0_93
+// _gather_93: kwarg_or_double_starred _loop0_92
static asdl_seq *
-_gather_94_rule(Parser *p)
+_gather_93_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -32973,27 +33077,27 @@ _gather_94_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // kwarg_or_double_starred _loop0_93
+ { // kwarg_or_double_starred _loop0_92
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_94[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "kwarg_or_double_starred _loop0_93"));
+ D(fprintf(stderr, "%*c> _gather_93[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "kwarg_or_double_starred _loop0_92"));
KeywordOrStarred* elem;
asdl_seq * seq;
if (
(elem = kwarg_or_double_starred_rule(p)) // kwarg_or_double_starred
&&
- (seq = _loop0_93_rule(p)) // _loop0_93
+ (seq = _loop0_92_rule(p)) // _loop0_92
)
{
- D(fprintf(stderr, "%*c+ _gather_94[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "kwarg_or_double_starred _loop0_93"));
+ D(fprintf(stderr, "%*c+ _gather_93[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "kwarg_or_double_starred _loop0_92"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_94[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "kwarg_or_double_starred _loop0_93"));
+ D(fprintf(stderr, "%*c%s _gather_93[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "kwarg_or_double_starred _loop0_92"));
}
_res = NULL;
done:
@@ -33001,9 +33105,9 @@ _gather_94_rule(Parser *p)
return _res;
}
-// _loop0_95: (',' star_target)
+// _loop0_94: (',' star_target)
static asdl_seq *
-_loop0_95_rule(Parser *p)
+_loop0_94_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33028,13 +33132,13 @@ _loop0_95_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_95[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_target)"));
- void *_tmp_165_var;
+ D(fprintf(stderr, "%*c> _loop0_94[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_target)"));
+ void *_tmp_164_var;
while (
- (_tmp_165_var = _tmp_165_rule(p)) // ',' star_target
+ (_tmp_164_var = _tmp_164_rule(p)) // ',' star_target
)
{
- _res = _tmp_165_var;
+ _res = _tmp_164_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -33051,7 +33155,7 @@ _loop0_95_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_95[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_94[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(',' star_target)"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -33068,9 +33172,9 @@ _loop0_95_rule(Parser *p)
return _seq;
}
-// _loop0_96: ',' star_target
+// _loop0_95: ',' star_target
static asdl_seq *
-_loop0_96_rule(Parser *p)
+_loop0_95_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33095,7 +33199,7 @@ _loop0_96_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_96[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target"));
+ D(fprintf(stderr, "%*c> _loop0_95[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target"));
Token * _literal;
expr_ty elem;
while (
@@ -33127,7 +33231,7 @@ _loop0_96_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_96[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_95[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' star_target"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -33144,9 +33248,9 @@ _loop0_96_rule(Parser *p)
return _seq;
}
-// _gather_97: star_target _loop0_96
+// _gather_96: star_target _loop0_95
static asdl_seq *
-_gather_97_rule(Parser *p)
+_gather_96_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33157,27 +33261,27 @@ _gather_97_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // star_target _loop0_96
+ { // star_target _loop0_95
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_97[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_target _loop0_96"));
+ D(fprintf(stderr, "%*c> _gather_96[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_target _loop0_95"));
expr_ty elem;
asdl_seq * seq;
if (
(elem = star_target_rule(p)) // star_target
&&
- (seq = _loop0_96_rule(p)) // _loop0_96
+ (seq = _loop0_95_rule(p)) // _loop0_95
)
{
- D(fprintf(stderr, "%*c+ _gather_97[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_target _loop0_96"));
+ D(fprintf(stderr, "%*c+ _gather_96[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_target _loop0_95"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_97[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_target _loop0_96"));
+ D(fprintf(stderr, "%*c%s _gather_96[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_target _loop0_95"));
}
_res = NULL;
done:
@@ -33185,9 +33289,9 @@ _gather_97_rule(Parser *p)
return _res;
}
-// _loop1_98: (',' star_target)
+// _loop1_97: (',' star_target)
static asdl_seq *
-_loop1_98_rule(Parser *p)
+_loop1_97_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33212,13 +33316,13 @@ _loop1_98_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_98[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_target)"));
- void *_tmp_165_var;
+ D(fprintf(stderr, "%*c> _loop1_97[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' star_target)"));
+ void *_tmp_164_var;
while (
- (_tmp_165_var = _tmp_165_rule(p)) // ',' star_target
+ (_tmp_164_var = _tmp_164_rule(p)) // ',' star_target
)
{
- _res = _tmp_165_var;
+ _res = _tmp_164_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -33235,7 +33339,7 @@ _loop1_98_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_98[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_97[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(',' star_target)"));
}
if (_n == 0 || p->error_indicator) {
@@ -33257,9 +33361,9 @@ _loop1_98_rule(Parser *p)
return _seq;
}
-// _tmp_99: !'*' star_target
+// _tmp_98: !'*' star_target
static void *
-_tmp_99_rule(Parser *p)
+_tmp_98_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33275,7 +33379,7 @@ _tmp_99_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_99[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "!'*' star_target"));
+ D(fprintf(stderr, "%*c> _tmp_98[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "!'*' star_target"));
expr_ty star_target_var;
if (
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 16) // token='*'
@@ -33283,12 +33387,12 @@ _tmp_99_rule(Parser *p)
(star_target_var = star_target_rule(p)) // star_target
)
{
- D(fprintf(stderr, "%*c+ _tmp_99[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "!'*' star_target"));
+ D(fprintf(stderr, "%*c+ _tmp_98[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "!'*' star_target"));
_res = star_target_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_99[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_98[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "!'*' star_target"));
}
_res = NULL;
@@ -33297,9 +33401,9 @@ _tmp_99_rule(Parser *p)
return _res;
}
-// _loop0_100: ',' del_target
+// _loop0_99: ',' del_target
static asdl_seq *
-_loop0_100_rule(Parser *p)
+_loop0_99_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33324,7 +33428,7 @@ _loop0_100_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_100[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' del_target"));
+ D(fprintf(stderr, "%*c> _loop0_99[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' del_target"));
Token * _literal;
expr_ty elem;
while (
@@ -33356,7 +33460,7 @@ _loop0_100_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_100[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_99[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' del_target"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -33373,9 +33477,9 @@ _loop0_100_rule(Parser *p)
return _seq;
}
-// _gather_101: del_target _loop0_100
+// _gather_100: del_target _loop0_99
static asdl_seq *
-_gather_101_rule(Parser *p)
+_gather_100_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33386,27 +33490,27 @@ _gather_101_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // del_target _loop0_100
+ { // del_target _loop0_99
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_101[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "del_target _loop0_100"));
+ D(fprintf(stderr, "%*c> _gather_100[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "del_target _loop0_99"));
expr_ty elem;
asdl_seq * seq;
if (
(elem = del_target_rule(p)) // del_target
&&
- (seq = _loop0_100_rule(p)) // _loop0_100
+ (seq = _loop0_99_rule(p)) // _loop0_99
)
{
- D(fprintf(stderr, "%*c+ _gather_101[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "del_target _loop0_100"));
+ D(fprintf(stderr, "%*c+ _gather_100[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "del_target _loop0_99"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_101[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "del_target _loop0_100"));
+ D(fprintf(stderr, "%*c%s _gather_100[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "del_target _loop0_99"));
}
_res = NULL;
done:
@@ -33414,9 +33518,9 @@ _gather_101_rule(Parser *p)
return _res;
}
-// _loop0_102: ',' expression
+// _loop0_101: ',' expression
static asdl_seq *
-_loop0_102_rule(Parser *p)
+_loop0_101_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33441,7 +33545,7 @@ _loop0_102_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_102[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' expression"));
+ D(fprintf(stderr, "%*c> _loop0_101[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' expression"));
Token * _literal;
expr_ty elem;
while (
@@ -33473,7 +33577,7 @@ _loop0_102_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_102[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_101[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' expression"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -33490,9 +33594,9 @@ _loop0_102_rule(Parser *p)
return _seq;
}
-// _gather_103: expression _loop0_102
+// _gather_102: expression _loop0_101
static asdl_seq *
-_gather_103_rule(Parser *p)
+_gather_102_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33503,27 +33607,27 @@ _gather_103_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // expression _loop0_102
+ { // expression _loop0_101
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_103[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression _loop0_102"));
+ D(fprintf(stderr, "%*c> _gather_102[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression _loop0_101"));
expr_ty elem;
asdl_seq * seq;
if (
(elem = expression_rule(p)) // expression
&&
- (seq = _loop0_102_rule(p)) // _loop0_102
+ (seq = _loop0_101_rule(p)) // _loop0_101
)
{
- D(fprintf(stderr, "%*c+ _gather_103[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression _loop0_102"));
+ D(fprintf(stderr, "%*c+ _gather_102[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression _loop0_101"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_103[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression _loop0_102"));
+ D(fprintf(stderr, "%*c%s _gather_102[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression _loop0_101"));
}
_res = NULL;
done:
@@ -33531,9 +33635,9 @@ _gather_103_rule(Parser *p)
return _res;
}
-// _tmp_104: NEWLINE INDENT
+// _tmp_103: NEWLINE INDENT
static void *
-_tmp_104_rule(Parser *p)
+_tmp_103_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33549,7 +33653,7 @@ _tmp_104_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_104[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE INDENT"));
+ D(fprintf(stderr, "%*c> _tmp_103[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE INDENT"));
Token * indent_var;
Token * newline_var;
if (
@@ -33558,12 +33662,12 @@ _tmp_104_rule(Parser *p)
(indent_var = _PyPegen_expect_token(p, INDENT)) // token='INDENT'
)
{
- D(fprintf(stderr, "%*c+ _tmp_104[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE INDENT"));
+ D(fprintf(stderr, "%*c+ _tmp_103[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE INDENT"));
_res = _PyPegen_dummy_name(p, newline_var, indent_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_104[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_103[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NEWLINE INDENT"));
}
_res = NULL;
@@ -33572,11 +33676,11 @@ _tmp_104_rule(Parser *p)
return _res;
}
-// _tmp_105:
+// _tmp_104:
// | (','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)
// | kwargs
static void *
-_tmp_105_rule(Parser *p)
+_tmp_104_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33592,18 +33696,18 @@ _tmp_105_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_105[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)"));
- void *_tmp_166_var;
+ D(fprintf(stderr, "%*c> _tmp_104[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)"));
+ void *_tmp_165_var;
if (
- (_tmp_166_var = _tmp_166_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs
+ (_tmp_165_var = _tmp_165_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs
)
{
- D(fprintf(stderr, "%*c+ _tmp_105[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)"));
- _res = _tmp_166_var;
+ D(fprintf(stderr, "%*c+ _tmp_104[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)"));
+ _res = _tmp_165_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_105[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_104[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs)"));
}
{ // kwargs
@@ -33611,18 +33715,18 @@ _tmp_105_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_105[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "kwargs"));
+ D(fprintf(stderr, "%*c> _tmp_104[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "kwargs"));
asdl_seq* kwargs_var;
if (
(kwargs_var = kwargs_rule(p)) // kwargs
)
{
- D(fprintf(stderr, "%*c+ _tmp_105[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "kwargs"));
+ D(fprintf(stderr, "%*c+ _tmp_104[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "kwargs"));
_res = kwargs_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_105[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_104[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "kwargs"));
}
_res = NULL;
@@ -33631,9 +33735,9 @@ _tmp_105_rule(Parser *p)
return _res;
}
-// _loop0_106: ',' (starred_expression !'=')
+// _loop0_105: ',' (starred_expression !'=')
static asdl_seq *
-_loop0_106_rule(Parser *p)
+_loop0_105_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33658,13 +33762,13 @@ _loop0_106_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_106[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (starred_expression !'=')"));
+ D(fprintf(stderr, "%*c> _loop0_105[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (starred_expression !'=')"));
Token * _literal;
void *elem;
while (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (elem = _tmp_167_rule(p)) // starred_expression !'='
+ (elem = _tmp_166_rule(p)) // starred_expression !'='
)
{
_res = elem;
@@ -33690,7 +33794,7 @@ _loop0_106_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_106[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_105[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (starred_expression !'=')"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -33707,9 +33811,9 @@ _loop0_106_rule(Parser *p)
return _seq;
}
-// _gather_107: (starred_expression !'=') _loop0_106
+// _gather_106: (starred_expression !'=') _loop0_105
static asdl_seq *
-_gather_107_rule(Parser *p)
+_gather_106_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33720,27 +33824,27 @@ _gather_107_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // (starred_expression !'=') _loop0_106
+ { // (starred_expression !'=') _loop0_105
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_107[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(starred_expression !'=') _loop0_106"));
+ D(fprintf(stderr, "%*c> _gather_106[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(starred_expression !'=') _loop0_105"));
void *elem;
asdl_seq * seq;
if (
- (elem = _tmp_167_rule(p)) // starred_expression !'='
+ (elem = _tmp_166_rule(p)) // starred_expression !'='
&&
- (seq = _loop0_106_rule(p)) // _loop0_106
+ (seq = _loop0_105_rule(p)) // _loop0_105
)
{
- D(fprintf(stderr, "%*c+ _gather_107[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(starred_expression !'=') _loop0_106"));
+ D(fprintf(stderr, "%*c+ _gather_106[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(starred_expression !'=') _loop0_105"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_107[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(starred_expression !'=') _loop0_106"));
+ D(fprintf(stderr, "%*c%s _gather_106[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(starred_expression !'=') _loop0_105"));
}
_res = NULL;
done:
@@ -33748,9 +33852,9 @@ _gather_107_rule(Parser *p)
return _res;
}
-// _tmp_108: args | expression for_if_clauses
+// _tmp_107: args | expression for_if_clauses
static void *
-_tmp_108_rule(Parser *p)
+_tmp_107_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33766,18 +33870,18 @@ _tmp_108_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_108[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "args"));
+ D(fprintf(stderr, "%*c> _tmp_107[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "args"));
expr_ty args_var;
if (
(args_var = args_rule(p)) // args
)
{
- D(fprintf(stderr, "%*c+ _tmp_108[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args"));
+ D(fprintf(stderr, "%*c+ _tmp_107[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args"));
_res = args_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_108[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_107[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "args"));
}
{ // expression for_if_clauses
@@ -33785,7 +33889,7 @@ _tmp_108_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_108[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses"));
+ D(fprintf(stderr, "%*c> _tmp_107[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses"));
expr_ty expression_var;
asdl_comprehension_seq* for_if_clauses_var;
if (
@@ -33794,12 +33898,12 @@ _tmp_108_rule(Parser *p)
(for_if_clauses_var = for_if_clauses_rule(p)) // for_if_clauses
)
{
- D(fprintf(stderr, "%*c+ _tmp_108[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses"));
+ D(fprintf(stderr, "%*c+ _tmp_107[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses"));
_res = _PyPegen_dummy_name(p, expression_var, for_if_clauses_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_108[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_107[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression for_if_clauses"));
}
_res = NULL;
@@ -33808,9 +33912,9 @@ _tmp_108_rule(Parser *p)
return _res;
}
-// _tmp_109: args ','
+// _tmp_108: args ','
static void *
-_tmp_109_rule(Parser *p)
+_tmp_108_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33826,7 +33930,7 @@ _tmp_109_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_109[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "args ','"));
+ D(fprintf(stderr, "%*c> _tmp_108[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "args ','"));
Token * _literal;
expr_ty args_var;
if (
@@ -33835,12 +33939,12 @@ _tmp_109_rule(Parser *p)
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_109[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args ','"));
+ D(fprintf(stderr, "%*c+ _tmp_108[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args ','"));
_res = _PyPegen_dummy_name(p, args_var, _literal);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_109[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_108[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "args ','"));
}
_res = NULL;
@@ -33849,9 +33953,9 @@ _tmp_109_rule(Parser *p)
return _res;
}
-// _tmp_110: ',' | ')'
+// _tmp_109: ',' | ')'
static void *
-_tmp_110_rule(Parser *p)
+_tmp_109_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33867,18 +33971,18 @@ _tmp_110_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_110[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_109[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_110[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_109[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_110[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_109[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
{ // ')'
@@ -33886,18 +33990,18 @@ _tmp_110_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_110[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c> _tmp_109[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 8)) // token=')'
)
{
- D(fprintf(stderr, "%*c+ _tmp_110[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c+ _tmp_109[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_110[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_109[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'"));
}
_res = NULL;
@@ -33906,9 +34010,9 @@ _tmp_110_rule(Parser *p)
return _res;
}
-// _tmp_111: 'True' | 'False' | 'None'
+// _tmp_110: 'True' | 'False' | 'None'
static void *
-_tmp_111_rule(Parser *p)
+_tmp_110_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -33924,18 +34028,18 @@ _tmp_111_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_111[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'"));
+ D(fprintf(stderr, "%*c> _tmp_110[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 622)) // token='True'
+ (_keyword = _PyPegen_expect_token(p, 623)) // token='True'
)
{
- D(fprintf(stderr, "%*c+ _tmp_111[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'"));
+ D(fprintf(stderr, "%*c+ _tmp_110[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_111[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_110[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'True'"));
}
{ // 'False'
@@ -33943,18 +34047,18 @@ _tmp_111_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_111[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'"));
+ D(fprintf(stderr, "%*c> _tmp_110[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 624)) // token='False'
+ (_keyword = _PyPegen_expect_token(p, 625)) // token='False'
)
{
- D(fprintf(stderr, "%*c+ _tmp_111[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'"));
+ D(fprintf(stderr, "%*c+ _tmp_110[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_111[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_110[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'False'"));
}
{ // 'None'
@@ -33962,18 +34066,18 @@ _tmp_111_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_111[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'"));
+ D(fprintf(stderr, "%*c> _tmp_110[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 623)) // token='None'
+ (_keyword = _PyPegen_expect_token(p, 624)) // token='None'
)
{
- D(fprintf(stderr, "%*c+ _tmp_111[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'"));
+ D(fprintf(stderr, "%*c+ _tmp_110[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_111[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_110[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'None'"));
}
_res = NULL;
@@ -33982,9 +34086,9 @@ _tmp_111_rule(Parser *p)
return _res;
}
-// _tmp_112: NAME '='
+// _tmp_111: NAME '='
static void *
-_tmp_112_rule(Parser *p)
+_tmp_111_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34000,7 +34104,7 @@ _tmp_112_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_112[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME '='"));
+ D(fprintf(stderr, "%*c> _tmp_111[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME '='"));
Token * _literal;
expr_ty name_var;
if (
@@ -34009,12 +34113,12 @@ _tmp_112_rule(Parser *p)
(_literal = _PyPegen_expect_token(p, 22)) // token='='
)
{
- D(fprintf(stderr, "%*c+ _tmp_112[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME '='"));
+ D(fprintf(stderr, "%*c+ _tmp_111[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME '='"));
_res = _PyPegen_dummy_name(p, name_var, _literal);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_112[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_111[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME '='"));
}
_res = NULL;
@@ -34023,9 +34127,9 @@ _tmp_112_rule(Parser *p)
return _res;
}
-// _loop1_113: (!STRING expression_without_invalid)
+// _loop1_112: (!STRING expression_without_invalid)
static asdl_seq *
-_loop1_113_rule(Parser *p)
+_loop1_112_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34050,13 +34154,13 @@ _loop1_113_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop1_113[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(!STRING expression_without_invalid)"));
- void *_tmp_168_var;
+ D(fprintf(stderr, "%*c> _loop1_112[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(!STRING expression_without_invalid)"));
+ void *_tmp_167_var;
while (
- (_tmp_168_var = _tmp_168_rule(p)) // !STRING expression_without_invalid
+ (_tmp_167_var = _tmp_167_rule(p)) // !STRING expression_without_invalid
)
{
- _res = _tmp_168_var;
+ _res = _tmp_167_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -34073,7 +34177,7 @@ _loop1_113_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop1_113[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop1_112[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(!STRING expression_without_invalid)"));
}
if (_n == 0 || p->error_indicator) {
@@ -34095,9 +34199,9 @@ _loop1_113_rule(Parser *p)
return _seq;
}
-// _tmp_114: NAME STRING | SOFT_KEYWORD
+// _tmp_113: NAME STRING | SOFT_KEYWORD
static void *
-_tmp_114_rule(Parser *p)
+_tmp_113_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34113,7 +34217,7 @@ _tmp_114_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_114[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME STRING"));
+ D(fprintf(stderr, "%*c> _tmp_113[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME STRING"));
expr_ty name_var;
expr_ty string_var;
if (
@@ -34122,12 +34226,12 @@ _tmp_114_rule(Parser *p)
(string_var = _PyPegen_string_token(p)) // STRING
)
{
- D(fprintf(stderr, "%*c+ _tmp_114[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME STRING"));
+ D(fprintf(stderr, "%*c+ _tmp_113[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME STRING"));
_res = _PyPegen_dummy_name(p, name_var, string_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_114[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_113[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME STRING"));
}
{ // SOFT_KEYWORD
@@ -34135,18 +34239,18 @@ _tmp_114_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_114[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD"));
+ D(fprintf(stderr, "%*c> _tmp_113[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD"));
expr_ty soft_keyword_var;
if (
(soft_keyword_var = _PyPegen_soft_keyword_token(p)) // SOFT_KEYWORD
)
{
- D(fprintf(stderr, "%*c+ _tmp_114[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD"));
+ D(fprintf(stderr, "%*c+ _tmp_113[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "SOFT_KEYWORD"));
_res = soft_keyword_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_114[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_113[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "SOFT_KEYWORD"));
}
_res = NULL;
@@ -34155,9 +34259,9 @@ _tmp_114_rule(Parser *p)
return _res;
}
-// _tmp_115: 'else' | ':'
+// _tmp_114: 'else' | ':'
static void *
-_tmp_115_rule(Parser *p)
+_tmp_114_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34173,18 +34277,18 @@ _tmp_115_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_115[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'else'"));
+ D(fprintf(stderr, "%*c> _tmp_114[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'else'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 686)) // token='else'
+ (_keyword = _PyPegen_expect_token(p, 691)) // token='else'
)
{
- D(fprintf(stderr, "%*c+ _tmp_115[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'else'"));
+ D(fprintf(stderr, "%*c+ _tmp_114[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'else'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_115[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_114[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'else'"));
}
{ // ':'
@@ -34192,18 +34296,18 @@ _tmp_115_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_115[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_114[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_115[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_114[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_115[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_114[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
_res = NULL;
@@ -34212,9 +34316,9 @@ _tmp_115_rule(Parser *p)
return _res;
}
-// _tmp_116: pass_stmt | break_stmt | continue_stmt
+// _tmp_115: pass_stmt | break_stmt | continue_stmt
static void *
-_tmp_116_rule(Parser *p)
+_tmp_115_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34230,18 +34334,18 @@ _tmp_116_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_116[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "pass_stmt"));
+ D(fprintf(stderr, "%*c> _tmp_115[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "pass_stmt"));
stmt_ty pass_stmt_var;
if (
(pass_stmt_var = pass_stmt_rule(p)) // pass_stmt
)
{
- D(fprintf(stderr, "%*c+ _tmp_116[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "pass_stmt"));
+ D(fprintf(stderr, "%*c+ _tmp_115[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "pass_stmt"));
_res = pass_stmt_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_116[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_115[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "pass_stmt"));
}
{ // break_stmt
@@ -34249,18 +34353,18 @@ _tmp_116_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_116[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "break_stmt"));
+ D(fprintf(stderr, "%*c> _tmp_115[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "break_stmt"));
stmt_ty break_stmt_var;
if (
(break_stmt_var = break_stmt_rule(p)) // break_stmt
)
{
- D(fprintf(stderr, "%*c+ _tmp_116[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "break_stmt"));
+ D(fprintf(stderr, "%*c+ _tmp_115[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "break_stmt"));
_res = break_stmt_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_116[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_115[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "break_stmt"));
}
{ // continue_stmt
@@ -34268,18 +34372,18 @@ _tmp_116_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_116[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "continue_stmt"));
+ D(fprintf(stderr, "%*c> _tmp_115[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "continue_stmt"));
stmt_ty continue_stmt_var;
if (
(continue_stmt_var = continue_stmt_rule(p)) // continue_stmt
)
{
- D(fprintf(stderr, "%*c+ _tmp_116[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "continue_stmt"));
+ D(fprintf(stderr, "%*c+ _tmp_115[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "continue_stmt"));
_res = continue_stmt_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_116[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_115[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "continue_stmt"));
}
_res = NULL;
@@ -34288,9 +34392,9 @@ _tmp_116_rule(Parser *p)
return _res;
}
-// _tmp_117: '=' | ':='
+// _tmp_116: '=' | ':='
static void *
-_tmp_117_rule(Parser *p)
+_tmp_116_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34306,18 +34410,18 @@ _tmp_117_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='"));
+ D(fprintf(stderr, "%*c> _tmp_116[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 22)) // token='='
)
{
- D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='"));
+ D(fprintf(stderr, "%*c+ _tmp_116[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_116[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'='"));
}
{ // ':='
@@ -34325,18 +34429,18 @@ _tmp_117_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':='"));
+ D(fprintf(stderr, "%*c> _tmp_116[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':='"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 53)) // token=':='
)
{
- D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':='"));
+ D(fprintf(stderr, "%*c+ _tmp_116[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':='"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_116[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':='"));
}
_res = NULL;
@@ -34345,9 +34449,9 @@ _tmp_117_rule(Parser *p)
return _res;
}
-// _tmp_118: list | tuple | genexp | 'True' | 'None' | 'False'
+// _tmp_117: list | tuple | genexp | 'True' | 'None' | 'False'
static void *
-_tmp_118_rule(Parser *p)
+_tmp_117_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34363,18 +34467,18 @@ _tmp_118_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_118[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "list"));
+ D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "list"));
expr_ty list_var;
if (
(list_var = list_rule(p)) // list
)
{
- D(fprintf(stderr, "%*c+ _tmp_118[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "list"));
+ D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "list"));
_res = list_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_118[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "list"));
}
{ // tuple
@@ -34382,18 +34486,18 @@ _tmp_118_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_118[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tuple"));
+ D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tuple"));
expr_ty tuple_var;
if (
(tuple_var = tuple_rule(p)) // tuple
)
{
- D(fprintf(stderr, "%*c+ _tmp_118[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tuple"));
+ D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tuple"));
_res = tuple_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_118[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "tuple"));
}
{ // genexp
@@ -34401,18 +34505,18 @@ _tmp_118_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_118[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "genexp"));
+ D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "genexp"));
expr_ty genexp_var;
if (
(genexp_var = genexp_rule(p)) // genexp
)
{
- D(fprintf(stderr, "%*c+ _tmp_118[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "genexp"));
+ D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "genexp"));
_res = genexp_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_118[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "genexp"));
}
{ // 'True'
@@ -34420,18 +34524,18 @@ _tmp_118_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_118[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'"));
+ D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'True'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 622)) // token='True'
+ (_keyword = _PyPegen_expect_token(p, 623)) // token='True'
)
{
- D(fprintf(stderr, "%*c+ _tmp_118[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'"));
+ D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'True'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_118[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'True'"));
}
{ // 'None'
@@ -34439,18 +34543,18 @@ _tmp_118_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_118[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'"));
+ D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'None'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 623)) // token='None'
+ (_keyword = _PyPegen_expect_token(p, 624)) // token='None'
)
{
- D(fprintf(stderr, "%*c+ _tmp_118[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'"));
+ D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'None'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_118[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'None'"));
}
{ // 'False'
@@ -34458,18 +34562,18 @@ _tmp_118_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_118[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'"));
+ D(fprintf(stderr, "%*c> _tmp_117[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'False'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 624)) // token='False'
+ (_keyword = _PyPegen_expect_token(p, 625)) // token='False'
)
{
- D(fprintf(stderr, "%*c+ _tmp_118[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'"));
+ D(fprintf(stderr, "%*c+ _tmp_117[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'False'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_118[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_117[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'False'"));
}
_res = NULL;
@@ -34478,9 +34582,9 @@ _tmp_118_rule(Parser *p)
return _res;
}
-// _loop0_119: star_named_expressions
+// _loop0_118: star_named_expressions
static asdl_seq *
-_loop0_119_rule(Parser *p)
+_loop0_118_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34505,7 +34609,7 @@ _loop0_119_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_119[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expressions"));
+ D(fprintf(stderr, "%*c> _loop0_118[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_named_expressions"));
asdl_expr_seq* star_named_expressions_var;
while (
(star_named_expressions_var = star_named_expressions_rule(p)) // star_named_expressions
@@ -34528,7 +34632,7 @@ _loop0_119_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_119[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_118[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_named_expressions"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -34545,9 +34649,9 @@ _loop0_119_rule(Parser *p)
return _seq;
}
-// _loop0_120: (star_targets '=')
+// _loop0_119: (star_targets '=')
static asdl_seq *
-_loop0_120_rule(Parser *p)
+_loop0_119_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34572,13 +34676,13 @@ _loop0_120_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_120[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')"));
- void *_tmp_155_var;
+ D(fprintf(stderr, "%*c> _loop0_119[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(star_targets '=')"));
+ void *_tmp_154_var;
while (
- (_tmp_155_var = _tmp_155_rule(p)) // star_targets '='
+ (_tmp_154_var = _tmp_154_rule(p)) // star_targets '='
)
{
- _res = _tmp_155_var;
+ _res = _tmp_154_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -34595,7 +34699,7 @@ _loop0_120_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_120[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_119[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(star_targets '=')"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -34612,9 +34716,9 @@ _loop0_120_rule(Parser *p)
return _seq;
}
-// _tmp_121: '[' | '(' | '{'
+// _tmp_120: '[' | '(' | '{'
static void *
-_tmp_121_rule(Parser *p)
+_tmp_120_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34630,18 +34734,18 @@ _tmp_121_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_121[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['"));
+ D(fprintf(stderr, "%*c> _tmp_120[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 9)) // token='['
)
{
- D(fprintf(stderr, "%*c+ _tmp_121[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['"));
+ D(fprintf(stderr, "%*c+ _tmp_120[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_121[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_120[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'['"));
}
{ // '('
@@ -34649,18 +34753,18 @@ _tmp_121_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_121[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'('"));
+ D(fprintf(stderr, "%*c> _tmp_120[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'('"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 7)) // token='('
)
{
- D(fprintf(stderr, "%*c+ _tmp_121[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'('"));
+ D(fprintf(stderr, "%*c+ _tmp_120[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'('"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_121[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_120[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'('"));
}
{ // '{'
@@ -34668,18 +34772,18 @@ _tmp_121_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_121[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'"));
+ D(fprintf(stderr, "%*c> _tmp_120[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 25)) // token='{'
)
{
- D(fprintf(stderr, "%*c+ _tmp_121[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'"));
+ D(fprintf(stderr, "%*c+ _tmp_120[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_121[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_120[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'{'"));
}
_res = NULL;
@@ -34688,9 +34792,9 @@ _tmp_121_rule(Parser *p)
return _res;
}
-// _tmp_122: '[' | '{'
+// _tmp_121: '[' | '{'
static void *
-_tmp_122_rule(Parser *p)
+_tmp_121_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34706,18 +34810,18 @@ _tmp_122_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_122[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['"));
+ D(fprintf(stderr, "%*c> _tmp_121[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'['"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 9)) // token='['
)
{
- D(fprintf(stderr, "%*c+ _tmp_122[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['"));
+ D(fprintf(stderr, "%*c+ _tmp_121[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'['"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_122[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_121[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'['"));
}
{ // '{'
@@ -34725,18 +34829,18 @@ _tmp_122_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_122[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'"));
+ D(fprintf(stderr, "%*c> _tmp_121[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'{'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 25)) // token='{'
)
{
- D(fprintf(stderr, "%*c+ _tmp_122[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'"));
+ D(fprintf(stderr, "%*c+ _tmp_121[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'{'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_122[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_121[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'{'"));
}
_res = NULL;
@@ -34745,9 +34849,9 @@ _tmp_122_rule(Parser *p)
return _res;
}
-// _tmp_123: slash_no_default | slash_with_default
+// _tmp_122: slash_no_default | slash_with_default
static void *
-_tmp_123_rule(Parser *p)
+_tmp_122_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34763,18 +34867,18 @@ _tmp_123_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_123[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_no_default"));
+ D(fprintf(stderr, "%*c> _tmp_122[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_no_default"));
asdl_arg_seq* slash_no_default_var;
if (
(slash_no_default_var = slash_no_default_rule(p)) // slash_no_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_123[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_no_default"));
+ D(fprintf(stderr, "%*c+ _tmp_122[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_no_default"));
_res = slash_no_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_123[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_122[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slash_no_default"));
}
{ // slash_with_default
@@ -34782,18 +34886,18 @@ _tmp_123_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_123[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_with_default"));
+ D(fprintf(stderr, "%*c> _tmp_122[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slash_with_default"));
SlashWithDefault* slash_with_default_var;
if (
(slash_with_default_var = slash_with_default_rule(p)) // slash_with_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_123[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_with_default"));
+ D(fprintf(stderr, "%*c+ _tmp_122[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slash_with_default"));
_res = slash_with_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_123[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_122[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slash_with_default"));
}
_res = NULL;
@@ -34802,9 +34906,9 @@ _tmp_123_rule(Parser *p)
return _res;
}
-// _tmp_124: ',' | param_no_default
+// _tmp_123: ',' | param_no_default
static void *
-_tmp_124_rule(Parser *p)
+_tmp_123_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34820,18 +34924,18 @@ _tmp_124_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_124[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_123[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_124[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_123[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_124[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_123[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
{ // param_no_default
@@ -34839,18 +34943,18 @@ _tmp_124_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_124[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
+ D(fprintf(stderr, "%*c> _tmp_123[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
arg_ty param_no_default_var;
if (
(param_no_default_var = param_no_default_rule(p)) // param_no_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_124[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default"));
+ D(fprintf(stderr, "%*c+ _tmp_123[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default"));
_res = param_no_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_124[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_123[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default"));
}
_res = NULL;
@@ -34859,9 +34963,9 @@ _tmp_124_rule(Parser *p)
return _res;
}
-// _tmp_125: ')' | ','
+// _tmp_124: ')' | ','
static void *
-_tmp_125_rule(Parser *p)
+_tmp_124_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34877,18 +34981,18 @@ _tmp_125_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_125[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c> _tmp_124[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 8)) // token=')'
)
{
- D(fprintf(stderr, "%*c+ _tmp_125[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c+ _tmp_124[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_125[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_124[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'"));
}
{ // ','
@@ -34896,18 +35000,18 @@ _tmp_125_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_125[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_124[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_125[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_124[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_125[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_124[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
_res = NULL;
@@ -34916,9 +35020,9 @@ _tmp_125_rule(Parser *p)
return _res;
}
-// _tmp_126: ')' | ',' (')' | '**')
+// _tmp_125: ')' | ',' (')' | '**')
static void *
-_tmp_126_rule(Parser *p)
+_tmp_125_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34934,18 +35038,18 @@ _tmp_126_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_126[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c> _tmp_125[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 8)) // token=')'
)
{
- D(fprintf(stderr, "%*c+ _tmp_126[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c+ _tmp_125[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_126[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_125[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'"));
}
{ // ',' (')' | '**')
@@ -34953,21 +35057,21 @@ _tmp_126_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_126[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')"));
+ D(fprintf(stderr, "%*c> _tmp_125[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')"));
Token * _literal;
- void *_tmp_169_var;
+ void *_tmp_168_var;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (_tmp_169_var = _tmp_169_rule(p)) // ')' | '**'
+ (_tmp_168_var = _tmp_168_rule(p)) // ')' | '**'
)
{
- D(fprintf(stderr, "%*c+ _tmp_126[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')"));
- _res = _PyPegen_dummy_name(p, _literal, _tmp_169_var);
+ D(fprintf(stderr, "%*c+ _tmp_125[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (')' | '**')"));
+ _res = _PyPegen_dummy_name(p, _literal, _tmp_168_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_126[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_125[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (')' | '**')"));
}
_res = NULL;
@@ -34976,9 +35080,9 @@ _tmp_126_rule(Parser *p)
return _res;
}
-// _tmp_127: param_no_default | ','
+// _tmp_126: param_no_default | ','
static void *
-_tmp_127_rule(Parser *p)
+_tmp_126_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -34994,18 +35098,18 @@ _tmp_127_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_127[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
+ D(fprintf(stderr, "%*c> _tmp_126[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "param_no_default"));
arg_ty param_no_default_var;
if (
(param_no_default_var = param_no_default_rule(p)) // param_no_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_127[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default"));
+ D(fprintf(stderr, "%*c+ _tmp_126[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "param_no_default"));
_res = param_no_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_127[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_126[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "param_no_default"));
}
{ // ','
@@ -35013,18 +35117,18 @@ _tmp_127_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_127[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_126[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_127[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_126[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_127[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_126[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
_res = NULL;
@@ -35033,9 +35137,9 @@ _tmp_127_rule(Parser *p)
return _res;
}
-// _tmp_128: '*' | '**' | '/'
+// _tmp_127: '*' | '**' | '/'
static void *
-_tmp_128_rule(Parser *p)
+_tmp_127_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35051,18 +35155,18 @@ _tmp_128_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_128[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'"));
+ D(fprintf(stderr, "%*c> _tmp_127[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
)
{
- D(fprintf(stderr, "%*c+ _tmp_128[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'"));
+ D(fprintf(stderr, "%*c+ _tmp_127[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_128[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_127[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'*'"));
}
{ // '**'
@@ -35070,18 +35174,18 @@ _tmp_128_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_128[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'"));
+ D(fprintf(stderr, "%*c> _tmp_127[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 35)) // token='**'
)
{
- D(fprintf(stderr, "%*c+ _tmp_128[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'"));
+ D(fprintf(stderr, "%*c+ _tmp_127[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_128[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_127[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'**'"));
}
{ // '/'
@@ -35089,18 +35193,18 @@ _tmp_128_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_128[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'"));
+ D(fprintf(stderr, "%*c> _tmp_127[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
)
{
- D(fprintf(stderr, "%*c+ _tmp_128[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'"));
+ D(fprintf(stderr, "%*c+ _tmp_127[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_128[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_127[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'/'"));
}
_res = NULL;
@@ -35109,9 +35213,9 @@ _tmp_128_rule(Parser *p)
return _res;
}
-// _tmp_129: lambda_slash_no_default | lambda_slash_with_default
+// _tmp_128: lambda_slash_no_default | lambda_slash_with_default
static void *
-_tmp_129_rule(Parser *p)
+_tmp_128_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35127,18 +35231,18 @@ _tmp_129_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_129[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default"));
+ D(fprintf(stderr, "%*c> _tmp_128[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default"));
asdl_arg_seq* lambda_slash_no_default_var;
if (
(lambda_slash_no_default_var = lambda_slash_no_default_rule(p)) // lambda_slash_no_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_129[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default"));
+ D(fprintf(stderr, "%*c+ _tmp_128[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_no_default"));
_res = lambda_slash_no_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_129[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_128[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_slash_no_default"));
}
{ // lambda_slash_with_default
@@ -35146,18 +35250,18 @@ _tmp_129_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_129[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default"));
+ D(fprintf(stderr, "%*c> _tmp_128[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default"));
SlashWithDefault* lambda_slash_with_default_var;
if (
(lambda_slash_with_default_var = lambda_slash_with_default_rule(p)) // lambda_slash_with_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_129[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default"));
+ D(fprintf(stderr, "%*c+ _tmp_128[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_slash_with_default"));
_res = lambda_slash_with_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_129[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_128[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_slash_with_default"));
}
_res = NULL;
@@ -35166,9 +35270,9 @@ _tmp_129_rule(Parser *p)
return _res;
}
-// _loop0_130: ',' lambda_param
+// _loop0_129: ',' lambda_param
static asdl_seq *
-_loop0_130_rule(Parser *p)
+_loop0_129_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35193,7 +35297,7 @@ _loop0_130_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_130[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' lambda_param"));
+ D(fprintf(stderr, "%*c> _loop0_129[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' lambda_param"));
Token * _literal;
arg_ty elem;
while (
@@ -35225,7 +35329,7 @@ _loop0_130_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_130[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_129[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' lambda_param"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -35242,9 +35346,9 @@ _loop0_130_rule(Parser *p)
return _seq;
}
-// _gather_131: lambda_param _loop0_130
+// _gather_130: lambda_param _loop0_129
static asdl_seq *
-_gather_131_rule(Parser *p)
+_gather_130_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35255,27 +35359,27 @@ _gather_131_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // lambda_param _loop0_130
+ { // lambda_param _loop0_129
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_131[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_130"));
+ D(fprintf(stderr, "%*c> _gather_130[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_129"));
arg_ty elem;
asdl_seq * seq;
if (
(elem = lambda_param_rule(p)) // lambda_param
&&
- (seq = _loop0_130_rule(p)) // _loop0_130
+ (seq = _loop0_129_rule(p)) // _loop0_129
)
{
- D(fprintf(stderr, "%*c+ _gather_131[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_130"));
+ D(fprintf(stderr, "%*c+ _gather_130[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param _loop0_129"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_131[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param _loop0_130"));
+ D(fprintf(stderr, "%*c%s _gather_130[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param _loop0_129"));
}
_res = NULL;
done:
@@ -35283,9 +35387,9 @@ _gather_131_rule(Parser *p)
return _res;
}
-// _tmp_132: ',' | lambda_param_no_default
+// _tmp_131: ',' | lambda_param_no_default
static void *
-_tmp_132_rule(Parser *p)
+_tmp_131_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35301,18 +35405,18 @@ _tmp_132_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_132[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_131[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_132[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_131[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_132[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_131[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
{ // lambda_param_no_default
@@ -35320,18 +35424,18 @@ _tmp_132_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_132[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
+ D(fprintf(stderr, "%*c> _tmp_131[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
arg_ty lambda_param_no_default_var;
if (
(lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_132[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
+ D(fprintf(stderr, "%*c+ _tmp_131[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
_res = lambda_param_no_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_132[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_131[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default"));
}
_res = NULL;
@@ -35340,9 +35444,9 @@ _tmp_132_rule(Parser *p)
return _res;
}
-// _tmp_133: ':' | ',' (':' | '**')
+// _tmp_132: ':' | ',' (':' | '**')
static void *
-_tmp_133_rule(Parser *p)
+_tmp_132_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35358,18 +35462,18 @@ _tmp_133_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_133[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_132[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_133[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_132[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_133[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_132[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
{ // ',' (':' | '**')
@@ -35377,21 +35481,21 @@ _tmp_133_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_133[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')"));
+ D(fprintf(stderr, "%*c> _tmp_132[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')"));
Token * _literal;
- void *_tmp_170_var;
+ void *_tmp_169_var;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (_tmp_170_var = _tmp_170_rule(p)) // ':' | '**'
+ (_tmp_169_var = _tmp_169_rule(p)) // ':' | '**'
)
{
- D(fprintf(stderr, "%*c+ _tmp_133[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')"));
- _res = _PyPegen_dummy_name(p, _literal, _tmp_170_var);
+ D(fprintf(stderr, "%*c+ _tmp_132[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' (':' | '**')"));
+ _res = _PyPegen_dummy_name(p, _literal, _tmp_169_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_133[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_132[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (':' | '**')"));
}
_res = NULL;
@@ -35400,9 +35504,9 @@ _tmp_133_rule(Parser *p)
return _res;
}
-// _tmp_134: lambda_param_no_default | ','
+// _tmp_133: lambda_param_no_default | ','
static void *
-_tmp_134_rule(Parser *p)
+_tmp_133_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35418,18 +35522,18 @@ _tmp_134_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_134[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
+ D(fprintf(stderr, "%*c> _tmp_133[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
arg_ty lambda_param_no_default_var;
if (
(lambda_param_no_default_var = lambda_param_no_default_rule(p)) // lambda_param_no_default
)
{
- D(fprintf(stderr, "%*c+ _tmp_134[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
+ D(fprintf(stderr, "%*c+ _tmp_133[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "lambda_param_no_default"));
_res = lambda_param_no_default_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_134[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_133[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambda_param_no_default"));
}
{ // ','
@@ -35437,18 +35541,18 @@ _tmp_134_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_134[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_133[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_134[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_133[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_134[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_133[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
_res = NULL;
@@ -35457,9 +35561,9 @@ _tmp_134_rule(Parser *p)
return _res;
}
-// _tmp_135: bitwise_or ((',' bitwise_or))* ','?
+// _tmp_134: bitwise_or ((',' bitwise_or))* ','?
static void *
-_tmp_135_rule(Parser *p)
+_tmp_134_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35475,25 +35579,25 @@ _tmp_135_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_135[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "bitwise_or ((',' bitwise_or))* ','?"));
- asdl_seq * _loop0_171_var;
+ D(fprintf(stderr, "%*c> _tmp_134[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "bitwise_or ((',' bitwise_or))* ','?"));
+ asdl_seq * _loop0_170_var;
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
expr_ty bitwise_or_var;
if (
(bitwise_or_var = bitwise_or_rule(p)) // bitwise_or
&&
- (_loop0_171_var = _loop0_171_rule(p)) // ((',' bitwise_or))*
+ (_loop0_170_var = _loop0_170_rule(p)) // ((',' bitwise_or))*
&&
(_opt_var = _PyPegen_expect_token(p, 12), !p->error_indicator) // ','?
)
{
- D(fprintf(stderr, "%*c+ _tmp_135[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "bitwise_or ((',' bitwise_or))* ','?"));
- _res = _PyPegen_dummy_name(p, bitwise_or_var, _loop0_171_var, _opt_var);
+ D(fprintf(stderr, "%*c+ _tmp_134[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "bitwise_or ((',' bitwise_or))* ','?"));
+ _res = _PyPegen_dummy_name(p, bitwise_or_var, _loop0_170_var, _opt_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_135[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_134[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "bitwise_or ((',' bitwise_or))* ','?"));
}
_res = NULL;
@@ -35502,9 +35606,9 @@ _tmp_135_rule(Parser *p)
return _res;
}
-// _loop0_136: ',' dotted_name
+// _loop0_135: ',' dotted_name
static asdl_seq *
-_loop0_136_rule(Parser *p)
+_loop0_135_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35529,7 +35633,7 @@ _loop0_136_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_136[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' dotted_name"));
+ D(fprintf(stderr, "%*c> _loop0_135[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' dotted_name"));
Token * _literal;
expr_ty elem;
while (
@@ -35561,7 +35665,7 @@ _loop0_136_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_136[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_135[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' dotted_name"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -35578,9 +35682,9 @@ _loop0_136_rule(Parser *p)
return _seq;
}
-// _gather_137: dotted_name _loop0_136
+// _gather_136: dotted_name _loop0_135
static asdl_seq *
-_gather_137_rule(Parser *p)
+_gather_136_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35591,27 +35695,27 @@ _gather_137_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // dotted_name _loop0_136
+ { // dotted_name _loop0_135
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_137[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dotted_name _loop0_136"));
+ D(fprintf(stderr, "%*c> _gather_136[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "dotted_name _loop0_135"));
expr_ty elem;
asdl_seq * seq;
if (
(elem = dotted_name_rule(p)) // dotted_name
&&
- (seq = _loop0_136_rule(p)) // _loop0_136
+ (seq = _loop0_135_rule(p)) // _loop0_135
)
{
- D(fprintf(stderr, "%*c+ _gather_137[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dotted_name _loop0_136"));
+ D(fprintf(stderr, "%*c+ _gather_136[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "dotted_name _loop0_135"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_137[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "dotted_name _loop0_136"));
+ D(fprintf(stderr, "%*c%s _gather_136[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "dotted_name _loop0_135"));
}
_res = NULL;
done:
@@ -35619,9 +35723,9 @@ _gather_137_rule(Parser *p)
return _res;
}
-// _tmp_138: NAME (',' | ')' | NEWLINE)
+// _tmp_137: NAME (',' | ')' | NEWLINE)
static void *
-_tmp_138_rule(Parser *p)
+_tmp_137_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35637,21 +35741,21 @@ _tmp_138_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_138[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME (',' | ')' | NEWLINE)"));
- void *_tmp_172_var;
+ D(fprintf(stderr, "%*c> _tmp_137[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME (',' | ')' | NEWLINE)"));
+ void *_tmp_171_var;
expr_ty name_var;
if (
(name_var = _PyPegen_name_token(p)) // NAME
&&
- (_tmp_172_var = _tmp_172_rule(p)) // ',' | ')' | NEWLINE
+ (_tmp_171_var = _tmp_171_rule(p)) // ',' | ')' | NEWLINE
)
{
- D(fprintf(stderr, "%*c+ _tmp_138[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME (',' | ')' | NEWLINE)"));
- _res = _PyPegen_dummy_name(p, name_var, _tmp_172_var);
+ D(fprintf(stderr, "%*c+ _tmp_137[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME (',' | ')' | NEWLINE)"));
+ _res = _PyPegen_dummy_name(p, name_var, _tmp_171_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_138[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_137[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME (',' | ')' | NEWLINE)"));
}
_res = NULL;
@@ -35660,9 +35764,9 @@ _tmp_138_rule(Parser *p)
return _res;
}
-// _loop0_139: ',' (expression ['as' star_target])
+// _loop0_138: ',' (expression ['as' star_target])
static asdl_seq *
-_loop0_139_rule(Parser *p)
+_loop0_138_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35687,13 +35791,13 @@ _loop0_139_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_139[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expression ['as' star_target])"));
+ D(fprintf(stderr, "%*c> _loop0_138[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expression ['as' star_target])"));
Token * _literal;
void *elem;
while (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (elem = _tmp_173_rule(p)) // expression ['as' star_target]
+ (elem = _tmp_172_rule(p)) // expression ['as' star_target]
)
{
_res = elem;
@@ -35719,7 +35823,7 @@ _loop0_139_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_139[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_138[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (expression ['as' star_target])"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -35736,9 +35840,9 @@ _loop0_139_rule(Parser *p)
return _seq;
}
-// _gather_140: (expression ['as' star_target]) _loop0_139
+// _gather_139: (expression ['as' star_target]) _loop0_138
static asdl_seq *
-_gather_140_rule(Parser *p)
+_gather_139_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35749,27 +35853,27 @@ _gather_140_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // (expression ['as' star_target]) _loop0_139
+ { // (expression ['as' star_target]) _loop0_138
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_140[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_139"));
+ D(fprintf(stderr, "%*c> _gather_139[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_138"));
void *elem;
asdl_seq * seq;
if (
- (elem = _tmp_173_rule(p)) // expression ['as' star_target]
+ (elem = _tmp_172_rule(p)) // expression ['as' star_target]
&&
- (seq = _loop0_139_rule(p)) // _loop0_139
+ (seq = _loop0_138_rule(p)) // _loop0_138
)
{
- D(fprintf(stderr, "%*c+ _gather_140[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_139"));
+ D(fprintf(stderr, "%*c+ _gather_139[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expression ['as' star_target]) _loop0_138"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_140[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expression ['as' star_target]) _loop0_139"));
+ D(fprintf(stderr, "%*c%s _gather_139[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expression ['as' star_target]) _loop0_138"));
}
_res = NULL;
done:
@@ -35777,9 +35881,9 @@ _gather_140_rule(Parser *p)
return _res;
}
-// _loop0_141: ',' (expressions ['as' star_target])
+// _loop0_140: ',' (expressions ['as' star_target])
static asdl_seq *
-_loop0_141_rule(Parser *p)
+_loop0_140_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35804,13 +35908,13 @@ _loop0_141_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_141[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expressions ['as' star_target])"));
+ D(fprintf(stderr, "%*c> _loop0_140[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' (expressions ['as' star_target])"));
Token * _literal;
void *elem;
while (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
- (elem = _tmp_174_rule(p)) // expressions ['as' star_target]
+ (elem = _tmp_173_rule(p)) // expressions ['as' star_target]
)
{
_res = elem;
@@ -35836,7 +35940,7 @@ _loop0_141_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_141[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_140[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' (expressions ['as' star_target])"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -35853,9 +35957,9 @@ _loop0_141_rule(Parser *p)
return _seq;
}
-// _gather_142: (expressions ['as' star_target]) _loop0_141
+// _gather_141: (expressions ['as' star_target]) _loop0_140
static asdl_seq *
-_gather_142_rule(Parser *p)
+_gather_141_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35866,27 +35970,27 @@ _gather_142_rule(Parser *p)
}
asdl_seq * _res = NULL;
int _mark = p->mark;
- { // (expressions ['as' star_target]) _loop0_141
+ { // (expressions ['as' star_target]) _loop0_140
if (p->error_indicator) {
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _gather_142[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_141"));
+ D(fprintf(stderr, "%*c> _gather_141[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_140"));
void *elem;
asdl_seq * seq;
if (
- (elem = _tmp_174_rule(p)) // expressions ['as' star_target]
+ (elem = _tmp_173_rule(p)) // expressions ['as' star_target]
&&
- (seq = _loop0_141_rule(p)) // _loop0_141
+ (seq = _loop0_140_rule(p)) // _loop0_140
)
{
- D(fprintf(stderr, "%*c+ _gather_142[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_141"));
+ D(fprintf(stderr, "%*c+ _gather_141[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(expressions ['as' star_target]) _loop0_140"));
_res = _PyPegen_seq_insert_in_front(p, elem, seq);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _gather_142[%d-%d]: %s failed!\n", p->level, ' ',
- p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expressions ['as' star_target]) _loop0_141"));
+ D(fprintf(stderr, "%*c%s _gather_141[%d-%d]: %s failed!\n", p->level, ' ',
+ p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(expressions ['as' star_target]) _loop0_140"));
}
_res = NULL;
done:
@@ -35894,9 +35998,9 @@ _gather_142_rule(Parser *p)
return _res;
}
-// _tmp_143: 'except' | 'finally'
+// _tmp_142: 'except' | 'finally'
static void *
-_tmp_143_rule(Parser *p)
+_tmp_142_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35912,18 +36016,18 @@ _tmp_143_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_143[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except'"));
+ D(fprintf(stderr, "%*c> _tmp_142[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'except'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 677)) // token='except'
+ (_keyword = _PyPegen_expect_token(p, 682)) // token='except'
)
{
- D(fprintf(stderr, "%*c+ _tmp_143[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except'"));
+ D(fprintf(stderr, "%*c+ _tmp_142[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_143[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_142[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'except'"));
}
{ // 'finally'
@@ -35931,18 +36035,18 @@ _tmp_143_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_143[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'finally'"));
+ D(fprintf(stderr, "%*c> _tmp_142[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'finally'"));
Token * _keyword;
if (
- (_keyword = _PyPegen_expect_token(p, 673)) // token='finally'
+ (_keyword = _PyPegen_expect_token(p, 678)) // token='finally'
)
{
- D(fprintf(stderr, "%*c+ _tmp_143[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'finally'"));
+ D(fprintf(stderr, "%*c+ _tmp_142[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'finally'"));
_res = _keyword;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_143[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_142[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'finally'"));
}
_res = NULL;
@@ -35951,9 +36055,9 @@ _tmp_143_rule(Parser *p)
return _res;
}
-// _loop0_144: block
+// _loop0_143: block
static asdl_seq *
-_loop0_144_rule(Parser *p)
+_loop0_143_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -35978,7 +36082,7 @@ _loop0_144_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_144[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "block"));
+ D(fprintf(stderr, "%*c> _loop0_143[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "block"));
asdl_stmt_seq* block_var;
while (
(block_var = block_rule(p)) // block
@@ -36001,7 +36105,7 @@ _loop0_144_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_144[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_143[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "block"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -36018,9 +36122,9 @@ _loop0_144_rule(Parser *p)
return _seq;
}
-// _tmp_145: expression ['as' NAME]
+// _tmp_144: expression ['as' NAME]
static void *
-_tmp_145_rule(Parser *p)
+_tmp_144_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36036,22 +36140,22 @@ _tmp_145_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_145[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]"));
+ D(fprintf(stderr, "%*c> _tmp_144[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]"));
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
expr_ty expression_var;
if (
(expression_var = expression_rule(p)) // expression
&&
- (_opt_var = _tmp_22_rule(p), !p->error_indicator) // ['as' NAME]
+ (_opt_var = _tmp_21_rule(p), !p->error_indicator) // ['as' NAME]
)
{
- D(fprintf(stderr, "%*c+ _tmp_145[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]"));
+ D(fprintf(stderr, "%*c+ _tmp_144[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' NAME]"));
_res = _PyPegen_dummy_name(p, expression_var, _opt_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_145[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_144[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression ['as' NAME]"));
}
_res = NULL;
@@ -36060,9 +36164,9 @@ _tmp_145_rule(Parser *p)
return _res;
}
-// _tmp_146: NEWLINE | ':'
+// _tmp_145: NEWLINE | ':'
static void *
-_tmp_146_rule(Parser *p)
+_tmp_145_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36078,18 +36182,18 @@ _tmp_146_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_146[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
+ D(fprintf(stderr, "%*c> _tmp_145[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
Token * newline_var;
if (
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
{
- D(fprintf(stderr, "%*c+ _tmp_146[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
+ D(fprintf(stderr, "%*c+ _tmp_145[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
_res = newline_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_146[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_145[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NEWLINE"));
}
{ // ':'
@@ -36097,18 +36201,18 @@ _tmp_146_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_146[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_145[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_146[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_145[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_146[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_145[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
_res = NULL;
@@ -36117,9 +36221,9 @@ _tmp_146_rule(Parser *p)
return _res;
}
-// _tmp_147: positional_patterns ','
+// _tmp_146: positional_patterns ','
static void *
-_tmp_147_rule(Parser *p)
+_tmp_146_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36135,7 +36239,7 @@ _tmp_147_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_147[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "positional_patterns ','"));
+ D(fprintf(stderr, "%*c> _tmp_146[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "positional_patterns ','"));
Token * _literal;
asdl_pattern_seq* positional_patterns_var;
if (
@@ -36144,12 +36248,12 @@ _tmp_147_rule(Parser *p)
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_147[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "positional_patterns ','"));
+ D(fprintf(stderr, "%*c+ _tmp_146[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "positional_patterns ','"));
_res = _PyPegen_dummy_name(p, positional_patterns_var, _literal);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_147[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_146[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "positional_patterns ','"));
}
_res = NULL;
@@ -36158,9 +36262,9 @@ _tmp_147_rule(Parser *p)
return _res;
}
-// _tmp_148: '}' | ','
+// _tmp_147: '}' | ','
static void *
-_tmp_148_rule(Parser *p)
+_tmp_147_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36176,18 +36280,18 @@ _tmp_148_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c> _tmp_147[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 26)) // token='}'
)
{
- D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c+ _tmp_147[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_147[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'}'"));
}
{ // ','
@@ -36195,18 +36299,18 @@ _tmp_148_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_147[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_147[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_147[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
_res = NULL;
@@ -36215,9 +36319,9 @@ _tmp_148_rule(Parser *p)
return _res;
}
-// _tmp_149: '=' | '!' | ':' | '}'
+// _tmp_148: '=' | '!' | ':' | '}'
static void *
-_tmp_149_rule(Parser *p)
+_tmp_148_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36233,18 +36337,18 @@ _tmp_149_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='"));
+ D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'='"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 22)) // token='='
)
{
- D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='"));
+ D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'='"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'='"));
}
{ // '!'
@@ -36252,18 +36356,18 @@ _tmp_149_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!'"));
+ D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 54)) // token='!'
)
{
- D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!'"));
+ D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'!'"));
}
{ // ':'
@@ -36271,18 +36375,18 @@ _tmp_149_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
{ // '}'
@@ -36290,18 +36394,18 @@ _tmp_149_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c> _tmp_148[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 26)) // token='}'
)
{
- D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c+ _tmp_148[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_148[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'}'"));
}
_res = NULL;
@@ -36310,9 +36414,9 @@ _tmp_149_rule(Parser *p)
return _res;
}
-// _tmp_150: '!' | ':' | '}'
+// _tmp_149: '!' | ':' | '}'
static void *
-_tmp_150_rule(Parser *p)
+_tmp_149_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36328,18 +36432,18 @@ _tmp_150_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!'"));
+ D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 54)) // token='!'
)
{
- D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!'"));
+ D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'!'"));
}
{ // ':'
@@ -36347,18 +36451,18 @@ _tmp_150_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
{ // '}'
@@ -36366,18 +36470,18 @@ _tmp_150_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c> _tmp_149[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 26)) // token='}'
)
{
- D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c+ _tmp_149[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_149[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'}'"));
}
_res = NULL;
@@ -36386,9 +36490,9 @@ _tmp_150_rule(Parser *p)
return _res;
}
-// _tmp_151: '!' NAME
+// _tmp_150: '!' NAME
static void *
-_tmp_151_rule(Parser *p)
+_tmp_150_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36404,7 +36508,7 @@ _tmp_151_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!' NAME"));
+ D(fprintf(stderr, "%*c> _tmp_150[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'!' NAME"));
Token * _literal;
expr_ty name_var;
if (
@@ -36413,12 +36517,12 @@ _tmp_151_rule(Parser *p)
(name_var = _PyPegen_name_token(p)) // NAME
)
{
- D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!' NAME"));
+ D(fprintf(stderr, "%*c+ _tmp_150[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'!' NAME"));
_res = _PyPegen_dummy_name(p, _literal, name_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_150[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'!' NAME"));
}
_res = NULL;
@@ -36427,9 +36531,9 @@ _tmp_151_rule(Parser *p)
return _res;
}
-// _tmp_152: ':' | '}'
+// _tmp_151: ':' | '}'
static void *
-_tmp_152_rule(Parser *p)
+_tmp_151_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36445,18 +36549,18 @@ _tmp_152_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
{ // '}'
@@ -36464,18 +36568,18 @@ _tmp_152_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c> _tmp_151[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'}'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 26)) // token='}'
)
{
- D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
+ D(fprintf(stderr, "%*c+ _tmp_151[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'}'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_151[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'}'"));
}
_res = NULL;
@@ -36484,9 +36588,9 @@ _tmp_152_rule(Parser *p)
return _res;
}
-// _tmp_153: '+' | '-' | '*' | '/' | '%' | '//' | '@'
+// _tmp_152: '+' | '-' | '*' | '/' | '%' | '//' | '@'
static void *
-_tmp_153_rule(Parser *p)
+_tmp_152_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36502,18 +36606,18 @@ _tmp_153_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+'"));
+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 14)) // token='+'
)
{
- D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+'"));
+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'+'"));
}
{ // '-'
@@ -36521,18 +36625,18 @@ _tmp_153_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'-'"));
+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'-'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 15)) // token='-'
)
{
- D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'-'"));
+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'-'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'-'"));
}
{ // '*'
@@ -36540,18 +36644,18 @@ _tmp_153_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'"));
+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'*'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 16)) // token='*'
)
{
- D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'"));
+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'*'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'*'"));
}
{ // '/'
@@ -36559,18 +36663,18 @@ _tmp_153_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'"));
+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'/'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 17)) // token='/'
)
{
- D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'"));
+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'/'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'/'"));
}
{ // '%'
@@ -36578,18 +36682,18 @@ _tmp_153_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'%'"));
+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'%'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 24)) // token='%'
)
{
- D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'%'"));
+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'%'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'%'"));
}
{ // '//'
@@ -36597,18 +36701,18 @@ _tmp_153_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'//'"));
+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'//'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 47)) // token='//'
)
{
- D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'//'"));
+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'//'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'//'"));
}
{ // '@'
@@ -36616,18 +36720,18 @@ _tmp_153_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'@'"));
+ D(fprintf(stderr, "%*c> _tmp_152[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'@'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 49)) // token='@'
)
{
- D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'@'"));
+ D(fprintf(stderr, "%*c+ _tmp_152[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'@'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_152[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'@'"));
}
_res = NULL;
@@ -36636,9 +36740,9 @@ _tmp_153_rule(Parser *p)
return _res;
}
-// _tmp_154: '+' | '-' | '~'
+// _tmp_153: '+' | '-' | '~'
static void *
-_tmp_154_rule(Parser *p)
+_tmp_153_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36654,18 +36758,18 @@ _tmp_154_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_154[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+'"));
+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'+'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 14)) // token='+'
)
{
- D(fprintf(stderr, "%*c+ _tmp_154[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+'"));
+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'+'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_154[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'+'"));
}
{ // '-'
@@ -36673,18 +36777,18 @@ _tmp_154_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_154[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'-'"));
+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'-'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 15)) // token='-'
)
{
- D(fprintf(stderr, "%*c+ _tmp_154[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'-'"));
+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'-'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_154[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'-'"));
}
{ // '~'
@@ -36692,18 +36796,18 @@ _tmp_154_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_154[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'~'"));
+ D(fprintf(stderr, "%*c> _tmp_153[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'~'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 31)) // token='~'
)
{
- D(fprintf(stderr, "%*c+ _tmp_154[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'~'"));
+ D(fprintf(stderr, "%*c+ _tmp_153[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'~'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_154[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_153[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'~'"));
}
_res = NULL;
@@ -36712,9 +36816,9 @@ _tmp_154_rule(Parser *p)
return _res;
}
-// _tmp_155: star_targets '='
+// _tmp_154: star_targets '='
static void *
-_tmp_155_rule(Parser *p)
+_tmp_154_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36730,7 +36834,7 @@ _tmp_155_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_155[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='"));
+ D(fprintf(stderr, "%*c> _tmp_154[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "star_targets '='"));
Token * _literal;
expr_ty z;
if (
@@ -36739,7 +36843,7 @@ _tmp_155_rule(Parser *p)
(_literal = _PyPegen_expect_token(p, 22)) // token='='
)
{
- D(fprintf(stderr, "%*c+ _tmp_155[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='"));
+ D(fprintf(stderr, "%*c+ _tmp_154[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "star_targets '='"));
_res = z;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -36749,7 +36853,7 @@ _tmp_155_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_155[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_154[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "star_targets '='"));
}
_res = NULL;
@@ -36758,9 +36862,9 @@ _tmp_155_rule(Parser *p)
return _res;
}
-// _tmp_156: '.' | '...'
+// _tmp_155: '.' | '...'
static void *
-_tmp_156_rule(Parser *p)
+_tmp_155_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36776,18 +36880,18 @@ _tmp_156_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_156[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'"));
+ D(fprintf(stderr, "%*c> _tmp_155[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'.'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 23)) // token='.'
)
{
- D(fprintf(stderr, "%*c+ _tmp_156[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'"));
+ D(fprintf(stderr, "%*c+ _tmp_155[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'.'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_156[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_155[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'.'"));
}
{ // '...'
@@ -36795,18 +36899,18 @@ _tmp_156_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_156[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'...'"));
+ D(fprintf(stderr, "%*c> _tmp_155[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'...'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 52)) // token='...'
)
{
- D(fprintf(stderr, "%*c+ _tmp_156[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'...'"));
+ D(fprintf(stderr, "%*c+ _tmp_155[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'...'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_156[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_155[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'...'"));
}
_res = NULL;
@@ -36815,9 +36919,9 @@ _tmp_156_rule(Parser *p)
return _res;
}
-// _tmp_157: '@' named_expression NEWLINE
+// _tmp_156: '@' named_expression NEWLINE
static void *
-_tmp_157_rule(Parser *p)
+_tmp_156_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36833,7 +36937,7 @@ _tmp_157_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_157[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE"));
+ D(fprintf(stderr, "%*c> _tmp_156[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE"));
Token * _literal;
expr_ty f;
Token * newline_var;
@@ -36845,7 +36949,7 @@ _tmp_157_rule(Parser *p)
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
{
- D(fprintf(stderr, "%*c+ _tmp_157[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE"));
+ D(fprintf(stderr, "%*c+ _tmp_156[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'@' named_expression NEWLINE"));
_res = f;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -36855,7 +36959,7 @@ _tmp_157_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_157[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_156[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'@' named_expression NEWLINE"));
}
_res = NULL;
@@ -36864,9 +36968,9 @@ _tmp_157_rule(Parser *p)
return _res;
}
-// _tmp_158: ',' star_expression
+// _tmp_157: ',' star_expression
static void *
-_tmp_158_rule(Parser *p)
+_tmp_157_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36882,7 +36986,7 @@ _tmp_158_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_158[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_expression"));
+ D(fprintf(stderr, "%*c> _tmp_157[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_expression"));
Token * _literal;
expr_ty c;
if (
@@ -36891,7 +36995,7 @@ _tmp_158_rule(Parser *p)
(c = star_expression_rule(p)) // star_expression
)
{
- D(fprintf(stderr, "%*c+ _tmp_158[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_expression"));
+ D(fprintf(stderr, "%*c+ _tmp_157[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_expression"));
_res = c;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -36901,7 +37005,7 @@ _tmp_158_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_158[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_157[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' star_expression"));
}
_res = NULL;
@@ -36910,9 +37014,9 @@ _tmp_158_rule(Parser *p)
return _res;
}
-// _tmp_159: 'or' conjunction
+// _tmp_158: 'or' conjunction
static void *
-_tmp_159_rule(Parser *p)
+_tmp_158_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36928,16 +37032,16 @@ _tmp_159_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_159[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'or' conjunction"));
+ D(fprintf(stderr, "%*c> _tmp_158[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'or' conjunction"));
Token * _keyword;
expr_ty c;
if (
- (_keyword = _PyPegen_expect_token(p, 588)) // token='or'
+ (_keyword = _PyPegen_expect_token(p, 589)) // token='or'
&&
(c = conjunction_rule(p)) // conjunction
)
{
- D(fprintf(stderr, "%*c+ _tmp_159[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'or' conjunction"));
+ D(fprintf(stderr, "%*c+ _tmp_158[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'or' conjunction"));
_res = c;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -36947,7 +37051,7 @@ _tmp_159_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_159[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_158[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'or' conjunction"));
}
_res = NULL;
@@ -36956,9 +37060,9 @@ _tmp_159_rule(Parser *p)
return _res;
}
-// _tmp_160: 'and' inversion
+// _tmp_159: 'and' inversion
static void *
-_tmp_160_rule(Parser *p)
+_tmp_159_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -36974,16 +37078,16 @@ _tmp_160_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_160[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'and' inversion"));
+ D(fprintf(stderr, "%*c> _tmp_159[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'and' inversion"));
Token * _keyword;
expr_ty c;
if (
- (_keyword = _PyPegen_expect_token(p, 589)) // token='and'
+ (_keyword = _PyPegen_expect_token(p, 590)) // token='and'
&&
(c = inversion_rule(p)) // inversion
)
{
- D(fprintf(stderr, "%*c+ _tmp_160[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'and' inversion"));
+ D(fprintf(stderr, "%*c+ _tmp_159[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'and' inversion"));
_res = c;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -36993,7 +37097,7 @@ _tmp_160_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_160[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_159[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'and' inversion"));
}
_res = NULL;
@@ -37002,9 +37106,9 @@ _tmp_160_rule(Parser *p)
return _res;
}
-// _tmp_161: slice | starred_expression
+// _tmp_160: slice | starred_expression
static void *
-_tmp_161_rule(Parser *p)
+_tmp_160_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37020,18 +37124,18 @@ _tmp_161_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slice"));
+ D(fprintf(stderr, "%*c> _tmp_160[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "slice"));
expr_ty slice_var;
if (
(slice_var = slice_rule(p)) // slice
)
{
- D(fprintf(stderr, "%*c+ _tmp_161[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slice"));
+ D(fprintf(stderr, "%*c+ _tmp_160[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "slice"));
_res = slice_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_161[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_160[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "slice"));
}
{ // starred_expression
@@ -37039,18 +37143,18 @@ _tmp_161_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression"));
+ D(fprintf(stderr, "%*c> _tmp_160[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression"));
expr_ty starred_expression_var;
if (
(starred_expression_var = starred_expression_rule(p)) // starred_expression
)
{
- D(fprintf(stderr, "%*c+ _tmp_161[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression"));
+ D(fprintf(stderr, "%*c+ _tmp_160[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression"));
_res = starred_expression_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_161[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_160[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "starred_expression"));
}
_res = NULL;
@@ -37059,9 +37163,9 @@ _tmp_161_rule(Parser *p)
return _res;
}
-// _tmp_162: fstring | string | tstring
+// _tmp_161: fstring | string | tstring
static void *
-_tmp_162_rule(Parser *p)
+_tmp_161_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37077,18 +37181,18 @@ _tmp_162_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_162[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "fstring"));
+ D(fprintf(stderr, "%*c> _tmp_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "fstring"));
expr_ty fstring_var;
if (
(fstring_var = fstring_rule(p)) // fstring
)
{
- D(fprintf(stderr, "%*c+ _tmp_162[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "fstring"));
+ D(fprintf(stderr, "%*c+ _tmp_161[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "fstring"));
_res = fstring_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_162[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_161[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "fstring"));
}
{ // string
@@ -37096,18 +37200,18 @@ _tmp_162_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_162[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "string"));
+ D(fprintf(stderr, "%*c> _tmp_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "string"));
expr_ty string_var;
if (
(string_var = string_rule(p)) // string
)
{
- D(fprintf(stderr, "%*c+ _tmp_162[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "string"));
+ D(fprintf(stderr, "%*c+ _tmp_161[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "string"));
_res = string_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_162[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_161[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "string"));
}
{ // tstring
@@ -37115,18 +37219,18 @@ _tmp_162_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_162[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tstring"));
+ D(fprintf(stderr, "%*c> _tmp_161[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "tstring"));
expr_ty tstring_var;
if (
(tstring_var = tstring_rule(p)) // tstring
)
{
- D(fprintf(stderr, "%*c+ _tmp_162[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tstring"));
+ D(fprintf(stderr, "%*c+ _tmp_161[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "tstring"));
_res = tstring_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_162[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_161[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "tstring"));
}
_res = NULL;
@@ -37135,9 +37239,9 @@ _tmp_162_rule(Parser *p)
return _res;
}
-// _tmp_163: 'if' disjunction
+// _tmp_162: 'if' disjunction
static void *
-_tmp_163_rule(Parser *p)
+_tmp_162_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37153,16 +37257,16 @@ _tmp_163_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_163[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'if' disjunction"));
+ D(fprintf(stderr, "%*c> _tmp_162[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'if' disjunction"));
Token * _keyword;
expr_ty z;
if (
- (_keyword = _PyPegen_expect_token(p, 682)) // token='if'
+ (_keyword = _PyPegen_expect_token(p, 687)) // token='if'
&&
(z = disjunction_rule(p)) // disjunction
)
{
- D(fprintf(stderr, "%*c+ _tmp_163[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'if' disjunction"));
+ D(fprintf(stderr, "%*c+ _tmp_162[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'if' disjunction"));
_res = z;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -37172,7 +37276,7 @@ _tmp_163_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_163[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_162[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'if' disjunction"));
}
_res = NULL;
@@ -37181,9 +37285,9 @@ _tmp_163_rule(Parser *p)
return _res;
}
-// _tmp_164: starred_expression | (assignment_expression | expression !':=') !'='
+// _tmp_163: starred_expression | (assignment_expression | expression !':=') !'='
static void *
-_tmp_164_rule(Parser *p)
+_tmp_163_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37199,18 +37303,18 @@ _tmp_164_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_164[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression"));
+ D(fprintf(stderr, "%*c> _tmp_163[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression"));
expr_ty starred_expression_var;
if (
(starred_expression_var = starred_expression_rule(p)) // starred_expression
)
{
- D(fprintf(stderr, "%*c+ _tmp_164[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression"));
+ D(fprintf(stderr, "%*c+ _tmp_163[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression"));
_res = starred_expression_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_164[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_163[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "starred_expression"));
}
{ // (assignment_expression | expression !':=') !'='
@@ -37218,20 +37322,20 @@ _tmp_164_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_164[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='"));
- void *_tmp_87_var;
+ D(fprintf(stderr, "%*c> _tmp_163[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='"));
+ void *_tmp_86_var;
if (
- (_tmp_87_var = _tmp_87_rule(p)) // assignment_expression | expression !':='
+ (_tmp_86_var = _tmp_86_rule(p)) // assignment_expression | expression !':='
&&
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 22) // token='='
)
{
- D(fprintf(stderr, "%*c+ _tmp_164[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='"));
- _res = _tmp_87_var;
+ D(fprintf(stderr, "%*c+ _tmp_163[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "(assignment_expression | expression !':=') !'='"));
+ _res = _tmp_86_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_164[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_163[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(assignment_expression | expression !':=') !'='"));
}
_res = NULL;
@@ -37240,9 +37344,9 @@ _tmp_164_rule(Parser *p)
return _res;
}
-// _tmp_165: ',' star_target
+// _tmp_164: ',' star_target
static void *
-_tmp_165_rule(Parser *p)
+_tmp_164_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37258,7 +37362,7 @@ _tmp_165_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_165[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target"));
+ D(fprintf(stderr, "%*c> _tmp_164[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' star_target"));
Token * _literal;
expr_ty c;
if (
@@ -37267,7 +37371,7 @@ _tmp_165_rule(Parser *p)
(c = star_target_rule(p)) // star_target
)
{
- D(fprintf(stderr, "%*c+ _tmp_165[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_target"));
+ D(fprintf(stderr, "%*c+ _tmp_164[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' star_target"));
_res = c;
if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1;
@@ -37277,7 +37381,7 @@ _tmp_165_rule(Parser *p)
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_165[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_164[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' star_target"));
}
_res = NULL;
@@ -37286,10 +37390,10 @@ _tmp_165_rule(Parser *p)
return _res;
}
-// _tmp_166:
+// _tmp_165:
// | ','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs
static void *
-_tmp_166_rule(Parser *p)
+_tmp_165_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37305,24 +37409,24 @@ _tmp_166_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_166[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs"));
- asdl_seq * _gather_89_var;
+ D(fprintf(stderr, "%*c> _tmp_165[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs"));
+ asdl_seq * _gather_88_var;
Token * _literal;
asdl_seq* kwargs_var;
if (
- (_gather_89_var = _gather_89_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+
+ (_gather_88_var = _gather_88_rule(p)) // ','.(starred_expression | (assignment_expression | expression !':=') !'=')+
&&
(_literal = _PyPegen_expect_token(p, 12)) // token=','
&&
(kwargs_var = kwargs_rule(p)) // kwargs
)
{
- D(fprintf(stderr, "%*c+ _tmp_166[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs"));
- _res = _PyPegen_dummy_name(p, _gather_89_var, _literal, kwargs_var);
+ D(fprintf(stderr, "%*c+ _tmp_165[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs"));
+ _res = _PyPegen_dummy_name(p, _gather_88_var, _literal, kwargs_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_166[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_165[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','.(starred_expression | (assignment_expression | expression !':=') !'=')+ ',' kwargs"));
}
_res = NULL;
@@ -37331,9 +37435,9 @@ _tmp_166_rule(Parser *p)
return _res;
}
-// _tmp_167: starred_expression !'='
+// _tmp_166: starred_expression !'='
static void *
-_tmp_167_rule(Parser *p)
+_tmp_166_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37349,7 +37453,7 @@ _tmp_167_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_167[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression !'='"));
+ D(fprintf(stderr, "%*c> _tmp_166[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "starred_expression !'='"));
expr_ty starred_expression_var;
if (
(starred_expression_var = starred_expression_rule(p)) // starred_expression
@@ -37357,12 +37461,12 @@ _tmp_167_rule(Parser *p)
_PyPegen_lookahead_with_int(0, _PyPegen_expect_token, p, 22) // token='='
)
{
- D(fprintf(stderr, "%*c+ _tmp_167[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression !'='"));
+ D(fprintf(stderr, "%*c+ _tmp_166[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "starred_expression !'='"));
_res = starred_expression_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_167[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_166[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "starred_expression !'='"));
}
_res = NULL;
@@ -37371,9 +37475,9 @@ _tmp_167_rule(Parser *p)
return _res;
}
-// _tmp_168: !STRING expression_without_invalid
+// _tmp_167: !STRING expression_without_invalid
static void *
-_tmp_168_rule(Parser *p)
+_tmp_167_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37389,20 +37493,20 @@ _tmp_168_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_168[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "!STRING expression_without_invalid"));
+ D(fprintf(stderr, "%*c> _tmp_167[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "!STRING expression_without_invalid"));
expr_ty expression_without_invalid_var;
if (
- _PyPegen_lookahead(0, (void *(*)(Parser *)) _PyPegen_string_token, p)
+ _PyPegen_lookahead(0, _PyPegen_string_token, p)
&&
(expression_without_invalid_var = expression_without_invalid_rule(p)) // expression_without_invalid
)
{
- D(fprintf(stderr, "%*c+ _tmp_168[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "!STRING expression_without_invalid"));
+ D(fprintf(stderr, "%*c+ _tmp_167[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "!STRING expression_without_invalid"));
_res = expression_without_invalid_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_168[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_167[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "!STRING expression_without_invalid"));
}
_res = NULL;
@@ -37411,9 +37515,9 @@ _tmp_168_rule(Parser *p)
return _res;
}
-// _tmp_169: ')' | '**'
+// _tmp_168: ')' | '**'
static void *
-_tmp_169_rule(Parser *p)
+_tmp_168_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37429,18 +37533,18 @@ _tmp_169_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_169[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c> _tmp_168[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 8)) // token=')'
)
{
- D(fprintf(stderr, "%*c+ _tmp_169[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c+ _tmp_168[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_169[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_168[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'"));
}
{ // '**'
@@ -37448,18 +37552,18 @@ _tmp_169_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_169[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'"));
+ D(fprintf(stderr, "%*c> _tmp_168[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 35)) // token='**'
)
{
- D(fprintf(stderr, "%*c+ _tmp_169[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'"));
+ D(fprintf(stderr, "%*c+ _tmp_168[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_169[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_168[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'**'"));
}
_res = NULL;
@@ -37468,9 +37572,9 @@ _tmp_169_rule(Parser *p)
return _res;
}
-// _tmp_170: ':' | '**'
+// _tmp_169: ':' | '**'
static void *
-_tmp_170_rule(Parser *p)
+_tmp_169_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37486,18 +37590,18 @@ _tmp_170_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_170[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c> _tmp_169[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "':'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 11)) // token=':'
)
{
- D(fprintf(stderr, "%*c+ _tmp_170[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
+ D(fprintf(stderr, "%*c+ _tmp_169[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "':'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_170[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_169[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "':'"));
}
{ // '**'
@@ -37505,18 +37609,18 @@ _tmp_170_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_170[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'"));
+ D(fprintf(stderr, "%*c> _tmp_169[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'**'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 35)) // token='**'
)
{
- D(fprintf(stderr, "%*c+ _tmp_170[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'"));
+ D(fprintf(stderr, "%*c+ _tmp_169[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'**'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_170[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_169[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'**'"));
}
_res = NULL;
@@ -37525,9 +37629,9 @@ _tmp_170_rule(Parser *p)
return _res;
}
-// _loop0_171: (',' bitwise_or)
+// _loop0_170: (',' bitwise_or)
static asdl_seq *
-_loop0_171_rule(Parser *p)
+_loop0_170_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37552,13 +37656,13 @@ _loop0_171_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _loop0_171[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' bitwise_or)"));
- void *_tmp_175_var;
+ D(fprintf(stderr, "%*c> _loop0_170[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "(',' bitwise_or)"));
+ void *_tmp_174_var;
while (
- (_tmp_175_var = _tmp_175_rule(p)) // ',' bitwise_or
+ (_tmp_174_var = _tmp_174_rule(p)) // ',' bitwise_or
)
{
- _res = _tmp_175_var;
+ _res = _tmp_174_var;
if (_n == _children_capacity) {
_children_capacity *= 2;
void **_new_children = PyMem_Realloc(_children, _children_capacity*sizeof(void *));
@@ -37575,7 +37679,7 @@ _loop0_171_rule(Parser *p)
_mark = p->mark;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _loop0_171[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _loop0_170[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "(',' bitwise_or)"));
}
asdl_seq *_seq = (asdl_seq*)_Py_asdl_generic_seq_new(_n, p->arena);
@@ -37592,9 +37696,9 @@ _loop0_171_rule(Parser *p)
return _seq;
}
-// _tmp_172: ',' | ')' | NEWLINE
+// _tmp_171: ',' | ')' | NEWLINE
static void *
-_tmp_172_rule(Parser *p)
+_tmp_171_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37610,18 +37714,18 @@ _tmp_172_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c> _tmp_171[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "','"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 12)) // token=','
)
{
- D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
+ D(fprintf(stderr, "%*c+ _tmp_171[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "','"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_171[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "','"));
}
{ // ')'
@@ -37629,18 +37733,18 @@ _tmp_172_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c> _tmp_171[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "')'"));
Token * _literal;
if (
(_literal = _PyPegen_expect_token(p, 8)) // token=')'
)
{
- D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
+ D(fprintf(stderr, "%*c+ _tmp_171[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "')'"));
_res = _literal;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_171[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "')'"));
}
{ // NEWLINE
@@ -37648,18 +37752,18 @@ _tmp_172_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
+ D(fprintf(stderr, "%*c> _tmp_171[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
Token * newline_var;
if (
(newline_var = _PyPegen_expect_token(p, NEWLINE)) // token='NEWLINE'
)
{
- D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
+ D(fprintf(stderr, "%*c+ _tmp_171[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NEWLINE"));
_res = newline_var;
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_171[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NEWLINE"));
}
_res = NULL;
@@ -37668,9 +37772,9 @@ _tmp_172_rule(Parser *p)
return _res;
}
-// _tmp_173: expression ['as' star_target]
+// _tmp_172: expression ['as' star_target]
static void *
-_tmp_173_rule(Parser *p)
+_tmp_172_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37686,22 +37790,22 @@ _tmp_173_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_173[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]"));
+ D(fprintf(stderr, "%*c> _tmp_172[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]"));
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
expr_ty expression_var;
if (
(expression_var = expression_rule(p)) // expression
&&
- (_opt_var = _tmp_176_rule(p), !p->error_indicator) // ['as' star_target]
+ (_opt_var = _tmp_175_rule(p), !p->error_indicator) // ['as' star_target]
)
{
- D(fprintf(stderr, "%*c+ _tmp_173[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]"));
+ D(fprintf(stderr, "%*c+ _tmp_172[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression ['as' star_target]"));
_res = _PyPegen_dummy_name(p, expression_var, _opt_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_173[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_172[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression ['as' star_target]"));
}
_res = NULL;
@@ -37710,9 +37814,9 @@ _tmp_173_rule(Parser *p)
return _res;
}
-// _tmp_174: expressions ['as' star_target]
+// _tmp_173: expressions ['as' star_target]
static void *
-_tmp_174_rule(Parser *p)
+_tmp_173_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37728,22 +37832,22 @@ _tmp_174_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_174[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]"));
+ D(fprintf(stderr, "%*c> _tmp_173[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]"));
void *_opt_var;
UNUSED(_opt_var); // Silence compiler warnings
expr_ty expressions_var;
if (
(expressions_var = expressions_rule(p)) // expressions
&&
- (_opt_var = _tmp_176_rule(p), !p->error_indicator) // ['as' star_target]
+ (_opt_var = _tmp_175_rule(p), !p->error_indicator) // ['as' star_target]
)
{
- D(fprintf(stderr, "%*c+ _tmp_174[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]"));
+ D(fprintf(stderr, "%*c+ _tmp_173[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expressions ['as' star_target]"));
_res = _PyPegen_dummy_name(p, expressions_var, _opt_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_174[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_173[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expressions ['as' star_target]"));
}
_res = NULL;
@@ -37752,9 +37856,9 @@ _tmp_174_rule(Parser *p)
return _res;
}
-// _tmp_175: ',' bitwise_or
+// _tmp_174: ',' bitwise_or
static void *
-_tmp_175_rule(Parser *p)
+_tmp_174_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37770,7 +37874,7 @@ _tmp_175_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_175[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' bitwise_or"));
+ D(fprintf(stderr, "%*c> _tmp_174[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "',' bitwise_or"));
Token * _literal;
expr_ty bitwise_or_var;
if (
@@ -37779,12 +37883,12 @@ _tmp_175_rule(Parser *p)
(bitwise_or_var = bitwise_or_rule(p)) // bitwise_or
)
{
- D(fprintf(stderr, "%*c+ _tmp_175[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' bitwise_or"));
+ D(fprintf(stderr, "%*c+ _tmp_174[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "',' bitwise_or"));
_res = _PyPegen_dummy_name(p, _literal, bitwise_or_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_175[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_174[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "',' bitwise_or"));
}
_res = NULL;
@@ -37793,9 +37897,9 @@ _tmp_175_rule(Parser *p)
return _res;
}
-// _tmp_176: 'as' star_target
+// _tmp_175: 'as' star_target
static void *
-_tmp_176_rule(Parser *p)
+_tmp_175_rule(Parser *p)
{
if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) {
_Pypegen_stack_overflow(p);
@@ -37811,21 +37915,21 @@ _tmp_176_rule(Parser *p)
p->level--;
return NULL;
}
- D(fprintf(stderr, "%*c> _tmp_176[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target"));
+ D(fprintf(stderr, "%*c> _tmp_175[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'as' star_target"));
Token * _keyword;
expr_ty star_target_var;
if (
- (_keyword = _PyPegen_expect_token(p, 680)) // token='as'
+ (_keyword = _PyPegen_expect_token(p, 685)) // token='as'
&&
(star_target_var = star_target_rule(p)) // star_target
)
{
- D(fprintf(stderr, "%*c+ _tmp_176[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target"));
+ D(fprintf(stderr, "%*c+ _tmp_175[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'as' star_target"));
_res = _PyPegen_dummy_name(p, _keyword, star_target_var);
goto done;
}
p->mark = _mark;
- D(fprintf(stderr, "%*c%s _tmp_176[%d-%d]: %s failed!\n", p->level, ' ',
+ D(fprintf(stderr, "%*c%s _tmp_175[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'as' star_target"));
}
_res = NULL;
diff --git a/Parser/pegen.c b/Parser/pegen.c
index 3efeba78450..50641de27d3 100644
--- a/Parser/pegen.c
+++ b/Parser/pegen.c
@@ -379,44 +379,34 @@ _PyPegen_is_memoized(Parser *p, int type, void *pres)
return 0;
}
-int
-_PyPegen_lookahead_with_name(int positive, expr_ty (func)(Parser *), Parser *p)
-{
- int mark = p->mark;
- void *res = func(p);
- p->mark = mark;
- return (res != NULL) == positive;
-}
-
-int
-_PyPegen_lookahead_with_string(int positive, expr_ty (func)(Parser *, const char*), Parser *p, const char* arg)
-{
- int mark = p->mark;
- void *res = func(p, arg);
- p->mark = mark;
- return (res != NULL) == positive;
-}
-
-int
-_PyPegen_lookahead_with_int(int positive, Token *(func)(Parser *, int), Parser *p, int arg)
-{
- int mark = p->mark;
- void *res = func(p, arg);
- p->mark = mark;
- return (res != NULL) == positive;
-}
-
-// gh-111178: Use _Py_NO_SANITIZE_UNDEFINED to disable sanitizer checks on
-// undefined behavior (UBsan) in this function, rather than changing 'func'
-// callback API.
-int _Py_NO_SANITIZE_UNDEFINED
-_PyPegen_lookahead(int positive, void *(func)(Parser *), Parser *p)
-{
- int mark = p->mark;
- void *res = func(p);
- p->mark = mark;
- return (res != NULL) == positive;
-}
+#define LOOKAHEAD1(NAME, RES_TYPE) \
+ int \
+ NAME (int positive, RES_TYPE (func)(Parser *), Parser *p) \
+ { \
+ int mark = p->mark; \
+ void *res = func(p); \
+ p->mark = mark; \
+ return (res != NULL) == positive; \
+ }
+
+LOOKAHEAD1(_PyPegen_lookahead, void *)
+LOOKAHEAD1(_PyPegen_lookahead_for_expr, expr_ty)
+LOOKAHEAD1(_PyPegen_lookahead_for_stmt, stmt_ty)
+#undef LOOKAHEAD1
+
+#define LOOKAHEAD2(NAME, RES_TYPE, T) \
+ int \
+ NAME (int positive, RES_TYPE (func)(Parser *, T), Parser *p, T arg) \
+ { \
+ int mark = p->mark; \
+ void *res = func(p, arg); \
+ p->mark = mark; \
+ return (res != NULL) == positive; \
+ }
+
+LOOKAHEAD2(_PyPegen_lookahead_with_int, Token *, int)
+LOOKAHEAD2(_PyPegen_lookahead_with_string, expr_ty, const char *)
+#undef LOOKAHEAD2
Token *
_PyPegen_expect_token(Parser *p, int type)
@@ -549,6 +539,21 @@ _PyPegen_new_identifier(Parser *p, const char *n)
}
id = id2;
}
+ static const char * const forbidden[] = {
+ "None",
+ "True",
+ "False",
+ NULL
+ };
+ for (int i = 0; forbidden[i] != NULL; i++) {
+ if (_PyUnicode_EqualToASCIIString(id, forbidden[i])) {
+ PyErr_Format(PyExc_ValueError,
+ "identifier field can't represent '%s' constant",
+ forbidden[i]);
+ Py_DECREF(id);
+ goto error;
+ }
+ }
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyUnicode_InternImmortal(interp, &id);
if (_PyArena_AddPyObject(p->arena, id) < 0)
@@ -605,7 +610,8 @@ expr_ty _PyPegen_soft_keyword_token(Parser *p) {
Py_ssize_t size;
PyBytes_AsStringAndSize(t->bytes, &the_token, &size);
for (char **keyword = p->soft_keywords; *keyword != NULL; keyword++) {
- if (strncmp(*keyword, the_token, (size_t)size) == 0) {
+ if (strlen(*keyword) == (size_t)size &&
+ strncmp(*keyword, the_token, (size_t)size) == 0) {
return _PyPegen_name_from_token(p, t);
}
}
diff --git a/Parser/pegen.h b/Parser/pegen.h
index 1862fd7407e..804f931871a 100644
--- a/Parser/pegen.h
+++ b/Parser/pegen.h
@@ -145,10 +145,11 @@ int _PyPegen_insert_memo(Parser *p, int mark, int type, void *node);
int _PyPegen_update_memo(Parser *p, int mark, int type, void *node);
int _PyPegen_is_memoized(Parser *p, int type, void *pres);
-int _PyPegen_lookahead_with_name(int, expr_ty (func)(Parser *), Parser *);
-int _PyPegen_lookahead_with_int(int, Token *(func)(Parser *, int), Parser *, int);
-int _PyPegen_lookahead_with_string(int , expr_ty (func)(Parser *, const char*), Parser *, const char*);
int _PyPegen_lookahead(int, void *(func)(Parser *), Parser *);
+int _PyPegen_lookahead_for_expr(int, expr_ty (func)(Parser *), Parser *);
+int _PyPegen_lookahead_for_stmt(int, stmt_ty (func)(Parser *), Parser *);
+int _PyPegen_lookahead_with_int(int, Token *(func)(Parser *, int), Parser *, int);
+int _PyPegen_lookahead_with_string(int, expr_ty (func)(Parser *, const char*), Parser *, const char*);
Token *_PyPegen_expect_token(Parser *p, int type);
void* _PyPegen_expect_forced_result(Parser *p, void* result, const char* expected);
diff --git a/Parser/string_parser.c b/Parser/string_parser.c
index d3631b114c5..ebe68989d1a 100644
--- a/Parser/string_parser.c
+++ b/Parser/string_parser.c
@@ -196,15 +196,18 @@ decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t)
len = (size_t)(p - buf);
s = buf;
- const char *first_invalid_escape;
- v = _PyUnicode_DecodeUnicodeEscapeInternal(s, (Py_ssize_t)len, NULL, NULL, &first_invalid_escape);
+ int first_invalid_escape_char;
+ const char *first_invalid_escape_ptr;
+ v = _PyUnicode_DecodeUnicodeEscapeInternal2(s, (Py_ssize_t)len, NULL, NULL,
+ &first_invalid_escape_char,
+ &first_invalid_escape_ptr);
// HACK: later we can simply pass the line no, since we don't preserve the tokens
// when we are decoding the string but we preserve the line numbers.
- if (v != NULL && first_invalid_escape != NULL && t != NULL) {
- if (warn_invalid_escape_sequence(parser, s, first_invalid_escape, t) < 0) {
- /* We have not decref u before because first_invalid_escape points
- inside u. */
+ if (v != NULL && first_invalid_escape_ptr != NULL && t != NULL) {
+ if (warn_invalid_escape_sequence(parser, s, first_invalid_escape_ptr, t) < 0) {
+ /* We have not decref u before because first_invalid_escape_ptr
+ points inside u. */
Py_XDECREF(u);
Py_DECREF(v);
return NULL;
@@ -217,14 +220,17 @@ decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t)
static PyObject *
decode_bytes_with_escapes(Parser *p, const char *s, Py_ssize_t len, Token *t)
{
- const char *first_invalid_escape;
- PyObject *result = _PyBytes_DecodeEscape(s, len, NULL, &first_invalid_escape);
+ int first_invalid_escape_char;
+ const char *first_invalid_escape_ptr;
+ PyObject *result = _PyBytes_DecodeEscape2(s, len, NULL,
+ &first_invalid_escape_char,
+ &first_invalid_escape_ptr);
if (result == NULL) {
return NULL;
}
- if (first_invalid_escape != NULL) {
- if (warn_invalid_escape_sequence(p, s, first_invalid_escape, t) < 0) {
+ if (first_invalid_escape_ptr != NULL) {
+ if (warn_invalid_escape_sequence(p, s, first_invalid_escape_ptr, t) < 0) {
Py_DECREF(result);
return NULL;
}
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 6f6d0cae580..577da65c7cd 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -8,6 +8,7 @@
#include <Python.h>
#include "pycore_initconfig.h" // _PyConfig_InitCompatConfig()
#include "pycore_runtime.h" // _PyRuntime
+#include "pycore_lock.h" // PyEvent
#include "pycore_pythread.h" // PyThread_start_joinable_thread()
#include "pycore_import.h" // _PyImport_FrozenBootstrap
#include <inttypes.h>
@@ -80,7 +81,7 @@ static void init_from_config_clear(PyConfig *config)
}
-static void _testembed_Py_InitializeFromConfig(void)
+static void _testembed_initialize(void)
{
PyConfig config;
_PyConfig_InitCompatConfig(&config);
@@ -88,16 +89,10 @@ static void _testembed_Py_InitializeFromConfig(void)
init_from_config_clear(&config);
}
-static void _testembed_Py_Initialize(void)
-{
- Py_SetProgramName(PROGRAM_NAME);
- Py_Initialize();
-}
-
static int test_import_in_subinterpreters(void)
{
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
PyThreadState_Swap(Py_NewInterpreter());
return PyRun_SimpleString("import readline"); // gh-124160
}
@@ -131,7 +126,7 @@ static int test_repeated_init_and_subinterpreters(void)
for (int i=1; i <= INIT_LOOPS; i++) {
printf("--- Pass %d ---\n", i);
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
mainstate = PyThreadState_Get();
PyEval_ReleaseThread(mainstate);
@@ -197,7 +192,7 @@ static int test_repeated_init_exec(void)
code = main_argv[i+2];
}
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
int err = PyRun_SimpleString(code);
Py_Finalize();
if (err) {
@@ -217,7 +212,7 @@ static int test_repeated_simple_init(void)
fprintf(stderr, "--- Loop #%d ---\n", i);
fflush(stderr);
- _testembed_Py_Initialize();
+ _testembed_initialize();
Py_Finalize();
printf("Finalized\n"); // Give test_embed some output to check
}
@@ -301,24 +296,8 @@ static int test_pre_initialization_api(void)
/* the test doesn't support custom memory allocators */
putenv("PYTHONMALLOC=");
- /* Leading "./" ensures getpath.c can still find the standard library */
- _Py_EMBED_PREINIT_CHECK("Checking Py_DecodeLocale\n");
- wchar_t *program = Py_DecodeLocale("./spam", NULL);
- if (program == NULL) {
- fprintf(stderr, "Fatal error: cannot decode program name\n");
- return 1;
- }
- _Py_EMBED_PREINIT_CHECK("Checking Py_SetProgramName\n");
- Py_SetProgramName(program);
-
- _Py_EMBED_PREINIT_CHECK("Checking !Py_IsInitialized pre-initialization\n");
- if (Py_IsInitialized()) {
- fprintf(stderr, "Fatal error: initialized before initialization!\n");
- return 1;
- }
-
_Py_EMBED_PREINIT_CHECK("Initializing interpreter\n");
- Py_Initialize();
+ _testembed_initialize();
_Py_EMBED_PREINIT_CHECK("Checking Py_IsInitialized post-initialization\n");
if (!Py_IsInitialized()) {
@@ -340,9 +319,6 @@ static int test_pre_initialization_api(void)
fprintf(stderr, "Fatal error: still initialized after finalization!\n");
return 1;
}
-
- _Py_EMBED_PREINIT_CHECK("Freeing memory allocated by Py_DecodeLocale\n");
- PyMem_RawFree(program);
return 0;
}
@@ -384,7 +360,7 @@ static int test_pre_initialization_sys_options(void)
dynamic_xoption = NULL;
_Py_EMBED_PREINIT_CHECK("Initializing interpreter\n");
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
_Py_EMBED_PREINIT_CHECK("Check sys module contents\n");
PyRun_SimpleString(
"import sys; "
@@ -431,7 +407,7 @@ static int test_bpo20891(void)
return 1;
}
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
unsigned long thrd = PyThread_start_new_thread(bpo20891_thread, &lock);
if (thrd == PYTHREAD_INVALID_THREAD_ID) {
@@ -454,7 +430,7 @@ static int test_bpo20891(void)
static int test_initialize_twice(void)
{
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
/* bpo-33932: Calling Py_Initialize() twice should do nothing
* (and not crash!). */
@@ -472,7 +448,7 @@ static int test_initialize_pymain(void)
L"print(f'Py_Main() after Py_Initialize: "
L"sys.argv={sys.argv}')"),
L"arg2"};
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
/* bpo-34008: Calling Py_Main() after Py_Initialize() must not crash */
Py_Main(Py_ARRAY_LENGTH(argv), argv);
@@ -495,7 +471,7 @@ dump_config(void)
static int test_init_initialize_config(void)
{
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
dump_config();
Py_Finalize();
return 0;
@@ -569,9 +545,6 @@ static int test_init_global_config(void)
putenv("PYTHONUTF8=0");
Py_UTF8Mode = 1;
- /* Test initialization from global configuration variables (Py_xxx) */
- Py_SetProgramName(L"./globalvar");
-
/* Py_IsolatedFlag is not tested */
Py_NoSiteFlag = 1;
Py_BytesWarningFlag = 1;
@@ -604,7 +577,7 @@ static int test_init_global_config(void)
/* FIXME: test Py_LegacyWindowsFSEncodingFlag */
/* FIXME: test Py_LegacyWindowsStdioFlag */
- Py_Initialize();
+ _testembed_initialize();
dump_config();
Py_Finalize();
return 0;
@@ -651,8 +624,8 @@ static int test_init_from_config(void)
putenv("PYTHONTRACEMALLOC=0");
config.tracemalloc = 2;
- putenv("PYTHONPROFILEIMPORTTIME=0");
- config.import_time = 1;
+ putenv("PYTHONPROFILEIMPORTTIME=1");
+ config.import_time = 2;
putenv("PYTHONNODEBUGRANGES=0");
config.code_debug_ranges = 0;
@@ -666,7 +639,6 @@ static int test_init_from_config(void)
putenv("PYTHONPYCACHEPREFIX=env_pycache_prefix");
config_set_string(&config, &config.pycache_prefix, L"conf_pycache_prefix");
- Py_SetProgramName(L"./globalvar");
config_set_string(&config, &config.program_name, L"./conf_program_name");
wchar_t* argv[] = {
@@ -853,7 +825,7 @@ static int test_init_compat_env(void)
/* Test initialization from environment variables */
Py_IgnoreEnvironmentFlag = 0;
set_all_env_vars();
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
dump_config();
Py_Finalize();
return 0;
@@ -889,7 +861,7 @@ static int test_init_env_dev_mode(void)
/* Test initialization from environment variables */
Py_IgnoreEnvironmentFlag = 0;
set_all_env_vars_dev_mode();
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
dump_config();
Py_Finalize();
return 0;
@@ -906,7 +878,7 @@ static int test_init_env_dev_mode_alloc(void)
#else
putenv("PYTHONMALLOC=mimalloc");
#endif
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
dump_config();
Py_Finalize();
return 0;
@@ -1246,7 +1218,7 @@ static int test_open_code_hook(void)
}
Py_IgnoreEnvironmentFlag = 0;
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
result = 0;
PyObject *r = PyFile_OpenCode("$$test-filename");
@@ -1310,7 +1282,7 @@ static int _test_audit(Py_ssize_t setValue)
Py_IgnoreEnvironmentFlag = 0;
PySys_AddAuditHook(_audit_hook, &sawSet);
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
if (PySys_Audit("_testembed.raise", NULL) == 0) {
printf("No error raised");
@@ -1369,7 +1341,7 @@ static int test_audit_tuple(void)
// we need at least one hook, otherwise code checking for
// PySys_AuditTuple() is skipped.
PySys_AddAuditHook(_audit_hook, &sawSet);
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
ASSERT(!PyErr_Occurred(), 0);
@@ -1422,7 +1394,7 @@ static int test_audit_subinterpreter(void)
{
Py_IgnoreEnvironmentFlag = 0;
PySys_AddAuditHook(_audit_subinterpreter_hook, NULL);
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
Py_NewInterpreter();
Py_NewInterpreter();
@@ -2166,13 +2138,13 @@ static int test_unicode_id_init(void)
};
// Initialize Python once without using the identifier
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
Py_Finalize();
// Now initialize Python multiple times and use the identifier.
// The first _PyUnicode_FromId() call initializes the identifier index.
for (int i=0; i<3; i++) {
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
PyObject *str1, *str2;
@@ -2195,7 +2167,7 @@ static int test_unicode_id_init(void)
static int test_init_main_interpreter_settings(void)
{
- _testembed_Py_Initialize();
+ _testembed_initialize();
(void) PyRun_SimpleStringFlags(
"import _testinternalcapi, json; "
"print(json.dumps(_testinternalcapi.get_interp_settings(0)))",
@@ -2206,7 +2178,7 @@ static int test_init_main_interpreter_settings(void)
static void do_init(void *unused)
{
- _testembed_Py_Initialize();
+ _testembed_initialize();
Py_Finalize();
}
@@ -2331,7 +2303,7 @@ unwrap_allocator(PyMemAllocatorEx *allocator)
static int
test_get_incomplete_frame(void)
{
- _testembed_Py_InitializeFromConfig();
+ _testembed_initialize();
PyMemAllocatorEx allocator;
wrap_allocator(&allocator);
// Force an allocation with an incomplete (generator) frame:
@@ -2341,6 +2313,32 @@ test_get_incomplete_frame(void)
return result;
}
+static void
+do_gilstate_ensure(void *event_ptr)
+{
+ PyEvent *event = (PyEvent *)event_ptr;
+ // Signal to the calling thread that we've started
+ _PyEvent_Notify(event);
+ PyGILState_Ensure(); // This should hang
+ assert(NULL);
+}
+
+static int
+test_gilstate_after_finalization(void)
+{
+ _testembed_initialize();
+ Py_Finalize();
+ PyThread_handle_t handle;
+ PyThread_ident_t ident;
+ PyEvent event = {0};
+ if (PyThread_start_joinable_thread(&do_gilstate_ensure, &event, &ident, &handle) < 0) {
+ return -1;
+ }
+ PyEvent_Wait(&event);
+ // We're now pretty confident that the thread went for
+ // PyGILState_Ensure(), but that means it got hung.
+ return PyThread_detach_thread(handle);
+}
/* *********************************************************
* List of test cases and the function that implements it.
@@ -2431,7 +2429,7 @@ static struct TestCase TestCases[] = {
{"test_frozenmain", test_frozenmain},
#endif
{"test_get_incomplete_frame", test_get_incomplete_frame},
-
+ {"test_gilstate_after_finalization", test_gilstate_after_finalization},
{NULL, NULL}
};
diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h
index b7d23f57018..dbeedb7ffe0 100644
--- a/Programs/test_frozenmain.h
+++ b/Programs/test_frozenmain.h
@@ -1,6 +1,6 @@
// Auto-generated by Programs/freeze_test_frozenmain.py
unsigned char M_test_frozenmain[] = {
- 227,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,
+ 227,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,
0,0,0,0,0,243,184,0,0,0,128,0,94,0,82,1,
73,0,116,0,94,0,82,1,73,1,116,1,93,2,33,0,
82,2,52,1,0,0,0,0,0,0,31,0,93,2,33,0,
diff --git a/Python/Python-ast.c b/Python/Python-ast.c
index 94d9a76d283..660bc598a48 100644
--- a/Python/Python-ast.c
+++ b/Python/Python-ast.c
@@ -5528,6 +5528,32 @@ ast_type_replace_check(PyObject *self,
Py_DECREF(unused);
}
}
+
+ // Discard fields from 'expecting' that default to None
+ PyObject *field_types = NULL;
+ if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self),
+ &_Py_ID(_field_types),
+ &field_types) < 0)
+ {
+ Py_DECREF(expecting);
+ return -1;
+ }
+ if (field_types != NULL) {
+ Py_ssize_t pos = 0;
+ PyObject *field_name, *field_type;
+ while (PyDict_Next(field_types, &pos, &field_name, &field_type)) {
+ if (_PyUnion_Check(field_type)) {
+ // optional field
+ if (PySet_Discard(expecting, field_name) < 0) {
+ Py_DECREF(expecting);
+ Py_DECREF(field_types);
+ return -1;
+ }
+ }
+ }
+ Py_DECREF(field_types);
+ }
+
// Now 'expecting' contains the fields or attributes
// that would not be filled inside ast_type_replace().
Py_ssize_t m = PySet_GET_SIZE(expecting);
@@ -5770,7 +5796,7 @@ ast_repr_list(PyObject *list, int depth)
for (Py_ssize_t i = 0; i < Py_MIN(length, 2); i++) {
if (i > 0) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
goto error;
}
}
@@ -5794,7 +5820,7 @@ ast_repr_list(PyObject *list, int depth)
}
if (i == 0 && length > 2) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ...", 5) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ...", 5) < 0) {
goto error;
}
}
@@ -5898,7 +5924,7 @@ ast_repr_max_depth(AST_object *self, int depth)
}
if (i > 0) {
- if (PyUnicodeWriter_WriteUTF8(writer, ", ", 2) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
Py_DECREF(name);
Py_DECREF(value_repr);
goto error;
@@ -6362,7 +6388,7 @@ init_types(void *arg)
" | UnaryOp(unaryop op, expr operand)\n"
" | Lambda(arguments args, expr body)\n"
" | IfExp(expr test, expr body, expr orelse)\n"
- " | Dict(expr* keys, expr* values)\n"
+ " | Dict(expr?* keys, expr* values)\n"
" | Set(expr* elts)\n"
" | ListComp(expr elt, comprehension* generators)\n"
" | SetComp(expr elt, comprehension* generators)\n"
@@ -6419,7 +6445,7 @@ init_types(void *arg)
if (!state->IfExp_type) return -1;
state->Dict_type = make_type(state, "Dict", state->expr_type, Dict_fields,
2,
- "Dict(expr* keys, expr* values)");
+ "Dict(expr?* keys, expr* values)");
if (!state->Dict_type) return -1;
state->Set_type = make_type(state, "Set", state->expr_type, Set_fields, 1,
"Set(expr* elts)");
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 39bf1b225cc..12e6172b0cf 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -6,7 +6,6 @@
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing()
#include "pycore_pystate.h" // _PyThreadState_GET()
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
#include "pycore_traceback.h" // _Py_DisplaySourceLine()
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
@@ -678,7 +677,7 @@ show_warning(PyThreadState *tstate, PyObject *filename, int lineno,
goto error;
}
- if (_PySys_GetOptionalAttr(&_Py_ID(stderr), &f_stderr) <= 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stderr), &f_stderr) <= 0) {
fprintf(stderr, "lost sys.stderr\n");
goto error;
}
diff --git a/Python/asm_trampoline.S b/Python/asm_trampoline.S
index 0a3265dfeee..616752459ba 100644
--- a/Python/asm_trampoline.S
+++ b/Python/asm_trampoline.S
@@ -9,6 +9,9 @@
# }
_Py_trampoline_func_start:
#ifdef __x86_64__
+#if defined(__CET__) && (__CET__ & 1)
+ endbr64
+#endif
sub $8, %rsp
call *%rcx
add $8, %rsp
@@ -34,3 +37,22 @@ _Py_trampoline_func_start:
.globl _Py_trampoline_func_end
_Py_trampoline_func_end:
.section .note.GNU-stack,"",@progbits
+# Note for indicating the assembly code supports CET
+#if defined(__x86_64__) && defined(__CET__) && (__CET__ & 1)
+ .section .note.gnu.property,"a"
+ .align 8
+ .long 1f - 0f
+ .long 4f - 1f
+ .long 5
+0:
+ .string "GNU"
+1:
+ .align 8
+ .long 0xc0000002
+ .long 3f - 2f
+2:
+ .long 0x3
+3:
+ .align 8
+4:
+#endif // __x86_64__
diff --git a/Python/ast_opt.c b/Python/ast_preprocess.c
index 4d5e5589ac0..bafd67ed790 100644
--- a/Python/ast_opt.c
+++ b/Python/ast_preprocess.c
@@ -1,4 +1,4 @@
-/* AST Optimizer */
+/* AST pre-processing */
#include "Python.h"
#include "pycore_ast.h" // _PyAST_GetDocString()
#include "pycore_c_array.h" // _Py_CArray_EnsureCapacity()
@@ -22,7 +22,7 @@ typedef struct {
_Py_c_array_t cf_finally; /* context for PEP 765 check */
int cf_finally_used;
-} _PyASTOptimizeState;
+} _PyASTPreprocessState;
#define ENTER_RECURSIVE() \
if (Py_EnterRecursiveCall(" during compilation")) { \
@@ -32,14 +32,14 @@ if (Py_EnterRecursiveCall(" during compilation")) { \
#define LEAVE_RECURSIVE() Py_LeaveRecursiveCall();
static ControlFlowInFinallyContext*
-get_cf_finally_top(_PyASTOptimizeState *state)
+get_cf_finally_top(_PyASTPreprocessState *state)
{
int idx = state->cf_finally_used;
return ((ControlFlowInFinallyContext*)state->cf_finally.array) + idx;
}
static int
-push_cf_context(_PyASTOptimizeState *state, stmt_ty node, bool finally, bool funcdef, bool loop)
+push_cf_context(_PyASTPreprocessState *state, stmt_ty node, bool finally, bool funcdef, bool loop)
{
if (_Py_CArray_EnsureCapacity(&state->cf_finally, state->cf_finally_used+1) < 0) {
return 0;
@@ -55,14 +55,14 @@ push_cf_context(_PyASTOptimizeState *state, stmt_ty node, bool finally, bool fun
}
static void
-pop_cf_context(_PyASTOptimizeState *state)
+pop_cf_context(_PyASTPreprocessState *state)
{
assert(state->cf_finally_used > 0);
state->cf_finally_used--;
}
static int
-control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTOptimizeState *state)
+control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTPreprocessState *state)
{
PyObject *msg = PyUnicode_FromFormat("'%s' in a 'finally' block", kw);
if (msg == NULL) {
@@ -76,7 +76,7 @@ control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTOptimizeState *
}
static int
-before_return(_PyASTOptimizeState *state, stmt_ty node_)
+before_return(_PyASTPreprocessState *state, stmt_ty node_)
{
if (state->cf_finally_used > 0) {
ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
@@ -90,7 +90,7 @@ before_return(_PyASTOptimizeState *state, stmt_ty node_)
}
static int
-before_loop_exit(_PyASTOptimizeState *state, stmt_ty node_, const char *kw)
+before_loop_exit(_PyASTPreprocessState *state, stmt_ty node_, const char *kw)
{
if (state->cf_finally_used > 0) {
ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
@@ -365,7 +365,7 @@ optimize_format(expr_ty node, PyObject *fmt, asdl_expr_seq *elts, PyArena *arena
}
static int
-fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
+fold_binop(expr_ty node, PyArena *arena, _PyASTPreprocessState *state)
{
if (state->syntax_check_only) {
return 1;
@@ -389,18 +389,18 @@ fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
return 1;
}
-static int astfold_mod(mod_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_match_case(match_case_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
-static int astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
+static int astfold_mod(mod_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_match_case(match_case_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
+static int astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTPreprocessState *state);
#define CALL(FUNC, TYPE, ARG) \
if (!FUNC((ARG), ctx_, state)) \
@@ -436,7 +436,7 @@ stmt_seq_remove_item(asdl_stmt_seq *stmts, Py_ssize_t idx)
}
static int
-astfold_body(asdl_stmt_seq *stmts, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_body(asdl_stmt_seq *stmts, PyArena *ctx_, _PyASTPreprocessState *state)
{
int docstring = _PyAST_GetDocString(stmts) != NULL;
if (docstring && (state->optimize >= 2)) {
@@ -466,7 +466,7 @@ astfold_body(asdl_stmt_seq *stmts, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-astfold_mod(mod_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_mod(mod_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
switch (node_->kind) {
case Module_kind:
@@ -488,7 +488,7 @@ astfold_mod(mod_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
ENTER_RECURSIVE();
switch (node_->kind) {
@@ -613,14 +613,14 @@ astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
CALL(astfold_expr, expr_ty, node_->value);
return 1;
}
static int
-astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
CALL(astfold_expr, expr_ty, node_->target);
CALL(astfold_expr, expr_ty, node_->iter);
@@ -629,7 +629,7 @@ astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState
}
static int
-astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
CALL_SEQ(astfold_arg, arg, node_->posonlyargs);
CALL_SEQ(astfold_arg, arg, node_->args);
@@ -642,7 +642,7 @@ astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->annotation);
@@ -651,7 +651,7 @@ astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
ENTER_RECURSIVE();
switch (node_->kind) {
@@ -806,7 +806,7 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
switch (node_->kind) {
case ExceptHandler_kind:
@@ -820,7 +820,7 @@ astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState
}
static int
-astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
CALL(astfold_expr, expr_ty, node_->context_expr);
CALL_OPT(astfold_expr, expr_ty, node_->optional_vars);
@@ -828,7 +828,7 @@ astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-fold_const_match_patterns(expr_ty node, PyArena *ctx_, _PyASTOptimizeState *state)
+fold_const_match_patterns(expr_ty node, PyArena *ctx_, _PyASTPreprocessState *state)
{
if (state->syntax_check_only) {
return 1;
@@ -869,7 +869,7 @@ fold_const_match_patterns(expr_ty node, PyArena *ctx_, _PyASTOptimizeState *stat
}
static int
-astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
// Currently, this is really only used to form complex/negative numeric
// constants in MatchValue and MatchMapping nodes
@@ -911,7 +911,7 @@ astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
}
static int
-astfold_match_case(match_case_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_match_case(match_case_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
CALL(astfold_pattern, expr_ty, node_->pattern);
CALL_OPT(astfold_expr, expr_ty, node_->guard);
@@ -920,7 +920,7 @@ astfold_match_case(match_case_ty node_, PyArena *ctx_, _PyASTOptimizeState *stat
}
static int
-astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
+astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTPreprocessState *state)
{
switch (node_->kind) {
case TypeVar_kind:
@@ -942,11 +942,11 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTOptimizeState *stat
#undef CALL_SEQ
int
-_PyAST_Optimize(mod_ty mod, PyArena *arena, PyObject *filename, int optimize,
- int ff_features, int syntax_check_only)
+_PyAST_Preprocess(mod_ty mod, PyArena *arena, PyObject *filename, int optimize,
+ int ff_features, int syntax_check_only)
{
- _PyASTOptimizeState state;
- memset(&state, 0, sizeof(_PyASTOptimizeState));
+ _PyASTPreprocessState state;
+ memset(&state, 0, sizeof(_PyASTPreprocessState));
state.filename = filename;
state.optimize = optimize;
state.ff_features = ff_features;
diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c
index c121ec096ae..557c12cfda6 100644
--- a/Python/ast_unparse.c
+++ b/Python/ast_unparse.c
@@ -702,6 +702,13 @@ append_templatestr(PyUnicodeWriter *writer, expr_ty e)
Py_ssize_t last_idx = 0;
Py_ssize_t len = asdl_seq_LEN(e->v.TemplateStr.values);
+ if (len == 0) {
+ int result = _write_values_subarray(writer, e->v.TemplateStr.values,
+ 0, len - 1, 't', arena);
+ _PyArena_Free(arena);
+ return result;
+ }
+
for (Py_ssize_t i = 0; i < len; i++) {
expr_ty value = asdl_seq_GET(e->v.TemplateStr.values, i);
@@ -742,6 +749,7 @@ append_templatestr(PyUnicodeWriter *writer, expr_ty e)
goto error;
}
}
+ _PyArena_Free(arena);
return 0;
@@ -774,33 +782,38 @@ append_joinedstr(PyUnicodeWriter *writer, expr_ty e, bool is_format_spec)
}
static int
-append_interpolation_value(PyUnicodeWriter *writer, expr_ty e)
+append_interpolation_str(PyUnicodeWriter *writer, PyObject *str)
{
const char *outer_brace = "{";
- /* Grammar allows PR_TUPLE, but use >PR_TEST for adding parenthesis
- around a lambda with ':' */
- PyObject *temp_fv_str = expr_as_unicode(e, PR_TEST + 1);
- if (!temp_fv_str) {
- return -1;
- }
- if (PyUnicode_Find(temp_fv_str, _Py_LATIN1_CHR('{'), 0, 1, 1) == 0) {
+ if (PyUnicode_Find(str, _Py_LATIN1_CHR('{'), 0, 1, 1) == 0) {
/* Expression starts with a brace, split it with a space from the outer
one. */
outer_brace = "{ ";
}
if (-1 == append_charp(writer, outer_brace)) {
- Py_DECREF(temp_fv_str);
return -1;
}
- if (-1 == PyUnicodeWriter_WriteStr(writer, temp_fv_str)) {
- Py_DECREF(temp_fv_str);
+ if (-1 == PyUnicodeWriter_WriteStr(writer, str)) {
return -1;
}
- Py_DECREF(temp_fv_str);
return 0;
}
static int
+append_interpolation_value(PyUnicodeWriter *writer, expr_ty e)
+{
+ /* Grammar allows PR_TUPLE, but use >PR_TEST for adding parenthesis
+ around a lambda with ':' */
+ PyObject *temp_fv_str = expr_as_unicode(e, PR_TEST + 1);
+ if (!temp_fv_str) {
+ return -1;
+ }
+ int result = append_interpolation_str(writer, temp_fv_str);
+ Py_DECREF(temp_fv_str);
+ return result;
+}
+
+static int
append_interpolation_conversion(PyUnicodeWriter *writer, int conversion)
{
if (conversion < 0) {
@@ -843,7 +856,7 @@ append_interpolation_format_spec(PyUnicodeWriter *writer, expr_ty e)
static int
append_interpolation(PyUnicodeWriter *writer, expr_ty e)
{
- if (-1 == append_interpolation_value(writer, e->v.Interpolation.value)) {
+ if (-1 == append_interpolation_str(writer, e->v.Interpolation.str)) {
return -1;
}
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index 3221d5acf96..51d7297ec24 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -14,7 +14,6 @@
#include "pycore_pyerrors.h" // _PyErr_NoMemory()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_pythonrun.h" // _Py_SourceAsString()
-#include "pycore_sysmodule.h" // _PySys_GetRequiredAttr()
#include "pycore_tuple.h" // _PyTuple_FromArray()
#include "pycore_cell.h" // PyCell_GetRef()
@@ -465,7 +464,7 @@ builtin_callable(PyObject *module, PyObject *obj)
static PyObject *
builtin_breakpoint(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *keywords)
{
- PyObject *hook = _PySys_GetRequiredAttrString("breakpointhook");
+ PyObject *hook = PySys_GetAttrString("breakpointhook");
if (hook == NULL) {
return NULL;
}
@@ -845,7 +844,7 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
goto error;
}
int syntax_check_only = ((flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST); /* unoptiomized AST */
- if (_PyCompile_AstOptimize(mod, filename, &cf, optimize,
+ if (_PyCompile_AstPreprocess(mod, filename, &cf, optimize,
arena, syntax_check_only) < 0) {
_PyArena_Free(arena);
goto error;
@@ -958,6 +957,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
PyObject *locals)
/*[clinic end generated code: output=0a0824aa70093116 input=7c7bce5299a89062]*/
{
+ PyThreadState *tstate = _PyThreadState_GET();
PyObject *result = NULL, *source_copy;
const char *str;
@@ -971,35 +971,46 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
: "globals must be a dict");
return NULL;
}
- if (globals == Py_None) {
+
+ int fromframe = 0;
+ if (globals != Py_None) {
+ Py_INCREF(globals);
+ }
+ else if (_PyEval_GetFrame() != NULL) {
+ fromframe = 1;
globals = PyEval_GetGlobals();
- if (locals == Py_None) {
- locals = _PyEval_GetFrameLocals();
- if (locals == NULL)
- return NULL;
- }
- else {
- Py_INCREF(locals);
- }
+ assert(globals != NULL);
+ Py_INCREF(globals);
}
- else if (locals == Py_None)
- locals = Py_NewRef(globals);
else {
- Py_INCREF(locals);
+ globals = _PyEval_GetGlobalsFromRunningMain(tstate);
+ if (globals == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ PyErr_SetString(PyExc_TypeError,
+ "eval must be given globals and locals "
+ "when called without a frame");
+ }
+ return NULL;
+ }
+ Py_INCREF(globals);
}
- if (globals == NULL || locals == NULL) {
- PyErr_SetString(PyExc_TypeError,
- "eval must be given globals and locals "
- "when called without a frame");
- goto error;
+ if (locals != Py_None) {
+ Py_INCREF(locals);
}
-
- int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
- if (r == 0) {
- r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
+ else if (fromframe) {
+ locals = _PyEval_GetFrameLocals();
+ if (locals == NULL) {
+ assert(PyErr_Occurred());
+ Py_DECREF(globals);
+ return NULL;
+ }
+ }
+ else {
+ locals = Py_NewRef(globals);
}
- if (r < 0) {
+
+ if (_PyEval_EnsureBuiltins(tstate, globals, NULL) < 0) {
goto error;
}
@@ -1040,6 +1051,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
}
error:
+ Py_XDECREF(globals);
Py_XDECREF(locals);
return result;
}
@@ -1070,29 +1082,44 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
PyObject *locals, PyObject *closure)
/*[clinic end generated code: output=7579eb4e7646743d input=25e989b6d87a3a21]*/
{
+ PyThreadState *tstate = _PyThreadState_GET();
PyObject *v;
- if (globals == Py_None) {
+ int fromframe = 0;
+ if (globals != Py_None) {
+ Py_INCREF(globals);
+ }
+ else if (_PyEval_GetFrame() != NULL) {
+ fromframe = 1;
globals = PyEval_GetGlobals();
- if (locals == Py_None) {
- locals = _PyEval_GetFrameLocals();
- if (locals == NULL)
- return NULL;
- }
- else {
- Py_INCREF(locals);
+ assert(globals != NULL);
+ Py_INCREF(globals);
+ }
+ else {
+ globals = _PyEval_GetGlobalsFromRunningMain(tstate);
+ if (globals == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ PyErr_SetString(PyExc_SystemError,
+ "globals and locals cannot be NULL");
+ }
+ goto error;
}
- if (!globals || !locals) {
- PyErr_SetString(PyExc_SystemError,
- "globals and locals cannot be NULL");
+ Py_INCREF(globals);
+ }
+
+ if (locals != Py_None) {
+ Py_INCREF(locals);
+ }
+ else if (fromframe) {
+ locals = _PyEval_GetFrameLocals();
+ if (locals == NULL) {
+ assert(PyErr_Occurred());
+ Py_DECREF(globals);
return NULL;
}
}
- else if (locals == Py_None) {
- locals = Py_NewRef(globals);
- }
else {
- Py_INCREF(locals);
+ locals = Py_NewRef(globals);
}
if (!PyDict_Check(globals)) {
@@ -1106,11 +1133,8 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
Py_TYPE(locals)->tp_name);
goto error;
}
- int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
- if (r == 0) {
- r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
- }
- if (r < 0) {
+
+ if (_PyEval_EnsureBuiltins(tstate, globals, NULL) < 0) {
goto error;
}
@@ -1187,11 +1211,13 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
}
if (v == NULL)
goto error;
+ Py_DECREF(globals);
Py_DECREF(locals);
Py_DECREF(v);
Py_RETURN_NONE;
error:
+ Py_XDECREF(globals);
Py_XDECREF(locals);
return NULL;
}
@@ -1241,10 +1267,21 @@ static PyObject *
builtin_globals_impl(PyObject *module)
/*[clinic end generated code: output=e5dd1527067b94d2 input=9327576f92bb48ba]*/
{
- PyObject *d;
-
- d = PyEval_GetGlobals();
- return Py_XNewRef(d);
+ PyObject *globals;
+ if (_PyEval_GetFrame() != NULL) {
+ globals = PyEval_GetGlobals();
+ assert(globals != NULL);
+ return Py_NewRef(globals);
+ }
+ PyThreadState *tstate = _PyThreadState_GET();
+ globals = _PyEval_GetGlobalsFromRunningMain(tstate);
+ if (globals == NULL) {
+ if (_PyErr_Occurred(tstate)) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+ }
+ return Py_NewRef(globals);
}
@@ -1888,7 +1925,21 @@ static PyObject *
builtin_locals_impl(PyObject *module)
/*[clinic end generated code: output=b46c94015ce11448 input=7874018d478d5c4b]*/
{
- return _PyEval_GetFrameLocals();
+ PyObject *locals;
+ if (_PyEval_GetFrame() != NULL) {
+ locals = _PyEval_GetFrameLocals();
+ assert(locals != NULL || PyErr_Occurred());
+ return locals;
+ }
+ PyThreadState *tstate = _PyThreadState_GET();
+ locals = _PyEval_GetGlobalsFromRunningMain(tstate);
+ if (locals == NULL) {
+ if (_PyErr_Occurred(tstate)) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+ }
+ return Py_NewRef(locals);
}
@@ -2164,7 +2215,7 @@ builtin_print_impl(PyObject *module, PyObject * const *args,
int i, err;
if (file == Py_None) {
- file = _PySys_GetRequiredAttr(&_Py_ID(stdout));
+ file = PySys_GetAttr(&_Py_ID(stdout));
if (file == NULL) {
return NULL;
}
@@ -2270,7 +2321,7 @@ builtin_input_impl(PyObject *module, PyObject *prompt)
int tty;
/* Check that stdin/out/err are intact */
- fin = _PySys_GetRequiredAttr(&_Py_ID(stdin));
+ fin = PySys_GetAttr(&_Py_ID(stdin));
if (fin == NULL) {
goto error;
}
@@ -2278,7 +2329,7 @@ builtin_input_impl(PyObject *module, PyObject *prompt)
PyErr_SetString(PyExc_RuntimeError, "lost sys.stdin");
goto error;
}
- fout = _PySys_GetRequiredAttr(&_Py_ID(stdout));
+ fout = PySys_GetAttr(&_Py_ID(stdout));
if (fout == NULL) {
goto error;
}
@@ -2286,7 +2337,7 @@ builtin_input_impl(PyObject *module, PyObject *prompt)
PyErr_SetString(PyExc_RuntimeError, "lost sys.stdout");
goto error;
}
- ferr = _PySys_GetRequiredAttr(&_Py_ID(stderr));
+ ferr = PySys_GetAttr(&_Py_ID(stderr));
if (ferr == NULL) {
goto error;
}
@@ -2624,7 +2675,22 @@ builtin_vars(PyObject *self, PyObject *args)
if (!PyArg_UnpackTuple(args, "vars", 0, 1, &v))
return NULL;
if (v == NULL) {
- d = _PyEval_GetFrameLocals();
+ if (_PyEval_GetFrame() != NULL) {
+ d = _PyEval_GetFrameLocals();
+ }
+ else {
+ PyThreadState *tstate = _PyThreadState_GET();
+ d = _PyEval_GetGlobalsFromRunningMain(tstate);
+ if (d == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ d = _PyEval_GetFrameLocals();
+ assert(_PyErr_Occurred(tstate));
+ }
+ }
+ else {
+ Py_INCREF(d);
+ }
+ }
}
else {
if (PyObject_GetOptionalAttr(v, &_Py_ID(__dict__), &d) == 0) {
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index f145bdef644..a5b74d88d7d 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -295,55 +295,18 @@ dummy_func(
value2 = PyStackRef_Borrow(GETLOCAL(oparg2));
}
- family(LOAD_CONST, 0) = {
- LOAD_CONST_MORTAL,
- LOAD_CONST_IMMORTAL,
- };
-
inst(LOAD_CONST, (-- value)) {
- /* We can't do this in the bytecode compiler as
- * marshalling can intern strings and make them immortal. */
- PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- value = PyStackRef_FromPyObjectNew(obj);
-#if ENABLE_SPECIALIZATION_FT
-#ifdef Py_GIL_DISABLED
- uint8_t expected = LOAD_CONST;
- if (!_Py_atomic_compare_exchange_uint8(
- &this_instr->op.code, &expected,
- _Py_IsImmortal(obj) ? LOAD_CONST_IMMORTAL : LOAD_CONST_MORTAL)) {
- // We might lose a race with instrumentation, which we don't care about.
- assert(expected >= MIN_INSTRUMENTED_OPCODE);
- }
-#else
- if (this_instr->op.code == LOAD_CONST) {
- this_instr->op.code = _Py_IsImmortal(obj) ? LOAD_CONST_IMMORTAL : LOAD_CONST_MORTAL;
- }
-#endif
-#endif
- }
-
- inst(LOAD_CONST_MORTAL, (-- value)) {
- PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- value = PyStackRef_FromPyObjectNewMortal(obj);
- }
-
- inst(LOAD_CONST_IMMORTAL, (-- value)) {
PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- assert(_Py_IsImmortal(obj));
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
}
replicate(4) inst(LOAD_SMALL_INT, (-- value)) {
assert(oparg < _PY_NSMALLPOSINTS);
PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
}
replicate(8) inst(STORE_FAST, (value --)) {
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
DEAD(value);
@@ -355,10 +318,6 @@ dummy_func(
};
inst(STORE_FAST_LOAD_FAST, (value1 -- value2)) {
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value1)
- );
uint32_t oparg1 = oparg >> 4;
uint32_t oparg2 = oparg & 15;
_PyStackRef tmp = GETLOCAL(oparg1);
@@ -369,14 +328,6 @@ dummy_func(
}
inst(STORE_FAST_STORE_FAST, (value2, value1 --)) {
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value1)
- );
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value2)
- );
uint32_t oparg1 = oparg >> 4;
uint32_t oparg2 = oparg & 15;
_PyStackRef tmp = GETLOCAL(oparg1);
@@ -390,7 +341,33 @@ dummy_func(
}
pure inst(POP_TOP, (value --)) {
- PyStackRef_CLOSE(value);
+ PyStackRef_XCLOSE(value);
+ }
+
+ op(_POP_TOP_NOP, (value --)) {
+ assert(PyStackRef_IsNull(value) || (!PyStackRef_RefcountOnObject(value)) ||
+ _Py_IsImmortal((PyStackRef_AsPyObjectBorrow(value))));
+ DEAD(value);
+ }
+
+ op(_POP_TOP_INT, (value --)) {
+ assert(PyLong_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
+ PyStackRef_CLOSE_SPECIALIZED(value, _PyLong_ExactDealloc);
+ }
+
+ op(_POP_TOP_FLOAT, (value --)) {
+ assert(PyFloat_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
+ PyStackRef_CLOSE_SPECIALIZED(value, _PyFloat_ExactDealloc);
+ }
+
+ op(_POP_TOP_UNICODE, (value --)) {
+ assert(PyUnicode_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
+ PyStackRef_CLOSE_SPECIALIZED(value, _PyUnicode_ExactDealloc);
+ }
+
+ tier2 op(_POP_TWO, (nos, tos --)) {
+ PyStackRef_CLOSE(tos);
+ PyStackRef_CLOSE(nos);
}
pure inst(PUSH_NULL, (-- res)) {
@@ -406,9 +383,14 @@ dummy_func(
PyStackRef_CLOSE(value);
}
- macro(POP_ITER) = POP_TOP;
- no_save_ip tier1 inst(INSTRUMENTED_END_FOR, (receiver, value -- receiver)) {
+ inst(POP_ITER, (iter, index_or_null -- )) {
+ (void)index_or_null;
+ DEAD(index_or_null);
+ PyStackRef_CLOSE(iter);
+ }
+
+ no_save_ip tier1 inst(INSTRUMENTED_END_FOR, (receiver, index_or_null, value -- receiver, index_or_null)) {
/* Need to create a fake StopIteration error here,
* to conform to PEP 380 */
if (PyStackRef_GenCheck(receiver)) {
@@ -420,7 +402,9 @@ dummy_func(
PyStackRef_CLOSE(value);
}
- tier1 inst(INSTRUMENTED_POP_ITER, (iter -- )) {
+ tier1 inst(INSTRUMENTED_POP_ITER, (iter, index_or_null -- )) {
+ (void)index_or_null;
+ DEAD(index_or_null);
INSTRUMENTED_JUMP(prev_instr, this_instr+1, PY_MONITORING_EVENT_BRANCH_RIGHT);
PyStackRef_CLOSE(iter);
}
@@ -606,12 +590,24 @@ dummy_func(
op(_GUARD_NOS_INT, (left, unused -- left, unused)) {
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
- EXIT_IF(!PyLong_CheckExact(left_o));
+ EXIT_IF(!_PyLong_CheckExactAndCompact(left_o));
}
op(_GUARD_TOS_INT, (value -- value)) {
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- EXIT_IF(!PyLong_CheckExact(value_o));
+ EXIT_IF(!_PyLong_CheckExactAndCompact(value_o));
+ }
+
+ op(_GUARD_NOS_OVERFLOWED, (left, unused -- left, unused)) {
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ assert(Py_TYPE(left_o) == &PyLong_Type);
+ EXIT_IF(!_PyLong_IsCompact((PyLongObject *)left_o));
+ }
+
+ op(_GUARD_TOS_OVERFLOWED, (value -- value)) {
+ PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
+ assert(Py_TYPE(value_o) == &PyLong_Type);
+ EXIT_IF(!_PyLong_IsCompact((PyLongObject *)value_o));
}
pure op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) {
@@ -619,14 +615,14 @@ dummy_func(
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o);
+ res = _PyCompactLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o);
+ EXIT_IF(PyStackRef_IsNull(res));
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
INPUTS_DEAD();
- ERROR_IF(res_o == NULL);
- res = PyStackRef_FromPyObjectSteal(res_o);
}
pure op(_BINARY_OP_ADD_INT, (left, right -- res)) {
@@ -634,14 +630,14 @@ dummy_func(
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
+ res = _PyCompactLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
+ EXIT_IF(PyStackRef_IsNull(res));
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
INPUTS_DEAD();
- ERROR_IF(res_o == NULL);
- res = PyStackRef_FromPyObjectSteal(res_o);
}
pure op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) {
@@ -649,20 +645,22 @@ dummy_func(
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
+ res = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
+ EXIT_IF(PyStackRef_IsNull(res));
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
INPUTS_DEAD();
- ERROR_IF(res_o == NULL);
- res = PyStackRef_FromPyObjectSteal(res_o);
}
macro(BINARY_OP_MULTIPLY_INT) =
_GUARD_TOS_INT + _GUARD_NOS_INT + unused/5 + _BINARY_OP_MULTIPLY_INT;
+
macro(BINARY_OP_ADD_INT) =
_GUARD_TOS_INT + _GUARD_NOS_INT + unused/5 + _BINARY_OP_ADD_INT;
+
macro(BINARY_OP_SUBTRACT_INT) =
_GUARD_TOS_INT + _GUARD_NOS_INT + unused/5 + _BINARY_OP_SUBTRACT_INT;
@@ -721,6 +719,52 @@ dummy_func(
ERROR_IF(PyStackRef_IsNull(res));
}
+
+ pure op(_BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS, (left, right -- res)) {
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval *
+ ((PyFloatObject *)right_o)->ob_fval;
+ res = PyStackRef_FromPyObjectSteal(PyFloat_FromDouble(dres));
+ INPUTS_DEAD();
+ ERROR_IF(PyStackRef_IsNull(res));
+ }
+
+ pure op(_BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS, (left, right -- res)) {
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval +
+ ((PyFloatObject *)right_o)->ob_fval;
+ res = PyStackRef_FromPyObjectSteal(PyFloat_FromDouble(dres));
+ INPUTS_DEAD();
+ ERROR_IF(PyStackRef_IsNull(res));
+ }
+
+ pure op(_BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS, (left, right -- res)) {
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval -
+ ((PyFloatObject *)right_o)->ob_fval;
+ res = PyStackRef_FromPyObjectSteal(PyFloat_FromDouble(dres));
+ INPUTS_DEAD();
+ ERROR_IF(PyStackRef_IsNull(res));
+ }
+
macro(BINARY_OP_MULTIPLY_FLOAT) =
_GUARD_TOS_FLOAT + _GUARD_NOS_FLOAT + unused/5 + _BINARY_OP_MULTIPLY_FLOAT;
macro(BINARY_OP_ADD_FLOAT) =
@@ -806,7 +850,7 @@ dummy_func(
DEOPT_IF(!res);
}
- pure op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) {
+ op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) {
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(INLINE_CACHE_ENTRIES_BINARY_OP == 5);
@@ -942,7 +986,7 @@ dummy_func(
PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
DEAD(sub_st);
PyStackRef_CLOSE(str_st);
- res = PyStackRef_FromPyObjectImmortal(res_o);
+ res = PyStackRef_FromPyObjectBorrow(res_o);
}
op(_GUARD_NOS_TUPLE, (nos, unused -- nos, unused)) {
@@ -1022,12 +1066,13 @@ dummy_func(
STAT_INC(BINARY_OP, hit);
}
- op(_BINARY_OP_SUBSCR_INIT_CALL, (container, sub, getitem -- new_frame: _PyInterpreterFrame* )) {
- new_frame = _PyFrame_PushUnchecked(tstate, getitem, 2, frame);
- new_frame->localsplus[0] = container;
- new_frame->localsplus[1] = sub;
+ op(_BINARY_OP_SUBSCR_INIT_CALL, (container, sub, getitem -- new_frame)) {
+ _PyInterpreterFrame* pushed_frame = _PyFrame_PushUnchecked(tstate, getitem, 2, frame);
+ pushed_frame->localsplus[0] = container;
+ pushed_frame->localsplus[1] = sub;
INPUTS_DEAD();
frame->return_offset = INSTRUCTION_SIZE;
+ new_frame = PyStackRef_Wrap(pushed_frame);
}
macro(BINARY_OP_SUBSCR_GETITEM) =
@@ -1169,6 +1214,17 @@ dummy_func(
tstate->current_frame = frame->previous;
assert(!_PyErr_Occurred(tstate));
PyObject *result = PyStackRef_AsPyObjectSteal(retval);
+#if !Py_TAIL_CALL_INTERP
+ assert(frame == &entry.frame);
+#endif
+#ifdef _Py_TIER2
+ _PyStackRef executor = frame->localsplus[0];
+ assert(tstate->current_executor == NULL);
+ if (!PyStackRef_IsNull(executor)) {
+ tstate->current_executor = PyStackRef_AsPyObjectBorrow(executor);
+ PyStackRef_CLOSE(executor);
+ }
+#endif
LLTRACE_RESUME_FRAME();
return result;
}
@@ -1322,20 +1378,21 @@ dummy_func(
macro(SEND) = _SPECIALIZE_SEND + _SEND;
- op(_SEND_GEN_FRAME, (receiver, v -- receiver, gen_frame: _PyInterpreterFrame *)) {
+ op(_SEND_GEN_FRAME, (receiver, v -- receiver, gen_frame)) {
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(receiver);
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type && Py_TYPE(gen) != &PyCoro_Type);
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING);
STAT_INC(SEND, hit);
- gen_frame = &gen->gi_iframe;
- _PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
+ _PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
+ _PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
DEAD(v);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert(INSTRUCTION_SIZE + oparg <= UINT16_MAX);
frame->return_offset = (uint16_t)(INSTRUCTION_SIZE + oparg);
- gen_frame->previous = frame;
+ pushed_frame->previous = frame;
+ gen_frame = PyStackRef_Wrap(pushed_frame);
}
macro(SEND_GEN) =
@@ -2270,19 +2327,18 @@ dummy_func(
#endif /* ENABLE_SPECIALIZATION_FT */
}
- op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) {
+ op(_LOAD_ATTR, (owner -- attr[1], self_or_null[oparg&1])) {
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
- PyObject *attr_o;
if (oparg & 1) {
/* Designed to work in tandem with CALL, pushes two values. */
- attr_o = NULL;
- int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
+ *attr = PyStackRef_NULL;
+ int is_meth = _PyObject_GetMethodStackRef(tstate, PyStackRef_AsPyObjectBorrow(owner), name, attr);
if (is_meth) {
/* We can bypass temporary bound method object.
meth is unbound method and obj is self.
meth | self | arg1 | ... | argN
*/
- assert(attr_o != NULL); // No errors on this branch
+ assert(!PyStackRef_IsNull(*attr)); // No errors on this branch
self_or_null[0] = owner; // Transfer ownership
DEAD(owner);
}
@@ -2294,17 +2350,17 @@ dummy_func(
meth | NULL | arg1 | ... | argN
*/
PyStackRef_CLOSE(owner);
- ERROR_IF(attr_o == NULL);
+ ERROR_IF(PyStackRef_IsNull(*attr));
self_or_null[0] = PyStackRef_NULL;
}
}
else {
/* Classic, pushes one value. */
- attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+ PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
PyStackRef_CLOSE(owner);
ERROR_IF(attr_o == NULL);
+ *attr = PyStackRef_FromPyObjectSteal(attr_o);
}
- attr = PyStackRef_FromPyObjectSteal(attr_o);
}
macro(LOAD_ATTR) =
@@ -2489,7 +2545,7 @@ dummy_func(
_LOAD_ATTR_CLASS +
_PUSH_NULL_CONDITIONAL;
- op(_LOAD_ATTR_PROPERTY_FRAME, (fget/4, owner -- new_frame: _PyInterpreterFrame *)) {
+ op(_LOAD_ATTR_PROPERTY_FRAME, (fget/4, owner -- new_frame)) {
assert((oparg & 1) == 0);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
@@ -2499,9 +2555,10 @@ dummy_func(
DEOPT_IF(code->co_argcount != 1);
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize));
STAT_INC(LOAD_ATTR, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
- new_frame->localsplus[0] = owner;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
+ pushed_frame->localsplus[0] = owner;
DEAD(owner);
+ new_frame = PyStackRef_Wrap(pushed_frame);
}
macro(LOAD_ATTR_PROPERTY) =
@@ -2711,8 +2768,8 @@ dummy_func(
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
- DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)left_o));
- DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)right_o));
+ assert(_PyLong_IsCompact((PyLongObject *)left_o));
+ assert(_PyLong_IsCompact((PyLongObject *)right_o));
STAT_INC(COMPARE_OP, hit);
assert(_PyLong_DigitCount((PyLongObject *)left_o) <= 1 &&
_PyLong_DigitCount((PyLongObject *)right_o) <= 1);
@@ -2912,8 +2969,7 @@ dummy_func(
}
else {
this_instr[1].counter = initial_jump_backoff_counter();
- assert(tstate->previous_executor == NULL);
- tstate->previous_executor = Py_None;
+ assert(tstate->current_executor == NULL);
GOTO_TIER_TWO(executor);
}
}
@@ -2965,7 +3021,7 @@ dummy_func(
assert(executor->vm_data.index == INSTR_OFFSET() - 1);
assert(executor->vm_data.code == code);
assert(executor->vm_data.valid);
- assert(tstate->previous_executor == NULL);
+ assert(tstate->current_executor == NULL);
/* If the eval breaker is set then stay in tier 1.
* This avoids any potentially infinite loops
* involving _RESUME_CHECK */
@@ -2978,8 +3034,6 @@ dummy_func(
}
DISPATCH_GOTO();
}
- tstate->previous_executor = Py_None;
- Py_INCREF(executor);
GOTO_TIER_TWO(executor);
#else
Py_FatalError("ENTER_EXECUTOR is not supported in this build");
@@ -3077,15 +3131,24 @@ dummy_func(
values_or_none = PyStackRef_FromPyObjectSteal(values_or_none_o);
}
- inst(GET_ITER, (iterable -- iter)) {
+ inst(GET_ITER, (iterable -- iter, index_or_null)) {
#ifdef Py_STATS
_Py_GatherStats_GetIter(iterable);
#endif
/* before: [obj]; after [getiter(obj)] */
- PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
- PyStackRef_CLOSE(iterable);
- ERROR_IF(iter_o == NULL);
- iter = PyStackRef_FromPyObjectSteal(iter_o);
+ PyTypeObject *tp = PyStackRef_TYPE(iterable);
+ if (tp == &PyTuple_Type || tp == &PyList_Type) {
+ iter = iterable;
+ DEAD(iterable);
+ index_or_null = PyStackRef_TagInt(0);
+ }
+ else {
+ PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
+ PyStackRef_CLOSE(iterable);
+ ERROR_IF(iter_o == NULL);
+ iter = PyStackRef_FromPyObjectSteal(iter_o);
+ index_or_null = PyStackRef_NULL;
+ }
}
inst(GET_YIELD_FROM_ITER, (iterable -- iter)) {
@@ -3132,11 +3195,11 @@ dummy_func(
FOR_ITER_GEN,
};
- specializing op(_SPECIALIZE_FOR_ITER, (counter/1, iter -- iter)) {
+ specializing op(_SPECIALIZE_FOR_ITER, (counter/1, iter, null_or_index -- iter, null_or_index)) {
#if ENABLE_SPECIALIZATION_FT
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
next_instr = this_instr;
- _Py_Specialize_ForIter(iter, next_instr, oparg);
+ _Py_Specialize_ForIter(iter, null_or_index, next_instr, oparg);
DISPATCH_SAME_OPARG();
}
OPCODE_DEFERRED_INC(FOR_ITER);
@@ -3144,111 +3207,71 @@ dummy_func(
#endif /* ENABLE_SPECIALIZATION_FT */
}
- replaced op(_FOR_ITER, (iter -- iter, next)) {
- /* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
- if (next_o == NULL) {
- if (_PyErr_Occurred(tstate)) {
- int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
- if (!matches) {
- ERROR_NO_POP();
- }
- _PyEval_MonitorRaise(tstate, frame, this_instr);
- _PyErr_Clear(tstate);
+ replaced op(_FOR_ITER, (iter, null_or_index -- iter, null_or_index, next)) {
+ _PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);
+ if (!PyStackRef_IsValid(item)) {
+ if (PyStackRef_IsError(item)) {
+ ERROR_NO_POP();
}
- /* iterator ended normally */
- assert(next_instr[oparg].op.code == END_FOR ||
- next_instr[oparg].op.code == INSTRUMENTED_END_FOR);
- /* Jump forward oparg, then skip following END_FOR */
+ // Jump forward by oparg and skip the following END_FOR
JUMPBY(oparg + 1);
DISPATCH();
}
- next = PyStackRef_FromPyObjectSteal(next_o);
- // Common case: no jump, leave it to the code generator
+ next = item;
}
- op(_FOR_ITER_TIER_TWO, (iter -- iter, next)) {
- /* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
- if (next_o == NULL) {
- if (_PyErr_Occurred(tstate)) {
- int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
- if (!matches) {
- ERROR_NO_POP();
- }
- _PyEval_MonitorRaise(tstate, frame, frame->instr_ptr);
- _PyErr_Clear(tstate);
+ op(_FOR_ITER_TIER_TWO, (iter, null_or_index -- iter, null_or_index, next)) {
+ _PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);
+ if (!PyStackRef_IsValid(item)) {
+ if (PyStackRef_IsError(item)) {
+ ERROR_NO_POP();
}
/* iterator ended normally */
/* The translator sets the deopt target just past the matching END_FOR */
EXIT_IF(true);
}
- next = PyStackRef_FromPyObjectSteal(next_o);
- // Common case: no jump, leave it to the code generator
+ next = item;
}
+
macro(FOR_ITER) = _SPECIALIZE_FOR_ITER + _FOR_ITER;
- inst(INSTRUMENTED_FOR_ITER, (unused/1, iter -- iter, next)) {
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
- if (next_o != NULL) {
- next = PyStackRef_FromPyObjectSteal(next_o);
- INSTRUMENTED_JUMP(this_instr, next_instr, PY_MONITORING_EVENT_BRANCH_LEFT);
- }
- else {
- if (_PyErr_Occurred(tstate)) {
- int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
- if (!matches) {
- ERROR_NO_POP();
- }
- _PyEval_MonitorRaise(tstate, frame, this_instr);
- _PyErr_Clear(tstate);
+ inst(INSTRUMENTED_FOR_ITER, (unused/1, iter, null_or_index -- iter, null_or_index, next)) {
+ _PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);
+ if (!PyStackRef_IsValid(item)) {
+ if (PyStackRef_IsError(item)) {
+ ERROR_NO_POP();
}
- /* iterator ended normally */
- assert(next_instr[oparg].op.code == END_FOR ||
- next_instr[oparg].op.code == INSTRUMENTED_END_FOR);
- /* Skip END_FOR */
+ // Jump forward by oparg and skip the following END_FOR
JUMPBY(oparg + 1);
DISPATCH();
}
+ next = item;
+ INSTRUMENTED_JUMP(this_instr, next_instr, PY_MONITORING_EVENT_BRANCH_LEFT);
}
-
- op(_ITER_CHECK_LIST, (iter -- iter)) {
+ op(_ITER_CHECK_LIST, (iter, null_or_index -- iter, null_or_index)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- EXIT_IF(Py_TYPE(iter_o) != &PyListIter_Type);
+ EXIT_IF(Py_TYPE(iter_o) != &PyList_Type);
+ assert(PyStackRef_IsTaggedInt(null_or_index));
#ifdef Py_GIL_DISABLED
- EXIT_IF(!_PyObject_IsUniquelyReferenced(iter_o));
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- EXIT_IF(!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) ||
- !_PyObject_GC_IS_SHARED(it->it_seq));
+ EXIT_IF(!_Py_IsOwnedByCurrentThread(iter_o) && !_PyObject_GC_IS_SHARED(iter_o));
#endif
}
- replaced op(_ITER_JUMP_LIST, (iter -- iter)) {
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
-// For free-threaded Python, the loop exit can happen at any point during
-// item retrieval, so it doesn't make much sense to check and jump
-// separately before item retrieval. Any length check we do here can be
-// invalid by the time we actually try to fetch the item.
+ replaced op(_ITER_JUMP_LIST, (iter, null_or_index -- iter, null_or_index)) {
#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- (void)iter_o;
+ // For free-threaded Python, the loop exit can happen at any point during
+ // item retrieval, so it doesn't make much sense to check and jump
+ // separately before item retrieval. Any length check we do here can be
+ // invalid by the time we actually try to fetch the item.
#else
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(list_o) == &PyList_Type);
STAT_INC(FOR_ITER, hit);
- PyListObject *seq = it->it_seq;
- if (seq == NULL || (size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) {
- it->it_index = -1;
- if (seq != NULL) {
- it->it_seq = NULL;
- Py_DECREF(seq);
- }
+ if ((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyList_GET_SIZE(list_o)) {
+ null_or_index = PyStackRef_TagInt(-1);
/* Jump forward oparg, then skip following END_FOR instruction */
JUMPBY(oparg + 1);
DISPATCH();
@@ -3257,73 +3280,54 @@ dummy_func(
}
// Only used by Tier 2
- op(_GUARD_NOT_EXHAUSTED_LIST, (iter -- iter)) {
+ op(_GUARD_NOT_EXHAUSTED_LIST, (iter, null_or_index -- iter, null_or_index)) {
#ifndef Py_GIL_DISABLED
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
- PyListObject *seq = it->it_seq;
- EXIT_IF(seq == NULL);
- if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) {
- it->it_index = -1;
- EXIT_IF(1);
- }
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(list_o) == &PyList_Type);
+ EXIT_IF((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyList_GET_SIZE(list_o));
#endif
}
- replaced op(_ITER_NEXT_LIST, (iter -- iter, next)) {
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
- PyListObject *seq = it->it_seq;
- assert(seq);
+ replaced op(_ITER_NEXT_LIST, (iter, null_or_index -- iter, null_or_index, next)) {
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(PyList_CheckExact(list_o));
#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
- _PyObject_GC_IS_SHARED(seq));
+ assert(_Py_IsOwnedByCurrentThread(list_o) ||
+ _PyObject_GC_IS_SHARED(list_o));
STAT_INC(FOR_ITER, hit);
- int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ int result = _PyList_GetItemRefNoLock((PyListObject *)list_o, PyStackRef_UntagInt(null_or_index), &next);
// A negative result means we lost a race with another thread
// and we need to take the slow path.
DEOPT_IF(result < 0);
if (result == 0) {
- it->it_index = -1;
+ null_or_index = PyStackRef_TagInt(-1);
/* Jump forward oparg, then skip following END_FOR instruction */
JUMPBY(oparg + 1);
DISPATCH();
}
- it->it_index++;
#else
- assert(it->it_index < PyList_GET_SIZE(seq));
- next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+ next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(list_o, PyStackRef_UntagInt(null_or_index)));
#endif
+ null_or_index = PyStackRef_IncrementTaggedIntNoOverflow(null_or_index);
}
// Only used by Tier 2
- op(_ITER_NEXT_LIST_TIER_TWO, (iter -- iter, next)) {
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
- PyListObject *seq = it->it_seq;
- assert(seq);
+ op(_ITER_NEXT_LIST_TIER_TWO, (iter, null_or_index -- iter, null_or_index, next)) {
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(PyList_CheckExact(list_o));
#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
- _PyObject_GC_IS_SHARED(seq));
+ assert(_Py_IsOwnedByCurrentThread((PyObject *)list_o) ||
+ _PyObject_GC_IS_SHARED(list_o));
STAT_INC(FOR_ITER, hit);
- int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ int result = _PyList_GetItemRefNoLock((PyListObject *)list_o, PyStackRef_UntagInt(null_or_index), &next);
// A negative result means we lost a race with another thread
// and we need to take the slow path.
- EXIT_IF(result < 0);
- if (result == 0) {
- it->it_index = -1;
- EXIT_IF(1);
- }
- it->it_index++;
+ DEOPT_IF(result <= 0);
#else
- assert(it->it_index < PyList_GET_SIZE(seq));
- next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+ assert(PyStackRef_UntagInt(null_or_index) < PyList_GET_SIZE(list_o));
+ next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(list_o, PyStackRef_UntagInt(null_or_index)));
#endif
+ null_or_index = PyStackRef_IncrementTaggedIntNoOverflow(null_or_index);
}
macro(FOR_ITER_LIST) =
@@ -3332,31 +3336,19 @@ dummy_func(
_ITER_JUMP_LIST +
_ITER_NEXT_LIST;
- op(_ITER_CHECK_TUPLE, (iter -- iter)) {
+ op(_ITER_CHECK_TUPLE, (iter, null_or_index -- iter, null_or_index)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- EXIT_IF(Py_TYPE(iter_o) != &PyTupleIter_Type);
-#ifdef Py_GIL_DISABLED
- EXIT_IF(!_PyObject_IsUniquelyReferenced(iter_o));
-#endif
+ EXIT_IF(Py_TYPE(iter_o) != &PyTuple_Type);
+ assert(PyStackRef_IsTaggedInt(null_or_index));
}
- replaced op(_ITER_JUMP_TUPLE, (iter -- iter)) {
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- (void)iter_o;
- assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
-#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
-#endif
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
+ replaced op(_ITER_JUMP_TUPLE, (iter, null_or_index -- iter, null_or_index)) {
+ PyObject *tuple_o = PyStackRef_AsPyObjectBorrow(iter);
+ (void)tuple_o;
+ assert(Py_TYPE(tuple_o) == &PyTuple_Type);
STAT_INC(FOR_ITER, hit);
- PyTupleObject *seq = it->it_seq;
- if (seq == NULL || (size_t)it->it_index >= (size_t)PyTuple_GET_SIZE(seq)) {
-#ifndef Py_GIL_DISABLED
- if (seq != NULL) {
- it->it_seq = NULL;
- Py_DECREF(seq);
- }
-#endif
+ if ((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyTuple_GET_SIZE(tuple_o)) {
+ null_or_index = PyStackRef_TagInt(-1);
/* Jump forward oparg, then skip following END_FOR instruction */
JUMPBY(oparg + 1);
DISPATCH();
@@ -3364,29 +3356,19 @@ dummy_func(
}
// Only used by Tier 2
- op(_GUARD_NOT_EXHAUSTED_TUPLE, (iter -- iter)) {
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
-#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
-#endif
- PyTupleObject *seq = it->it_seq;
- EXIT_IF(seq == NULL);
- EXIT_IF(it->it_index >= PyTuple_GET_SIZE(seq));
+ op(_GUARD_NOT_EXHAUSTED_TUPLE, (iter, null_or_index -- iter, null_or_index)) {
+ PyObject *tuple_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(tuple_o) == &PyTuple_Type);
+ EXIT_IF((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyTuple_GET_SIZE(tuple_o));
}
- op(_ITER_NEXT_TUPLE, (iter -- iter, next)) {
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
- PyTupleObject *seq = it->it_seq;
-#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
-#endif
- assert(seq);
- assert(it->it_index < PyTuple_GET_SIZE(seq));
- next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq, it->it_index++));
+ op(_ITER_NEXT_TUPLE, (iter, null_or_index -- iter, null_or_index, next)) {
+ PyObject *tuple_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(tuple_o) == &PyTuple_Type);
+ uintptr_t i = PyStackRef_UntagInt(null_or_index);
+ assert((size_t)i < (size_t)PyTuple_GET_SIZE(tuple_o));
+ next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(tuple_o, i));
+ null_or_index = PyStackRef_IncrementTaggedIntNoOverflow(null_or_index);
}
macro(FOR_ITER_TUPLE) =
@@ -3395,7 +3377,7 @@ dummy_func(
_ITER_JUMP_TUPLE +
_ITER_NEXT_TUPLE;
- op(_ITER_CHECK_RANGE, (iter -- iter)) {
+ op(_ITER_CHECK_RANGE, (iter, null_or_index -- iter, null_or_index)) {
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
EXIT_IF(Py_TYPE(r) != &PyRangeIter_Type);
#ifdef Py_GIL_DISABLED
@@ -3403,7 +3385,7 @@ dummy_func(
#endif
}
- replaced op(_ITER_JUMP_RANGE, (iter -- iter)) {
+ replaced op(_ITER_JUMP_RANGE, (iter, null_or_index -- iter, null_or_index)) {
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
#ifdef Py_GIL_DISABLED
@@ -3418,13 +3400,13 @@ dummy_func(
}
// Only used by Tier 2
- op(_GUARD_NOT_EXHAUSTED_RANGE, (iter -- iter)) {
+ op(_GUARD_NOT_EXHAUSTED_RANGE, (iter, null_or_index -- iter, null_or_index)) {
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
EXIT_IF(r->len <= 0);
}
- op(_ITER_NEXT_RANGE, (iter -- iter, next)) {
+ op(_ITER_NEXT_RANGE, (iter, null_or_index -- iter, null_or_index, next)) {
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
#ifdef Py_GIL_DISABLED
@@ -3445,7 +3427,7 @@ dummy_func(
_ITER_JUMP_RANGE +
_ITER_NEXT_RANGE;
- op(_FOR_ITER_GEN_FRAME, (iter -- iter, gen_frame: _PyInterpreterFrame*)) {
+ op(_FOR_ITER_GEN_FRAME, (iter, null -- iter, null, gen_frame)) {
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type);
#ifdef Py_GIL_DISABLED
@@ -3457,14 +3439,15 @@ dummy_func(
#endif
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING);
STAT_INC(FOR_ITER, hit);
- gen_frame = &gen->gi_iframe;
- _PyFrame_StackPush(gen_frame, PyStackRef_None);
+ _PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
+ _PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
- gen_frame->previous = frame;
+ pushed_frame->previous = frame;
// oparg is the return offset from the next instruction.
frame->return_offset = (uint16_t)(INSTRUCTION_SIZE + oparg);
+ gen_frame = PyStackRef_Wrap(pushed_frame);
}
macro(FOR_ITER_GEN) =
@@ -3816,7 +3799,7 @@ dummy_func(
macro(CALL) = _SPECIALIZE_CALL + unused/2 + _MAYBE_EXPAND_METHOD + _DO_CALL + _CHECK_PERIODIC;
macro(INSTRUMENTED_CALL) = unused/3 + _MAYBE_EXPAND_METHOD + _MONITOR_CALL + _DO_CALL + _CHECK_PERIODIC;
- op(_PY_FRAME_GENERAL, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) {
+ op(_PY_FRAME_GENERAL, (callable, self_or_null, args[oparg] -- new_frame)) {
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
// oparg counts all of the args, but *not* self:
@@ -3838,7 +3821,7 @@ dummy_func(
if (temp == NULL) {
ERROR_NO_POP();
}
- new_frame = temp;
+ new_frame = PyStackRef_Wrap(temp);
}
op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, unused, unused[oparg] -- callable, unused, unused[oparg])) {
@@ -3975,27 +3958,26 @@ dummy_func(
DEOPT_IF(tstate->py_recursion_remaining <= 1);
}
- replicate(5) pure op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) {
+ replicate(5) pure op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame)) {
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
INPUTS_DEAD();
+ new_frame = PyStackRef_Wrap(pushed_frame);
}
- op(_PUSH_FRAME, (new_frame: _PyInterpreterFrame* -- )) {
- // Write it out explicitly because it's subtly different.
- // Eventually this should be the only occurrence of this code.
+ op(_PUSH_FRAME, (new_frame -- )) {
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
DEAD(new_frame);
SYNC_SP();
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -4033,6 +4015,15 @@ dummy_func(
DEOPT_IF(!PyStackRef_IsNull(null));
}
+ op(_GUARD_NOS_NOT_NULL, (nos, unused -- nos, unused)) {
+ PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
+ EXIT_IF(o == NULL);
+ }
+
+ op(_GUARD_THIRD_NULL, (null, unused, unused -- null, unused, unused)) {
+ DEOPT_IF(!PyStackRef_IsNull(null));
+ }
+
op(_GUARD_CALLABLE_TYPE_1, (callable, unused, unused -- callable, unused, unused)) {
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
DEOPT_IF(callable_o != (PyObject *)&PyType_Type);
@@ -4138,7 +4129,7 @@ dummy_func(
PyStackRef_CLOSE(temp);
}
- op(_CREATE_INIT_FRAME, (init, self, args[oparg] -- init_frame: _PyInterpreterFrame *)) {
+ op(_CREATE_INIT_FRAME, (init, self, args[oparg] -- init_frame)) {
_PyInterpreterFrame *shim = _PyFrame_PushTrampolineUnchecked(
tstate, (PyCodeObject *)&_Py_InitCleanup, 1, frame);
assert(_PyFrame_GetBytecode(shim)[0].op.code == EXIT_INIT_CHECK);
@@ -4155,12 +4146,12 @@ dummy_func(
_PyEval_FrameClearAndPop(tstate, shim);
ERROR_NO_POP();
}
- init_frame = temp;
frame->return_offset = 1 + INLINE_CACHE_ENTRIES_CALL;
/* Account for pushing the extra frame.
* We don't check recursion depth here,
* as it will be checked after start_frame */
tstate->py_recursion_remaining--;
+ init_frame = PyStackRef_Wrap(temp);
}
macro(CALL_ALLOC_AND_ENTER_INIT) =
@@ -4318,22 +4309,25 @@ dummy_func(
_CALL_BUILTIN_FAST_WITH_KEYWORDS +
_CHECK_PERIODIC;
- inst(CALL_LEN, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) {
- /* len(o) */
- PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
+ macro(CALL_LEN) =
+ unused/1 +
+ unused/2 +
+ _GUARD_NOS_NULL +
+ _GUARD_CALLABLE_LEN +
+ _CALL_LEN;
- int total_args = oparg;
- if (!PyStackRef_IsNull(self_or_null)) {
- args--;
- total_args++;
- }
- DEOPT_IF(total_args != 1);
+ op(_GUARD_CALLABLE_LEN, (callable, unused, unused -- callable, unused, unused)){
+ PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
PyInterpreterState *interp = tstate->interp;
DEOPT_IF(callable_o != interp->callable_cache.len);
+ }
+
+ op(_CALL_LEN, (callable, null, arg -- res)) {
+ /* len(o) */
+ (void)null;
STAT_INC(CALL, hit);
- _PyStackRef arg_stackref = args[0];
- PyObject *arg = PyStackRef_AsPyObjectBorrow(arg_stackref);
- Py_ssize_t len_i = PyObject_Length(arg);
+ PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg);
+ Py_ssize_t len_i = PyObject_Length(arg_o);
if (len_i < 0) {
ERROR_NO_POP();
}
@@ -4342,48 +4336,63 @@ dummy_func(
if (res_o == NULL) {
ERROR_NO_POP();
}
- PyStackRef_CLOSE(arg_stackref);
- DEAD(args);
- DEAD(self_or_null);
+ PyStackRef_CLOSE(arg);
+ DEAD(null);
PyStackRef_CLOSE(callable);
res = PyStackRef_FromPyObjectSteal(res_o);
}
- inst(CALL_ISINSTANCE, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) {
- /* isinstance(o, o2) */
+ op(_GUARD_CALLABLE_ISINSTANCE, (callable, unused, unused, unused -- callable, unused, unused, unused)) {
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
-
- int total_args = oparg;
- _PyStackRef *arguments = args;
- if (!PyStackRef_IsNull(self_or_null)) {
- arguments--;
- total_args++;
- }
- DEOPT_IF(total_args != 2);
PyInterpreterState *interp = tstate->interp;
DEOPT_IF(callable_o != interp->callable_cache.isinstance);
+ }
+
+ op(_CALL_ISINSTANCE, (callable, null, instance, cls -- res)) {
+ /* isinstance(o, o2) */
STAT_INC(CALL, hit);
- _PyStackRef cls_stackref = arguments[1];
- _PyStackRef inst_stackref = arguments[0];
- int retval = PyObject_IsInstance(PyStackRef_AsPyObjectBorrow(inst_stackref), PyStackRef_AsPyObjectBorrow(cls_stackref));
+ PyObject *inst_o = PyStackRef_AsPyObjectBorrow(instance);
+ PyObject *cls_o = PyStackRef_AsPyObjectBorrow(cls);
+ int retval = PyObject_IsInstance(inst_o, cls_o);
if (retval < 0) {
ERROR_NO_POP();
}
+ (void)null; // Silence compiler warnings about unused variables
+ PyStackRef_CLOSE(cls);
+ PyStackRef_CLOSE(instance);
+ DEAD(null);
+ PyStackRef_CLOSE(callable);
res = retval ? PyStackRef_True : PyStackRef_False;
assert((!PyStackRef_IsNull(res)) ^ (_PyErr_Occurred(tstate) != NULL));
- DECREF_INPUTS();
+ }
+
+ macro(CALL_ISINSTANCE) =
+ unused/1 +
+ unused/2 +
+ _GUARD_THIRD_NULL +
+ _GUARD_CALLABLE_ISINSTANCE +
+ _CALL_ISINSTANCE;
+
+ macro(CALL_LIST_APPEND) =
+ unused/1 +
+ unused/2 +
+ _GUARD_CALLABLE_LIST_APPEND +
+ _GUARD_NOS_NOT_NULL +
+ _GUARD_NOS_LIST +
+ _CALL_LIST_APPEND;
+
+ op(_GUARD_CALLABLE_LIST_APPEND, (callable, unused, unused -- callable, unused, unused)){
+ PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
+ PyInterpreterState *interp = tstate->interp;
+ DEOPT_IF(callable_o != interp->callable_cache.list_append);
}
// This is secretly a super-instruction
- inst(CALL_LIST_APPEND, (unused/1, unused/2, callable, self, arg -- )) {
+ op(_CALL_LIST_APPEND, (callable, self, arg -- )) {
assert(oparg == 1);
- PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
- PyInterpreterState *interp = tstate->interp;
- DEOPT_IF(callable_o != interp->callable_cache.list_append);
- DEOPT_IF(self_o == NULL);
- DEOPT_IF(!PyList_Check(self_o));
+ DEOPT_IF(!PyList_CheckExact(self_o));
DEOPT_IF(!LOCK_OBJECT(self_o));
STAT_INC(CALL, hit);
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
@@ -4668,7 +4677,7 @@ dummy_func(
res = PyStackRef_FromPyObjectSteal(res_o);
}
- op(_PY_FRAME_KW, (callable, self_or_null, args[oparg], kwnames -- new_frame: _PyInterpreterFrame*)) {
+ op(_PY_FRAME_KW, (callable, self_or_null, args[oparg], kwnames -- new_frame)) {
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
// oparg counts all of the args, but *not* self:
@@ -4695,7 +4704,7 @@ dummy_func(
DEAD(callable);
SYNC_SP();
ERROR_IF(temp == NULL);
- new_frame = temp;
+ new_frame = PyStackRef_Wrap(temp);
}
op(_CHECK_FUNCTION_VERSION_KW, (func_version/2, callable, unused, unused[oparg], unused -- callable, unused, unused[oparg], unused)) {
@@ -5014,8 +5023,7 @@ dummy_func(
res = PyStackRef_FromPyObjectSteal(res_o);
}
- pure inst(COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) {
- assert(oparg > 0);
+ pure replicate(1:4) inst(COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) {
top = PyStackRef_DUP(bottom);
}
@@ -5048,12 +5056,11 @@ dummy_func(
macro(BINARY_OP) = _SPECIALIZE_BINARY_OP + unused/4 + _BINARY_OP;
- pure inst(SWAP, (bottom, unused[oparg-2], top --
+ pure replicate(2:4) inst(SWAP, (bottom, unused[oparg-2], top --
bottom, unused[oparg-2], top)) {
_PyStackRef temp = bottom;
bottom = top;
top = temp;
- assert(oparg >= 2);
}
inst(INSTRUMENTED_LINE, ( -- )) {
@@ -5254,7 +5261,6 @@ dummy_func(
exit->temperature = initial_temperature_backoff_counter();
Py_CLEAR(exit->executor);
}
- tstate->previous_executor = (PyObject *)current_executor;
if (exit->executor == NULL) {
_Py_BackoffCounter temperature = exit->temperature;
if (!backoff_counter_triggers(temperature)) {
@@ -5277,7 +5283,6 @@ dummy_func(
}
exit->executor = executor;
}
- Py_INCREF(exit->executor);
GOTO_TIER_TWO(exit->executor);
}
@@ -5295,18 +5300,75 @@ dummy_func(
}
tier2 pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) {
- value = PyStackRef_FromPyObjectImmortal(ptr);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ }
+
+ tier2 op(_POP_CALL, (callable, null --)) {
+ (void)null; // Silence compiler warnings about unused variables
+ DEAD(null);
+ PyStackRef_CLOSE(callable);
+ }
+
+ tier2 op(_POP_CALL_ONE, (callable, null, pop --)) {
+ PyStackRef_CLOSE(pop);
+ (void)null; // Silence compiler warnings about unused variables
+ DEAD(null);
+ PyStackRef_CLOSE(callable);
+ }
+
+ tier2 op(_POP_CALL_TWO, (callable, null, pop1, pop2 --)) {
+ PyStackRef_CLOSE(pop2);
+ PyStackRef_CLOSE(pop1);
+ (void)null; // Silence compiler warnings about unused variables
+ DEAD(null);
+ PyStackRef_CLOSE(callable);
+ }
+
+ tier2 op(_POP_TOP_LOAD_CONST_INLINE_BORROW, (ptr/4, pop -- value)) {
+ PyStackRef_CLOSE(pop);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ }
+
+ tier2 op(_POP_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, pop1, pop2 -- value)) {
+ PyStackRef_CLOSE(pop2);
+ PyStackRef_CLOSE(pop1);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ }
+
+ tier2 op(_POP_CALL_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null -- value)) {
+ (void)null; // Silence compiler warnings about unused variables
+ DEAD(null);
+ PyStackRef_CLOSE(callable);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
}
- tier2 pure op (_POP_TOP_LOAD_CONST_INLINE_BORROW, (ptr/4, pop -- value)) {
+ tier2 op(_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, pop -- value)) {
PyStackRef_CLOSE(pop);
- value = PyStackRef_FromPyObjectImmortal(ptr);
+ (void)null; // Silence compiler warnings about unused variables
+ DEAD(null);
+ PyStackRef_CLOSE(callable);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
}
- tier2 pure op(_POP_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, pop1, pop2 -- value)) {
+ tier2 op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, pop1, pop2 -- value)) {
PyStackRef_CLOSE(pop2);
PyStackRef_CLOSE(pop1);
- value = PyStackRef_FromPyObjectImmortal(ptr);
+ (void)null; // Silence compiler warnings about unused variables
+ DEAD(null);
+ PyStackRef_CLOSE(callable);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ }
+
+ tier2 op(_LOAD_CONST_UNDER_INLINE, (ptr/4, old -- value, new)) {
+ new = old;
+ DEAD(old);
+ value = PyStackRef_FromPyObjectNew(ptr);
+ }
+
+ tier2 op(_LOAD_CONST_UNDER_INLINE_BORROW, (ptr/4, old -- value, new)) {
+ new = old;
+ DEAD(old);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
}
tier2 op(_CHECK_FUNCTION, (func_version/2 -- )) {
@@ -5316,7 +5378,6 @@ dummy_func(
}
tier2 op(_START_EXECUTOR, (executor/4 --)) {
- Py_CLEAR(tstate->previous_executor);
#ifndef _Py_JIT
current_executor = (_PyExecutorObject*)executor;
#endif
@@ -5337,12 +5398,10 @@ dummy_func(
}
tier2 op(_DEOPT, (--)) {
- tstate->previous_executor = (PyObject *)current_executor;
GOTO_TIER_ONE(_PyFrame_GetBytecode(frame) + CURRENT_TARGET());
}
tier2 op(_ERROR_POP_N, (target/2 --)) {
- tstate->previous_executor = (PyObject *)current_executor;
assert(oparg == 0);
frame->instr_ptr = _PyFrame_GetBytecode(frame) + target;
SYNC_SP();
@@ -5463,6 +5522,17 @@ dummy_func(
if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
/* Restore previous frame and exit */
tstate->current_frame = frame->previous;
+#if !Py_TAIL_CALL_INTERP
+ assert(frame == &entry.frame);
+#endif
+#ifdef _Py_TIER2
+ _PyStackRef executor = frame->localsplus[0];
+ assert(tstate->current_executor == NULL);
+ if (!PyStackRef_IsNull(executor)) {
+ tstate->current_executor = PyStackRef_AsPyObjectBorrow(executor);
+ PyStackRef_CLOSE(executor);
+ }
+#endif
return NULL;
}
next_instr = frame->instr_ptr;
diff --git a/Python/ceval.c b/Python/ceval.c
index c777e7944f6..50665defd38 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -8,7 +8,7 @@
#include "pycore_backoff.h"
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_cell.h" // PyCell_GetRef()
-#include "pycore_ceval.h"
+#include "pycore_ceval.h" // SPECIAL___ENTER__
#include "pycore_code.h"
#include "pycore_dict.h"
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
@@ -139,6 +139,19 @@
#endif
+static void
+check_invalid_reentrancy(void)
+{
+#if defined(Py_DEBUG) && defined(Py_GIL_DISABLED)
+ // In the free-threaded build, the interpreter must not be re-entered if
+ // the world-is-stopped. If so, that's a bug somewhere (quite likely in
+ // the painfully complex typeobject code).
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ assert(!interp->stoptheworld.world_stopped);
+#endif
+}
+
+
#ifdef Py_DEBUG
static void
dump_item(_PyStackRef item)
@@ -333,13 +346,13 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count)
{
uintptr_t here_addr = _Py_get_machine_stack_pointer();
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
- if (here_addr > _tstate->c_stack_soft_limit + margin_count * PYOS_STACK_MARGIN_BYTES) {
+ if (here_addr > _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES) {
return 0;
}
if (_tstate->c_stack_hard_limit == 0) {
_Py_InitializeRecursionLimits(tstate);
}
- return here_addr <= _tstate->c_stack_soft_limit + margin_count * PYOS_STACK_MARGIN_BYTES;
+ return here_addr <= _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES;
}
void
@@ -360,9 +373,6 @@ _Py_EnterRecursiveCallUnchecked(PyThreadState *tstate)
# define Py_C_STACK_SIZE 1200000
#elif defined(__sparc__)
# define Py_C_STACK_SIZE 1600000
-#elif defined(__wasi__)
- /* Web assembly has two stacks, so this isn't really the stack depth */
-# define Py_C_STACK_SIZE 131072 // wasi-libc DEFAULT_STACK_SIZE
#elif defined(__hppa__) || defined(__powerpc64__)
# define Py_C_STACK_SIZE 2000000
#else
@@ -438,8 +448,8 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate)
_tstate->c_stack_top = (uintptr_t)high;
ULONG guarantee = 0;
SetThreadStackGuarantee(&guarantee);
- _tstate->c_stack_hard_limit = ((uintptr_t)low) + guarantee + PYOS_STACK_MARGIN_BYTES;
- _tstate->c_stack_soft_limit = _tstate->c_stack_hard_limit + PYOS_STACK_MARGIN_BYTES;
+ _tstate->c_stack_hard_limit = ((uintptr_t)low) + guarantee + _PyOS_STACK_MARGIN_BYTES;
+ _tstate->c_stack_soft_limit = _tstate->c_stack_hard_limit + _PyOS_STACK_MARGIN_BYTES;
#else
uintptr_t here_addr = _Py_get_machine_stack_pointer();
# if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && !defined(__NetBSD__)
@@ -459,9 +469,9 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate)
// Thread sanitizer crashes if we use a bit more than half the stack.
_tstate->c_stack_soft_limit = base + (stack_size / 2);
#else
- _tstate->c_stack_soft_limit = base + PYOS_STACK_MARGIN_BYTES * 2;
+ _tstate->c_stack_soft_limit = base + _PyOS_STACK_MARGIN_BYTES * 2;
#endif
- _tstate->c_stack_hard_limit = base + PYOS_STACK_MARGIN_BYTES;
+ _tstate->c_stack_hard_limit = base + _PyOS_STACK_MARGIN_BYTES;
assert(_tstate->c_stack_soft_limit < here_addr);
assert(here_addr < _tstate->c_stack_top);
return;
@@ -469,7 +479,7 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate)
# endif
_tstate->c_stack_top = _Py_SIZE_ROUND_UP(here_addr, 4096);
_tstate->c_stack_soft_limit = _tstate->c_stack_top - Py_C_STACK_SIZE;
- _tstate->c_stack_hard_limit = _tstate->c_stack_top - (Py_C_STACK_SIZE + PYOS_STACK_MARGIN_BYTES);
+ _tstate->c_stack_hard_limit = _tstate->c_stack_top - (Py_C_STACK_SIZE + _PyOS_STACK_MARGIN_BYTES);
#endif
}
@@ -990,10 +1000,16 @@ _PyObjectArray_Free(PyObject **array, PyObject **scratch)
#define DONT_SLP_VECTORIZE
#endif
+typedef struct {
+ _PyInterpreterFrame frame;
+ _PyStackRef stack[1];
+} _PyEntryFrame;
+
PyObject* _Py_HOT_FUNCTION DONT_SLP_VECTORIZE
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
{
_Py_EnsureTstateNotNULL(tstate);
+ check_invalid_reentrancy();
CALL_STAT_INC(pyeval_calls);
#if USE_COMPUTED_GOTOS && !Py_TAIL_CALL_INTERP
@@ -1009,7 +1025,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
int oparg; /* Current opcode argument, if any */
assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL);
#endif
- _PyInterpreterFrame entry_frame;
+ _PyEntryFrame entry;
if (_Py_EnterRecursiveCallTstate(tstate, "")) {
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
@@ -1021,30 +1037,37 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
* These are cached values from the frame and code object. */
_Py_CODEUNIT *next_instr;
_PyStackRef *stack_pointer;
- entry_frame.localsplus[0] = PyStackRef_NULL;
+ entry.stack[0] = PyStackRef_NULL;
#ifdef Py_STACKREF_DEBUG
- entry_frame.f_funcobj = PyStackRef_None;
+ entry.frame.f_funcobj = PyStackRef_None;
#elif defined(Py_DEBUG)
/* Set these to invalid but identifiable values for debugging. */
- entry_frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0};
- entry_frame.f_locals = (PyObject*)0xaaa1;
- entry_frame.frame_obj = (PyFrameObject*)0xaaa2;
- entry_frame.f_globals = (PyObject*)0xaaa3;
- entry_frame.f_builtins = (PyObject*)0xaaa4;
+ entry.frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0};
+ entry.frame.f_locals = (PyObject*)0xaaa1;
+ entry.frame.frame_obj = (PyFrameObject*)0xaaa2;
+ entry.frame.f_globals = (PyObject*)0xaaa3;
+ entry.frame.f_builtins = (PyObject*)0xaaa4;
#endif
- entry_frame.f_executable = PyStackRef_None;
- entry_frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
- entry_frame.stackpointer = entry_frame.localsplus;
- entry_frame.owner = FRAME_OWNED_BY_INTERPRETER;
- entry_frame.visited = 0;
- entry_frame.return_offset = 0;
+ entry.frame.f_executable = PyStackRef_None;
+ entry.frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
+ entry.frame.stackpointer = entry.stack;
+ entry.frame.owner = FRAME_OWNED_BY_INTERPRETER;
+ entry.frame.visited = 0;
+ entry.frame.return_offset = 0;
#ifdef Py_DEBUG
- entry_frame.lltrace = 0;
+ entry.frame.lltrace = 0;
#endif
/* Push frame */
- entry_frame.previous = tstate->current_frame;
- frame->previous = &entry_frame;
+ entry.frame.previous = tstate->current_frame;
+ frame->previous = &entry.frame;
tstate->current_frame = frame;
+ entry.frame.localsplus[0] = PyStackRef_NULL;
+#ifdef _Py_TIER2
+ if (tstate->current_executor != NULL) {
+ entry.frame.localsplus[0] = PyStackRef_FromPyObjectNew(tstate->current_executor);
+ tstate->current_executor = NULL;
+ }
+#endif
/* support for generator.throw() */
if (throwflag) {
@@ -1071,9 +1094,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
stack_pointer = _PyFrame_GetStackPointer(frame);
#if Py_TAIL_CALL_INTERP
# if Py_STATS
- return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0, lastopcode);
+ return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0, lastopcode);
# else
- return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0);
+ return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0);
# endif
#else
goto error;
@@ -2723,10 +2746,9 @@ _PyEval_GetFrameLocals(void)
return locals;
}
-PyObject *
-PyEval_GetGlobals(void)
+static PyObject *
+_PyEval_GetGlobals(PyThreadState *tstate)
{
- PyThreadState *tstate = _PyThreadState_GET();
_PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
if (current_frame == NULL) {
return NULL;
@@ -2734,6 +2756,120 @@ PyEval_GetGlobals(void)
return current_frame->f_globals;
}
+PyObject *
+PyEval_GetGlobals(void)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ return _PyEval_GetGlobals(tstate);
+}
+
+PyObject *
+_PyEval_GetGlobalsFromRunningMain(PyThreadState *tstate)
+{
+ if (!_PyInterpreterState_IsRunningMain(tstate->interp)) {
+ return NULL;
+ }
+ PyObject *mod = _Py_GetMainModule(tstate);
+ if (_Py_CheckMainModule(mod) < 0) {
+ Py_XDECREF(mod);
+ return NULL;
+ }
+ PyObject *globals = PyModule_GetDict(mod); // borrowed
+ Py_DECREF(mod);
+ return globals;
+}
+
+static PyObject *
+get_globals_builtins(PyObject *globals)
+{
+ PyObject *builtins = NULL;
+ if (PyDict_Check(globals)) {
+ if (PyDict_GetItemRef(globals, &_Py_ID(__builtins__), &builtins) < 0) {
+ return NULL;
+ }
+ }
+ else {
+ if (PyMapping_GetOptionalItem(
+ globals, &_Py_ID(__builtins__), &builtins) < 0)
+ {
+ return NULL;
+ }
+ }
+ return builtins;
+}
+
+static int
+set_globals_builtins(PyObject *globals, PyObject *builtins)
+{
+ if (PyDict_Check(globals)) {
+ if (PyDict_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) {
+ return -1;
+ }
+ }
+ else {
+ if (PyObject_SetItem(globals, &_Py_ID(__builtins__), builtins) < 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+_PyEval_EnsureBuiltins(PyThreadState *tstate, PyObject *globals,
+ PyObject **p_builtins)
+{
+ PyObject *builtins = get_globals_builtins(globals);
+ if (builtins == NULL) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ builtins = PyEval_GetBuiltins(); // borrowed
+ if (builtins == NULL) {
+ assert(_PyErr_Occurred(tstate));
+ return -1;
+ }
+ Py_INCREF(builtins);
+ if (set_globals_builtins(globals, builtins) < 0) {
+ Py_DECREF(builtins);
+ return -1;
+ }
+ }
+ if (p_builtins != NULL) {
+ *p_builtins = builtins;
+ }
+ else {
+ Py_DECREF(builtins);
+ }
+ return 0;
+}
+
+int
+_PyEval_EnsureBuiltinsWithModule(PyThreadState *tstate, PyObject *globals,
+ PyObject **p_builtins)
+{
+ PyObject *builtins = get_globals_builtins(globals);
+ if (builtins == NULL) {
+ if (_PyErr_Occurred(tstate)) {
+ return -1;
+ }
+ builtins = PyImport_ImportModuleLevel("builtins", NULL, NULL, NULL, 0);
+ if (builtins == NULL) {
+ return -1;
+ }
+ if (set_globals_builtins(globals, builtins) < 0) {
+ Py_DECREF(builtins);
+ return -1;
+ }
+ }
+ if (p_builtins != NULL) {
+ *p_builtins = builtins;
+ }
+ else {
+ Py_DECREF(builtins);
+ }
+ return 0;
+}
+
PyObject*
PyEval_GetFrameLocals(void)
{
@@ -2956,7 +3092,7 @@ _PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name)
int is_possibly_shadowing_stdlib = 0;
if (is_possibly_shadowing) {
PyObject *stdlib_modules;
- if (_PySys_GetOptionalAttrString("stdlib_module_names", &stdlib_modules) < 0) {
+ if (PySys_GetOptionalAttrString("stdlib_module_names", &stdlib_modules) < 0) {
goto done;
}
if (stdlib_modules && PyAnySet_Check(stdlib_modules)) {
@@ -3167,7 +3303,7 @@ _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwarg
else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
PyObject *exc = _PyErr_GetRaisedException(tstate);
PyObject *args = PyException_GetArgs(exc);
- if (exc && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1) {
+ if (PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1) {
_PyErr_Clear(tstate);
PyObject *funcstr = _PyObject_FunctionStr(func);
if (funcstr != NULL) {
@@ -3416,6 +3552,50 @@ _PyEval_LoadName(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject *na
return value;
}
+static _PyStackRef
+foriter_next(PyObject *seq, _PyStackRef index)
+{
+ assert(PyStackRef_IsTaggedInt(index));
+ assert(PyTuple_CheckExact(seq) || PyList_CheckExact(seq));
+ intptr_t i = PyStackRef_UntagInt(index);
+ if (PyTuple_CheckExact(seq)) {
+ size_t size = PyTuple_GET_SIZE(seq);
+ if ((size_t)i >= size) {
+ return PyStackRef_NULL;
+ }
+ return PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq, i));
+ }
+ PyObject *item = _PyList_GetItemRef((PyListObject *)seq, i);
+ if (item == NULL) {
+ return PyStackRef_NULL;
+ }
+ return PyStackRef_FromPyObjectSteal(item);
+}
+
+_PyStackRef _PyForIter_VirtualIteratorNext(PyThreadState* tstate, _PyInterpreterFrame* frame, _PyStackRef iter, _PyStackRef* index_ptr)
+{
+ PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ _PyStackRef index = *index_ptr;
+ if (PyStackRef_IsTaggedInt(index)) {
+ *index_ptr = PyStackRef_IncrementTaggedIntNoOverflow(index);
+ return foriter_next(iter_o, index);
+ }
+ PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
+ if (next_o == NULL) {
+ if (_PyErr_Occurred(tstate)) {
+ if (_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {
+ _PyEval_MonitorRaise(tstate, frame, frame->instr_ptr);
+ _PyErr_Clear(tstate);
+ }
+ else {
+ return PyStackRef_ERROR;
+ }
+ }
+ return PyStackRef_NULL;
+ }
+ return PyStackRef_FromPyObjectSteal(next_o);
+}
+
/* Check if a 'cls' provides the given special method. */
static inline int
type_has_special_method(PyTypeObject *cls, PyObject *name)
diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c
index 5b5018a6373..aa68371ac8f 100644
--- a/Python/ceval_gil.c
+++ b/Python/ceval_gil.c
@@ -1218,30 +1218,30 @@ static inline int run_remote_debugger_source(PyObject *source)
// Note that this function is inline to avoid creating a PLT entry
// that would be an easy target for a ROP gadget.
-static inline void run_remote_debugger_script(const char *path)
+static inline void run_remote_debugger_script(PyObject *path)
{
- if (0 != PySys_Audit("remote_debugger_script", "s", path)) {
+ if (0 != PySys_Audit("cpython.remote_debugger_script", "O", path)) {
PyErr_FormatUnraisable(
- "Audit hook failed for remote debugger script %s", path);
+ "Audit hook failed for remote debugger script %U", path);
return;
}
// Open the debugger script with the open code hook, and reopen the
// resulting file object to get a C FILE* object.
- PyObject* fileobj = PyFile_OpenCode(path);
+ PyObject* fileobj = PyFile_OpenCodeObject(path);
if (!fileobj) {
- PyErr_FormatUnraisable("Can't open debugger script %s", path);
+ PyErr_FormatUnraisable("Can't open debugger script %U", path);
return;
}
PyObject* source = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(read));
if (!source) {
- PyErr_FormatUnraisable("Error reading debugger script %s", path);
+ PyErr_FormatUnraisable("Error reading debugger script %U", path);
}
PyObject* res = PyObject_CallMethodNoArgs(fileobj, &_Py_ID(close));
if (!res) {
- PyErr_FormatUnraisable("Error closing debugger script %s", path);
+ PyErr_FormatUnraisable("Error closing debugger script %U", path);
} else {
Py_DECREF(res);
}
@@ -1249,7 +1249,7 @@ static inline void run_remote_debugger_script(const char *path)
if (source) {
if (0 != run_remote_debugger_source(source)) {
- PyErr_FormatUnraisable("Error executing debugger script %s", path);
+ PyErr_FormatUnraisable("Error executing debugger script %U", path);
}
Py_DECREF(source);
}
@@ -1278,7 +1278,14 @@ int _PyRunRemoteDebugger(PyThreadState *tstate)
pathsz);
path[pathsz - 1] = '\0';
if (*path) {
- run_remote_debugger_script(path);
+ PyObject *path_obj = PyUnicode_DecodeFSDefault(path);
+ if (path_obj == NULL) {
+ PyErr_FormatUnraisable("Can't decode debugger script");
+ }
+ else {
+ run_remote_debugger_script(path_obj);
+ Py_DECREF(path_obj);
+ }
}
PyMem_Free(path);
}
@@ -1380,6 +1387,10 @@ _Py_HandlePending(PyThreadState *tstate)
_Py_unset_eval_breaker_bit(tstate, _PY_EVAL_EXPLICIT_MERGE_BIT);
_Py_brc_merge_refcounts(tstate);
}
+ /* Process deferred memory frees held by QSBR */
+ if (_Py_qsbr_should_process(((_PyThreadStateImpl *)tstate)->qsbr)) {
+ _PyMem_ProcessDelayed(tstate);
+ }
#endif
/* GC scheduled to run */
diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h
index e1d2673848c..187ec8fdd26 100644
--- a/Python/ceval_macros.h
+++ b/Python/ceval_macros.h
@@ -359,12 +359,12 @@ _PyFrame_SetStackPointer(frame, stack_pointer)
do { \
OPT_STAT_INC(traces_executed); \
_PyExecutorObject *_executor = (EXECUTOR); \
+ tstate->current_executor = (PyObject *)_executor; \
jit_func jitted = _executor->jit_code; \
/* Keep the shim frame alive via the executor: */ \
Py_INCREF(_executor); \
next_instr = jitted(frame, stack_pointer, tstate); \
Py_DECREF(_executor); \
- Py_CLEAR(tstate->previous_executor); \
frame = tstate->current_frame; \
stack_pointer = _PyFrame_GetStackPointer(frame); \
if (next_instr == NULL) { \
@@ -377,7 +377,9 @@ do { \
#define GOTO_TIER_TWO(EXECUTOR) \
do { \
OPT_STAT_INC(traces_executed); \
- next_uop = (EXECUTOR)->trace; \
+ _PyExecutorObject *_executor = (EXECUTOR); \
+ tstate->current_executor = (PyObject *)_executor; \
+ next_uop = _executor->trace; \
assert(next_uop->opcode == _START_EXECUTOR); \
goto enter_tier_two; \
} while (0)
@@ -386,10 +388,11 @@ do { \
#define GOTO_TIER_ONE(TARGET) \
do \
{ \
+ tstate->current_executor = NULL; \
next_instr = (TARGET); \
+ assert(tstate->current_executor == NULL); \
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); \
_PyFrame_SetStackPointer(frame, stack_pointer); \
- Py_CLEAR(tstate->previous_executor); \
stack_pointer = _PyFrame_GetStackPointer(frame); \
if (next_instr == NULL) \
{ \
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index 8b73ccefc30..a47e4d11b54 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -1821,6 +1821,90 @@ exit:
return return_value;
}
+PyDoc_STRVAR(_jit_is_available__doc__,
+"is_available($module, /)\n"
+"--\n"
+"\n"
+"Return True if the current Python executable supports JIT compilation, and False otherwise.");
+
+#define _JIT_IS_AVAILABLE_METHODDEF \
+ {"is_available", (PyCFunction)_jit_is_available, METH_NOARGS, _jit_is_available__doc__},
+
+static int
+_jit_is_available_impl(PyObject *module);
+
+static PyObject *
+_jit_is_available(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+ int _return_value;
+
+ _return_value = _jit_is_available_impl(module);
+ if ((_return_value == -1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_jit_is_enabled__doc__,
+"is_enabled($module, /)\n"
+"--\n"
+"\n"
+"Return True if JIT compilation is enabled for the current Python process (implies sys._jit.is_available()), and False otherwise.");
+
+#define _JIT_IS_ENABLED_METHODDEF \
+ {"is_enabled", (PyCFunction)_jit_is_enabled, METH_NOARGS, _jit_is_enabled__doc__},
+
+static int
+_jit_is_enabled_impl(PyObject *module);
+
+static PyObject *
+_jit_is_enabled(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+ int _return_value;
+
+ _return_value = _jit_is_enabled_impl(module);
+ if ((_return_value == -1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_jit_is_active__doc__,
+"is_active($module, /)\n"
+"--\n"
+"\n"
+"Return True if the topmost Python frame is currently executing JIT code (implies sys._jit.is_enabled()), and False otherwise.");
+
+#define _JIT_IS_ACTIVE_METHODDEF \
+ {"is_active", (PyCFunction)_jit_is_active, METH_NOARGS, _jit_is_active__doc__},
+
+static int
+_jit_is_active_impl(PyObject *module);
+
+static PyObject *
+_jit_is_active(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+ int _return_value;
+
+ _return_value = _jit_is_active_impl(module);
+ if ((_return_value == -1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+ return return_value;
+}
+
#ifndef SYS_GETWINDOWSVERSION_METHODDEF
#define SYS_GETWINDOWSVERSION_METHODDEF
#endif /* !defined(SYS_GETWINDOWSVERSION_METHODDEF) */
@@ -1864,4 +1948,4 @@ exit:
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=1aca52cefbeb800f input=a9049054013a1b77]*/
+/*[clinic end generated code: output=449d16326e69dcf6 input=a9049054013a1b77]*/
diff --git a/Python/codegen.c b/Python/codegen.c
index 683601103ec..27fe8e1957b 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -28,6 +28,7 @@
#include "pycore_pystate.h" // _Py_GetConfig()
#include "pycore_symtable.h" // PySTEntryObject
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString
+#include "pycore_ceval.h" // SPECIAL___ENTER__
#define NEED_OPCODE_METADATA
#include "pycore_opcode_metadata.h" // _PyOpcode_opcode_metadata, _PyOpcode_num_popped/pushed
@@ -527,6 +528,15 @@ codegen_unwind_fblock(compiler *c, location *ploc,
case COMPILE_FBLOCK_FOR_LOOP:
/* Pop the iterator */
if (preserve_tos) {
+ ADDOP_I(c, *ploc, SWAP, 3);
+ }
+ ADDOP(c, *ploc, POP_TOP);
+ ADDOP(c, *ploc, POP_TOP);
+ return SUCCESS;
+
+ case COMPILE_FBLOCK_ASYNC_FOR_LOOP:
+ /* Pop the iterator */
+ if (preserve_tos) {
ADDOP_I(c, *ploc, SWAP, 2);
}
ADDOP(c, *ploc, POP_TOP);
@@ -629,7 +639,8 @@ codegen_unwind_fblock_stack(compiler *c, location *ploc,
c, *ploc, "'break', 'continue' and 'return' cannot appear in an except* block");
}
if (loop != NULL && (top->fb_type == COMPILE_FBLOCK_WHILE_LOOP ||
- top->fb_type == COMPILE_FBLOCK_FOR_LOOP)) {
+ top->fb_type == COMPILE_FBLOCK_FOR_LOOP ||
+ top->fb_type == COMPILE_FBLOCK_ASYNC_FOR_LOOP)) {
*loop = top;
return SUCCESS;
}
@@ -2125,7 +2136,7 @@ codegen_async_for(compiler *c, stmt_ty s)
ADDOP(c, LOC(s->v.AsyncFor.iter), GET_AITER);
USE_LABEL(c, start);
- RETURN_IF_ERROR(_PyCompile_PushFBlock(c, loc, COMPILE_FBLOCK_FOR_LOOP, start, end, NULL));
+ RETURN_IF_ERROR(_PyCompile_PushFBlock(c, loc, COMPILE_FBLOCK_ASYNC_FOR_LOOP, start, end, NULL));
/* SETUP_FINALLY to guard the __anext__ call */
ADDOP_JUMP(c, loc, SETUP_FINALLY, except);
@@ -2142,7 +2153,7 @@ codegen_async_for(compiler *c, stmt_ty s)
/* Mark jump as artificial */
ADDOP_JUMP(c, NO_LOCATION, JUMP, start);
- _PyCompile_PopFBlock(c, COMPILE_FBLOCK_FOR_LOOP, start);
+ _PyCompile_PopFBlock(c, COMPILE_FBLOCK_ASYNC_FOR_LOOP, start);
/* Except block for __anext__ */
USE_LABEL(c, except);
@@ -3895,10 +3906,11 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end)
NEW_JUMP_TARGET_LABEL(c, loop);
NEW_JUMP_TARGET_LABEL(c, cleanup);
+ ADDOP(c, loc, PUSH_NULL); // Push NULL index for loop
USE_LABEL(c, loop);
ADDOP_JUMP(c, loc, FOR_ITER, cleanup);
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
- ADDOP_I(c, loc, LIST_APPEND, 2);
+ ADDOP_I(c, loc, LIST_APPEND, 3);
ADDOP_JUMP(c, loc, JUMP, loop);
}
else {
@@ -4442,13 +4454,12 @@ codegen_sync_comprehension_generator(compiler *c, location loc,
}
if (IS_JUMP_TARGET_LABEL(start)) {
VISIT(c, expr, gen->iter);
- ADDOP(c, LOC(gen->iter), GET_ITER);
}
}
}
if (IS_JUMP_TARGET_LABEL(start)) {
- depth++;
+ depth += 2;
ADDOP(c, LOC(gen->iter), GET_ITER);
USE_LABEL(c, start);
ADDOP_JUMP(c, LOC(gen->iter), FOR_ITER, anchor);
@@ -4543,9 +4554,9 @@ codegen_async_comprehension_generator(compiler *c, location loc,
else {
/* Sub-iter - calculate on the fly */
VISIT(c, expr, gen->iter);
- ADDOP(c, LOC(gen->iter), GET_AITER);
}
}
+ ADDOP(c, LOC(gen->iter), GET_AITER);
USE_LABEL(c, start);
/* Runtime will push a block here, so we need to account for that */
@@ -4757,19 +4768,6 @@ pop_inlined_comprehension_state(compiler *c, location loc,
return SUCCESS;
}
-static inline int
-codegen_comprehension_iter(compiler *c, comprehension_ty comp)
-{
- VISIT(c, expr, comp->iter);
- if (comp->is_async) {
- ADDOP(c, LOC(comp->iter), GET_AITER);
- }
- else {
- ADDOP(c, LOC(comp->iter), GET_ITER);
- }
- return SUCCESS;
-}
-
static int
codegen_comprehension(compiler *c, expr_ty e, int type,
identifier name, asdl_comprehension_seq *generators, expr_ty elt,
@@ -4789,9 +4787,7 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
outermost = (comprehension_ty) asdl_seq_GET(generators, 0);
if (is_inlined) {
- if (codegen_comprehension_iter(c, outermost)) {
- goto error;
- }
+ VISIT(c, expr, outermost->iter);
if (push_inlined_comprehension_state(c, loc, entry, &inline_state)) {
goto error;
}
diff --git a/Python/compile.c b/Python/compile.c
index 15ef7214d44..c04391e682f 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -135,7 +135,7 @@ compiler_setup(compiler *c, mod_ty mod, PyObject *filename,
c->c_optimize = (optimize == -1) ? _Py_GetConfig()->optimization_level : optimize;
c->c_save_nested_seqs = false;
- if (!_PyAST_Optimize(mod, arena, filename, c->c_optimize, merged, 0)) {
+ if (!_PyAST_Preprocess(mod, arena, filename, c->c_optimize, merged, 0)) {
return ERROR;
}
c->c_st = _PySymtable_Build(mod, filename, &c->c_future);
@@ -1481,8 +1481,8 @@ _PyAST_Compile(mod_ty mod, PyObject *filename, PyCompilerFlags *pflags,
}
int
-_PyCompile_AstOptimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
- int optimize, PyArena *arena, int no_const_folding)
+_PyCompile_AstPreprocess(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
+ int optimize, PyArena *arena, int no_const_folding)
{
_PyFutureFeatures future;
if (!_PyFuture_FromAST(mod, filename, &future)) {
@@ -1492,7 +1492,7 @@ _PyCompile_AstOptimize(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
if (optimize == -1) {
optimize = _Py_GetConfig()->optimization_level;
}
- if (!_PyAST_Optimize(mod, arena, filename, optimize, flags, no_const_folding)) {
+ if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags, no_const_folding)) {
return -1;
}
return 0;
diff --git a/Python/context.c b/Python/context.c
index dceaae9b429..9927cab915c 100644
--- a/Python/context.c
+++ b/Python/context.c
@@ -979,7 +979,7 @@ contextvar_tp_repr(PyObject *op)
return NULL;
}
- if (PyUnicodeWriter_WriteUTF8(writer, "<ContextVar name=", 17) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, "<ContextVar name=", 17) < 0) {
goto error;
}
if (PyUnicodeWriter_WriteRepr(writer, self->var_name) < 0) {
@@ -987,7 +987,7 @@ contextvar_tp_repr(PyObject *op)
}
if (self->var_default != NULL) {
- if (PyUnicodeWriter_WriteUTF8(writer, " default=", 9) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, " default=", 9) < 0) {
goto error;
}
if (PyUnicodeWriter_WriteRepr(writer, self->var_default) < 0) {
@@ -1182,15 +1182,15 @@ token_tp_repr(PyObject *op)
if (writer == NULL) {
return NULL;
}
- if (PyUnicodeWriter_WriteUTF8(writer, "<Token", 6) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, "<Token", 6) < 0) {
goto error;
}
if (self->tok_used) {
- if (PyUnicodeWriter_WriteUTF8(writer, " used", 5) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, " used", 5) < 0) {
goto error;
}
}
- if (PyUnicodeWriter_WriteUTF8(writer, " var=", 5) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, " var=", 5) < 0) {
goto error;
}
if (PyUnicodeWriter_WriteRepr(writer, (PyObject *)self->tok_var) < 0) {
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index a9f9b785629..16a23f0351c 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -6,8 +6,14 @@
#include "osdefs.h" // MAXPATHLEN
#include "pycore_ceval.h" // _Py_simple_func
#include "pycore_crossinterp.h" // _PyXIData_t
+#include "pycore_function.h" // _PyFunction_VerifyStateless()
+#include "pycore_global_strings.h" // _Py_ID()
+#include "pycore_import.h" // _PyImport_SetModule()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_namespace.h" // _PyNamespace_New()
+#include "pycore_pythonrun.h" // _Py_SourceAsString()
+#include "pycore_runtime.h" // _PyRuntime
+#include "pycore_setobject.h" // _PySet_NextEntry()
#include "pycore_typeobject.h" // _PyStaticType_InitBuiltin()
@@ -19,6 +25,7 @@ _Py_GetMainfile(char *buffer, size_t maxlen)
PyThreadState *tstate = _PyThreadState_GET();
PyObject *module = _Py_GetMainModule(tstate);
if (_Py_CheckMainModule(module) < 0) {
+ Py_XDECREF(module);
return -1;
}
Py_ssize_t size = _PyModule_GetFilenameUTF8(module, buffer, maxlen);
@@ -28,27 +35,6 @@ _Py_GetMainfile(char *buffer, size_t maxlen)
static PyObject *
-import_get_module(PyThreadState *tstate, const char *modname)
-{
- PyObject *module = NULL;
- if (strcmp(modname, "__main__") == 0) {
- module = _Py_GetMainModule(tstate);
- if (_Py_CheckMainModule(module) < 0) {
- assert(_PyErr_Occurred(tstate));
- return NULL;
- }
- }
- else {
- module = PyImport_ImportModule(modname);
- if (module == NULL) {
- return NULL;
- }
- }
- return module;
-}
-
-
-static PyObject *
runpy_run_path(const char *filename, const char *modname)
{
PyObject *run_path = PyImport_ImportModuleAttrString("runpy", "run_path");
@@ -67,97 +53,192 @@ runpy_run_path(const char *filename, const char *modname)
}
-static PyObject *
-pyerr_get_message(PyObject *exc)
+static void
+set_exc_with_cause(PyObject *exctype, const char *msg)
{
- assert(!PyErr_Occurred());
- PyObject *args = PyException_GetArgs(exc);
- if (args == NULL || args == Py_None || PyObject_Size(args) < 1) {
- return NULL;
- }
- if (PyUnicode_Check(args)) {
- return args;
- }
- PyObject *msg = PySequence_GetItem(args, 0);
- Py_DECREF(args);
- if (msg == NULL) {
- PyErr_Clear();
- return NULL;
- }
- if (!PyUnicode_Check(msg)) {
- Py_DECREF(msg);
- return NULL;
- }
- return msg;
+ PyObject *cause = PyErr_GetRaisedException();
+ PyErr_SetString(exctype, msg);
+ PyObject *exc = PyErr_GetRaisedException();
+ PyException_SetCause(exc, cause);
+ PyErr_SetRaisedException(exc);
}
-#define MAX_MODNAME (255)
-#define MAX_ATTRNAME (255)
-struct attributeerror_info {
- char modname[MAX_MODNAME+1];
- char attrname[MAX_ATTRNAME+1];
+/****************************/
+/* module duplication utils */
+/****************************/
+
+struct sync_module_result {
+ PyObject *module;
+ PyObject *loaded;
+ PyObject *failed;
+};
+
+struct sync_module {
+ const char *filename;
+ char _filename[MAXPATHLEN+1];
+ struct sync_module_result cached;
};
+static void
+sync_module_clear(struct sync_module *data)
+{
+ data->filename = NULL;
+ Py_CLEAR(data->cached.module);
+ Py_CLEAR(data->cached.loaded);
+ Py_CLEAR(data->cached.failed);
+}
+
+static void
+sync_module_capture_exc(PyThreadState *tstate, struct sync_module *data)
+{
+ assert(_PyErr_Occurred(tstate));
+ PyObject *context = data->cached.failed;
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ _PyErr_SetRaisedException(tstate, Py_NewRef(exc));
+ if (context != NULL) {
+ PyException_SetContext(exc, context);
+ }
+ data->cached.failed = exc;
+}
+
+
static int
-_parse_attributeerror(PyObject *exc, struct attributeerror_info *info)
+ensure_isolated_main(PyThreadState *tstate, struct sync_module *main)
{
- assert(exc != NULL);
- assert(PyErr_GivenExceptionMatches(exc, PyExc_AttributeError));
- int res = -1;
+ // Load the module from the original file (or from a cache).
- PyObject *msgobj = pyerr_get_message(exc);
- if (msgobj == NULL) {
+ // First try the local cache.
+ if (main->cached.failed != NULL) {
+ // We'll deal with this in apply_isolated_main().
+ assert(main->cached.module == NULL);
+ assert(main->cached.loaded == NULL);
+ return 0;
+ }
+ else if (main->cached.loaded != NULL) {
+ assert(main->cached.module != NULL);
+ return 0;
+ }
+ assert(main->cached.module == NULL);
+
+ if (main->filename == NULL) {
+ _PyErr_SetString(tstate, PyExc_NotImplementedError, "");
return -1;
}
- const char *err = PyUnicode_AsUTF8(msgobj);
- if (strncmp(err, "module '", 8) != 0) {
- goto finally;
+ // It wasn't in the local cache so we'll need to populate it.
+ PyObject *mod = _Py_GetMainModule(tstate);
+ if (_Py_CheckMainModule(mod) < 0) {
+ // This is probably unrecoverable, so don't bother caching the error.
+ assert(_PyErr_Occurred(tstate));
+ Py_XDECREF(mod);
+ return -1;
}
- err += 8;
+ PyObject *loaded = NULL;
- const char *matched = strchr(err, '\'');
- if (matched == NULL) {
- goto finally;
+ // Try the per-interpreter cache for the loaded module.
+ // XXX Store it in sys.modules?
+ PyObject *interpns = PyInterpreterState_GetDict(tstate->interp);
+ assert(interpns != NULL);
+ PyObject *key = PyUnicode_FromString("CACHED_MODULE_NS___main__");
+ if (key == NULL) {
+ // It's probably unrecoverable, so don't bother caching the error.
+ Py_DECREF(mod);
+ return -1;
}
- Py_ssize_t len = matched - err;
- if (len > MAX_MODNAME) {
- goto finally;
+ else if (PyDict_GetItemRef(interpns, key, &loaded) < 0) {
+ // It's probably unrecoverable, so don't bother caching the error.
+ Py_DECREF(mod);
+ Py_DECREF(key);
+ return -1;
}
- (void)strncpy(info->modname, err, len);
- info->modname[len] = '\0';
- err = matched;
+ else if (loaded == NULL) {
+ // It wasn't already loaded from file.
+ loaded = PyModule_NewObject(&_Py_ID(__main__));
+ if (loaded == NULL) {
+ goto error;
+ }
+ PyObject *ns = _PyModule_GetDict(loaded);
- if (strncmp(err, "' has no attribute '", 20) != 0) {
- goto finally;
- }
- err += 20;
+ // We don't want to trigger "if __name__ == '__main__':",
+ // so we use a bogus module name.
+ PyObject *loaded_ns =
+ runpy_run_path(main->filename, "<fake __main__>");
+ if (loaded_ns == NULL) {
+ goto error;
+ }
+ int res = PyDict_Update(ns, loaded_ns);
+ Py_DECREF(loaded_ns);
+ if (res < 0) {
+ goto error;
+ }
- matched = strchr(err, '\'');
- if (matched == NULL) {
- goto finally;
- }
- len = matched - err;
- if (len > MAX_ATTRNAME) {
- goto finally;
+ // Set the per-interpreter cache entry.
+ if (PyDict_SetItem(interpns, key, loaded) < 0) {
+ goto error;
+ }
}
- (void)strncpy(info->attrname, err, len);
- info->attrname[len] = '\0';
- err = matched + 1;
- if (strlen(err) > 0) {
- goto finally;
+ Py_DECREF(key);
+ main->cached = (struct sync_module_result){
+ .module = mod,
+ .loaded = loaded,
+ };
+ return 0;
+
+error:
+ sync_module_capture_exc(tstate, main);
+ Py_XDECREF(loaded);
+ Py_DECREF(mod);
+ Py_XDECREF(key);
+ return -1;
+}
+
+#ifndef NDEBUG
+static int
+main_mod_matches(PyObject *expected)
+{
+ PyObject *mod = PyImport_GetModule(&_Py_ID(__main__));
+ Py_XDECREF(mod);
+ return mod == expected;
+}
+#endif
+
+static int
+apply_isolated_main(PyThreadState *tstate, struct sync_module *main)
+{
+ assert((main->cached.loaded == NULL) == (main->cached.loaded == NULL));
+ if (main->cached.failed != NULL) {
+ // It must have failed previously.
+ assert(main->cached.loaded == NULL);
+ _PyErr_SetRaisedException(tstate, main->cached.failed);
+ return -1;
}
- res = 0;
+ assert(main->cached.loaded != NULL);
-finally:
- Py_DECREF(msgobj);
- return res;
+ assert(main_mod_matches(main->cached.module));
+ if (_PyImport_SetModule(&_Py_ID(__main__), main->cached.loaded) < 0) {
+ sync_module_capture_exc(tstate, main);
+ return -1;
+ }
+ return 0;
}
-#undef MAX_MODNAME
-#undef MAX_ATTRNAME
+static void
+restore_main(PyThreadState *tstate, struct sync_module *main)
+{
+ assert(main->cached.failed == NULL);
+ assert(main->cached.module != NULL);
+ assert(main->cached.loaded != NULL);
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ assert(main_mod_matches(main->cached.loaded));
+ int res = _PyImport_SetModule(&_Py_ID(__main__), main->cached.module);
+ assert(res == 0);
+ if (res < 0) {
+ PyErr_FormatUnraisable("Exception ignored while restoring __main__");
+ }
+ _PyErr_SetRaisedException(tstate, exc);
+}
/**************/
@@ -207,16 +288,16 @@ _Py_CallInInterpreterAndRawFree(PyInterpreterState *interp,
/* cross-interpreter data */
/**************************/
-/* registry of {type -> xidatafunc} */
+/* registry of {type -> _PyXIData_getdata_t} */
-/* For now we use a global registry of shareable classes. An
- alternative would be to add a tp_* slot for a class's
- xidatafunc. It would be simpler and more efficient. */
+/* For now we use a global registry of shareable classes.
+ An alternative would be to add a tp_* slot for a class's
+ _PyXIData_getdata_t. It would be simpler and more efficient. */
static void xid_lookup_init(_PyXIData_lookup_t *);
static void xid_lookup_fini(_PyXIData_lookup_t *);
struct _dlcontext;
-static xidatafunc lookup_getdata(struct _dlcontext *, PyObject *);
+static _PyXIData_getdata_t lookup_getdata(struct _dlcontext *, PyObject *);
#include "crossinterp_data_lookup.h"
@@ -340,7 +421,7 @@ _set_xid_lookup_failure(PyThreadState *tstate, PyObject *obj, const char *msg,
set_notshareableerror(tstate, cause, 0, msg);
}
else {
- msg = "%S does not support cross-interpreter data";
+ msg = "%R does not support cross-interpreter data";
format_notshareableerror(tstate, cause, 0, msg, obj);
}
}
@@ -353,8 +434,8 @@ _PyObject_CheckXIData(PyThreadState *tstate, PyObject *obj)
if (get_lookup_context(tstate, &ctx) < 0) {
return -1;
}
- xidatafunc getdata = lookup_getdata(&ctx, obj);
- if (getdata == NULL) {
+ _PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
+ if (getdata.basic == NULL && getdata.fallback == NULL) {
if (!_PyErr_Occurred(tstate)) {
_set_xid_lookup_failure(tstate, obj, NULL, NULL);
}
@@ -385,9 +466,9 @@ _check_xidata(PyThreadState *tstate, _PyXIData_t *xidata)
return 0;
}
-int
-_PyObject_GetXIData(PyThreadState *tstate,
- PyObject *obj, _PyXIData_t *xidata)
+static int
+_get_xidata(PyThreadState *tstate,
+ PyObject *obj, xidata_fallback_t fallback, _PyXIData_t *xidata)
{
PyInterpreterState *interp = tstate->interp;
@@ -395,6 +476,7 @@ _PyObject_GetXIData(PyThreadState *tstate,
assert(xidata->obj == NULL);
if (xidata->data != NULL || xidata->obj != NULL) {
_PyErr_SetString(tstate, PyExc_ValueError, "xidata not cleared");
+ return -1;
}
// Call the "getdata" func for the object.
@@ -403,8 +485,8 @@ _PyObject_GetXIData(PyThreadState *tstate,
return -1;
}
Py_INCREF(obj);
- xidatafunc getdata = lookup_getdata(&ctx, obj);
- if (getdata == NULL) {
+ _PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
+ if (getdata.basic == NULL && getdata.fallback == NULL) {
if (PyErr_Occurred()) {
Py_DECREF(obj);
return -1;
@@ -416,7 +498,9 @@ _PyObject_GetXIData(PyThreadState *tstate,
}
return -1;
}
- int res = getdata(tstate, obj, xidata);
+ int res = getdata.basic != NULL
+ ? getdata.basic(tstate, obj, xidata)
+ : getdata.fallback(tstate, obj, fallback, xidata);
Py_DECREF(obj);
if (res != 0) {
PyObject *cause = _PyErr_GetRaisedException(tstate);
@@ -436,6 +520,51 @@ _PyObject_GetXIData(PyThreadState *tstate,
return 0;
}
+int
+_PyObject_GetXIDataNoFallback(PyThreadState *tstate,
+ PyObject *obj, _PyXIData_t *xidata)
+{
+ return _get_xidata(tstate, obj, _PyXIDATA_XIDATA_ONLY, xidata);
+}
+
+int
+_PyObject_GetXIData(PyThreadState *tstate,
+ PyObject *obj, xidata_fallback_t fallback,
+ _PyXIData_t *xidata)
+{
+ switch (fallback) {
+ case _PyXIDATA_XIDATA_ONLY:
+ return _get_xidata(tstate, obj, fallback, xidata);
+ case _PyXIDATA_FULL_FALLBACK:
+ if (_get_xidata(tstate, obj, fallback, xidata) == 0) {
+ return 0;
+ }
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ if (PyFunction_Check(obj)) {
+ if (_PyFunction_GetXIData(tstate, obj, xidata) == 0) {
+ Py_DECREF(exc);
+ return 0;
+ }
+ _PyErr_Clear(tstate);
+ }
+ // We could try _PyMarshal_GetXIData() but we won't for now.
+ if (_PyPickle_GetXIData(tstate, obj, xidata) == 0) {
+ Py_DECREF(exc);
+ return 0;
+ }
+ // Raise the original exception.
+ _PyErr_SetRaisedException(tstate, exc);
+ return -1;
+ default:
+#ifdef Py_DEBUG
+ Py_FatalError("unsupported xidata fallback option");
+#endif
+ _PyErr_SetString(tstate, PyExc_SystemError,
+ "unsupported xidata fallback option");
+ return -1;
+ }
+}
+
/* pickle C-API */
@@ -456,28 +585,6 @@ _PyPickle_Dumps(struct _pickle_context *ctx, PyObject *obj)
}
-struct sync_module_result {
- PyObject *module;
- PyObject *loaded;
- PyObject *failed;
-};
-
-struct sync_module {
- const char *filename;
- char _filename[MAXPATHLEN+1];
- struct sync_module_result cached;
-};
-
-static void
-sync_module_clear(struct sync_module *data)
-{
- data->filename = NULL;
- Py_CLEAR(data->cached.module);
- Py_CLEAR(data->cached.loaded);
- Py_CLEAR(data->cached.failed);
-}
-
-
struct _unpickle_context {
PyThreadState *tstate;
// We only special-case the __main__ module,
@@ -491,139 +598,88 @@ _unpickle_context_clear(struct _unpickle_context *ctx)
sync_module_clear(&ctx->main);
}
-static struct sync_module_result
-_unpickle_context_get_module(struct _unpickle_context *ctx,
- const char *modname)
+static int
+check_missing___main___attr(PyObject *exc)
{
- if (strcmp(modname, "__main__") == 0) {
- return ctx->main.cached;
+ assert(!PyErr_Occurred());
+ if (!PyErr_GivenExceptionMatches(exc, PyExc_AttributeError)) {
+ return 0;
}
- else {
- return (struct sync_module_result){
- .failed = PyExc_NotImplementedError,
- };
+
+ // Get the error message.
+ PyObject *args = PyException_GetArgs(exc);
+ if (args == NULL || args == Py_None || PyObject_Size(args) < 1) {
+ assert(!PyErr_Occurred());
+ return 0;
+ }
+ PyObject *msgobj = args;
+ if (!PyUnicode_Check(msgobj)) {
+ msgobj = PySequence_GetItem(args, 0);
+ Py_DECREF(args);
+ if (msgobj == NULL) {
+ PyErr_Clear();
+ return 0;
+ }
}
+ const char *err = PyUnicode_AsUTF8(msgobj);
+
+ // Check if it's a missing __main__ attr.
+ int cmp = strncmp(err, "module '__main__' has no attribute '", 36);
+ Py_DECREF(msgobj);
+ return cmp == 0;
}
-static struct sync_module_result
-_unpickle_context_set_module(struct _unpickle_context *ctx,
- const char *modname)
+static PyObject *
+_PyPickle_Loads(struct _unpickle_context *ctx, PyObject *pickled)
{
- struct sync_module_result res = {0};
- struct sync_module_result *cached = NULL;
- const char *filename = NULL;
- if (strcmp(modname, "__main__") == 0) {
- cached = &ctx->main.cached;
- filename = ctx->main.filename;
- }
- else {
- res.failed = PyExc_NotImplementedError;
- goto finally;
- }
+ PyThreadState *tstate = ctx->tstate;
- res.module = import_get_module(ctx->tstate, modname);
- if (res.module == NULL) {
- res.failed = _PyErr_GetRaisedException(ctx->tstate);
- assert(res.failed != NULL);
- goto finally;
+ PyObject *exc = NULL;
+ PyObject *loads = PyImport_ImportModuleAttrString("pickle", "loads");
+ if (loads == NULL) {
+ return NULL;
}
- if (filename == NULL) {
- Py_CLEAR(res.module);
- res.failed = PyExc_NotImplementedError;
+ // Make an initial attempt to unpickle.
+ PyObject *obj = PyObject_CallOneArg(loads, pickled);
+ if (obj != NULL) {
goto finally;
}
- res.loaded = runpy_run_path(filename, modname);
- if (res.loaded == NULL) {
- Py_CLEAR(res.module);
- res.failed = _PyErr_GetRaisedException(ctx->tstate);
- assert(res.failed != NULL);
+ assert(_PyErr_Occurred(tstate));
+ if (ctx == NULL) {
goto finally;
}
-
-finally:
- if (cached != NULL) {
- assert(cached->module == NULL);
- assert(cached->loaded == NULL);
- assert(cached->failed == NULL);
- *cached = res;
- }
- return res;
-}
-
-
-static int
-_handle_unpickle_missing_attr(struct _unpickle_context *ctx, PyObject *exc)
-{
- // The caller must check if an exception is set or not when -1 is returned.
- assert(!_PyErr_Occurred(ctx->tstate));
- assert(PyErr_GivenExceptionMatches(exc, PyExc_AttributeError));
- struct attributeerror_info info;
- if (_parse_attributeerror(exc, &info) < 0) {
- return -1;
+ exc = _PyErr_GetRaisedException(tstate);
+ if (!check_missing___main___attr(exc)) {
+ goto finally;
}
- // Get the module.
- struct sync_module_result mod = _unpickle_context_get_module(ctx, info.modname);
- if (mod.failed != NULL) {
- // It must have failed previously.
- return -1;
- }
- if (mod.module == NULL) {
- mod = _unpickle_context_set_module(ctx, info.modname);
- if (mod.failed != NULL) {
- return -1;
- }
- assert(mod.module != NULL);
+ // Temporarily swap in a fake __main__ loaded from the original
+ // file and cached. Note that functions will use the cached ns
+ // for __globals__, // not the actual module.
+ if (ensure_isolated_main(tstate, &ctx->main) < 0) {
+ goto finally;
}
-
- // Bail out if it is unexpectedly set already.
- if (PyObject_HasAttrString(mod.module, info.attrname)) {
- return -1;
+ if (apply_isolated_main(tstate, &ctx->main) < 0) {
+ goto finally;
}
- // Try setting the attribute.
- PyObject *value = NULL;
- if (PyDict_GetItemStringRef(mod.loaded, info.attrname, &value) <= 0) {
- return -1;
- }
- assert(value != NULL);
- int res = PyObject_SetAttrString(mod.module, info.attrname, value);
- Py_DECREF(value);
- if (res < 0) {
- return -1;
+ // Try to unpickle once more.
+ obj = PyObject_CallOneArg(loads, pickled);
+ restore_main(tstate, &ctx->main);
+ if (obj == NULL) {
+ goto finally;
}
+ Py_CLEAR(exc);
- return 0;
-}
-
-static PyObject *
-_PyPickle_Loads(struct _unpickle_context *ctx, PyObject *pickled)
-{
- PyObject *loads = PyImport_ImportModuleAttrString("pickle", "loads");
- if (loads == NULL) {
- return NULL;
- }
- PyObject *obj = PyObject_CallOneArg(loads, pickled);
- if (ctx != NULL) {
- while (obj == NULL) {
- assert(_PyErr_Occurred(ctx->tstate));
- if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
- // We leave other failures unhandled.
- break;
- }
- // Try setting the attr if not set.
- PyObject *exc = _PyErr_GetRaisedException(ctx->tstate);
- if (_handle_unpickle_missing_attr(ctx, exc) < 0) {
- // Any resulting exceptions are ignored
- // in favor of the original.
- _PyErr_SetRaisedException(ctx->tstate, exc);
- break;
- }
- Py_CLEAR(exc);
- // Retry with the attribute set.
- obj = PyObject_CallOneArg(loads, pickled);
+finally:
+ if (exc != NULL) {
+ if (_PyErr_Occurred(tstate)) {
+ sync_module_capture_exc(tstate, &ctx->main);
}
+ // We restore the original exception.
+ // It might make sense to chain it (__context__).
+ _PyErr_SetRaisedException(tstate, exc);
}
Py_DECREF(loads);
return obj;
@@ -781,6 +837,138 @@ _PyMarshal_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
}
+/* script wrapper */
+
+static int
+verify_script(PyThreadState *tstate, PyCodeObject *co, int checked, int pure)
+{
+ // Make sure it isn't a closure and (optionally) doesn't use globals.
+ PyObject *builtins = NULL;
+ if (pure) {
+ builtins = _PyEval_GetBuiltins(tstate);
+ assert(builtins != NULL);
+ }
+ if (checked) {
+ assert(_PyCode_VerifyStateless(tstate, co, NULL, NULL, builtins) == 0);
+ }
+ else if (_PyCode_VerifyStateless(tstate, co, NULL, NULL, builtins) < 0) {
+ return -1;
+ }
+ // Make sure it doesn't have args.
+ if (co->co_argcount > 0
+ || co->co_posonlyargcount > 0
+ || co->co_kwonlyargcount > 0
+ || co->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
+ {
+ _PyErr_SetString(tstate, PyExc_ValueError,
+ "code with args not supported");
+ return -1;
+ }
+ // Make sure it doesn't return anything.
+ if (!_PyCode_ReturnsOnlyNone(co)) {
+ _PyErr_SetString(tstate, PyExc_ValueError,
+ "code that returns a value is not a script");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+get_script_xidata(PyThreadState *tstate, PyObject *obj, int pure,
+ _PyXIData_t *xidata)
+{
+ // Get the corresponding code object.
+ PyObject *code = NULL;
+ int checked = 0;
+ if (PyCode_Check(obj)) {
+ code = obj;
+ Py_INCREF(code);
+ }
+ else if (PyFunction_Check(obj)) {
+ code = PyFunction_GET_CODE(obj);
+ assert(code != NULL);
+ Py_INCREF(code);
+ if (pure) {
+ if (_PyFunction_VerifyStateless(tstate, obj) < 0) {
+ goto error;
+ }
+ checked = 1;
+ }
+ }
+ else {
+ const char *filename = "<script>";
+ int optimize = 0;
+ PyCompilerFlags cf = _PyCompilerFlags_INIT;
+ cf.cf_flags = PyCF_SOURCE_IS_UTF8;
+ PyObject *ref = NULL;
+ const char *script = _Py_SourceAsString(obj, "???", "???", &cf, &ref);
+ if (script == NULL) {
+ if (!_PyObject_SupportedAsScript(obj)) {
+ // We discard the raised exception.
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "unsupported script %R", obj);
+ }
+ goto error;
+ }
+#ifdef Py_GIL_DISABLED
+ // Don't immortalize code constants to avoid memory leaks.
+ ((_PyThreadStateImpl *)tstate)->suppress_co_const_immortalization++;
+#endif
+ code = Py_CompileStringExFlags(
+ script, filename, Py_file_input, &cf, optimize);
+#ifdef Py_GIL_DISABLED
+ ((_PyThreadStateImpl *)tstate)->suppress_co_const_immortalization--;
+#endif
+ Py_XDECREF(ref);
+ if (code == NULL) {
+ goto error;
+ }
+ // Compiled text can't have args or any return statements,
+ // nor be a closure. It can use globals though.
+ if (!pure) {
+ // We don't need to check for globals either.
+ checked = 1;
+ }
+ }
+
+ // Make sure it's actually a script.
+ if (verify_script(tstate, (PyCodeObject *)code, checked, pure) < 0) {
+ goto error;
+ }
+
+ // Convert the code object.
+ int res = _PyCode_GetXIData(tstate, code, xidata);
+ Py_DECREF(code);
+ if (res < 0) {
+ return -1;
+ }
+ return 0;
+
+error:
+ Py_XDECREF(code);
+ PyObject *cause = _PyErr_GetRaisedException(tstate);
+ assert(cause != NULL);
+ _set_xid_lookup_failure(
+ tstate, NULL, "object not a valid script", cause);
+ Py_DECREF(cause);
+ return -1;
+}
+
+int
+_PyCode_GetScriptXIData(PyThreadState *tstate,
+ PyObject *obj, _PyXIData_t *xidata)
+{
+ return get_script_xidata(tstate, obj, 0, xidata);
+}
+
+int
+_PyCode_GetPureScriptXIData(PyThreadState *tstate,
+ PyObject *obj, _PyXIData_t *xidata)
+{
+ return get_script_xidata(tstate, obj, 1, xidata);
+}
+
+
/* using cross-interpreter data */
PyObject *
@@ -1127,8 +1315,14 @@ _excinfo_normalize_type(struct _excinfo_type *info,
*p_module = module;
}
+static int
+excinfo_is_set(_PyXI_excinfo *info)
+{
+ return info->type.name != NULL || info->msg != NULL;
+}
+
static void
-_PyXI_excinfo_Clear(_PyXI_excinfo *info)
+_PyXI_excinfo_clear(_PyXI_excinfo *info)
{
_excinfo_clear_type(&info->type);
if (info->msg != NULL) {
@@ -1178,7 +1372,7 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
assert(exc != NULL);
if (PyErr_GivenExceptionMatches(exc, PyExc_MemoryError)) {
- _PyXI_excinfo_Clear(info);
+ _PyXI_excinfo_clear(info);
return NULL;
}
const char *failure = NULL;
@@ -1224,7 +1418,7 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
error:
assert(failure != NULL);
- _PyXI_excinfo_Clear(info);
+ _PyXI_excinfo_clear(info);
return failure;
}
@@ -1275,7 +1469,7 @@ _PyXI_excinfo_InitFromObject(_PyXI_excinfo *info, PyObject *obj)
error:
assert(failure != NULL);
- _PyXI_excinfo_Clear(info);
+ _PyXI_excinfo_clear(info);
return failure;
}
@@ -1288,6 +1482,11 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
if (tbexc == NULL) {
PyErr_Clear();
}
+ else {
+ PyErr_SetObject(exctype, tbexc);
+ Py_DECREF(tbexc);
+ return;
+ }
}
PyObject *formatted = _PyXI_excinfo_format(info);
@@ -1433,13 +1632,17 @@ error:
}
-int
-_PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc)
+_PyXI_excinfo *
+_PyXI_NewExcInfo(PyObject *exc)
{
assert(!PyErr_Occurred());
if (exc == NULL || exc == Py_None) {
PyErr_SetString(PyExc_ValueError, "missing exc");
- return -1;
+ return NULL;
+ }
+ _PyXI_excinfo *info = PyMem_RawCalloc(1, sizeof(_PyXI_excinfo));
+ if (info == NULL) {
+ return NULL;
}
const char *failure;
if (PyExceptionInstance_Check(exc) || PyExceptionClass_Check(exc)) {
@@ -1449,10 +1652,18 @@ _PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc)
failure = _PyXI_excinfo_InitFromObject(info, exc);
}
if (failure != NULL) {
- PyErr_SetString(PyExc_Exception, failure);
- return -1;
+ PyMem_RawFree(info);
+ set_exc_with_cause(PyExc_Exception, failure);
+ return NULL;
}
- return 0;
+ return info;
+}
+
+void
+_PyXI_FreeExcInfo(_PyXI_excinfo *info)
+{
+ _PyXI_excinfo_clear(info);
+ PyMem_RawFree(info);
}
PyObject *
@@ -1467,12 +1678,6 @@ _PyXI_ExcInfoAsObject(_PyXI_excinfo *info)
return _PyXI_excinfo_AsObject(info);
}
-void
-_PyXI_ClearExcInfo(_PyXI_excinfo *info)
-{
- _PyXI_excinfo_Clear(info);
-}
-
/***************************/
/* short-term data sharing */
@@ -1486,14 +1691,9 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
PyThreadState *tstate = _PyThreadState_GET();
assert(!PyErr_Occurred());
+ assert(code != _PyXI_ERR_NO_ERROR);
+ assert(code != _PyXI_ERR_UNCAUGHT_EXCEPTION);
switch (code) {
- case _PyXI_ERR_NO_ERROR: _Py_FALLTHROUGH;
- case _PyXI_ERR_UNCAUGHT_EXCEPTION:
- // There is nothing to apply.
-#ifdef Py_DEBUG
- Py_UNREACHABLE();
-#endif
- return 0;
case _PyXI_ERR_OTHER:
// XXX msg?
PyErr_SetNone(PyExc_InterpreterError);
@@ -1513,12 +1713,20 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
PyErr_SetString(PyExc_InterpreterError,
"failed to apply namespace to __main__");
break;
+ case _PyXI_ERR_PRESERVE_FAILURE:
+ PyErr_SetString(PyExc_InterpreterError,
+ "failed to preserve objects across session");
+ break;
+ case _PyXI_ERR_EXC_PROPAGATION_FAILURE:
+ PyErr_SetString(PyExc_InterpreterError,
+ "failed to transfer exception between interpreters");
+ break;
case _PyXI_ERR_NOT_SHAREABLE:
_set_xid_lookup_failure(tstate, NULL, NULL, NULL);
break;
default:
#ifdef Py_DEBUG
- Py_UNREACHABLE();
+ Py_FatalError("unsupported error code");
#else
PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
#endif
@@ -1527,70 +1735,267 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
return -1;
}
+/* basic failure info */
+
+struct xi_failure {
+ // The kind of error to propagate.
+ _PyXI_errcode code;
+ // The propagated message.
+ const char *msg;
+ int msg_owned;
+}; // _PyXI_failure
+
+#define XI_FAILURE_INIT (_PyXI_failure){ .code = _PyXI_ERR_NO_ERROR }
+
+static void
+clear_xi_failure(_PyXI_failure *failure)
+{
+ if (failure->msg != NULL && failure->msg_owned) {
+ PyMem_RawFree((void*)failure->msg);
+ }
+ *failure = XI_FAILURE_INIT;
+}
+
+static void
+copy_xi_failure(_PyXI_failure *dest, _PyXI_failure *src)
+{
+ *dest = *src;
+ dest->msg_owned = 0;
+}
+
+_PyXI_failure *
+_PyXI_NewFailure(void)
+{
+ _PyXI_failure *failure = PyMem_RawMalloc(sizeof(_PyXI_failure));
+ if (failure == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ *failure = XI_FAILURE_INIT;
+ return failure;
+}
+
+void
+_PyXI_FreeFailure(_PyXI_failure *failure)
+{
+ clear_xi_failure(failure);
+ PyMem_RawFree(failure);
+}
+
+_PyXI_errcode
+_PyXI_GetFailureCode(_PyXI_failure *failure)
+{
+ if (failure == NULL) {
+ return _PyXI_ERR_NO_ERROR;
+ }
+ return failure->code;
+}
+
+void
+_PyXI_InitFailureUTF8(_PyXI_failure *failure,
+ _PyXI_errcode code, const char *msg)
+{
+ *failure = (_PyXI_failure){
+ .code = code,
+ .msg = msg,
+ .msg_owned = 0,
+ };
+}
+
+int
+_PyXI_InitFailure(_PyXI_failure *failure, _PyXI_errcode code, PyObject *obj)
+{
+ PyObject *msgobj = PyObject_Str(obj);
+ if (msgobj == NULL) {
+ return -1;
+ }
+ // This will leak if not paired with clear_xi_failure().
+ // That happens automatically in _capture_current_exception().
+ const char *msg = _copy_string_obj_raw(msgobj, NULL);
+ Py_DECREF(msgobj);
+ if (PyErr_Occurred()) {
+ return -1;
+ }
+ *failure = (_PyXI_failure){
+ .code = code,
+ .msg = msg,
+ .msg_owned = 1,
+ };
+ return 0;
+}
+
/* shared exceptions */
-static const char *
-_PyXI_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code)
+typedef struct {
+ // The originating interpreter.
+ PyInterpreterState *interp;
+ // The error to propagate, if different from the uncaught exception.
+ _PyXI_failure *override;
+ _PyXI_failure _override;
+ // The exception information to propagate, if applicable.
+ // This is populated only for some error codes,
+ // but always for _PyXI_ERR_UNCAUGHT_EXCEPTION.
+ _PyXI_excinfo uncaught;
+} _PyXI_error;
+
+static void
+xi_error_clear(_PyXI_error *err)
{
- if (error->interp == NULL) {
- error->interp = PyInterpreterState_Get();
+ err->interp = NULL;
+ if (err->override != NULL) {
+ clear_xi_failure(err->override);
}
+ _PyXI_excinfo_clear(&err->uncaught);
+}
- const char *failure = NULL;
- if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- // There is an unhandled exception we need to propagate.
- failure = _PyXI_excinfo_InitFromException(&error->uncaught, excobj);
- if (failure != NULL) {
- // We failed to initialize error->uncaught.
- // XXX Print the excobj/traceback? Emit a warning?
- // XXX Print the current exception/traceback?
- if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
- error->code = _PyXI_ERR_NO_MEMORY;
- }
- else {
- error->code = _PyXI_ERR_OTHER;
- }
- PyErr_Clear();
+static int
+xi_error_is_set(_PyXI_error *error)
+{
+ if (error->override != NULL) {
+ assert(error->override->code != _PyXI_ERR_NO_ERROR);
+ assert(error->override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION
+ || excinfo_is_set(&error->uncaught));
+ return 1;
+ }
+ return excinfo_is_set(&error->uncaught);
+}
+
+static int
+xi_error_has_override(_PyXI_error *err)
+{
+ if (err->override == NULL) {
+ return 0;
+ }
+ return (err->override->code != _PyXI_ERR_NO_ERROR
+ && err->override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION);
+}
+
+static PyObject *
+xi_error_resolve_current_exc(PyThreadState *tstate,
+ _PyXI_failure *override)
+{
+ assert(override == NULL || override->code != _PyXI_ERR_NO_ERROR);
+
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ if (exc == NULL) {
+ assert(override == NULL
+ || override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION);
+ }
+ else if (override == NULL) {
+ // This is equivalent to _PyXI_ERR_UNCAUGHT_EXCEPTION.
+ }
+ else if (override->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ // We want to actually capture the current exception.
+ }
+ else if (exc != NULL) {
+ // It might make sense to do similarly for other codes.
+ if (override->code == _PyXI_ERR_ALREADY_RUNNING) {
+ // We don't need the exception info.
+ Py_CLEAR(exc);
+ }
+ // ...else we want to actually capture the current exception.
+ }
+ return exc;
+}
+
+static void
+xi_error_set_override(PyThreadState *tstate, _PyXI_error *err,
+ _PyXI_failure *override)
+{
+ assert(err->override == NULL);
+ assert(override != NULL);
+ assert(override->code != _PyXI_ERR_NO_ERROR);
+ // Use xi_error_set_exc() instead of setting _PyXI_ERR_UNCAUGHT_EXCEPTION..
+ assert(override->code != _PyXI_ERR_UNCAUGHT_EXCEPTION);
+ err->override = &err->_override;
+ // The caller still owns override->msg.
+ copy_xi_failure(&err->_override, override);
+ err->interp = tstate->interp;
+}
+
+static void
+xi_error_set_override_code(PyThreadState *tstate, _PyXI_error *err,
+ _PyXI_errcode code)
+{
+ _PyXI_failure override = XI_FAILURE_INIT;
+ override.code = code;
+ xi_error_set_override(tstate, err, &override);
+}
+
+static const char *
+xi_error_set_exc(PyThreadState *tstate, _PyXI_error *err, PyObject *exc)
+{
+ assert(!_PyErr_Occurred(tstate));
+ assert(!xi_error_is_set(err));
+ assert(err->override == NULL);
+ assert(err->interp == NULL);
+ assert(exc != NULL);
+ const char *failure =
+ _PyXI_excinfo_InitFromException(&err->uncaught, exc);
+ if (failure != NULL) {
+ // We failed to initialize err->uncaught.
+ // XXX Print the excobj/traceback? Emit a warning?
+ // XXX Print the current exception/traceback?
+ if (_PyErr_ExceptionMatches(tstate, PyExc_MemoryError)) {
+ xi_error_set_override_code(tstate, err, _PyXI_ERR_NO_MEMORY);
}
else {
- error->code = code;
+ xi_error_set_override_code(tstate, err, _PyXI_ERR_OTHER);
}
- assert(error->code != _PyXI_ERR_NO_ERROR);
- }
- else {
- // There is an error code we need to propagate.
- assert(excobj == NULL);
- assert(code != _PyXI_ERR_NO_ERROR);
- error->code = code;
- _PyXI_excinfo_Clear(&error->uncaught);
+ PyErr_Clear();
}
return failure;
}
-PyObject *
-_PyXI_ApplyError(_PyXI_error *error)
+static PyObject *
+_PyXI_ApplyError(_PyXI_error *error, const char *failure)
{
PyThreadState *tstate = PyThreadState_Get();
- if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- // Raise an exception that proxies the propagated exception.
+
+ if (failure != NULL) {
+ xi_error_clear(error);
+ return NULL;
+ }
+
+ _PyXI_errcode code = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ if (error->override != NULL) {
+ code = error->override->code;
+ assert(code != _PyXI_ERR_NO_ERROR);
+ }
+
+ if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ // We will raise an exception that proxies the propagated exception.
return _PyXI_excinfo_AsObject(&error->uncaught);
}
- else if (error->code == _PyXI_ERR_NOT_SHAREABLE) {
+ else if (code == _PyXI_ERR_NOT_SHAREABLE) {
// Propagate the exception directly.
assert(!_PyErr_Occurred(tstate));
- _set_xid_lookup_failure(tstate, NULL, error->uncaught.msg, NULL);
+ PyObject *cause = NULL;
+ if (excinfo_is_set(&error->uncaught)) {
+ // Maybe instead set a PyExc_ExceptionSnapshot as __cause__?
+ // That type doesn't exist currently
+ // but would look like interpreters.ExecutionFailed.
+ _PyXI_excinfo_Apply(&error->uncaught, PyExc_Exception);
+ cause = _PyErr_GetRaisedException(tstate);
+ }
+ const char *msg = error->override != NULL
+ ? error->override->msg
+ : error->uncaught.msg;
+ _set_xid_lookup_failure(tstate, NULL, msg, cause);
+ Py_XDECREF(cause);
}
else {
// Raise an exception corresponding to the code.
- assert(error->code != _PyXI_ERR_NO_ERROR);
- (void)_PyXI_ApplyErrorCode(error->code, error->interp);
- if (error->uncaught.type.name != NULL || error->uncaught.msg != NULL) {
+ (void)_PyXI_ApplyErrorCode(code, error->interp);
+ assert(error->override == NULL || error->override->msg == NULL);
+ if (excinfo_is_set(&error->uncaught)) {
// __context__ will be set to a proxy of the propagated exception.
- PyObject *exc = PyErr_GetRaisedException();
+ // (or use PyExc_ExceptionSnapshot like _PyXI_ERR_NOT_SHAREABLE?)
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
_PyXI_excinfo_Apply(&error->uncaught, PyExc_InterpreterError);
- PyObject *exc2 = PyErr_GetRaisedException();
+ PyObject *exc2 = _PyErr_GetRaisedException(tstate);
PyException_SetContext(exc, exc2);
- PyErr_SetRaisedException(exc);
+ _PyErr_SetRaisedException(tstate, exc);
}
}
assert(PyErr_Occurred());
@@ -1621,6 +2026,7 @@ typedef struct _sharednsitem {
// in a different interpreter to release the XI data.
} _PyXI_namespace_item;
+#ifndef NDEBUG
static int
_sharednsitem_is_initialized(_PyXI_namespace_item *item)
{
@@ -1629,6 +2035,7 @@ _sharednsitem_is_initialized(_PyXI_namespace_item *item)
}
return 0;
}
+#endif
static int
_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
@@ -1656,7 +2063,8 @@ _sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid)
}
static int
-_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
+_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value,
+ xidata_fallback_t fallback)
{
assert(_sharednsitem_is_initialized(item));
assert(item->xidata == NULL);
@@ -1665,7 +2073,7 @@ _sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
return -1;
}
PyThreadState *tstate = PyThreadState_Get();
- if (_PyObject_GetXIData(tstate, value, item->xidata) != 0) {
+ if (_PyObject_GetXIData(tstate, value, fallback, item->xidata) < 0) {
PyMem_RawFree(item->xidata);
item->xidata = NULL;
// The caller may want to propagate PyExc_NotShareableError
@@ -1697,7 +2105,8 @@ _sharednsitem_clear(_PyXI_namespace_item *item)
}
static int
-_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
+_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns,
+ xidata_fallback_t fallback)
{
assert(item->name != NULL);
assert(item->xidata == NULL);
@@ -1709,7 +2118,7 @@ _sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
// When applied, this item will be set to the default (or fail).
return 0;
}
- if (_sharednsitem_set_value(item, value) < 0) {
+ if (_sharednsitem_set_value(item, value, fallback) < 0) {
return -1;
}
return 0;
@@ -1739,156 +2148,212 @@ _sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
return res;
}
-struct _sharedns {
- Py_ssize_t len;
- _PyXI_namespace_item *items;
-};
-static _PyXI_namespace *
-_sharedns_new(void)
-{
- _PyXI_namespace *ns = PyMem_RawCalloc(sizeof(_PyXI_namespace), 1);
- if (ns == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- *ns = (_PyXI_namespace){ 0 };
- return ns;
-}
+typedef struct {
+ Py_ssize_t maxitems;
+ Py_ssize_t numnames;
+ Py_ssize_t numvalues;
+ _PyXI_namespace_item items[1];
+} _PyXI_namespace;
+#ifndef NDEBUG
static int
-_sharedns_is_initialized(_PyXI_namespace *ns)
+_sharedns_check_counts(_PyXI_namespace *ns)
{
- if (ns->len == 0) {
- assert(ns->items == NULL);
+ if (ns->maxitems <= 0) {
+ return 0;
+ }
+ if (ns->numnames < 0) {
+ return 0;
+ }
+ if (ns->numnames > ns->maxitems) {
+ return 0;
+ }
+ if (ns->numvalues < 0) {
+ return 0;
+ }
+ if (ns->numvalues > ns->numnames) {
return 0;
}
-
- assert(ns->len > 0);
- assert(ns->items != NULL);
- assert(_sharednsitem_is_initialized(&ns->items[0]));
- assert(ns->len == 1
- || _sharednsitem_is_initialized(&ns->items[ns->len - 1]));
return 1;
}
-#define HAS_COMPLETE_DATA 1
-#define HAS_PARTIAL_DATA 2
-
static int
-_sharedns_has_xidata(_PyXI_namespace *ns, int64_t *p_interpid)
+_sharedns_check_consistency(_PyXI_namespace *ns)
{
- // We expect _PyXI_namespace to always be initialized.
- assert(_sharedns_is_initialized(ns));
- int res = 0;
- _PyXI_namespace_item *item0 = &ns->items[0];
- if (!_sharednsitem_is_initialized(item0)) {
+ if (!_sharedns_check_counts(ns)) {
return 0;
}
- int64_t interpid0 = -1;
- if (!_sharednsitem_has_value(item0, &interpid0)) {
- return 0;
+
+ Py_ssize_t i = 0;
+ _PyXI_namespace_item *item;
+ if (ns->numvalues > 0) {
+ item = &ns->items[0];
+ if (!_sharednsitem_is_initialized(item)) {
+ return 0;
+ }
+ int64_t interpid0 = -1;
+ if (!_sharednsitem_has_value(item, &interpid0)) {
+ return 0;
+ }
+ i += 1;
+ for (; i < ns->numvalues; i++) {
+ item = &ns->items[i];
+ if (!_sharednsitem_is_initialized(item)) {
+ return 0;
+ }
+ int64_t interpid = -1;
+ if (!_sharednsitem_has_value(item, &interpid)) {
+ return 0;
+ }
+ if (interpid != interpid0) {
+ return 0;
+ }
+ }
}
- if (ns->len > 1) {
- // At this point we know it is has at least partial data.
- _PyXI_namespace_item *itemN = &ns->items[ns->len-1];
- if (!_sharednsitem_is_initialized(itemN)) {
- res = HAS_PARTIAL_DATA;
- goto finally;
+ for (; i < ns->numnames; i++) {
+ item = &ns->items[i];
+ if (!_sharednsitem_is_initialized(item)) {
+ return 0;
}
- int64_t interpidN = -1;
- if (!_sharednsitem_has_value(itemN, &interpidN)) {
- res = HAS_PARTIAL_DATA;
- goto finally;
+ if (_sharednsitem_has_value(item, NULL)) {
+ return 0;
}
- assert(interpidN == interpid0);
}
- res = HAS_COMPLETE_DATA;
- *p_interpid = interpid0;
-
-finally:
- return res;
+ for (; i < ns->maxitems; i++) {
+ item = &ns->items[i];
+ if (_sharednsitem_is_initialized(item)) {
+ return 0;
+ }
+ if (_sharednsitem_has_value(item, NULL)) {
+ return 0;
+ }
+ }
+ return 1;
}
+#endif
-static void
-_sharedns_clear(_PyXI_namespace *ns)
+static _PyXI_namespace *
+_sharedns_alloc(Py_ssize_t maxitems)
{
- if (!_sharedns_is_initialized(ns)) {
- return;
+ if (maxitems < 0) {
+ if (!PyErr_Occurred()) {
+ PyErr_BadInternalCall();
+ }
+ return NULL;
+ }
+ else if (maxitems == 0) {
+ PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
+ return NULL;
+ }
+
+ // Check for overflow.
+ size_t fixedsize = sizeof(_PyXI_namespace) - sizeof(_PyXI_namespace_item);
+ if ((size_t)maxitems >
+ ((size_t)PY_SSIZE_T_MAX - fixedsize) / sizeof(_PyXI_namespace_item))
+ {
+ PyErr_NoMemory();
+ return NULL;
}
- // If the cross-interpreter data were allocated as part of
- // _PyXI_namespace_item (instead of dynamically), this is where
- // we would need verify that we are clearing the items in the
- // correct interpreter, to avoid a race with releasing the XI data
- // via a pending call. See _sharedns_has_xidata().
- for (Py_ssize_t i=0; i < ns->len; i++) {
- _sharednsitem_clear(&ns->items[i]);
+ // Allocate the value, including items.
+ size_t size = fixedsize + sizeof(_PyXI_namespace_item) * maxitems;
+
+ _PyXI_namespace *ns = PyMem_RawCalloc(size, 1);
+ if (ns == NULL) {
+ PyErr_NoMemory();
+ return NULL;
}
- PyMem_RawFree(ns->items);
- ns->items = NULL;
- ns->len = 0;
+ ns->maxitems = maxitems;
+ assert(_sharedns_check_consistency(ns));
+ return ns;
}
static void
_sharedns_free(_PyXI_namespace *ns)
{
- _sharedns_clear(ns);
+ // If we weren't always dynamically allocating the cross-interpreter
+ // data in each item then we would need to use a pending call
+ // to call _sharedns_free(), to avoid the race between freeing
+ // the shared namespace and releasing the XI data.
+ assert(_sharedns_check_counts(ns));
+ Py_ssize_t i = 0;
+ _PyXI_namespace_item *item;
+ if (ns->numvalues > 0) {
+ // One or more items may have interpreter-specific data.
+#ifndef NDEBUG
+ int64_t interpid = PyInterpreterState_GetID(PyInterpreterState_Get());
+ int64_t interpid_i;
+#endif
+ for (; i < ns->numvalues; i++) {
+ item = &ns->items[i];
+ assert(_sharednsitem_is_initialized(item));
+ // While we do want to ensure consistency across items,
+ // technically they don't need to match the current
+ // interpreter. However, we keep the constraint for
+ // simplicity, by giving _PyXI_FreeNamespace() the exclusive
+ // responsibility of dealing with the owning interpreter.
+ assert(_sharednsitem_has_value(item, &interpid_i));
+ assert(interpid_i == interpid);
+ _sharednsitem_clear(item);
+ }
+ }
+ for (; i < ns->numnames; i++) {
+ item = &ns->items[i];
+ assert(_sharednsitem_is_initialized(item));
+ assert(!_sharednsitem_has_value(item, NULL));
+ _sharednsitem_clear(item);
+ }
+#ifndef NDEBUG
+ for (; i < ns->maxitems; i++) {
+ item = &ns->items[i];
+ assert(!_sharednsitem_is_initialized(item));
+ assert(!_sharednsitem_has_value(item, NULL));
+ }
+#endif
+
PyMem_RawFree(ns);
}
-static int
-_sharedns_init(_PyXI_namespace *ns, PyObject *names)
+static _PyXI_namespace *
+_create_sharedns(PyObject *names)
{
- assert(!_sharedns_is_initialized(ns));
assert(names != NULL);
- Py_ssize_t len = PyDict_CheckExact(names)
+ Py_ssize_t numnames = PyDict_CheckExact(names)
? PyDict_Size(names)
: PySequence_Size(names);
- if (len < 0) {
- return -1;
- }
- if (len == 0) {
- PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
- return -1;
- }
- assert(len > 0);
- // Allocate the items.
- _PyXI_namespace_item *items =
- PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
- if (items == NULL) {
- PyErr_NoMemory();
- return -1;
+ _PyXI_namespace *ns = _sharedns_alloc(numnames);
+ if (ns == NULL) {
+ return NULL;
}
+ _PyXI_namespace_item *items = ns->items;
// Fill in the names.
- Py_ssize_t i = -1;
if (PyDict_CheckExact(names)) {
+ Py_ssize_t i = 0;
Py_ssize_t pos = 0;
- for (i=0; i < len; i++) {
- PyObject *key;
- if (!PyDict_Next(names, &pos, &key, NULL)) {
- // This should not be possible.
- assert(0);
- goto error;
- }
- if (_sharednsitem_init(&items[i], key) < 0) {
+ PyObject *name;
+ while(PyDict_Next(names, &pos, &name, NULL)) {
+ if (_sharednsitem_init(&items[i], name) < 0) {
goto error;
}
+ ns->numnames += 1;
+ i += 1;
}
}
else if (PySequence_Check(names)) {
- for (i=0; i < len; i++) {
- PyObject *key = PySequence_GetItem(names, i);
- if (key == NULL) {
+ for (Py_ssize_t i = 0; i < numnames; i++) {
+ PyObject *name = PySequence_GetItem(names, i);
+ if (name == NULL) {
goto error;
}
- int res = _sharednsitem_init(&items[i], key);
- Py_DECREF(key);
+ int res = _sharednsitem_init(&items[i], name);
+ Py_DECREF(name);
if (res < 0) {
goto error;
}
+ ns->numnames += 1;
}
}
else {
@@ -1896,140 +2361,84 @@ _sharedns_init(_PyXI_namespace *ns, PyObject *names)
"non-sequence namespace not supported");
goto error;
}
-
- ns->items = items;
- ns->len = len;
- assert(_sharedns_is_initialized(ns));
- return 0;
+ assert(ns->numnames == ns->maxitems);
+ return ns;
error:
- for (Py_ssize_t j=0; j < i; j++) {
- _sharednsitem_clear(&items[j]);
- }
- PyMem_RawFree(items);
- assert(!_sharedns_is_initialized(ns));
- return -1;
+ _sharedns_free(ns);
+ return NULL;
}
-void
-_PyXI_FreeNamespace(_PyXI_namespace *ns)
-{
- if (!_sharedns_is_initialized(ns)) {
- return;
- }
+static void _propagate_not_shareable_error(PyThreadState *,
+ _PyXI_failure *);
- int64_t interpid = -1;
- if (!_sharedns_has_xidata(ns, &interpid)) {
- _sharedns_free(ns);
- return;
- }
-
- if (interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) {
- _sharedns_free(ns);
- }
- else {
- // If we weren't always dynamically allocating the cross-interpreter
- // data in each item then we would need to using a pending call
- // to call _sharedns_free(), to avoid the race between freeing
- // the shared namespace and releasing the XI data.
- _sharedns_free(ns);
- }
-}
-
-_PyXI_namespace *
-_PyXI_NamespaceFromNames(PyObject *names)
+static int
+_fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj,
+ xidata_fallback_t fallback, _PyXI_failure *p_err)
{
- if (names == NULL || names == Py_None) {
- return NULL;
- }
-
- _PyXI_namespace *ns = _sharedns_new();
- if (ns == NULL) {
- return NULL;
- }
-
- if (_sharedns_init(ns, names) < 0) {
- PyMem_RawFree(ns);
- if (PySequence_Size(names) == 0) {
- PyErr_Clear();
- }
- return NULL;
- }
-
- return ns;
-}
-
-#ifndef NDEBUG
-static int _session_is_active(_PyXI_session *);
-#endif
-static void _propagate_not_shareable_error(_PyXI_session *);
-
-int
-_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj,
- _PyXI_session *session)
-{
- // session must be entered already, if provided.
- assert(session == NULL || _session_is_active(session));
- assert(_sharedns_is_initialized(ns));
- for (Py_ssize_t i=0; i < ns->len; i++) {
- _PyXI_namespace_item *item = &ns->items[i];
- if (_sharednsitem_copy_from_ns(item, nsobj) < 0) {
- _propagate_not_shareable_error(session);
+ // All items are expected to be shareable.
+ assert(_sharedns_check_counts(ns));
+ assert(ns->numnames == ns->maxitems);
+ assert(ns->numvalues == 0);
+ PyThreadState *tstate = PyThreadState_Get();
+ for (Py_ssize_t i=0; i < ns->maxitems; i++) {
+ if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj, fallback) < 0) {
+ if (p_err != NULL) {
+ _propagate_not_shareable_error(tstate, p_err);
+ }
// Clear out the ones we set so far.
for (Py_ssize_t j=0; j < i; j++) {
_sharednsitem_clear_value(&ns->items[j]);
+ ns->numvalues -= 1;
}
return -1;
}
+ ns->numvalues += 1;
}
return 0;
}
-// All items are expected to be shareable.
-static _PyXI_namespace *
-_PyXI_NamespaceFromDict(PyObject *nsobj, _PyXI_session *session)
+static int
+_sharedns_free_pending(void *data)
{
- // session must be entered already, if provided.
- assert(session == NULL || _session_is_active(session));
- if (nsobj == NULL || nsobj == Py_None) {
- return NULL;
- }
- if (!PyDict_CheckExact(nsobj)) {
- PyErr_SetString(PyExc_TypeError, "expected a dict");
- return NULL;
- }
+ _sharedns_free((_PyXI_namespace *)data);
+ return 0;
+}
- _PyXI_namespace *ns = _sharedns_new();
- if (ns == NULL) {
- return NULL;
+static void
+_destroy_sharedns(_PyXI_namespace *ns)
+{
+ assert(_sharedns_check_counts(ns));
+ assert(ns->numnames == ns->maxitems);
+ if (ns->numvalues == 0) {
+ _sharedns_free(ns);
+ return;
}
- if (_sharedns_init(ns, nsobj) < 0) {
- if (PyDict_Size(nsobj) == 0) {
- PyMem_RawFree(ns);
- PyErr_Clear();
- return NULL;
- }
- goto error;
+ int64_t interpid0;
+ if (!_sharednsitem_has_value(&ns->items[0], &interpid0)) {
+ // This shouldn't have been possible.
+ // We can deal with it in _sharedns_free().
+ _sharedns_free(ns);
+ return;
}
-
- if (_PyXI_FillNamespaceFromDict(ns, nsobj, session) < 0) {
- goto error;
+ PyInterpreterState *interp = _PyInterpreterState_LookUpID(interpid0);
+ if (interp == PyInterpreterState_Get()) {
+ _sharedns_free(ns);
+ return;
}
- return ns;
-
-error:
- assert(PyErr_Occurred()
- || (session != NULL && session->error_override != NULL));
- _sharedns_free(ns);
- return NULL;
+ // One or more items may have interpreter-specific data.
+ // Currently the xidata for each value is dynamically allocated,
+ // so technically we don't need to worry about that.
+ // However, explicitly adding a pending call here is simpler.
+ (void)_Py_CallInInterpreter(interp, _sharedns_free_pending, ns);
}
-int
-_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
+static int
+_apply_sharedns(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
{
- for (Py_ssize_t i=0; i < ns->len; i++) {
+ for (Py_ssize_t i=0; i < ns->maxitems; i++) {
if (_sharednsitem_apply(&ns->items[i], nsobj, dflt) != 0) {
return -1;
}
@@ -2038,9 +2447,69 @@ _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
}
-/**********************/
-/* high-level helpers */
-/**********************/
+/*********************************/
+/* switched-interpreter sessions */
+/*********************************/
+
+struct xi_session {
+#define SESSION_UNUSED 0
+#define SESSION_ACTIVE 1
+ int status;
+ int switched;
+
+ // Once a session has been entered, this is the tstate that was
+ // current before the session. If it is different from cur_tstate
+ // then we must have switched interpreters. Either way, this will
+ // be the current tstate once we exit the session.
+ PyThreadState *prev_tstate;
+ // Once a session has been entered, this is the current tstate.
+ // It must be current when the session exits.
+ PyThreadState *init_tstate;
+ // This is true if init_tstate needs cleanup during exit.
+ int own_init_tstate;
+
+ // This is true if, while entering the session, init_thread took
+ // "ownership" of the interpreter's __main__ module. This means
+ // it is the only thread that is allowed to run code there.
+ // (Caveat: for now, users may still run exec() against the
+ // __main__ module's dict, though that isn't advisable.)
+ int running;
+ // This is a cached reference to the __dict__ of the entered
+ // interpreter's __main__ module. It is looked up when at the
+ // beginning of the session as a convenience.
+ PyObject *main_ns;
+
+ // This is a dict of objects that will be available (via sharing)
+ // once the session exits. Do not access this directly; use
+ // _PyXI_Preserve() and _PyXI_GetPreserved() instead;
+ PyObject *_preserved;
+};
+
+_PyXI_session *
+_PyXI_NewSession(void)
+{
+ _PyXI_session *session = PyMem_RawCalloc(1, sizeof(_PyXI_session));
+ if (session == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ return session;
+}
+
+void
+_PyXI_FreeSession(_PyXI_session *session)
+{
+ assert(session->status == SESSION_UNUSED);
+ PyMem_RawFree(session);
+}
+
+
+static inline int
+_session_is_active(_PyXI_session *session)
+{
+ return session->status == SESSION_ACTIVE;
+}
+
/* enter/exit a cross-interpreter session */
@@ -2048,29 +2517,33 @@ static void
_enter_session(_PyXI_session *session, PyInterpreterState *interp)
{
// Set here and cleared in _exit_session().
+ assert(session->status == SESSION_UNUSED);
assert(!session->own_init_tstate);
assert(session->init_tstate == NULL);
assert(session->prev_tstate == NULL);
// Set elsewhere and cleared in _exit_session().
assert(!session->running);
assert(session->main_ns == NULL);
- // Set elsewhere and cleared in _capture_current_exception().
- assert(session->error_override == NULL);
- // Set elsewhere and cleared in _PyXI_ApplyCapturedException().
- assert(session->error == NULL);
// Switch to interpreter.
PyThreadState *tstate = PyThreadState_Get();
PyThreadState *prev = tstate;
- if (interp != tstate->interp) {
+ int same_interp = (interp == tstate->interp);
+ if (!same_interp) {
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_EXEC);
// XXX Possible GILState issues?
- session->prev_tstate = PyThreadState_Swap(tstate);
- assert(session->prev_tstate == prev);
- session->own_init_tstate = 1;
+ PyThreadState *swapped = PyThreadState_Swap(tstate);
+ assert(swapped == prev);
+ (void)swapped;
}
- session->init_tstate = tstate;
- session->prev_tstate = prev;
+
+ *session = (_PyXI_session){
+ .status = SESSION_ACTIVE,
+ .switched = !same_interp,
+ .init_tstate = tstate,
+ .prev_tstate = prev,
+ .own_init_tstate = !same_interp,
+ };
}
static void
@@ -2079,16 +2552,16 @@ _exit_session(_PyXI_session *session)
PyThreadState *tstate = session->init_tstate;
assert(tstate != NULL);
assert(PyThreadState_Get() == tstate);
+ assert(!_PyErr_Occurred(tstate));
// Release any of the entered interpreters resources.
- if (session->main_ns != NULL) {
- Py_CLEAR(session->main_ns);
- }
+ Py_CLEAR(session->main_ns);
+ Py_CLEAR(session->_preserved);
// Ensure this thread no longer owns __main__.
if (session->running) {
_PyInterpreterState_SetNotRunningMain(tstate->interp);
- assert(!PyErr_Occurred());
+ assert(!_PyErr_Occurred(tstate));
session->running = 0;
}
@@ -2104,25 +2577,15 @@ _exit_session(_PyXI_session *session)
else {
assert(!session->own_init_tstate);
}
- session->prev_tstate = NULL;
- session->init_tstate = NULL;
-}
-#ifndef NDEBUG
-static int
-_session_is_active(_PyXI_session *session)
-{
- return (session->init_tstate != NULL);
+ *session = (_PyXI_session){0};
}
-#endif
static void
-_propagate_not_shareable_error(_PyXI_session *session)
+_propagate_not_shareable_error(PyThreadState *tstate,
+ _PyXI_failure *override)
{
- if (session == NULL) {
- return;
- }
- PyThreadState *tstate = PyThreadState_Get();
+ assert(override != NULL);
PyObject *exctype = get_notshareableerror_type(tstate);
if (exctype == NULL) {
PyErr_FormatUnraisable(
@@ -2131,166 +2594,463 @@ _propagate_not_shareable_error(_PyXI_session *session)
}
if (PyErr_ExceptionMatches(exctype)) {
// We want to propagate the exception directly.
- session->_error_override = _PyXI_ERR_NOT_SHAREABLE;
- session->error_override = &session->_error_override;
+ *override = (_PyXI_failure){
+ .code = _PyXI_ERR_NOT_SHAREABLE,
+ };
}
}
-static void
-_capture_current_exception(_PyXI_session *session)
+
+static int _ensure_main_ns(_PyXI_session *, _PyXI_failure *);
+static const char * capture_session_error(_PyXI_session *, _PyXI_error *,
+ _PyXI_failure *);
+
+int
+_PyXI_Enter(_PyXI_session *session,
+ PyInterpreterState *interp, PyObject *nsupdates,
+ _PyXI_session_result *result)
{
- assert(session->error == NULL);
- if (!PyErr_Occurred()) {
- assert(session->error_override == NULL);
- return;
+#ifndef NDEBUG
+ PyThreadState *tstate = _PyThreadState_GET(); // Only used for asserts
+#endif
+
+ // Convert the attrs for cross-interpreter use.
+ _PyXI_namespace *sharedns = NULL;
+ if (nsupdates != NULL) {
+ assert(PyDict_Check(nsupdates));
+ Py_ssize_t len = PyDict_Size(nsupdates);
+ if (len < 0) {
+ if (result != NULL) {
+ result->errcode = _PyXI_ERR_APPLY_NS_FAILURE;
+ }
+ return -1;
+ }
+ if (len > 0) {
+ sharedns = _create_sharedns(nsupdates);
+ if (sharedns == NULL) {
+ if (result != NULL) {
+ result->errcode = _PyXI_ERR_APPLY_NS_FAILURE;
+ }
+ return -1;
+ }
+ // For now we limit it to shareable objects.
+ xidata_fallback_t fallback = _PyXIDATA_XIDATA_ONLY;
+ _PyXI_failure _err = XI_FAILURE_INIT;
+ if (_fill_sharedns(sharedns, nsupdates, fallback, &_err) < 0) {
+ assert(_PyErr_Occurred(tstate));
+ if (_err.code == _PyXI_ERR_NO_ERROR) {
+ _err.code = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ }
+ _destroy_sharedns(sharedns);
+ if (result != NULL) {
+ assert(_err.msg == NULL);
+ result->errcode = _err.code;
+ }
+ return -1;
+ }
+ }
}
- // Handle the exception override.
- _PyXI_errcode *override = session->error_override;
- session->error_override = NULL;
- _PyXI_errcode errcode = override != NULL
- ? *override
- : _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ // Switch to the requested interpreter (if necessary).
+ _enter_session(session, interp);
+ _PyXI_failure override = XI_FAILURE_INIT;
+ override.code = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+#ifndef NDEBUG
+ tstate = _PyThreadState_GET();
+#endif
- // Pop the exception object.
- PyObject *excval = NULL;
- if (errcode == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- // We want to actually capture the current exception.
- excval = PyErr_GetRaisedException();
+ // Ensure this thread owns __main__.
+ if (_PyInterpreterState_SetRunningMain(interp) < 0) {
+ // In the case where we didn't switch interpreters, it would
+ // be more efficient to leave the exception in place and return
+ // immediately. However, life is simpler if we don't.
+ override.code = _PyXI_ERR_ALREADY_RUNNING;
+ goto error;
}
- else if (errcode == _PyXI_ERR_ALREADY_RUNNING) {
- // We don't need the exception info.
- PyErr_Clear();
+ session->running = 1;
+
+ // Apply the cross-interpreter data.
+ if (sharedns != NULL) {
+ if (_ensure_main_ns(session, &override) < 0) {
+ goto error;
+ }
+ if (_apply_sharedns(sharedns, session->main_ns, NULL) < 0) {
+ override.code = _PyXI_ERR_APPLY_NS_FAILURE;
+ goto error;
+ }
+ _destroy_sharedns(sharedns);
}
- else {
- // We could do a variety of things here, depending on errcode.
- // However, for now we simply capture the exception and save
- // the errcode.
- excval = PyErr_GetRaisedException();
+
+ override.code = _PyXI_ERR_NO_ERROR;
+ assert(!_PyErr_Occurred(tstate));
+ return 0;
+
+error:
+ // We want to propagate all exceptions here directly (best effort).
+ assert(override.code != _PyXI_ERR_NO_ERROR);
+ _PyXI_error err = {0};
+ const char *failure = capture_session_error(session, &err, &override);
+
+ // Exit the session.
+ _exit_session(session);
+#ifndef NDEBUG
+ tstate = _PyThreadState_GET();
+#endif
+
+ if (sharedns != NULL) {
+ _destroy_sharedns(sharedns);
}
- // Capture the exception.
- _PyXI_error *err = &session->_error;
- *err = (_PyXI_error){
- .interp = session->init_tstate->interp,
- };
- const char *failure;
- if (excval == NULL) {
- failure = _PyXI_InitError(err, NULL, errcode);
+ // Apply the error from the other interpreter.
+ PyObject *excinfo = _PyXI_ApplyError(&err, failure);
+ xi_error_clear(&err);
+ if (excinfo != NULL) {
+ if (result != NULL) {
+ result->excinfo = excinfo;
+ }
+ else {
+#ifdef Py_DEBUG
+ fprintf(stderr, "_PyXI_Enter(): uncaught exception discarded");
+#endif
+ Py_DECREF(excinfo);
+ }
+ }
+ assert(_PyErr_Occurred(tstate));
+
+ return -1;
+}
+
+static int _pop_preserved(_PyXI_session *, _PyXI_namespace **, PyObject **,
+ _PyXI_failure *);
+static int _finish_preserved(_PyXI_namespace *, PyObject **);
+
+int
+_PyXI_Exit(_PyXI_session *session, _PyXI_failure *override,
+ _PyXI_session_result *result)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ int res = 0;
+
+ // Capture the raised exception, if any.
+ _PyXI_error err = {0};
+ const char *failure = NULL;
+ if (override != NULL && override->code == _PyXI_ERR_NO_ERROR) {
+ assert(override->msg == NULL);
+ override = NULL;
+ }
+ if (_PyErr_Occurred(tstate)) {
+ failure = capture_session_error(session, &err, override);
}
else {
- failure = _PyXI_InitError(err, excval, _PyXI_ERR_UNCAUGHT_EXCEPTION);
- Py_DECREF(excval);
- if (failure == NULL && override != NULL) {
- err->code = errcode;
- }
+ assert(override == NULL);
}
- // Handle capture failure.
- if (failure != NULL) {
- // XXX Make this error message more generic.
- fprintf(stderr,
- "RunFailedError: script raised an uncaught exception (%s)",
- failure);
- err = NULL;
+ // Capture the preserved namespace.
+ _PyXI_namespace *preserved = NULL;
+ PyObject *preservedobj = NULL;
+ if (result != NULL) {
+ assert(!_PyErr_Occurred(tstate));
+ _PyXI_failure _override = XI_FAILURE_INIT;
+ if (_pop_preserved(
+ session, &preserved, &preservedobj, &_override) < 0)
+ {
+ assert(preserved == NULL);
+ assert(preservedobj == NULL);
+ if (xi_error_is_set(&err)) {
+ // XXX Chain the exception (i.e. set __context__)?
+ PyErr_FormatUnraisable(
+ "Exception ignored while capturing preserved objects");
+ clear_xi_failure(&_override);
+ }
+ else {
+ if (_override.code == _PyXI_ERR_NO_ERROR) {
+ _override.code = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ }
+ failure = capture_session_error(session, &err, &_override);
+ }
+ }
}
- // Finished!
- assert(!PyErr_Occurred());
- session->error = err;
-}
+ // Exit the session.
+ assert(!_PyErr_Occurred(tstate));
+ _exit_session(session);
+ tstate = _PyThreadState_GET();
+
+ // Restore the preserved namespace.
+ assert(preserved == NULL || preservedobj == NULL);
+ if (_finish_preserved(preserved, &preservedobj) < 0) {
+ assert(preservedobj == NULL);
+ if (xi_error_is_set(&err)) {
+ // XXX Chain the exception (i.e. set __context__)?
+ PyErr_FormatUnraisable(
+ "Exception ignored while capturing preserved objects");
+ }
+ else {
+ xi_error_set_override_code(
+ tstate, &err, _PyXI_ERR_PRESERVE_FAILURE);
+ _propagate_not_shareable_error(tstate, err.override);
+ }
+ }
+ if (result != NULL) {
+ result->preserved = preservedobj;
+ result->errcode = err.override != NULL
+ ? err.override->code
+ : _PyXI_ERR_NO_ERROR;
+ }
-PyObject *
-_PyXI_ApplyCapturedException(_PyXI_session *session)
-{
- assert(!PyErr_Occurred());
- assert(session->error != NULL);
- PyObject *res = _PyXI_ApplyError(session->error);
- assert((res == NULL) != (PyErr_Occurred() == NULL));
- session->error = NULL;
+ // Apply the error from the other interpreter, if any.
+ if (xi_error_is_set(&err)) {
+ res = -1;
+ assert(!_PyErr_Occurred(tstate));
+ PyObject *excinfo = _PyXI_ApplyError(&err, failure);
+ if (excinfo == NULL) {
+ assert(_PyErr_Occurred(tstate));
+ if (result != NULL && !xi_error_has_override(&err)) {
+ _PyXI_ClearResult(result);
+ *result = (_PyXI_session_result){
+ .errcode = _PyXI_ERR_EXC_PROPAGATION_FAILURE,
+ };
+ }
+ }
+ else if (result != NULL) {
+ result->excinfo = excinfo;
+ }
+ else {
+#ifdef Py_DEBUG
+ fprintf(stderr, "_PyXI_Exit(): uncaught exception discarded");
+#endif
+ Py_DECREF(excinfo);
+ }
+ xi_error_clear(&err);
+ }
return res;
}
-int
-_PyXI_HasCapturedException(_PyXI_session *session)
-{
- return session->error != NULL;
-}
-int
-_PyXI_Enter(_PyXI_session *session,
- PyInterpreterState *interp, PyObject *nsupdates)
+/* in an active cross-interpreter session */
+
+static const char *
+capture_session_error(_PyXI_session *session, _PyXI_error *err,
+ _PyXI_failure *override)
{
- // Convert the attrs for cross-interpreter use.
- _PyXI_namespace *sharedns = NULL;
- if (nsupdates != NULL) {
- sharedns = _PyXI_NamespaceFromDict(nsupdates, NULL);
- if (sharedns == NULL && PyErr_Occurred()) {
- assert(session->error == NULL);
- return -1;
+ assert(_session_is_active(session));
+ assert(!xi_error_is_set(err));
+ PyThreadState *tstate = session->init_tstate;
+
+ // Normalize the exception override.
+ if (override != NULL) {
+ if (override->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ assert(override->msg == NULL);
+ override = NULL;
+ }
+ else {
+ assert(override->code != _PyXI_ERR_NO_ERROR);
}
}
- // Switch to the requested interpreter (if necessary).
- _enter_session(session, interp);
- PyThreadState *session_tstate = session->init_tstate;
- _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ // Handle the exception, if any.
+ const char *failure = NULL;
+ PyObject *exc = xi_error_resolve_current_exc(tstate, override);
+ if (exc != NULL) {
+ // There is an unhandled exception we need to preserve.
+ failure = xi_error_set_exc(tstate, err, exc);
+ Py_DECREF(exc);
+ if (_PyErr_Occurred(tstate)) {
+ PyErr_FormatUnraisable(failure);
+ }
+ }
- // Ensure this thread owns __main__.
- if (_PyInterpreterState_SetRunningMain(interp) < 0) {
- // In the case where we didn't switch interpreters, it would
- // be more efficient to leave the exception in place and return
- // immediately. However, life is simpler if we don't.
- errcode = _PyXI_ERR_ALREADY_RUNNING;
- goto error;
+ // Handle the override.
+ if (override != NULL && failure == NULL) {
+ xi_error_set_override(tstate, err, override);
}
- session->running = 1;
+ // Finished!
+ assert(!_PyErr_Occurred(tstate));
+ return failure;
+}
+
+static int
+_ensure_main_ns(_PyXI_session *session, _PyXI_failure *failure)
+{
+ assert(_session_is_active(session));
+ PyThreadState *tstate = session->init_tstate;
+ if (session->main_ns != NULL) {
+ return 0;
+ }
// Cache __main__.__dict__.
- PyObject *main_mod = _Py_GetMainModule(session_tstate);
+ PyObject *main_mod = _Py_GetMainModule(tstate);
if (_Py_CheckMainModule(main_mod) < 0) {
- errcode = _PyXI_ERR_MAIN_NS_FAILURE;
- goto error;
+ Py_XDECREF(main_mod);
+ if (failure != NULL) {
+ *failure = (_PyXI_failure){
+ .code = _PyXI_ERR_MAIN_NS_FAILURE,
+ };
+ }
+ return -1;
}
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
Py_DECREF(main_mod);
if (ns == NULL) {
- errcode = _PyXI_ERR_MAIN_NS_FAILURE;
- goto error;
+ if (failure != NULL) {
+ *failure = (_PyXI_failure){
+ .code = _PyXI_ERR_MAIN_NS_FAILURE,
+ };
+ }
+ return -1;
}
session->main_ns = Py_NewRef(ns);
+ return 0;
+}
- // Apply the cross-interpreter data.
- if (sharedns != NULL) {
- if (_PyXI_ApplyNamespace(sharedns, ns, NULL) < 0) {
- errcode = _PyXI_ERR_APPLY_NS_FAILURE;
+PyObject *
+_PyXI_GetMainNamespace(_PyXI_session *session, _PyXI_failure *failure)
+{
+ if (!_session_is_active(session)) {
+ PyErr_SetString(PyExc_RuntimeError, "session not active");
+ return NULL;
+ }
+ if (_ensure_main_ns(session, failure) < 0) {
+ return NULL;
+ }
+ return session->main_ns;
+}
+
+
+static int
+_pop_preserved(_PyXI_session *session,
+ _PyXI_namespace **p_xidata, PyObject **p_obj,
+ _PyXI_failure *p_failure)
+{
+ _PyXI_failure failure = XI_FAILURE_INIT;
+ _PyXI_namespace *xidata = NULL;
+ assert(_PyThreadState_GET() == session->init_tstate); // active session
+
+ if (session->_preserved == NULL) {
+ *p_xidata = NULL;
+ *p_obj = NULL;
+ return 0;
+ }
+ if (session->init_tstate == session->prev_tstate) {
+ // We did not switch interpreters.
+ *p_xidata = NULL;
+ *p_obj = session->_preserved;
+ session->_preserved = NULL;
+ return 0;
+ }
+ *p_obj = NULL;
+
+ // We did switch interpreters.
+ Py_ssize_t len = PyDict_Size(session->_preserved);
+ if (len < 0) {
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
+ goto error;
+ }
+ else if (len == 0) {
+ *p_xidata = NULL;
+ }
+ else {
+ _PyXI_namespace *xidata = _create_sharedns(session->_preserved);
+ if (xidata == NULL) {
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
goto error;
}
- _PyXI_FreeNamespace(sharedns);
+ if (_fill_sharedns(xidata, session->_preserved,
+ _PyXIDATA_FULL_FALLBACK, &failure) < 0)
+ {
+ if (failure.code != _PyXI_ERR_NOT_SHAREABLE) {
+ assert(failure.msg != NULL);
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
+ }
+ goto error;
+ }
+ *p_xidata = xidata;
}
+ Py_CLEAR(session->_preserved);
+ return 0;
- errcode = _PyXI_ERR_NO_ERROR;
- assert(!PyErr_Occurred());
+error:
+ if (p_failure != NULL) {
+ *p_failure = failure;
+ }
+ if (xidata != NULL) {
+ _destroy_sharedns(xidata);
+ }
+ return -1;
+}
+
+static int
+_finish_preserved(_PyXI_namespace *xidata, PyObject **p_preserved)
+{
+ if (xidata == NULL) {
+ return 0;
+ }
+ int res = -1;
+ if (p_preserved != NULL) {
+ PyObject *ns = PyDict_New();
+ if (ns == NULL) {
+ goto finally;
+ }
+ if (_apply_sharedns(xidata, ns, NULL) < 0) {
+ Py_CLEAR(ns);
+ goto finally;
+ }
+ *p_preserved = ns;
+ }
+ res = 0;
+
+finally:
+ _destroy_sharedns(xidata);
+ return res;
+}
+
+int
+_PyXI_Preserve(_PyXI_session *session, const char *name, PyObject *value,
+ _PyXI_failure *p_failure)
+{
+ _PyXI_failure failure = XI_FAILURE_INIT;
+ if (!_session_is_active(session)) {
+ PyErr_SetString(PyExc_RuntimeError, "session not active");
+ return -1;
+ }
+ if (session->_preserved == NULL) {
+ session->_preserved = PyDict_New();
+ if (session->_preserved == NULL) {
+ set_exc_with_cause(PyExc_RuntimeError,
+ "failed to initialize preserved objects");
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
+ goto error;
+ }
+ }
+ if (PyDict_SetItemString(session->_preserved, name, value) < 0) {
+ set_exc_with_cause(PyExc_RuntimeError, "failed to preserve object");
+ failure.code = _PyXI_ERR_PRESERVE_FAILURE;
+ goto error;
+ }
return 0;
error:
- assert(PyErr_Occurred());
- // We want to propagate all exceptions here directly (best effort).
- assert(errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION);
- session->error_override = &errcode;
- _capture_current_exception(session);
- _exit_session(session);
- if (sharedns != NULL) {
- _PyXI_FreeNamespace(sharedns);
+ if (p_failure != NULL) {
+ *p_failure = failure;
}
return -1;
}
+PyObject *
+_PyXI_GetPreserved(_PyXI_session_result *result, const char *name)
+{
+ PyObject *value = NULL;
+ if (result->preserved != NULL) {
+ (void)PyDict_GetItemStringRef(result->preserved, name, &value);
+ }
+ return value;
+}
+
void
-_PyXI_Exit(_PyXI_session *session)
+_PyXI_ClearResult(_PyXI_session_result *result)
{
- _capture_current_exception(session);
- _exit_session(session);
+ Py_CLEAR(result->preserved);
+ Py_CLEAR(result->excinfo);
}
diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h
index efef1e06d82..c3c76ae8d9a 100644
--- a/Python/crossinterp_data_lookup.h
+++ b/Python/crossinterp_data_lookup.h
@@ -12,7 +12,8 @@ typedef _PyXIData_regitem_t dlregitem_t;
// forward
static void _xidregistry_init(dlregistry_t *);
static void _xidregistry_fini(dlregistry_t *);
-static xidatafunc _lookup_getdata_from_registry(dlcontext_t *, PyObject *);
+static _PyXIData_getdata_t _lookup_getdata_from_registry(
+ dlcontext_t *, PyObject *);
/* used in crossinterp.c */
@@ -49,7 +50,7 @@ get_lookup_context(PyThreadState *tstate, dlcontext_t *res)
return 0;
}
-static xidatafunc
+static _PyXIData_getdata_t
lookup_getdata(dlcontext_t *ctx, PyObject *obj)
{
/* Cross-interpreter objects are looked up by exact match on the class.
@@ -87,25 +88,52 @@ _PyXIData_FormatNotShareableError(PyThreadState *tstate,
va_end(vargs);
}
+int
+_PyXI_UnwrapNotShareableError(PyThreadState * tstate, _PyXI_failure *failure)
+{
+ PyObject *exctype = get_notshareableerror_type(tstate);
+ assert(exctype != NULL);
+ if (!_PyErr_ExceptionMatches(tstate, exctype)) {
+ return -1;
+ }
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ if (failure != NULL) {
+ _PyXI_errcode code = _PyXI_ERR_NOT_SHAREABLE;
+ if (_PyXI_InitFailure(failure, code, exc) < 0) {
+ return -1;
+ }
+ }
+ PyObject *cause = PyException_GetCause(exc);
+ if (cause != NULL) {
+ Py_DECREF(exc);
+ exc = cause;
+ }
+ else {
+ assert(PyException_GetContext(exc) == NULL);
+ }
+ _PyErr_SetRaisedException(tstate, exc);
+ return 0;
+}
-xidatafunc
+
+_PyXIData_getdata_t
_PyXIData_Lookup(PyThreadState *tstate, PyObject *obj)
{
dlcontext_t ctx;
if (get_lookup_context(tstate, &ctx) < 0) {
- return NULL;
+ return (_PyXIData_getdata_t){0};
}
return lookup_getdata(&ctx, obj);
}
/***********************************************/
-/* a registry of {type -> xidatafunc} */
+/* a registry of {type -> _PyXIData_getdata_t} */
/***********************************************/
-/* For now we use a global registry of shareable classes. An
- alternative would be to add a tp_* slot for a class's
- xidatafunc. It would be simpler and more efficient. */
+/* For now we use a global registry of shareable classes.
+ An alternative would be to add a tp_* slot for a class's
+ _PyXIData_getdata_t. It would be simpler and more efficient. */
/* registry lifecycle */
@@ -200,7 +228,7 @@ _xidregistry_find_type(dlregistry_t *xidregistry, PyTypeObject *cls)
return NULL;
}
-static xidatafunc
+static _PyXIData_getdata_t
_lookup_getdata_from_registry(dlcontext_t *ctx, PyObject *obj)
{
PyTypeObject *cls = Py_TYPE(obj);
@@ -209,10 +237,12 @@ _lookup_getdata_from_registry(dlcontext_t *ctx, PyObject *obj)
_xidregistry_lock(xidregistry);
dlregitem_t *matched = _xidregistry_find_type(xidregistry, cls);
- xidatafunc func = matched != NULL ? matched->getdata : NULL;
+ _PyXIData_getdata_t getdata = matched != NULL
+ ? matched->getdata
+ : (_PyXIData_getdata_t){0};
_xidregistry_unlock(xidregistry);
- return func;
+ return getdata;
}
@@ -220,12 +250,13 @@ _lookup_getdata_from_registry(dlcontext_t *ctx, PyObject *obj)
static int
_xidregistry_add_type(dlregistry_t *xidregistry,
- PyTypeObject *cls, xidatafunc getdata)
+ PyTypeObject *cls, _PyXIData_getdata_t getdata)
{
dlregitem_t *newhead = PyMem_RawMalloc(sizeof(dlregitem_t));
if (newhead == NULL) {
return -1;
}
+ assert((getdata.basic == NULL) != (getdata.fallback == NULL));
*newhead = (dlregitem_t){
// We do not keep a reference, to avoid keeping the class alive.
.cls = cls,
@@ -283,13 +314,13 @@ _xidregistry_clear(dlregistry_t *xidregistry)
int
_PyXIData_RegisterClass(PyThreadState *tstate,
- PyTypeObject *cls, xidatafunc getdata)
+ PyTypeObject *cls, _PyXIData_getdata_t getdata)
{
if (!PyType_Check(cls)) {
PyErr_Format(PyExc_ValueError, "only classes may be registered");
return -1;
}
- if (getdata == NULL) {
+ if (getdata.basic == NULL && getdata.fallback == NULL) {
PyErr_Format(PyExc_ValueError, "missing 'getdata' func");
return -1;
}
@@ -304,7 +335,8 @@ _PyXIData_RegisterClass(PyThreadState *tstate,
dlregitem_t *matched = _xidregistry_find_type(xidregistry, cls);
if (matched != NULL) {
- assert(matched->getdata == getdata);
+ assert(matched->getdata.basic == getdata.basic);
+ assert(matched->getdata.fallback == getdata.fallback);
matched->refcount += 1;
goto finally;
}
@@ -608,7 +640,8 @@ _tuple_shared_free(void* data)
}
static int
-_tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
+_tuple_shared(PyThreadState *tstate, PyObject *obj, xidata_fallback_t fallback,
+ _PyXIData_t *xidata)
{
Py_ssize_t len = PyTuple_GET_SIZE(obj);
if (len < 0) {
@@ -636,7 +669,7 @@ _tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
int res = -1;
if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) {
- res = _PyObject_GetXIData(tstate, item, xidata_i);
+ res = _PyObject_GetXIData(tstate, item, fallback, xidata_i);
_Py_LeaveRecursiveCallTstate(tstate);
}
if (res < 0) {
@@ -654,43 +687,149 @@ error:
return -1;
}
+// code
+
+PyObject *
+_PyCode_FromXIData(_PyXIData_t *xidata)
+{
+ return _PyMarshal_ReadObjectFromXIData(xidata);
+}
+
+int
+_PyCode_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
+{
+ if (!PyCode_Check(obj)) {
+ _PyXIData_FormatNotShareableError(tstate, "expected code, got %R", obj);
+ return -1;
+ }
+ if (_PyMarshal_GetXIData(tstate, obj, xidata) < 0) {
+ return -1;
+ }
+ assert(_PyXIData_CHECK_NEW_OBJECT(xidata, _PyMarshal_ReadObjectFromXIData));
+ _PyXIData_SET_NEW_OBJECT(xidata, _PyCode_FromXIData);
+ return 0;
+}
+
+// function
+
+PyObject *
+_PyFunction_FromXIData(_PyXIData_t *xidata)
+{
+ // For now "stateless" functions are the only ones we must accommodate.
+
+ PyObject *code = _PyMarshal_ReadObjectFromXIData(xidata);
+ if (code == NULL) {
+ return NULL;
+ }
+ // Create a new function.
+ // For stateless functions (no globals) we use __main__ as __globals__,
+ // just like we do for builtins like exec().
+ assert(PyCode_Check(code));
+ PyThreadState *tstate = _PyThreadState_GET();
+ PyObject *globals = _PyEval_GetGlobalsFromRunningMain(tstate); // borrowed
+ if (globals == NULL) {
+ if (_PyErr_Occurred(tstate)) {
+ Py_DECREF(code);
+ return NULL;
+ }
+ globals = PyDict_New();
+ if (globals == NULL) {
+ Py_DECREF(code);
+ return NULL;
+ }
+ }
+ else {
+ Py_INCREF(globals);
+ }
+ if (_PyEval_EnsureBuiltins(tstate, globals, NULL) < 0) {
+ Py_DECREF(code);
+ Py_DECREF(globals);
+ return NULL;
+ }
+ PyObject *func = PyFunction_New(code, globals);
+ Py_DECREF(code);
+ Py_DECREF(globals);
+ return func;
+}
+
+int
+_PyFunction_GetXIData(PyThreadState *tstate, PyObject *func,
+ _PyXIData_t *xidata)
+{
+ if (!PyFunction_Check(func)) {
+ const char *msg = "expected a function, got %R";
+ format_notshareableerror(tstate, NULL, 0, msg, func);
+ return -1;
+ }
+ if (_PyFunction_VerifyStateless(tstate, func) < 0) {
+ PyObject *cause = _PyErr_GetRaisedException(tstate);
+ assert(cause != NULL);
+ const char *msg = "only stateless functions are shareable";
+ set_notshareableerror(tstate, cause, 0, msg);
+ Py_DECREF(cause);
+ return -1;
+ }
+ PyObject *code = PyFunction_GET_CODE(func);
+
+ // Ideally code objects would be immortal and directly shareable.
+ // In the meantime, we use marshal.
+ if (_PyMarshal_GetXIData(tstate, code, xidata) < 0) {
+ return -1;
+ }
+ // Replace _PyMarshal_ReadObjectFromXIData.
+ // (_PyFunction_FromXIData() will call it.)
+ _PyXIData_SET_NEW_OBJECT(xidata, _PyFunction_FromXIData);
+ return 0;
+}
+
+
// registration
static void
_register_builtins_for_crossinterpreter_data(dlregistry_t *xidregistry)
{
+#define REGISTER(TYPE, GETDATA) \
+ _xidregistry_add_type(xidregistry, (PyTypeObject *)TYPE, \
+ ((_PyXIData_getdata_t){.basic=(GETDATA)}))
+#define REGISTER_FALLBACK(TYPE, GETDATA) \
+ _xidregistry_add_type(xidregistry, (PyTypeObject *)TYPE, \
+ ((_PyXIData_getdata_t){.fallback=(GETDATA)}))
// None
- if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) {
+ if (REGISTER(Py_TYPE(Py_None), _none_shared) != 0) {
Py_FatalError("could not register None for cross-interpreter sharing");
}
// int
- if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) {
+ if (REGISTER(&PyLong_Type, _long_shared) != 0) {
Py_FatalError("could not register int for cross-interpreter sharing");
}
// bytes
- if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _PyBytes_GetXIData) != 0) {
+ if (REGISTER(&PyBytes_Type, _PyBytes_GetXIData) != 0) {
Py_FatalError("could not register bytes for cross-interpreter sharing");
}
// str
- if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) {
+ if (REGISTER(&PyUnicode_Type, _str_shared) != 0) {
Py_FatalError("could not register str for cross-interpreter sharing");
}
// bool
- if (_xidregistry_add_type(xidregistry, &PyBool_Type, _bool_shared) != 0) {
+ if (REGISTER(&PyBool_Type, _bool_shared) != 0) {
Py_FatalError("could not register bool for cross-interpreter sharing");
}
// float
- if (_xidregistry_add_type(xidregistry, &PyFloat_Type, _float_shared) != 0) {
+ if (REGISTER(&PyFloat_Type, _float_shared) != 0) {
Py_FatalError("could not register float for cross-interpreter sharing");
}
// tuple
- if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) {
+ if (REGISTER_FALLBACK(&PyTuple_Type, _tuple_shared) != 0) {
Py_FatalError("could not register tuple for cross-interpreter sharing");
}
+
+ // For now, we do not register PyCode_Type or PyFunction_Type.
+#undef REGISTER
+#undef REGISTER_FALLBACK
}
diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h
index ca4ca1cf123..98411adc5eb 100644
--- a/Python/crossinterp_exceptions.h
+++ b/Python/crossinterp_exceptions.h
@@ -7,13 +7,6 @@ _ensure_current_cause(PyThreadState *tstate, PyObject *cause)
}
PyObject *exc = _PyErr_GetRaisedException(tstate);
assert(exc != NULL);
- PyObject *ctx = PyException_GetContext(exc);
- if (ctx == NULL) {
- PyException_SetContext(exc, Py_NewRef(cause));
- }
- else {
- Py_DECREF(ctx);
- }
assert(PyException_GetCause(exc) == NULL);
PyException_SetCause(exc, Py_NewRef(cause));
_PyErr_SetRaisedException(tstate, exc);
@@ -24,7 +17,7 @@ _ensure_current_cause(PyThreadState *tstate, PyObject *cause)
static PyTypeObject _PyExc_InterpreterError = {
PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "interpreters.InterpreterError",
+ .tp_name = "concurrent.interpreters.InterpreterError",
.tp_doc = PyDoc_STR("A cross-interpreter operation failed"),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
@@ -37,7 +30,7 @@ PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError;
static PyTypeObject _PyExc_InterpreterNotFoundError = {
PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "interpreters.InterpreterNotFoundError",
+ .tp_name = "concurrent.interpreters.InterpreterNotFoundError",
.tp_doc = PyDoc_STR("An interpreter was not found"),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
@@ -51,7 +44,7 @@ PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFou
static int
_init_notshareableerror(exceptions_t *state)
{
- const char *name = "interpreters.NotShareableError";
+ const char *name = "concurrent.interpreters.NotShareableError";
PyObject *base = PyExc_TypeError;
PyObject *ns = NULL;
PyObject *exctype = PyErr_NewException(name, base, ns);
diff --git a/Python/dynload_win.c b/Python/dynload_win.c
index 6324063401e..de9b0a77817 100644
--- a/Python/dynload_win.c
+++ b/Python/dynload_win.c
@@ -108,7 +108,7 @@ static char *GetPythonImport (HINSTANCE hModule)
char *pch;
/* Don't claim that python3.dll is a Python DLL. */
-#ifdef _DEBUG
+#ifdef Py_DEBUG
if (strcmp(import_name, "python3_d.dll") == 0) {
#else
if (strcmp(import_name, "python3.dll") == 0) {
@@ -120,7 +120,7 @@ static char *GetPythonImport (HINSTANCE hModule)
/* Ensure python prefix is followed only
by numbers to the end of the basename */
pch = import_name + 6;
-#ifdef _DEBUG
+#ifdef Py_DEBUG
while (*pch && pch[0] != '_' && pch[1] != 'd' && pch[2] != '.') {
#else
while (*pch && *pch != '.') {
@@ -300,7 +300,7 @@ dl_funcptr _PyImport_FindSharedFuncptrWindows(const char *prefix,
char buffer[256];
PyOS_snprintf(buffer, sizeof(buffer),
-#ifdef _DEBUG
+#ifdef Py_DEBUG
"python%d%d_d.dll",
#else
"python%d%d.dll",
diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c
index a7bb685bf3d..75b98a04723 100644
--- a/Python/emscripten_trampoline.c
+++ b/Python/emscripten_trampoline.c
@@ -35,7 +35,7 @@ EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), {
// (type $type1 (func (param i32) (result i32)))
// (type $type2 (func (param i32 i32) (result i32)))
// (type $type3 (func (param i32 i32 i32) (result i32)))
-// (type $blocktype (func (param i32) (result)))
+// (type $blocktype (func (param) (result)))
// (table $funcs (import "e" "t") 0 funcref)
// (export "f" (func $f))
// (func $f (param $fptr i32) (result i32)
@@ -44,42 +44,43 @@ EM_JS(CountArgsFunc, _PyEM_GetCountArgsPtr, (), {
// table.get $funcs
// local.tee $fref
// ref.test $type3
-// (block $b (type $blocktype)
-// i32.eqz
-// br_if $b
+// if $blocktype
// i32.const 3
// return
-// )
+// end
// local.get $fref
// ref.test $type2
-// (block $b (type $blocktype)
-// i32.eqz
-// br_if $b
+// if $blocktype
// i32.const 2
// return
-// )
+// end
// local.get $fref
// ref.test $type1
-// (block $b (type $blocktype)
-// i32.eqz
-// br_if $b
+// if $blocktype
// i32.const 1
// return
-// )
+// end
// local.get $fref
// ref.test $type0
-// (block $b (type $blocktype)
-// i32.eqz
-// br_if $b
+// if $blocktype
// i32.const 0
// return
-// )
+// end
// i32.const -1
// )
// )
function getPyEMCountArgsPtr() {
- let isIOS = globalThis.navigator && /iPad|iPhone|iPod/.test(navigator.platform);
+ // Starting with iOS 18.3.1, WebKit on iOS has an issue with the garbage
+ // collector that breaks the call trampoline. See #130418 and
+ // https://bugs.webkit.org/show_bug.cgi?id=293113 for details.
+ let isIOS = globalThis.navigator && (
+ /iPad|iPhone|iPod/.test(navigator.userAgent) ||
+ // Starting with iPadOS 13, iPads might send a platform string that looks like a desktop Mac.
+ // To differentiate, we check if the platform is 'MacIntel' (common for Macs and newer iPads)
+ // AND if the device has multi-touch capabilities (navigator.maxTouchPoints > 1)
+ (navigator.platform === 'MacIntel' && typeof navigator.maxTouchPoints !== 'undefined' && navigator.maxTouchPoints > 1)
+ );
if (isIOS) {
return 0;
}
@@ -88,13 +89,13 @@ function getPyEMCountArgsPtr() {
const code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, // \0asm magic number
0x01, 0x00, 0x00, 0x00, // version 1
- 0x01, 0x1b, // Type section, body is 0x1b bytes
+ 0x01, 0x1a, // Type section, body is 0x1a bytes
0x05, // 6 entries
- 0x60, 0x00, 0x01, 0x7f, // (type $type0 (func (param) (result i32)))
- 0x60, 0x01, 0x7f, 0x01, 0x7f, // (type $type1 (func (param i32) (result i32)))
- 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, // (type $type2 (func (param i32 i32) (result i32)))
- 0x60, 0x03, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, // (type $type3 (func (param i32 i32 i32) (result i32)))
- 0x60, 0x01, 0x7f, 0x00, // (type $blocktype (func (param i32) (result)))
+ 0x60, 0x00, 0x01, 0x7f, // (type $type0 (func (param) (result i32)))
+ 0x60, 0x01, 0x7f, 0x01, 0x7f, // (type $type1 (func (param i32) (result i32)))
+ 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, // (type $type2 (func (param i32 i32) (result i32)))
+ 0x60, 0x03, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, // (type $type3 (func (param i32 i32 i32) (result i32)))
+ 0x60, 0x00, 0x00, // (type $blocktype (func (param) (result)))
0x02, 0x09, // Import section, 0x9 byte body
0x01, // 1 import (table $funcs (import "e" "t") 0 funcref)
0x01, 0x65, // "e"
@@ -110,44 +111,36 @@ function getPyEMCountArgsPtr() {
0x00, // a function
0x00, // at index 0
- 0x0a, 0x44, // Code section,
- 0x01, 0x42, // one entry of length 50
+ 0x0a, 56, // Code section,
+ 0x01, 54, // one entry of length 54
0x01, 0x01, 0x70, // one local of type funcref
// Body of the function
0x20, 0x00, // local.get $fptr
0x25, 0x00, // table.get $funcs
0x22, 0x01, // local.tee $fref
0xfb, 0x14, 0x03, // ref.test $type3
- 0x02, 0x04, // block $b (type $blocktype)
- 0x45, // i32.eqz
- 0x0d, 0x00, // br_if $b
+ 0x04, 0x04, // if (type $blocktype)
0x41, 0x03, // i32.const 3
0x0f, // return
0x0b, // end block
0x20, 0x01, // local.get $fref
0xfb, 0x14, 0x02, // ref.test $type2
- 0x02, 0x04, // block $b (type $blocktype)
- 0x45, // i32.eqz
- 0x0d, 0x00, // br_if $b
+ 0x04, 0x04, // if (type $blocktype)
0x41, 0x02, // i32.const 2
0x0f, // return
0x0b, // end block
0x20, 0x01, // local.get $fref
0xfb, 0x14, 0x01, // ref.test $type1
- 0x02, 0x04, // block $b (type $blocktype)
- 0x45, // i32.eqz
- 0x0d, 0x00, // br_if $b
+ 0x04, 0x04, // if (type $blocktype)
0x41, 0x01, // i32.const 1
0x0f, // return
0x0b, // end block
0x20, 0x01, // local.get $fref
0xfb, 0x14, 0x00, // ref.test $type0
- 0x02, 0x04, // block $b (type $blocktype)
- 0x45, // i32.eqz
- 0x0d, 0x00, // br_if $b
+ 0x04, 0x04, // if (type $blocktype)
0x41, 0x00, // i32.const 0
0x0f, // return
0x0b, // end block
diff --git a/Python/errors.c b/Python/errors.c
index 81f267b043a..a3122f76bdd 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -10,7 +10,6 @@
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_structseq.h" // _PyStructSequence_FiniBuiltin()
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
#include "pycore_traceback.h" // _PyTraceBack_FromFrame()
#include "pycore_unicodeobject.h" // _PyUnicode_Equal()
@@ -1570,7 +1569,7 @@ write_unraisable_exc(PyThreadState *tstate, PyObject *exc_type,
PyObject *obj)
{
PyObject *file;
- if (_PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
return -1;
}
if (file == NULL || file == Py_None) {
@@ -1677,7 +1676,7 @@ format_unraisable_v(const char *format, va_list va, PyObject *obj)
}
PyObject *hook;
- if (_PySys_GetOptionalAttr(&_Py_ID(unraisablehook), &hook) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(unraisablehook), &hook) < 0) {
Py_DECREF(hook_args);
err_msg_str = NULL;
obj = NULL;
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 70f092e4c6f..276c320c5f4 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -319,25 +319,11 @@
break;
}
- /* _LOAD_CONST is not a viable micro-op for tier 2 because it uses the 'this_instr' variable */
-
- case _LOAD_CONST_MORTAL: {
- _PyStackRef value;
- oparg = CURRENT_OPARG();
- PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- value = PyStackRef_FromPyObjectNewMortal(obj);
- stack_pointer[0] = value;
- stack_pointer += 1;
- assert(WITHIN_STACK_BOUNDS());
- break;
- }
-
- case _LOAD_CONST_IMMORTAL: {
+ case _LOAD_CONST: {
_PyStackRef value;
oparg = CURRENT_OPARG();
PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- assert(_Py_IsImmortal(obj));
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -350,7 +336,7 @@
assert(oparg == CURRENT_OPARG());
assert(oparg < _PY_NSMALLPOSINTS);
PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -363,7 +349,7 @@
assert(oparg == CURRENT_OPARG());
assert(oparg < _PY_NSMALLPOSINTS);
PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -376,7 +362,7 @@
assert(oparg == CURRENT_OPARG());
assert(oparg < _PY_NSMALLPOSINTS);
PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -389,7 +375,7 @@
assert(oparg == CURRENT_OPARG());
assert(oparg < _PY_NSMALLPOSINTS);
PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -401,7 +387,7 @@
oparg = CURRENT_OPARG();
assert(oparg < _PY_NSMALLPOSINTS);
PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -413,10 +399,6 @@
oparg = 0;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -432,10 +414,6 @@
oparg = 1;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -451,10 +429,6 @@
oparg = 2;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -470,10 +444,6 @@
oparg = 3;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -489,10 +459,6 @@
oparg = 4;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -508,10 +474,6 @@
oparg = 5;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -527,10 +489,6 @@
oparg = 6;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -546,10 +504,6 @@
oparg = 7;
assert(oparg == CURRENT_OPARG());
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -564,10 +518,6 @@
_PyStackRef value;
oparg = CURRENT_OPARG();
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -584,7 +534,65 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(value);
+ PyStackRef_XCLOSE(value);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ break;
+ }
+
+ case _POP_TOP_NOP: {
+ _PyStackRef value;
+ value = stack_pointer[-1];
+ assert(PyStackRef_IsNull(value) || (!PyStackRef_RefcountOnObject(value)) ||
+ _Py_IsImmortal((PyStackRef_AsPyObjectBorrow(value))));
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TOP_INT: {
+ _PyStackRef value;
+ value = stack_pointer[-1];
+ assert(PyLong_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
+ PyStackRef_CLOSE_SPECIALIZED(value, _PyLong_ExactDealloc);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TOP_FLOAT: {
+ _PyStackRef value;
+ value = stack_pointer[-1];
+ assert(PyFloat_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
+ PyStackRef_CLOSE_SPECIALIZED(value, _PyFloat_ExactDealloc);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TOP_UNICODE: {
+ _PyStackRef value;
+ value = stack_pointer[-1];
+ assert(PyUnicode_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
+ PyStackRef_CLOSE_SPECIALIZED(value, _PyUnicode_ExactDealloc);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TWO: {
+ _PyStackRef tos;
+ _PyStackRef nos;
+ tos = stack_pointer[-1];
+ nos = stack_pointer[-2];
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(tos);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(nos);
stack_pointer = _PyFrame_GetStackPointer(frame);
break;
}
@@ -609,6 +617,20 @@
break;
}
+ case _POP_ITER: {
+ _PyStackRef index_or_null;
+ _PyStackRef iter;
+ index_or_null = stack_pointer[-1];
+ iter = stack_pointer[-2];
+ (void)index_or_null;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(iter);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ break;
+ }
+
case _END_SEND: {
_PyStackRef value;
_PyStackRef receiver;
@@ -870,7 +892,7 @@
_PyStackRef left;
left = stack_pointer[-2];
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
- if (!PyLong_CheckExact(left_o)) {
+ if (!_PyLong_CheckExactAndCompact(left_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
@@ -881,7 +903,31 @@
_PyStackRef value;
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ break;
+ }
+
+ case _GUARD_NOS_OVERFLOWED: {
+ _PyStackRef left;
+ left = stack_pointer[-2];
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ assert(Py_TYPE(left_o) == &PyLong_Type);
+ if (!_PyLong_IsCompact((PyLongObject *)left_o)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ break;
+ }
+
+ case _GUARD_TOS_OVERFLOWED: {
+ _PyStackRef value;
+ value = stack_pointer[-1];
+ PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
+ assert(Py_TYPE(value_o) == &PyLong_Type);
+ if (!_PyLong_IsCompact((PyLongObject *)value_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
@@ -898,18 +944,15 @@
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = _PyCompactLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
- if (res_o == NULL) {
- stack_pointer += -2;
- assert(WITHIN_STACK_BOUNDS());
- JUMP_TO_ERROR();
- }
- res = PyStackRef_FromPyObjectSteal(res_o);
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -926,18 +969,15 @@
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = _PyCompactLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
- if (res_o == NULL) {
- stack_pointer += -2;
- assert(WITHIN_STACK_BOUNDS());
- JUMP_TO_ERROR();
- }
- res = PyStackRef_FromPyObjectSteal(res_o);
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -954,18 +994,15 @@
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
- if (res_o == NULL) {
- stack_pointer += -2;
- assert(WITHIN_STACK_BOUNDS());
- JUMP_TO_ERROR();
- }
- res = PyStackRef_FromPyObjectSteal(res_o);
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -1075,6 +1112,87 @@
break;
}
+ case _BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS: {
+ _PyStackRef right;
+ _PyStackRef left;
+ _PyStackRef res;
+ right = stack_pointer[-1];
+ left = stack_pointer[-2];
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval *
+ ((PyFloatObject *)right_o)->ob_fval;
+ res = PyStackRef_FromPyObjectSteal(PyFloat_FromDouble(dres));
+ if (PyStackRef_IsNull(res)) {
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ JUMP_TO_ERROR();
+ }
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS: {
+ _PyStackRef right;
+ _PyStackRef left;
+ _PyStackRef res;
+ right = stack_pointer[-1];
+ left = stack_pointer[-2];
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval +
+ ((PyFloatObject *)right_o)->ob_fval;
+ res = PyStackRef_FromPyObjectSteal(PyFloat_FromDouble(dres));
+ if (PyStackRef_IsNull(res)) {
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ JUMP_TO_ERROR();
+ }
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS: {
+ _PyStackRef right;
+ _PyStackRef left;
+ _PyStackRef res;
+ right = stack_pointer[-1];
+ left = stack_pointer[-2];
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval -
+ ((PyFloatObject *)right_o)->ob_fval;
+ res = PyStackRef_FromPyObjectSteal(PyFloat_FromDouble(dres));
+ if (PyStackRef_IsNull(res)) {
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ JUMP_TO_ERROR();
+ }
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
case _BINARY_OP_ADD_UNICODE: {
_PyStackRef right;
_PyStackRef left;
@@ -1403,7 +1521,7 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(str_st);
stack_pointer = _PyFrame_GetStackPointer(frame);
- res = PyStackRef_FromPyObjectImmortal(res_o);
+ res = PyStackRef_FromPyObjectBorrow(res_o);
stack_pointer[0] = res;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -1569,15 +1687,16 @@
_PyStackRef getitem;
_PyStackRef sub;
_PyStackRef container;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
getitem = stack_pointer[-1];
sub = stack_pointer[-2];
container = stack_pointer[-3];
- new_frame = _PyFrame_PushUnchecked(tstate, getitem, 2, frame);
- new_frame->localsplus[0] = container;
- new_frame->localsplus[1] = sub;
+ _PyInterpreterFrame* pushed_frame = _PyFrame_PushUnchecked(tstate, getitem, 2, frame);
+ pushed_frame->localsplus[0] = container;
+ pushed_frame->localsplus[1] = sub;
frame->return_offset = 6 ;
- stack_pointer[-3].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-3] = new_frame;
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -1925,7 +2044,7 @@
case _SEND_GEN_FRAME: {
_PyStackRef v;
_PyStackRef receiver;
- _PyInterpreterFrame *gen_frame;
+ _PyStackRef gen_frame;
oparg = CURRENT_OPARG();
v = stack_pointer[-1];
receiver = stack_pointer[-2];
@@ -1939,15 +2058,16 @@
JUMP_TO_JUMP_TARGET();
}
STAT_INC(SEND, hit);
- gen_frame = &gen->gi_iframe;
- _PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
+ _PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
+ _PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert( 2 + oparg <= UINT16_MAX);
frame->return_offset = (uint16_t)( 2 + oparg);
- gen_frame->previous = frame;
- stack_pointer[-1].bits = (uintptr_t)gen_frame;
+ pushed_frame->previous = frame;
+ gen_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-1] = gen_frame;
break;
}
@@ -3181,20 +3301,20 @@
case _LOAD_ATTR: {
_PyStackRef owner;
- _PyStackRef attr;
+ _PyStackRef *attr;
_PyStackRef *self_or_null;
oparg = CURRENT_OPARG();
owner = stack_pointer[-1];
+ attr = &stack_pointer[-1];
self_or_null = &stack_pointer[0];
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
- PyObject *attr_o;
if (oparg & 1) {
- attr_o = NULL;
+ *attr = PyStackRef_NULL;
_PyFrame_SetStackPointer(frame, stack_pointer);
- int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
+ int is_meth = _PyObject_GetMethodStackRef(tstate, PyStackRef_AsPyObjectBorrow(owner), name, attr);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (is_meth) {
- assert(attr_o != NULL);
+ assert(!PyStackRef_IsNull(*attr));
self_or_null[0] = owner;
}
else {
@@ -3203,7 +3323,7 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(owner);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (attr_o == NULL) {
+ if (PyStackRef_IsNull(*attr)) {
JUMP_TO_ERROR();
}
self_or_null[0] = PyStackRef_NULL;
@@ -3212,7 +3332,7 @@
}
else {
_PyFrame_SetStackPointer(frame, stack_pointer);
- attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+ PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
stack_pointer = _PyFrame_GetStackPointer(frame);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -3222,10 +3342,9 @@
if (attr_o == NULL) {
JUMP_TO_ERROR();
}
+ *attr = PyStackRef_FromPyObjectSteal(attr_o);
stack_pointer += 1;
}
- attr = PyStackRef_FromPyObjectSteal(attr_o);
- stack_pointer[-1] = attr;
stack_pointer += (oparg&1);
assert(WITHIN_STACK_BOUNDS());
break;
@@ -3489,7 +3608,7 @@
case _LOAD_ATTR_PROPERTY_FRAME: {
_PyStackRef owner;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = CURRENT_OPARG();
owner = stack_pointer[-1];
PyObject *fget = (PyObject *)CURRENT_OPERAND0();
@@ -3514,9 +3633,10 @@
JUMP_TO_JUMP_TARGET();
}
STAT_INC(LOAD_ATTR, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
- new_frame->localsplus[0] = owner;
- stack_pointer[-1].bits = (uintptr_t)new_frame;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
+ pushed_frame->localsplus[0] = owner;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-1] = new_frame;
break;
}
@@ -3735,14 +3855,8 @@
left = stack_pointer[-2];
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
- if (!_PyLong_IsCompact((PyLongObject *)left_o)) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- if (!_PyLong_IsCompact((PyLongObject *)right_o)) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
+ assert(_PyLong_IsCompact((PyLongObject *)left_o));
+ assert(_PyLong_IsCompact((PyLongObject *)right_o));
STAT_INC(COMPARE_OP, hit);
assert(_PyLong_DigitCount((PyLongObject *)left_o) <= 1 &&
_PyLong_DigitCount((PyLongObject *)right_o) <= 1);
@@ -4204,25 +4318,37 @@
case _GET_ITER: {
_PyStackRef iterable;
_PyStackRef iter;
+ _PyStackRef index_or_null;
iterable = stack_pointer[-1];
#ifdef Py_STATS
_PyFrame_SetStackPointer(frame, stack_pointer);
_Py_GatherStats_GetIter(iterable);
stack_pointer = _PyFrame_GetStackPointer(frame);
#endif
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
- stack_pointer = _PyFrame_GetStackPointer(frame);
- stack_pointer += -1;
- assert(WITHIN_STACK_BOUNDS());
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(iterable);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (iter_o == NULL) {
- JUMP_TO_ERROR();
+
+ PyTypeObject *tp = PyStackRef_TYPE(iterable);
+ if (tp == &PyTuple_Type || tp == &PyList_Type) {
+ iter = iterable;
+ index_or_null = PyStackRef_TagInt(0);
}
- iter = PyStackRef_FromPyObjectSteal(iter_o);
- stack_pointer[0] = iter;
+ else {
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(iterable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (iter_o == NULL) {
+ JUMP_TO_ERROR();
+ }
+ iter = PyStackRef_FromPyObjectSteal(iter_o);
+ index_or_null = PyStackRef_NULL;
+ stack_pointer += 1;
+ }
+ stack_pointer[-1] = iter;
+ stack_pointer[0] = index_or_null;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -4269,32 +4395,25 @@
/* _FOR_ITER is not a viable micro-op for tier 2 because it is replaced */
case _FOR_ITER_TIER_TWO: {
+ _PyStackRef null_or_index;
_PyStackRef iter;
_PyStackRef next;
- iter = stack_pointer[-1];
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
_PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
+ _PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (next_o == NULL) {
- if (_PyErr_Occurred(tstate)) {
- _PyFrame_SetStackPointer(frame, stack_pointer);
- int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (!matches) {
- JUMP_TO_ERROR();
- }
- _PyFrame_SetStackPointer(frame, stack_pointer);
- _PyEval_MonitorRaise(tstate, frame, frame->instr_ptr);
- _PyErr_Clear(tstate);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (!PyStackRef_IsValid(item)) {
+ if (PyStackRef_IsError(item)) {
+ JUMP_TO_ERROR();
}
if (true) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
}
- next = PyStackRef_FromPyObjectSteal(next_o);
+ next = item;
+ stack_pointer[-1] = null_or_index;
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -4304,21 +4423,18 @@
/* _INSTRUMENTED_FOR_ITER is not a viable micro-op for tier 2 because it is instrumented */
case _ITER_CHECK_LIST: {
+ _PyStackRef null_or_index;
_PyStackRef iter;
- iter = stack_pointer[-1];
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- if (Py_TYPE(iter_o) != &PyListIter_Type) {
+ if (Py_TYPE(iter_o) != &PyList_Type) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
+ assert(PyStackRef_IsTaggedInt(null_or_index));
#ifdef Py_GIL_DISABLED
- if (!_PyObject_IsUniquelyReferenced(iter_o)) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- if (!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) ||
- !_PyObject_GC_IS_SHARED(it->it_seq)) {
+ if (!_Py_IsOwnedByCurrentThread(iter_o) && !_PyObject_GC_IS_SHARED(iter_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
@@ -4329,24 +4445,17 @@
/* _ITER_JUMP_LIST is not a viable micro-op for tier 2 because it is replaced */
case _GUARD_NOT_EXHAUSTED_LIST: {
+ _PyStackRef null_or_index;
_PyStackRef iter;
- iter = stack_pointer[-1];
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
#ifndef Py_GIL_DISABLED
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
- PyListObject *seq = it->it_seq;
- if (seq == NULL) {
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(list_o) == &PyList_Type);
+ if ((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyList_GET_SIZE(list_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
- if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) {
- it->it_index = -1;
- if (1) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- }
#endif
break;
}
@@ -4354,38 +4463,30 @@
/* _ITER_NEXT_LIST is not a viable micro-op for tier 2 because it is replaced */
case _ITER_NEXT_LIST_TIER_TWO: {
+ _PyStackRef null_or_index;
_PyStackRef iter;
_PyStackRef next;
- iter = stack_pointer[-1];
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
- PyListObject *seq = it->it_seq;
- assert(seq);
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(PyList_CheckExact(list_o));
#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
- _PyObject_GC_IS_SHARED(seq));
+ assert(_Py_IsOwnedByCurrentThread((PyObject *)list_o) ||
+ _PyObject_GC_IS_SHARED(list_o));
STAT_INC(FOR_ITER, hit);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ int result = _PyList_GetItemRefNoLock((PyListObject *)list_o, PyStackRef_UntagInt(null_or_index), &next);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (result < 0) {
+ if (result <= 0) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
- if (result == 0) {
- it->it_index = -1;
- if (1) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- }
- it->it_index++;
#else
- assert(it->it_index < PyList_GET_SIZE(seq));
- next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+ assert(PyStackRef_UntagInt(null_or_index) < PyList_GET_SIZE(list_o));
+ next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(list_o, PyStackRef_UntagInt(null_or_index)));
#endif
+ null_or_index = PyStackRef_IncrementTaggedIntNoOverflow(null_or_index);
+ stack_pointer[-1] = null_or_index;
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -4393,39 +4494,29 @@
}
case _ITER_CHECK_TUPLE: {
+ _PyStackRef null_or_index;
_PyStackRef iter;
- iter = stack_pointer[-1];
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- if (Py_TYPE(iter_o) != &PyTupleIter_Type) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- #ifdef Py_GIL_DISABLED
- if (!_PyObject_IsUniquelyReferenced(iter_o)) {
+ if (Py_TYPE(iter_o) != &PyTuple_Type) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
- #endif
+ assert(PyStackRef_IsTaggedInt(null_or_index));
break;
}
/* _ITER_JUMP_TUPLE is not a viable micro-op for tier 2 because it is replaced */
case _GUARD_NOT_EXHAUSTED_TUPLE: {
+ _PyStackRef null_or_index;
_PyStackRef iter;
- iter = stack_pointer[-1];
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
- #ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- #endif
- PyTupleObject *seq = it->it_seq;
- if (seq == NULL) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- if (it->it_index >= PyTuple_GET_SIZE(seq)) {
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
+ PyObject *tuple_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(tuple_o) == &PyTuple_Type);
+ if ((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyTuple_GET_SIZE(tuple_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
@@ -4433,19 +4524,18 @@
}
case _ITER_NEXT_TUPLE: {
+ _PyStackRef null_or_index;
_PyStackRef iter;
_PyStackRef next;
- iter = stack_pointer[-1];
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
- PyTupleObject *seq = it->it_seq;
- #ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- #endif
- assert(seq);
- assert(it->it_index < PyTuple_GET_SIZE(seq));
- next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq, it->it_index++));
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
+ PyObject *tuple_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(tuple_o) == &PyTuple_Type);
+ uintptr_t i = PyStackRef_UntagInt(null_or_index);
+ assert((size_t)i < (size_t)PyTuple_GET_SIZE(tuple_o));
+ next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(tuple_o, i));
+ null_or_index = PyStackRef_IncrementTaggedIntNoOverflow(null_or_index);
+ stack_pointer[-1] = null_or_index;
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -4454,7 +4544,7 @@
case _ITER_CHECK_RANGE: {
_PyStackRef iter;
- iter = stack_pointer[-1];
+ iter = stack_pointer[-2];
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
if (Py_TYPE(r) != &PyRangeIter_Type) {
UOP_STAT_INC(uopcode, miss);
@@ -4473,7 +4563,7 @@
case _GUARD_NOT_EXHAUSTED_RANGE: {
_PyStackRef iter;
- iter = stack_pointer[-1];
+ iter = stack_pointer[-2];
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
if (r->len <= 0) {
@@ -4486,7 +4576,7 @@
case _ITER_NEXT_RANGE: {
_PyStackRef iter;
_PyStackRef next;
- iter = stack_pointer[-1];
+ iter = stack_pointer[-2];
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
assert(Py_TYPE(r) == &PyRangeIter_Type);
#ifdef Py_GIL_DISABLED
@@ -4509,9 +4599,9 @@
case _FOR_ITER_GEN_FRAME: {
_PyStackRef iter;
- _PyInterpreterFrame *gen_frame;
+ _PyStackRef gen_frame;
oparg = CURRENT_OPARG();
- iter = stack_pointer[-1];
+ iter = stack_pointer[-2];
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
if (Py_TYPE(gen) != &PyGen_Type) {
UOP_STAT_INC(uopcode, miss);
@@ -4529,14 +4619,15 @@
JUMP_TO_JUMP_TARGET();
}
STAT_INC(FOR_ITER, hit);
- gen_frame = &gen->gi_iframe;
- _PyFrame_StackPush(gen_frame, PyStackRef_None);
+ _PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
+ _PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
- gen_frame->previous = frame;
+ pushed_frame->previous = frame;
frame->return_offset = (uint16_t)( 2 + oparg);
- stack_pointer[0].bits = (uintptr_t)gen_frame;
+ gen_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[0] = gen_frame;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -4817,7 +4908,7 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = CURRENT_OPARG();
args = &stack_pointer[-oparg];
self_or_null = stack_pointer[-1 - oparg];
@@ -4842,8 +4933,8 @@
if (temp == NULL) {
JUMP_TO_ERROR();
}
- new_frame = temp;
- stack_pointer[0].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(temp);
+ stack_pointer[0] = new_frame;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -5109,7 +5200,7 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = 0;
assert(oparg == CURRENT_OPARG());
args = &stack_pointer[-oparg];
@@ -5117,13 +5208,14 @@
callable = stack_pointer[-2 - oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
- stack_pointer[-2 - oparg].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -5133,7 +5225,7 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = 1;
assert(oparg == CURRENT_OPARG());
args = &stack_pointer[-oparg];
@@ -5141,13 +5233,14 @@
callable = stack_pointer[-2 - oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
- stack_pointer[-2 - oparg].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -5157,7 +5250,7 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = 2;
assert(oparg == CURRENT_OPARG());
args = &stack_pointer[-oparg];
@@ -5165,13 +5258,14 @@
callable = stack_pointer[-2 - oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
- stack_pointer[-2 - oparg].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -5181,7 +5275,7 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = 3;
assert(oparg == CURRENT_OPARG());
args = &stack_pointer[-oparg];
@@ -5189,13 +5283,14 @@
callable = stack_pointer[-2 - oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
- stack_pointer[-2 - oparg].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -5205,7 +5300,7 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = 4;
assert(oparg == CURRENT_OPARG());
args = &stack_pointer[-oparg];
@@ -5213,13 +5308,14 @@
callable = stack_pointer[-2 - oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
- stack_pointer[-2 - oparg].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -5229,34 +5325,35 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = CURRENT_OPARG();
args = &stack_pointer[-oparg];
self_or_null = stack_pointer[-1 - oparg];
callable = stack_pointer[-2 - oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
- stack_pointer[-2 - oparg].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(pushed_frame);
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _PUSH_FRAME: {
- _PyInterpreterFrame *new_frame;
- new_frame = (_PyInterpreterFrame *)stack_pointer[-1].bits;
+ _PyStackRef new_frame;
+ new_frame = stack_pointer[-1];
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -5276,6 +5373,27 @@
break;
}
+ case _GUARD_NOS_NOT_NULL: {
+ _PyStackRef nos;
+ nos = stack_pointer[-2];
+ PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
+ if (o == NULL) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ break;
+ }
+
+ case _GUARD_THIRD_NULL: {
+ _PyStackRef null;
+ null = stack_pointer[-3];
+ if (!PyStackRef_IsNull(null)) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
+ break;
+ }
+
case _GUARD_CALLABLE_TYPE_1: {
_PyStackRef callable;
callable = stack_pointer[-3];
@@ -5450,7 +5568,7 @@
_PyStackRef *args;
_PyStackRef self;
_PyStackRef init;
- _PyInterpreterFrame *init_frame;
+ _PyStackRef init_frame;
oparg = CURRENT_OPARG();
args = &stack_pointer[-oparg];
self = stack_pointer[-1 - oparg];
@@ -5474,10 +5592,10 @@
stack_pointer = _PyFrame_GetStackPointer(frame);
JUMP_TO_ERROR();
}
- init_frame = temp;
frame->return_offset = 1 + INLINE_CACHE_ENTRIES_CALL;
tstate->py_recursion_remaining--;
- stack_pointer[0].bits = (uintptr_t)init_frame;
+ init_frame = PyStackRef_Wrap(temp);
+ stack_pointer[0] = init_frame;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -5804,35 +5922,31 @@
break;
}
- case _CALL_LEN: {
- _PyStackRef *args;
- _PyStackRef self_or_null;
+ case _GUARD_CALLABLE_LEN: {
_PyStackRef callable;
- _PyStackRef res;
- oparg = CURRENT_OPARG();
- args = &stack_pointer[-oparg];
- self_or_null = stack_pointer[-1 - oparg];
- callable = stack_pointer[-2 - oparg];
+ callable = stack_pointer[-3];
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
- int total_args = oparg;
- if (!PyStackRef_IsNull(self_or_null)) {
- args--;
- total_args++;
- }
- if (total_args != 1) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
PyInterpreterState *interp = tstate->interp;
if (callable_o != interp->callable_cache.len) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
+ break;
+ }
+
+ case _CALL_LEN: {
+ _PyStackRef arg;
+ _PyStackRef null;
+ _PyStackRef callable;
+ _PyStackRef res;
+ arg = stack_pointer[-1];
+ null = stack_pointer[-2];
+ callable = stack_pointer[-3];
+ (void)null;
STAT_INC(CALL, hit);
- _PyStackRef arg_stackref = args[0];
- PyObject *arg = PyStackRef_AsPyObjectBorrow(arg_stackref);
+ PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg);
_PyFrame_SetStackPointer(frame, stack_pointer);
- Py_ssize_t len_i = PyObject_Length(arg);
+ Py_ssize_t len_i = PyObject_Length(arg_o);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (len_i < 0) {
JUMP_TO_ERROR();
@@ -5842,10 +5956,12 @@
if (res_o == NULL) {
JUMP_TO_ERROR();
}
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(arg_stackref);
+ PyStackRef_CLOSE(arg);
stack_pointer = _PyFrame_GetStackPointer(frame);
- stack_pointer += -2 - oparg;
+ stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(callable);
@@ -5857,59 +5973,70 @@
break;
}
- case _CALL_ISINSTANCE: {
- _PyStackRef *args;
- _PyStackRef self_or_null;
+ case _GUARD_CALLABLE_ISINSTANCE: {
_PyStackRef callable;
- _PyStackRef res;
- oparg = CURRENT_OPARG();
- args = &stack_pointer[-oparg];
- self_or_null = stack_pointer[-1 - oparg];
- callable = stack_pointer[-2 - oparg];
+ callable = stack_pointer[-4];
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
- int total_args = oparg;
- _PyStackRef *arguments = args;
- if (!PyStackRef_IsNull(self_or_null)) {
- arguments--;
- total_args++;
- }
- if (total_args != 2) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
PyInterpreterState *interp = tstate->interp;
if (callable_o != interp->callable_cache.isinstance) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
+ break;
+ }
+
+ case _CALL_ISINSTANCE: {
+ _PyStackRef cls;
+ _PyStackRef instance;
+ _PyStackRef null;
+ _PyStackRef callable;
+ _PyStackRef res;
+ cls = stack_pointer[-1];
+ instance = stack_pointer[-2];
+ null = stack_pointer[-3];
+ callable = stack_pointer[-4];
STAT_INC(CALL, hit);
- _PyStackRef cls_stackref = arguments[1];
- _PyStackRef inst_stackref = arguments[0];
+ PyObject *inst_o = PyStackRef_AsPyObjectBorrow(instance);
+ PyObject *cls_o = PyStackRef_AsPyObjectBorrow(cls);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int retval = PyObject_IsInstance(PyStackRef_AsPyObjectBorrow(inst_stackref), PyStackRef_AsPyObjectBorrow(cls_stackref));
+ int retval = PyObject_IsInstance(inst_o, cls_o);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (retval < 0) {
JUMP_TO_ERROR();
}
- res = retval ? PyStackRef_True : PyStackRef_False;
- assert((!PyStackRef_IsNull(res)) ^ (_PyErr_Occurred(tstate) != NULL));
+ (void)null;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- _PyStackRef tmp = callable;
- callable = res;
- stack_pointer[-2 - oparg] = callable;
- PyStackRef_CLOSE(tmp);
- for (int _i = oparg; --_i >= 0;) {
- tmp = args[_i];
- args[_i] = PyStackRef_NULL;
- PyStackRef_CLOSE(tmp);
- }
- tmp = self_or_null;
- self_or_null = PyStackRef_NULL;
- stack_pointer[-1 - oparg] = self_or_null;
- PyStackRef_XCLOSE(tmp);
+ PyStackRef_CLOSE(cls);
stack_pointer = _PyFrame_GetStackPointer(frame);
- stack_pointer += -1 - oparg;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(instance);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = retval ? PyStackRef_True : PyStackRef_False;
+ assert((!PyStackRef_IsNull(res)) ^ (_PyErr_Occurred(tstate) != NULL));
+ stack_pointer[0] = res;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _GUARD_CALLABLE_LIST_APPEND: {
+ _PyStackRef callable;
+ callable = stack_pointer[-3];
+ PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
+ PyInterpreterState *interp = tstate->interp;
+ if (callable_o != interp->callable_cache.list_append) {
+ UOP_STAT_INC(uopcode, miss);
+ JUMP_TO_JUMP_TARGET();
+ }
break;
}
@@ -5922,18 +6049,8 @@
self = stack_pointer[-2];
callable = stack_pointer[-3];
assert(oparg == 1);
- PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
- PyInterpreterState *interp = tstate->interp;
- if (callable_o != interp->callable_cache.list_append) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- if (self_o == NULL) {
- UOP_STAT_INC(uopcode, miss);
- JUMP_TO_JUMP_TARGET();
- }
- if (!PyList_Check(self_o)) {
+ if (!PyList_CheckExact(self_o)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
@@ -6331,7 +6448,7 @@
_PyStackRef *args;
_PyStackRef self_or_null;
_PyStackRef callable;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
oparg = CURRENT_OPARG();
kwnames = stack_pointer[-1];
args = &stack_pointer[-1 - oparg];
@@ -6365,8 +6482,8 @@
if (temp == NULL) {
JUMP_TO_ERROR();
}
- new_frame = temp;
- stack_pointer[0].bits = (uintptr_t)new_frame;
+ new_frame = PyStackRef_Wrap(temp);
+ stack_pointer[0] = new_frame;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -6769,12 +6886,44 @@
break;
}
+ case _COPY_1: {
+ _PyStackRef bottom;
+ _PyStackRef top;
+ bottom = stack_pointer[-1];
+ top = PyStackRef_DUP(bottom);
+ stack_pointer[0] = top;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _COPY_2: {
+ _PyStackRef bottom;
+ _PyStackRef top;
+ bottom = stack_pointer[-2];
+ top = PyStackRef_DUP(bottom);
+ stack_pointer[0] = top;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _COPY_3: {
+ _PyStackRef bottom;
+ _PyStackRef top;
+ bottom = stack_pointer[-3];
+ top = PyStackRef_DUP(bottom);
+ stack_pointer[0] = top;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
case _COPY: {
_PyStackRef bottom;
_PyStackRef top;
oparg = CURRENT_OPARG();
bottom = stack_pointer[-1 - (oparg-1)];
- assert(oparg > 0);
top = PyStackRef_DUP(bottom);
stack_pointer[0] = top;
stack_pointer += 1;
@@ -6814,6 +6963,32 @@
break;
}
+ case _SWAP_2: {
+ _PyStackRef top;
+ _PyStackRef bottom;
+ top = stack_pointer[-1];
+ bottom = stack_pointer[-2];
+ _PyStackRef temp = bottom;
+ bottom = top;
+ top = temp;
+ stack_pointer[-2] = bottom;
+ stack_pointer[-1] = top;
+ break;
+ }
+
+ case _SWAP_3: {
+ _PyStackRef top;
+ _PyStackRef bottom;
+ top = stack_pointer[-1];
+ bottom = stack_pointer[-3];
+ _PyStackRef temp = bottom;
+ bottom = top;
+ top = temp;
+ stack_pointer[-3] = bottom;
+ stack_pointer[-1] = top;
+ break;
+ }
+
case _SWAP: {
_PyStackRef top;
_PyStackRef bottom;
@@ -6823,7 +6998,6 @@
_PyStackRef temp = bottom;
bottom = top;
top = temp;
- assert(oparg >= 2);
stack_pointer[-2 - (oparg-2)] = bottom;
stack_pointer[-1] = top;
break;
@@ -6969,7 +7143,6 @@
Py_CLEAR(exit->executor);
stack_pointer = _PyFrame_GetStackPointer(frame);
}
- tstate->previous_executor = (PyObject *)current_executor;
if (exit->executor == NULL) {
_Py_BackoffCounter temperature = exit->temperature;
if (!backoff_counter_triggers(temperature)) {
@@ -6994,7 +7167,6 @@
}
exit->executor = executor;
}
- Py_INCREF(exit->executor);
GOTO_TIER_TWO(exit->executor);
break;
}
@@ -7037,13 +7209,76 @@
case _LOAD_CONST_INLINE_BORROW: {
_PyStackRef value;
PyObject *ptr = (PyObject *)CURRENT_OPERAND0();
- value = PyStackRef_FromPyObjectImmortal(ptr);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
}
+ case _POP_CALL: {
+ _PyStackRef null;
+ _PyStackRef callable;
+ null = stack_pointer[-1];
+ callable = stack_pointer[-2];
+ (void)null;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ break;
+ }
+
+ case _POP_CALL_ONE: {
+ _PyStackRef pop;
+ _PyStackRef null;
+ _PyStackRef callable;
+ pop = stack_pointer[-1];
+ null = stack_pointer[-2];
+ callable = stack_pointer[-3];
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(pop);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ (void)null;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ break;
+ }
+
+ case _POP_CALL_TWO: {
+ _PyStackRef pop2;
+ _PyStackRef pop1;
+ _PyStackRef null;
+ _PyStackRef callable;
+ pop2 = stack_pointer[-1];
+ pop1 = stack_pointer[-2];
+ null = stack_pointer[-3];
+ callable = stack_pointer[-4];
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(pop2);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(pop1);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ (void)null;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ break;
+ }
+
case _POP_TOP_LOAD_CONST_INLINE_BORROW: {
_PyStackRef pop;
_PyStackRef value;
@@ -7054,7 +7289,7 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(pop);
stack_pointer = _PyFrame_GetStackPointer(frame);
- value = PyStackRef_FromPyObjectImmortal(ptr);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -7078,13 +7313,124 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(pop1);
stack_pointer = _PyFrame_GetStackPointer(frame);
- value = PyStackRef_FromPyObjectImmortal(ptr);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ stack_pointer[0] = value;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_CALL_LOAD_CONST_INLINE_BORROW: {
+ _PyStackRef null;
+ _PyStackRef callable;
+ _PyStackRef value;
+ null = stack_pointer[-1];
+ callable = stack_pointer[-2];
+ PyObject *ptr = (PyObject *)CURRENT_OPERAND0();
+ (void)null;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ stack_pointer[0] = value;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW: {
+ _PyStackRef pop;
+ _PyStackRef null;
+ _PyStackRef callable;
+ _PyStackRef value;
+ pop = stack_pointer[-1];
+ null = stack_pointer[-2];
+ callable = stack_pointer[-3];
+ PyObject *ptr = (PyObject *)CURRENT_OPERAND0();
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(pop);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ (void)null;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ stack_pointer[0] = value;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW: {
+ _PyStackRef pop2;
+ _PyStackRef pop1;
+ _PyStackRef null;
+ _PyStackRef callable;
+ _PyStackRef value;
+ pop2 = stack_pointer[-1];
+ pop1 = stack_pointer[-2];
+ null = stack_pointer[-3];
+ callable = stack_pointer[-4];
+ PyObject *ptr = (PyObject *)CURRENT_OPERAND0();
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(pop2);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(pop1);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ (void)null;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ value = PyStackRef_FromPyObjectBorrow(ptr);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
}
+ case _LOAD_CONST_UNDER_INLINE: {
+ _PyStackRef old;
+ _PyStackRef value;
+ _PyStackRef new;
+ old = stack_pointer[-1];
+ PyObject *ptr = (PyObject *)CURRENT_OPERAND0();
+ new = old;
+ value = PyStackRef_FromPyObjectNew(ptr);
+ stack_pointer[-1] = value;
+ stack_pointer[0] = new;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _LOAD_CONST_UNDER_INLINE_BORROW: {
+ _PyStackRef old;
+ _PyStackRef value;
+ _PyStackRef new;
+ old = stack_pointer[-1];
+ PyObject *ptr = (PyObject *)CURRENT_OPERAND0();
+ new = old;
+ value = PyStackRef_FromPyObjectBorrow(ptr);
+ stack_pointer[-1] = value;
+ stack_pointer[0] = new;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
case _CHECK_FUNCTION: {
uint32_t func_version = (uint32_t)CURRENT_OPERAND0();
assert(PyStackRef_FunctionCheck(frame->f_funcobj));
@@ -7098,9 +7444,6 @@
case _START_EXECUTOR: {
PyObject *executor = (PyObject *)CURRENT_OPERAND0();
- _PyFrame_SetStackPointer(frame, stack_pointer);
- Py_CLEAR(tstate->previous_executor);
- stack_pointer = _PyFrame_GetStackPointer(frame);
#ifndef _Py_JIT
current_executor = (_PyExecutorObject*)executor;
#endif
@@ -7123,7 +7466,6 @@
}
case _DEOPT: {
- tstate->previous_executor = (PyObject *)current_executor;
GOTO_TIER_ONE(_PyFrame_GetBytecode(frame) + CURRENT_TARGET());
break;
}
@@ -7131,7 +7473,6 @@
case _ERROR_POP_N: {
oparg = CURRENT_OPARG();
uint32_t target = (uint32_t)CURRENT_OPERAND0();
- tstate->previous_executor = (PyObject *)current_executor;
assert(oparg == 0);
frame->instr_ptr = _PyFrame_GetBytecode(frame) + target;
GOTO_TIER_ONE(NULL);
diff --git a/Python/fileutils.c b/Python/fileutils.c
index 78603d40704..2a3f12d4e87 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -2784,6 +2784,43 @@ error:
return -1;
}
#else /* MS_WINDOWS */
+
+// The Windows Games API family doesn't expose GetNamedPipeHandleStateW so attempt
+// to load it directly from the Kernel32.dll
+#if !defined(MS_WINDOWS_APP) && !defined(MS_WINDOWS_SYSTEM)
+BOOL
+GetNamedPipeHandleStateW(HANDLE hNamedPipe, LPDWORD lpState, LPDWORD lpCurInstances, LPDWORD lpMaxCollectionCount,
+ LPDWORD lpCollectDataTimeout, LPWSTR lpUserName, DWORD nMaxUserNameSize)
+{
+ static int initialized = 0;
+ typedef BOOL(__stdcall* PGetNamedPipeHandleStateW) (
+ HANDLE hNamedPipe, LPDWORD lpState, LPDWORD lpCurInstances, LPDWORD lpMaxCollectionCount,
+ LPDWORD lpCollectDataTimeout, LPWSTR lpUserName, DWORD nMaxUserNameSize);
+ static PGetNamedPipeHandleStateW _GetNamedPipeHandleStateW;
+
+ if (initialized == 0) {
+ HMODULE api = LoadLibraryExW(L"Kernel32.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (api) {
+ _GetNamedPipeHandleStateW = (PGetNamedPipeHandleStateW)GetProcAddress(
+ api, "GetNamedPipeHandleStateW");
+ }
+ else {
+ _GetNamedPipeHandleStateW = NULL;
+ }
+ initialized = 1;
+ }
+
+ if (!_GetNamedPipeHandleStateW) {
+ SetLastError(E_NOINTERFACE);
+ return FALSE;
+ }
+
+ return _GetNamedPipeHandleStateW(
+ hNamedPipe, lpState, lpCurInstances, lpMaxCollectionCount, lpCollectDataTimeout, lpUserName, nMaxUserNameSize
+ );
+}
+#endif /* !MS_WINDOWS_APP && !MS_WINDOWS_SYSTEM */
+
int
_Py_get_blocking(int fd)
{
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index e0bb5615db3..1cb6f03169e 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -299,26 +299,34 @@ basicblock_returns(const basicblock *b) {
}
static void
-dump_basicblock(const basicblock *b)
+dump_basicblock(const basicblock *b, bool highlight)
{
const char *b_return = basicblock_returns(b) ? "return " : "";
+ if (highlight) {
+ fprintf(stderr, ">>> ");
+ }
fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, preds: %d %s\n",
b->b_label.id, b->b_except_handler, b->b_cold, b->b_warm, BB_NO_FALLTHROUGH(b), b, b->b_iused,
b->b_startdepth, b->b_predecessors, b_return);
+ int depth = b->b_startdepth;
if (b->b_instr) {
int i;
for (i = 0; i < b->b_iused; i++) {
- fprintf(stderr, " [%02d] ", i);
+ fprintf(stderr, " [%02d] depth: %d ", i, depth);
dump_instr(b->b_instr + i);
+
+ int popped = _PyOpcode_num_popped(b->b_instr[i].i_opcode, b->b_instr[i].i_oparg);
+ int pushed = _PyOpcode_num_pushed(b->b_instr[i].i_opcode, b->b_instr[i].i_oparg);
+ depth += (pushed - popped);
}
}
}
void
-_PyCfgBuilder_DumpGraph(const basicblock *entryblock)
+_PyCfgBuilder_DumpGraph(const basicblock *entryblock, const basicblock *mark)
{
for (const basicblock *b = entryblock; b != NULL; b = b->b_next) {
- dump_basicblock(b);
+ dump_basicblock(b, b == mark);
}
}
@@ -1884,6 +1892,10 @@ eval_const_unaryop(PyObject *operand, int opcode, int oparg)
result = PyNumber_Negative(operand);
break;
case UNARY_INVERT:
+ // XXX: This should be removed once the ~bool depreciation expires.
+ if (PyBool_Check(operand)) {
+ return NULL;
+ }
result = PyNumber_Invert(operand);
break;
case UNARY_NOT: {
@@ -2795,6 +2807,11 @@ optimize_load_fast(cfg_builder *g)
assert(opcode != EXTENDED_ARG);
switch (opcode) {
// Opcodes that load and store locals
+ case DELETE_FAST: {
+ kill_local(instr_flags, &refs, oparg);
+ break;
+ }
+
case LOAD_FAST: {
PUSH_REF(i, oparg);
break;
@@ -2857,8 +2874,11 @@ optimize_load_fast(cfg_builder *g)
// how many inputs should be left on the stack.
// Opcodes that consume no inputs
+ case FORMAT_SIMPLE:
case GET_ANEXT:
+ case GET_ITER:
case GET_LEN:
+ case GET_YIELD_FROM_ITER:
case IMPORT_FROM:
case MATCH_KEYS:
case MATCH_MAPPING:
@@ -2893,6 +2913,16 @@ optimize_load_fast(cfg_builder *g)
break;
}
+ case END_SEND:
+ case SET_FUNCTION_ATTRIBUTE: {
+ assert(_PyOpcode_num_popped(opcode, oparg) == 2);
+ assert(_PyOpcode_num_pushed(opcode, oparg) == 1);
+ ref tos = ref_stack_pop(&refs);
+ ref_stack_pop(&refs);
+ PUSH_REF(tos.instr, tos.local);
+ break;
+ }
+
// Opcodes that consume some inputs and push new values
case CHECK_EXC_MATCH: {
ref_stack_pop(&refs);
@@ -2922,6 +2952,14 @@ optimize_load_fast(cfg_builder *g)
break;
}
+ case LOAD_SPECIAL:
+ case PUSH_EXC_INFO: {
+ ref tos = ref_stack_pop(&refs);
+ PUSH_REF(i, NOT_LOCAL);
+ PUSH_REF(tos.instr, tos.local);
+ break;
+ }
+
case SEND: {
load_fast_push_block(&sp, instr->i_target, refs.size);
ref_stack_pop(&refs);
diff --git a/Python/gc.c b/Python/gc.c
index b7b48c8af39..02135a3fb44 100644
--- a/Python/gc.c
+++ b/Python/gc.c
@@ -1,6 +1,6 @@
// This implements the reference cycle garbage collector.
// The Python module interface to the collector is in gcmodule.c.
-// See https://devguide.python.org/internals/garbage-collector/
+// See InternalDocs/garbage_collector.md for more infromation.
#include "Python.h"
#include "pycore_ceval.h" // _Py_set_eval_breaker_bit()
@@ -493,6 +493,7 @@ update_refs(PyGC_Head *containers)
next = GC_NEXT(gc);
PyObject *op = FROM_GC(gc);
if (_Py_IsImmortal(op)) {
+ assert(!_Py_IsStaticImmortal(op));
_PyObject_GC_UNTRACK(op);
gc = next;
continue;
diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c
index 2db75e0fd41..5aaa68c5b51 100644
--- a/Python/gc_free_threading.c
+++ b/Python/gc_free_threading.c
@@ -17,6 +17,30 @@
#include "pydtrace.h"
+// Platform-specific includes for get_process_mem_usage().
+#ifdef _WIN32
+ #include <windows.h>
+ #include <psapi.h> // For GetProcessMemoryInfo
+#elif defined(__linux__)
+ #include <unistd.h> // For sysconf, getpid
+#elif defined(__APPLE__)
+ #include <mach/mach.h>
+ #include <mach/task.h> // Required for TASK_VM_INFO
+ #include <unistd.h> // For sysconf, getpid
+#elif defined(__FreeBSD__)
+ #include <sys/types.h>
+ #include <sys/sysctl.h>
+ #include <sys/user.h> // Requires sys/user.h for kinfo_proc definition
+ #include <kvm.h>
+ #include <unistd.h> // For sysconf, getpid
+ #include <fcntl.h> // For O_RDONLY
+ #include <limits.h> // For _POSIX2_LINE_MAX
+#elif defined(__OpenBSD__)
+ #include <sys/types.h>
+ #include <sys/sysctl.h>
+ #include <sys/user.h> // For kinfo_proc
+ #include <unistd.h> // For sysconf, getpid
+#endif
// enable the "mark alive" pass of GC
#define GC_ENABLE_MARK_ALIVE 1
@@ -1878,6 +1902,185 @@ cleanup_worklist(struct worklist *worklist)
}
}
+// Return the memory usage (typically RSS + swap) of the process, in units of
+// KB. Returns -1 if this operation is not supported or on failure.
+static Py_ssize_t
+get_process_mem_usage(void)
+{
+#ifdef _WIN32
+ // Windows implementation using GetProcessMemoryInfo
+ // Returns WorkingSetSize + PagefileUsage
+ PROCESS_MEMORY_COUNTERS pmc;
+ HANDLE hProcess = GetCurrentProcess();
+ if (NULL == hProcess) {
+ // Should not happen for the current process
+ return -1;
+ }
+
+ // GetProcessMemoryInfo returns non-zero on success
+ if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
+ // Values are in bytes, convert to KB.
+ return (Py_ssize_t)((pmc.WorkingSetSize + pmc.PagefileUsage) / 1024);
+ }
+ else {
+ return -1;
+ }
+
+#elif __linux__
+ FILE* fp = fopen("/proc/self/status", "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ char line_buffer[256];
+ long long rss_kb = -1;
+ long long swap_kb = -1;
+
+ while (fgets(line_buffer, sizeof(line_buffer), fp) != NULL) {
+ if (rss_kb == -1 && strncmp(line_buffer, "VmRSS:", 6) == 0) {
+ sscanf(line_buffer + 6, "%lld", &rss_kb);
+ }
+ else if (swap_kb == -1 && strncmp(line_buffer, "VmSwap:", 7) == 0) {
+ sscanf(line_buffer + 7, "%lld", &swap_kb);
+ }
+ if (rss_kb != -1 && swap_kb != -1) {
+ break; // Found both
+ }
+ }
+ fclose(fp);
+
+ if (rss_kb != -1 && swap_kb != -1) {
+ return (Py_ssize_t)(rss_kb + swap_kb);
+ }
+ return -1;
+
+#elif defined(__APPLE__)
+ // --- MacOS (Darwin) ---
+ // Returns phys_footprint (RAM + compressed memory)
+ task_vm_info_data_t vm_info;
+ mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
+ kern_return_t kerr;
+
+ kerr = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vm_info, &count);
+ if (kerr != KERN_SUCCESS) {
+ return -1;
+ }
+ // phys_footprint is in bytes. Convert to KB.
+ return (Py_ssize_t)(vm_info.phys_footprint / 1024);
+
+#elif defined(__FreeBSD__)
+ // NOTE: Returns RSS only. Per-process swap usage isn't readily available
+ long page_size_kb = sysconf(_SC_PAGESIZE) / 1024;
+ if (page_size_kb <= 0) {
+ return -1;
+ }
+
+ // Using /dev/null for vmcore avoids needing dump file.
+ // NULL for kernel file uses running kernel.
+ char errbuf[_POSIX2_LINE_MAX]; // For kvm error messages
+ kvm_t *kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, errbuf);
+ if (kd == NULL) {
+ return -1;
+ }
+
+ // KERN_PROC_PID filters for the specific process ID
+ // n_procs will contain the number of processes returned (should be 1 or 0)
+ pid_t pid = getpid();
+ int n_procs;
+ struct kinfo_proc *kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &n_procs);
+ if (kp == NULL) {
+ kvm_close(kd);
+ return -1;
+ }
+
+ Py_ssize_t rss_kb = -1;
+ if (n_procs > 0) {
+ // kp[0] contains the info for our process
+ // ki_rssize is in pages. Convert to KB.
+ rss_kb = (Py_ssize_t)kp->ki_rssize * page_size_kb;
+ }
+ else {
+ // Process with PID not found, shouldn't happen for self.
+ rss_kb = -1;
+ }
+
+ kvm_close(kd);
+ return rss_kb;
+
+#elif defined(__OpenBSD__)
+ // NOTE: Returns RSS only. Per-process swap usage isn't readily available
+ long page_size_kb = sysconf(_SC_PAGESIZE) / 1024;
+ if (page_size_kb <= 0) {
+ return -1;
+ }
+
+ struct kinfo_proc kp;
+ pid_t pid = getpid();
+ int mib[6];
+ size_t len = sizeof(kp);
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ mib[4] = sizeof(struct kinfo_proc); // size of the structure we want
+ mib[5] = 1; // want 1 structure back
+ if (sysctl(mib, 6, &kp, &len, NULL, 0) == -1) {
+ return -1;
+ }
+
+ if (len > 0) {
+ // p_vm_rssize is in pages on OpenBSD. Convert to KB.
+ return (Py_ssize_t)kp.p_vm_rssize * page_size_kb;
+ }
+ else {
+ // Process info not returned
+ return -1;
+ }
+#else
+ // Unsupported platform
+ return -1;
+#endif
+}
+
+static bool
+gc_should_collect_mem_usage(GCState *gcstate)
+{
+ Py_ssize_t mem = get_process_mem_usage();
+ if (mem < 0) {
+ // Reading process memory usage is not support or failed.
+ return true;
+ }
+ int threshold = gcstate->young.threshold;
+ Py_ssize_t deferred = _Py_atomic_load_ssize_relaxed(&gcstate->deferred_count);
+ if (deferred > threshold * 40) {
+ // Too many new container objects since last GC, even though memory use
+ // might not have increased much. This is intended to avoid resource
+ // exhaustion if some objects consume resources but don't result in a
+ // memory usage increase. We use 40x as the factor here because older
+ // versions of Python would do full collections after roughly every
+ // 70,000 new container objects.
+ return true;
+ }
+ Py_ssize_t last_mem = _Py_atomic_load_ssize_relaxed(&gcstate->last_mem);
+ Py_ssize_t mem_threshold = Py_MAX(last_mem / 10, 128);
+ if ((mem - last_mem) > mem_threshold) {
+ // The process memory usage has increased too much, do a collection.
+ return true;
+ }
+ else {
+ // The memory usage has not increased enough, defer the collection and
+ // clear the young object count so we don't check memory usage again
+ // on the next call to gc_should_collect().
+ PyMutex_Lock(&gcstate->mutex);
+ int young_count = _Py_atomic_exchange_int(&gcstate->young.count, 0);
+ _Py_atomic_store_ssize_relaxed(&gcstate->deferred_count,
+ gcstate->deferred_count + young_count);
+ PyMutex_Unlock(&gcstate->mutex);
+ return false;
+ }
+}
+
static bool
gc_should_collect(GCState *gcstate)
{
@@ -1887,11 +2090,17 @@ gc_should_collect(GCState *gcstate)
if (count <= threshold || threshold == 0 || !gc_enabled) {
return false;
}
- // Avoid quadratic behavior by scaling threshold to the number of live
- // objects. A few tests rely on immediate scheduling of the GC so we ignore
- // the scaled threshold if generations[1].threshold is set to zero.
- return (count > gcstate->long_lived_total / 4 ||
- gcstate->old[0].threshold == 0);
+ if (gcstate->old[0].threshold == 0) {
+ // A few tests rely on immediate scheduling of the GC so we ignore the
+ // extra conditions if generations[1].threshold is set to zero.
+ return true;
+ }
+ if (count < gcstate->long_lived_total / 4) {
+ // Avoid quadratic behavior by scaling threshold to the number of live
+ // objects.
+ return false;
+ }
+ return gc_should_collect_mem_usage(gcstate);
}
static void
@@ -1940,6 +2149,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
}
state->gcstate->young.count = 0;
+ state->gcstate->deferred_count = 0;
for (int i = 1; i <= generation; ++i) {
state->gcstate->old[i-1].count = 0;
}
@@ -2033,6 +2243,11 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
// to be freed.
delete_garbage(state);
+ // Store the current memory usage, can be smaller now if breaking cycles
+ // freed some memory.
+ Py_ssize_t last_mem = get_process_mem_usage();
+ _Py_atomic_store_ssize_relaxed(&state->gcstate->last_mem, last_mem);
+
// Append objects with legacy finalizers to the "gc.garbage" list.
handle_legacy_finalizers(state);
}
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 08b72a092aa..bb153bc1c0e 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -158,7 +158,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -168,7 +168,7 @@
{
left = stack_pointer[-2];
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
- if (!PyLong_CheckExact(left_o)) {
+ if (!_PyLong_CheckExactAndCompact(left_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -182,16 +182,16 @@
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = _PyCompactLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res)) {
+ UPDATE_MISS_STATS(BINARY_OP);
+ assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
+ JUMP_TO_PREDICTED(BINARY_OP);
+ }
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
- if (res_o == NULL) {
- JUMP_TO_LABEL(pop_2_error);
- }
- res = PyStackRef_FromPyObjectSteal(res_o);
}
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -483,7 +483,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -493,7 +493,7 @@
{
left = stack_pointer[-2];
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
- if (!PyLong_CheckExact(left_o)) {
+ if (!_PyLong_CheckExactAndCompact(left_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -507,16 +507,16 @@
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = _PyCompactLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res)) {
+ UPDATE_MISS_STATS(BINARY_OP);
+ assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
+ JUMP_TO_PREDICTED(BINARY_OP);
+ }
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
- if (res_o == NULL) {
- JUMP_TO_LABEL(pop_2_error);
- }
- res = PyStackRef_FromPyObjectSteal(res_o);
}
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -604,7 +604,7 @@
_PyStackRef container;
_PyStackRef getitem;
_PyStackRef sub;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 5 cache entries */
// _CHECK_PEP_523
{
@@ -650,19 +650,20 @@
// _BINARY_OP_SUBSCR_INIT_CALL
{
sub = stack_pointer[-1];
- new_frame = _PyFrame_PushUnchecked(tstate, getitem, 2, frame);
- new_frame->localsplus[0] = container;
- new_frame->localsplus[1] = sub;
+ _PyInterpreterFrame* pushed_frame = _PyFrame_PushUnchecked(tstate, getitem, 2, frame);
+ pushed_frame->localsplus[0] = container;
+ pushed_frame->localsplus[1] = sub;
frame->return_offset = 6 ;
+ new_frame = PyStackRef_Wrap(pushed_frame);
}
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -693,7 +694,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -855,7 +856,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -905,7 +906,7 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(str_st);
stack_pointer = _PyFrame_GetStackPointer(frame);
- res = PyStackRef_FromPyObjectImmortal(res_o);
+ res = PyStackRef_FromPyObjectBorrow(res_o);
}
stack_pointer[0] = res;
stack_pointer += 1;
@@ -933,7 +934,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -1063,7 +1064,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -1073,7 +1074,7 @@
{
left = stack_pointer[-2];
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
- if (!PyLong_CheckExact(left_o)) {
+ if (!_PyLong_CheckExactAndCompact(left_o)) {
UPDATE_MISS_STATS(BINARY_OP);
assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
JUMP_TO_PREDICTED(BINARY_OP);
@@ -1087,16 +1088,16 @@
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
STAT_INC(BINARY_OP, hit);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res)) {
+ UPDATE_MISS_STATS(BINARY_OP);
+ assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
+ JUMP_TO_PREDICTED(BINARY_OP);
+ }
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
- if (res_o == NULL) {
- JUMP_TO_LABEL(pop_2_error);
- }
- res = PyStackRef_FromPyObjectSteal(res_o);
}
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -1708,8 +1709,8 @@
_PyStackRef init;
_PyStackRef self;
_PyStackRef *args;
- _PyInterpreterFrame *init_frame;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef init_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -1792,17 +1793,17 @@
stack_pointer = _PyFrame_GetStackPointer(frame);
JUMP_TO_LABEL(error);
}
- init_frame = temp;
frame->return_offset = 1 + INLINE_CACHE_ENTRIES_CALL;
tstate->py_recursion_remaining--;
+ init_frame = PyStackRef_Wrap(temp);
}
// _PUSH_FRAME
{
new_frame = init_frame;
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -1828,7 +1829,7 @@
_PyStackRef null;
_PyStackRef self_or_null;
_PyStackRef *args;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -1921,12 +1922,13 @@
args = &stack_pointer[-oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
+ new_frame = PyStackRef_Wrap(pushed_frame);
}
// _SAVE_RETURN_OFFSET
{
@@ -1940,11 +1942,11 @@
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
stack_pointer += -2 - oparg;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -1970,7 +1972,7 @@
_PyStackRef null;
_PyStackRef self_or_null;
_PyStackRef *args;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -2056,7 +2058,7 @@
if (temp == NULL) {
JUMP_TO_LABEL(error);
}
- new_frame = temp;
+ new_frame = PyStackRef_Wrap(temp);
}
// _SAVE_RETURN_OFFSET
{
@@ -2070,9 +2072,9 @@
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -2777,60 +2779,67 @@
next_instr += 4;
INSTRUCTION_STATS(CALL_ISINSTANCE);
static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size");
+ _PyStackRef null;
_PyStackRef callable;
- _PyStackRef self_or_null;
- _PyStackRef *args;
+ _PyStackRef instance;
+ _PyStackRef cls;
_PyStackRef res;
/* Skip 1 cache entry */
/* Skip 2 cache entries */
- args = &stack_pointer[-oparg];
- self_or_null = stack_pointer[-1 - oparg];
- callable = stack_pointer[-2 - oparg];
- PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
- int total_args = oparg;
- _PyStackRef *arguments = args;
- if (!PyStackRef_IsNull(self_or_null)) {
- arguments--;
- total_args++;
- }
- if (total_args != 2) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- PyInterpreterState *interp = tstate->interp;
- if (callable_o != interp->callable_cache.isinstance) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- STAT_INC(CALL, hit);
- _PyStackRef cls_stackref = arguments[1];
- _PyStackRef inst_stackref = arguments[0];
- _PyFrame_SetStackPointer(frame, stack_pointer);
- int retval = PyObject_IsInstance(PyStackRef_AsPyObjectBorrow(inst_stackref), PyStackRef_AsPyObjectBorrow(cls_stackref));
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (retval < 0) {
- JUMP_TO_LABEL(error);
+ // _GUARD_THIRD_NULL
+ {
+ null = stack_pointer[-3];
+ if (!PyStackRef_IsNull(null)) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
}
- res = retval ? PyStackRef_True : PyStackRef_False;
- assert((!PyStackRef_IsNull(res)) ^ (_PyErr_Occurred(tstate) != NULL));
- _PyFrame_SetStackPointer(frame, stack_pointer);
- _PyStackRef tmp = callable;
- callable = res;
- stack_pointer[-2 - oparg] = callable;
- PyStackRef_CLOSE(tmp);
- for (int _i = oparg; --_i >= 0;) {
- tmp = args[_i];
- args[_i] = PyStackRef_NULL;
- PyStackRef_CLOSE(tmp);
+ // _GUARD_CALLABLE_ISINSTANCE
+ {
+ callable = stack_pointer[-4];
+ PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
+ PyInterpreterState *interp = tstate->interp;
+ if (callable_o != interp->callable_cache.isinstance) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
}
- tmp = self_or_null;
- self_or_null = PyStackRef_NULL;
- stack_pointer[-1 - oparg] = self_or_null;
- PyStackRef_XCLOSE(tmp);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- stack_pointer += -1 - oparg;
+ // _CALL_ISINSTANCE
+ {
+ cls = stack_pointer[-1];
+ instance = stack_pointer[-2];
+ STAT_INC(CALL, hit);
+ PyObject *inst_o = PyStackRef_AsPyObjectBorrow(instance);
+ PyObject *cls_o = PyStackRef_AsPyObjectBorrow(cls);
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ int retval = PyObject_IsInstance(inst_o, cls_o);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (retval < 0) {
+ JUMP_TO_LABEL(error);
+ }
+ (void)null;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(cls);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(instance);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = retval ? PyStackRef_True : PyStackRef_False;
+ assert((!PyStackRef_IsNull(res)) ^ (_PyErr_Occurred(tstate) != NULL));
+ }
+ stack_pointer[0] = res;
+ stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
DISPATCH();
}
@@ -3033,7 +3042,7 @@
_PyStackRef self_or_null;
_PyStackRef *args;
_PyStackRef kwnames;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -3120,7 +3129,7 @@
if (temp == NULL) {
JUMP_TO_LABEL(error);
}
- new_frame = temp;
+ new_frame = PyStackRef_Wrap(temp);
}
// _SAVE_RETURN_OFFSET
{
@@ -3134,9 +3143,9 @@
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -3297,7 +3306,7 @@
_PyStackRef self_or_null;
_PyStackRef *args;
_PyStackRef kwnames;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -3357,7 +3366,7 @@
if (temp == NULL) {
JUMP_TO_LABEL(error);
}
- new_frame = temp;
+ new_frame = PyStackRef_Wrap(temp);
}
// _SAVE_RETURN_OFFSET
{
@@ -3371,9 +3380,9 @@
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -3395,55 +3404,61 @@
next_instr += 4;
INSTRUCTION_STATS(CALL_LEN);
static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size");
+ _PyStackRef null;
_PyStackRef callable;
- _PyStackRef self_or_null;
- _PyStackRef *args;
+ _PyStackRef arg;
_PyStackRef res;
/* Skip 1 cache entry */
/* Skip 2 cache entries */
- args = &stack_pointer[-oparg];
- self_or_null = stack_pointer[-1 - oparg];
- callable = stack_pointer[-2 - oparg];
- PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
- int total_args = oparg;
- if (!PyStackRef_IsNull(self_or_null)) {
- args--;
- total_args++;
- }
- if (total_args != 1) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- PyInterpreterState *interp = tstate->interp;
- if (callable_o != interp->callable_cache.len) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- STAT_INC(CALL, hit);
- _PyStackRef arg_stackref = args[0];
- PyObject *arg = PyStackRef_AsPyObjectBorrow(arg_stackref);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- Py_ssize_t len_i = PyObject_Length(arg);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (len_i < 0) {
- JUMP_TO_LABEL(error);
+ // _GUARD_NOS_NULL
+ {
+ null = stack_pointer[-2];
+ if (!PyStackRef_IsNull(null)) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
}
- PyObject *res_o = PyLong_FromSsize_t(len_i);
- assert((res_o != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
- if (res_o == NULL) {
- JUMP_TO_LABEL(error);
+ // _GUARD_CALLABLE_LEN
+ {
+ callable = stack_pointer[-3];
+ PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
+ PyInterpreterState *interp = tstate->interp;
+ if (callable_o != interp->callable_cache.len) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
+ }
+ // _CALL_LEN
+ {
+ arg = stack_pointer[-1];
+ (void)null;
+ STAT_INC(CALL, hit);
+ PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg);
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ Py_ssize_t len_i = PyObject_Length(arg_o);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (len_i < 0) {
+ JUMP_TO_LABEL(error);
+ }
+ PyObject *res_o = PyLong_FromSsize_t(len_i);
+ assert((res_o != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
+ if (res_o == NULL) {
+ JUMP_TO_LABEL(error);
+ }
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(arg);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ res = PyStackRef_FromPyObjectSteal(res_o);
}
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(arg_stackref);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- stack_pointer += -2 - oparg;
- assert(WITHIN_STACK_BOUNDS());
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(callable);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- res = PyStackRef_FromPyObjectSteal(res_o);
stack_pointer[0] = res;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -3462,58 +3477,79 @@
INSTRUCTION_STATS(CALL_LIST_APPEND);
static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size");
_PyStackRef callable;
+ _PyStackRef nos;
_PyStackRef self;
_PyStackRef arg;
/* Skip 1 cache entry */
/* Skip 2 cache entries */
- arg = stack_pointer[-1];
- self = stack_pointer[-2];
- callable = stack_pointer[-3];
- assert(oparg == 1);
- PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
- PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
- PyInterpreterState *interp = tstate->interp;
- if (callable_o != interp->callable_cache.list_append) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- if (self_o == NULL) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- if (!PyList_Check(self_o)) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- if (!LOCK_OBJECT(self_o)) {
- UPDATE_MISS_STATS(CALL);
- assert(_PyOpcode_Deopt[opcode] == (CALL));
- JUMP_TO_PREDICTED(CALL);
- }
- STAT_INC(CALL, hit);
- int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
- UNLOCK_OBJECT(self_o);
- stack_pointer += -2;
- assert(WITHIN_STACK_BOUNDS());
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(self);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- stack_pointer += -1;
- assert(WITHIN_STACK_BOUNDS());
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(callable);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (err) {
- JUMP_TO_LABEL(error);
+ // _GUARD_CALLABLE_LIST_APPEND
+ {
+ callable = stack_pointer[-3];
+ PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
+ PyInterpreterState *interp = tstate->interp;
+ if (callable_o != interp->callable_cache.list_append) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
}
- #if TIER_ONE
+ // _GUARD_NOS_NOT_NULL
+ {
+ nos = stack_pointer[-2];
+ PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
+ if (o == NULL) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
+ }
+ // _GUARD_NOS_LIST
+ {
+ PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
+ if (!PyList_CheckExact(o)) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
+ }
+ // _CALL_LIST_APPEND
+ {
+ arg = stack_pointer[-1];
+ self = nos;
+ assert(oparg == 1);
+ PyObject *self_o = PyStackRef_AsPyObjectBorrow(self);
+ if (!PyList_CheckExact(self_o)) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
+ if (!LOCK_OBJECT(self_o)) {
+ UPDATE_MISS_STATS(CALL);
+ assert(_PyOpcode_Deopt[opcode] == (CALL));
+ JUMP_TO_PREDICTED(CALL);
+ }
+ STAT_INC(CALL, hit);
+ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
+ UNLOCK_OBJECT(self_o);
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(self);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(callable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (err) {
+ JUMP_TO_LABEL(error);
+ }
+ #if TIER_ONE
- assert(next_instr->op.code == POP_TOP);
- SKIP_OVER(1);
- #endif
+ assert(next_instr->op.code == POP_TOP);
+ SKIP_OVER(1);
+ #endif
+ }
DISPATCH();
}
@@ -4129,7 +4165,7 @@
_PyStackRef callable;
_PyStackRef self_or_null;
_PyStackRef *args;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -4193,12 +4229,13 @@
args = &stack_pointer[-oparg];
int has_self = !PyStackRef_IsNull(self_or_null);
STAT_INC(CALL, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
- _PyStackRef *first_non_self_local = new_frame->localsplus + has_self;
- new_frame->localsplus[0] = self_or_null;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame);
+ _PyStackRef *first_non_self_local = pushed_frame->localsplus + has_self;
+ pushed_frame->localsplus[0] = self_or_null;
for (int i = 0; i < oparg; i++) {
first_non_self_local[i] = args[i];
}
+ new_frame = PyStackRef_Wrap(pushed_frame);
}
// _SAVE_RETURN_OFFSET
{
@@ -4212,11 +4249,11 @@
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
stack_pointer += -2 - oparg;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -4241,7 +4278,7 @@
_PyStackRef callable;
_PyStackRef self_or_null;
_PyStackRef *args;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -4300,7 +4337,7 @@
if (temp == NULL) {
JUMP_TO_LABEL(error);
}
- new_frame = temp;
+ new_frame = PyStackRef_Wrap(temp);
}
// _SAVE_RETURN_OFFSET
{
@@ -4314,9 +4351,9 @@
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -4856,7 +4893,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(COMPARE_OP);
assert(_PyOpcode_Deopt[opcode] == (COMPARE_OP));
JUMP_TO_PREDICTED(COMPARE_OP);
@@ -4866,7 +4903,7 @@
{
left = stack_pointer[-2];
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
- if (!PyLong_CheckExact(left_o)) {
+ if (!_PyLong_CheckExactAndCompact(left_o)) {
UPDATE_MISS_STATS(COMPARE_OP);
assert(_PyOpcode_Deopt[opcode] == (COMPARE_OP));
JUMP_TO_PREDICTED(COMPARE_OP);
@@ -4878,16 +4915,8 @@
right = value;
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
- if (!_PyLong_IsCompact((PyLongObject *)left_o)) {
- UPDATE_MISS_STATS(COMPARE_OP);
- assert(_PyOpcode_Deopt[opcode] == (COMPARE_OP));
- JUMP_TO_PREDICTED(COMPARE_OP);
- }
- if (!_PyLong_IsCompact((PyLongObject *)right_o)) {
- UPDATE_MISS_STATS(COMPARE_OP);
- assert(_PyOpcode_Deopt[opcode] == (COMPARE_OP));
- JUMP_TO_PREDICTED(COMPARE_OP);
- }
+ assert(_PyLong_IsCompact((PyLongObject *)left_o));
+ assert(_PyLong_IsCompact((PyLongObject *)right_o));
STAT_INC(COMPARE_OP, hit);
assert(_PyLong_DigitCount((PyLongObject *)left_o) <= 1 &&
_PyLong_DigitCount((PyLongObject *)right_o) <= 1);
@@ -5182,7 +5211,6 @@
_PyStackRef bottom;
_PyStackRef top;
bottom = stack_pointer[-1 - (oparg-1)];
- assert(oparg > 0);
top = PyStackRef_DUP(bottom);
stack_pointer[0] = top;
stack_pointer += 1;
@@ -5557,7 +5585,7 @@
assert(executor->vm_data.index == INSTR_OFFSET() - 1);
assert(executor->vm_data.code == code);
assert(executor->vm_data.valid);
- assert(tstate->previous_executor == NULL);
+ assert(tstate->current_executor == NULL);
if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) {
opcode = executor->vm_data.opcode;
oparg = (oparg & ~255) | executor->vm_data.oparg;
@@ -5567,8 +5595,6 @@
}
DISPATCH_GOTO();
}
- tstate->previous_executor = Py_None;
- Py_INCREF(executor);
GOTO_TIER_TWO(executor);
#else
Py_FatalError("ENTER_EXECUTOR is not supported in this build");
@@ -5699,17 +5725,19 @@
_Py_CODEUNIT* const this_instr = next_instr - 2;
(void)this_instr;
_PyStackRef iter;
+ _PyStackRef null_or_index;
_PyStackRef next;
// _SPECIALIZE_FOR_ITER
{
- iter = stack_pointer[-1];
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
uint16_t counter = read_u16(&this_instr[1].cache);
(void)counter;
#if ENABLE_SPECIALIZATION_FT
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
next_instr = this_instr;
_PyFrame_SetStackPointer(frame, stack_pointer);
- _Py_Specialize_ForIter(iter, next_instr, oparg);
+ _Py_Specialize_ForIter(iter, null_or_index, next_instr, oparg);
stack_pointer = _PyFrame_GetStackPointer(frame);
DISPATCH_SAME_OPARG();
}
@@ -5719,30 +5747,20 @@
}
// _FOR_ITER
{
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
_PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
+ _PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (next_o == NULL) {
- if (_PyErr_Occurred(tstate)) {
- _PyFrame_SetStackPointer(frame, stack_pointer);
- int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (!matches) {
- JUMP_TO_LABEL(error);
- }
- _PyFrame_SetStackPointer(frame, stack_pointer);
- _PyEval_MonitorRaise(tstate, frame, this_instr);
- _PyErr_Clear(tstate);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (!PyStackRef_IsValid(item)) {
+ if (PyStackRef_IsError(item)) {
+ JUMP_TO_LABEL(error);
}
- assert(next_instr[oparg].op.code == END_FOR ||
- next_instr[oparg].op.code == INSTRUMENTED_END_FOR);
JUMPBY(oparg + 1);
+ stack_pointer[-1] = null_or_index;
DISPATCH();
}
- next = PyStackRef_FromPyObjectSteal(next_o);
+ next = item;
}
+ stack_pointer[-1] = null_or_index;
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -5761,8 +5779,8 @@
INSTRUCTION_STATS(FOR_ITER_GEN);
static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size");
_PyStackRef iter;
- _PyInterpreterFrame *gen_frame;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef gen_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -5774,7 +5792,7 @@
}
// _FOR_ITER_GEN_FRAME
{
- iter = stack_pointer[-1];
+ iter = stack_pointer[-2];
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
if (Py_TYPE(gen) != &PyGen_Type) {
UPDATE_MISS_STATS(FOR_ITER);
@@ -5794,21 +5812,22 @@
JUMP_TO_PREDICTED(FOR_ITER);
}
STAT_INC(FOR_ITER, hit);
- gen_frame = &gen->gi_iframe;
- _PyFrame_StackPush(gen_frame, PyStackRef_None);
+ _PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
+ _PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
- gen_frame->previous = frame;
+ pushed_frame->previous = frame;
frame->return_offset = (uint16_t)( 2 + oparg);
+ gen_frame = PyStackRef_Wrap(pushed_frame);
}
// _PUSH_FRAME
{
new_frame = gen_frame;
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -5831,26 +5850,22 @@
INSTRUCTION_STATS(FOR_ITER_LIST);
static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size");
_PyStackRef iter;
+ _PyStackRef null_or_index;
_PyStackRef next;
/* Skip 1 cache entry */
// _ITER_CHECK_LIST
{
- iter = stack_pointer[-1];
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- if (Py_TYPE(iter_o) != &PyListIter_Type) {
+ if (Py_TYPE(iter_o) != &PyList_Type) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
+ assert(PyStackRef_IsTaggedInt(null_or_index));
#ifdef Py_GIL_DISABLED
- if (!_PyObject_IsUniquelyReferenced(iter_o)) {
- UPDATE_MISS_STATS(FOR_ITER);
- assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
- JUMP_TO_PREDICTED(FOR_ITER);
- }
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- if (!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) ||
- !_PyObject_GC_IS_SHARED(it->it_seq)) {
+ if (!_Py_IsOwnedByCurrentThread(iter_o) && !_PyObject_GC_IS_SHARED(iter_o)) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
@@ -5859,42 +5874,30 @@
}
// _ITER_JUMP_LIST
{
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- (void)iter_o;
+
#else
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(list_o) == &PyList_Type);
STAT_INC(FOR_ITER, hit);
- PyListObject *seq = it->it_seq;
- if (seq == NULL || (size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) {
- it->it_index = -1;
- if (seq != NULL) {
- it->it_seq = NULL;
- _PyFrame_SetStackPointer(frame, stack_pointer);
- Py_DECREF(seq);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- }
+ if ((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyList_GET_SIZE(list_o)) {
+ null_or_index = PyStackRef_TagInt(-1);
JUMPBY(oparg + 1);
+ stack_pointer[-1] = null_or_index;
DISPATCH();
}
#endif
}
// _ITER_NEXT_LIST
{
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyListIter_Type);
- PyListObject *seq = it->it_seq;
- assert(seq);
+ PyObject *list_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(PyList_CheckExact(list_o));
#ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- assert(_Py_IsOwnedByCurrentThread((PyObject *)seq) ||
- _PyObject_GC_IS_SHARED(seq));
+ assert(_Py_IsOwnedByCurrentThread(list_o) ||
+ _PyObject_GC_IS_SHARED(list_o));
STAT_INC(FOR_ITER, hit);
_PyFrame_SetStackPointer(frame, stack_pointer);
- int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
+ int result = _PyList_GetItemRefNoLock((PyListObject *)list_o, PyStackRef_UntagInt(null_or_index), &next);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (result < 0) {
UPDATE_MISS_STATS(FOR_ITER);
@@ -5902,16 +5905,17 @@
JUMP_TO_PREDICTED(FOR_ITER);
}
if (result == 0) {
- it->it_index = -1;
+ null_or_index = PyStackRef_TagInt(-1);
JUMPBY(oparg + 1);
+ stack_pointer[-1] = null_or_index;
DISPATCH();
}
- it->it_index++;
#else
- assert(it->it_index < PyList_GET_SIZE(seq));
- next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(seq, it->it_index++));
+ next = PyStackRef_FromPyObjectNew(PyList_GET_ITEM(list_o, PyStackRef_UntagInt(null_or_index)));
#endif
+ null_or_index = PyStackRef_IncrementTaggedIntNoOverflow(null_or_index);
}
+ stack_pointer[-1] = null_or_index;
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -5934,7 +5938,7 @@
/* Skip 1 cache entry */
// _ITER_CHECK_RANGE
{
- iter = stack_pointer[-1];
+ iter = stack_pointer[-2];
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
if (Py_TYPE(r) != &PyRangeIter_Type) {
UPDATE_MISS_STATS(FOR_ITER);
@@ -5997,63 +6001,44 @@
INSTRUCTION_STATS(FOR_ITER_TUPLE);
static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size");
_PyStackRef iter;
+ _PyStackRef null_or_index;
_PyStackRef next;
/* Skip 1 cache entry */
// _ITER_CHECK_TUPLE
{
- iter = stack_pointer[-1];
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- if (Py_TYPE(iter_o) != &PyTupleIter_Type) {
+ if (Py_TYPE(iter_o) != &PyTuple_Type) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
- #ifdef Py_GIL_DISABLED
- if (!_PyObject_IsUniquelyReferenced(iter_o)) {
- UPDATE_MISS_STATS(FOR_ITER);
- assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
- JUMP_TO_PREDICTED(FOR_ITER);
- }
- #endif
+ assert(PyStackRef_IsTaggedInt(null_or_index));
}
// _ITER_JUMP_TUPLE
{
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- (void)iter_o;
- assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
- #ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- #endif
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
+ PyObject *tuple_o = PyStackRef_AsPyObjectBorrow(iter);
+ (void)tuple_o;
+ assert(Py_TYPE(tuple_o) == &PyTuple_Type);
STAT_INC(FOR_ITER, hit);
- PyTupleObject *seq = it->it_seq;
- if (seq == NULL || (size_t)it->it_index >= (size_t)PyTuple_GET_SIZE(seq)) {
- #ifndef Py_GIL_DISABLED
- if (seq != NULL) {
- it->it_seq = NULL;
- _PyFrame_SetStackPointer(frame, stack_pointer);
- Py_DECREF(seq);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- }
- #endif
-
+ if ((size_t)PyStackRef_UntagInt(null_or_index) >= (size_t)PyTuple_GET_SIZE(tuple_o)) {
+ null_or_index = PyStackRef_TagInt(-1);
JUMPBY(oparg + 1);
+ stack_pointer[-1] = null_or_index;
DISPATCH();
}
}
// _ITER_NEXT_TUPLE
{
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
- _PyTupleIterObject *it = (_PyTupleIterObject *)iter_o;
- assert(Py_TYPE(iter_o) == &PyTupleIter_Type);
- PyTupleObject *seq = it->it_seq;
- #ifdef Py_GIL_DISABLED
- assert(_PyObject_IsUniquelyReferenced(iter_o));
- #endif
- assert(seq);
- assert(it->it_index < PyTuple_GET_SIZE(seq));
- next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq, it->it_index++));
+ PyObject *tuple_o = PyStackRef_AsPyObjectBorrow(iter);
+ assert(Py_TYPE(tuple_o) == &PyTuple_Type);
+ uintptr_t i = PyStackRef_UntagInt(null_or_index);
+ assert((size_t)i < (size_t)PyTuple_GET_SIZE(tuple_o));
+ next = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(tuple_o, i));
+ null_or_index = PyStackRef_IncrementTaggedIntNoOverflow(null_or_index);
}
+ stack_pointer[-1] = null_or_index;
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -6184,25 +6169,37 @@
INSTRUCTION_STATS(GET_ITER);
_PyStackRef iterable;
_PyStackRef iter;
+ _PyStackRef index_or_null;
iterable = stack_pointer[-1];
#ifdef Py_STATS
_PyFrame_SetStackPointer(frame, stack_pointer);
_Py_GatherStats_GetIter(iterable);
stack_pointer = _PyFrame_GetStackPointer(frame);
#endif
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
- stack_pointer = _PyFrame_GetStackPointer(frame);
- stack_pointer += -1;
- assert(WITHIN_STACK_BOUNDS());
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(iterable);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (iter_o == NULL) {
- JUMP_TO_LABEL(error);
+
+ PyTypeObject *tp = PyStackRef_TYPE(iterable);
+ if (tp == &PyTuple_Type || tp == &PyList_Type) {
+ iter = iterable;
+ index_or_null = PyStackRef_TagInt(0);
}
- iter = PyStackRef_FromPyObjectSteal(iter_o);
- stack_pointer[0] = iter;
+ else {
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(iterable);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (iter_o == NULL) {
+ JUMP_TO_LABEL(error);
+ }
+ iter = PyStackRef_FromPyObjectSteal(iter_o);
+ index_or_null = PyStackRef_NULL;
+ stack_pointer += 1;
+ }
+ stack_pointer[-1] = iter;
+ stack_pointer[0] = index_or_null;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
DISPATCH();
@@ -6967,7 +6964,7 @@
_PyStackRef receiver;
_PyStackRef value;
value = stack_pointer[-1];
- receiver = stack_pointer[-2];
+ receiver = stack_pointer[-3];
if (PyStackRef_GenCheck(receiver)) {
_PyFrame_SetStackPointer(frame, stack_pointer);
int err = monitor_stop_iteration(tstate, frame, this_instr, PyStackRef_AsPyObjectBorrow(value));
@@ -7029,35 +7026,25 @@
next_instr += 2;
INSTRUCTION_STATS(INSTRUMENTED_FOR_ITER);
_PyStackRef iter;
+ _PyStackRef null_or_index;
_PyStackRef next;
/* Skip 1 cache entry */
- iter = stack_pointer[-1];
- PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
+ null_or_index = stack_pointer[-1];
+ iter = stack_pointer[-2];
_PyFrame_SetStackPointer(frame, stack_pointer);
- PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
+ _PyStackRef item = _PyForIter_VirtualIteratorNext(tstate, frame, iter, &null_or_index);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (next_o != NULL) {
- next = PyStackRef_FromPyObjectSteal(next_o);
- INSTRUMENTED_JUMP(this_instr, next_instr, PY_MONITORING_EVENT_BRANCH_LEFT);
- }
- else {
- if (_PyErr_Occurred(tstate)) {
- _PyFrame_SetStackPointer(frame, stack_pointer);
- int matches = _PyErr_ExceptionMatches(tstate, PyExc_StopIteration);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (!matches) {
- JUMP_TO_LABEL(error);
- }
- _PyFrame_SetStackPointer(frame, stack_pointer);
- _PyEval_MonitorRaise(tstate, frame, this_instr);
- _PyErr_Clear(tstate);
- stack_pointer = _PyFrame_GetStackPointer(frame);
+ if (!PyStackRef_IsValid(item)) {
+ if (PyStackRef_IsError(item)) {
+ JUMP_TO_LABEL(error);
}
- assert(next_instr[oparg].op.code == END_FOR ||
- next_instr[oparg].op.code == INSTRUMENTED_END_FOR);
JUMPBY(oparg + 1);
+ stack_pointer[-1] = null_or_index;
DISPATCH();
}
+ next = item;
+ INSTRUMENTED_JUMP(this_instr, next_instr, PY_MONITORING_EVENT_BRANCH_LEFT);
+ stack_pointer[-1] = null_or_index;
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -7324,9 +7311,12 @@
next_instr += 1;
INSTRUCTION_STATS(INSTRUMENTED_POP_ITER);
_PyStackRef iter;
- iter = stack_pointer[-1];
+ _PyStackRef index_or_null;
+ index_or_null = stack_pointer[-1];
+ iter = stack_pointer[-2];
+ (void)index_or_null;
INSTRUMENTED_JUMP(prev_instr, this_instr+1, PY_MONITORING_EVENT_BRANCH_RIGHT);
- stack_pointer += -1;
+ stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(iter);
@@ -7652,6 +7642,22 @@
tstate->current_frame = frame->previous;
assert(!_PyErr_Occurred(tstate));
PyObject *result = PyStackRef_AsPyObjectSteal(retval);
+ #if !Py_TAIL_CALL_INTERP
+ assert(frame == &entry.frame);
+ #endif
+ #ifdef _Py_TIER2
+ _PyStackRef executor = frame->localsplus[0];
+ assert(tstate->current_executor == NULL);
+ if (!PyStackRef_IsNull(executor)) {
+ tstate->current_executor = PyStackRef_AsPyObjectBorrow(executor);
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ PyStackRef_CLOSE(executor);
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ stack_pointer += 1;
+ }
+ #endif
LLTRACE_RESUME_FRAME();
return result;
}
@@ -7786,8 +7792,7 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
this_instr[1].counter = initial_jump_backoff_counter();
stack_pointer = _PyFrame_GetStackPointer(frame);
- assert(tstate->previous_executor == NULL);
- tstate->previous_executor = Py_None;
+ assert(tstate->current_executor == NULL);
GOTO_TIER_TWO(executor);
}
}
@@ -7936,7 +7941,7 @@
_Py_CODEUNIT* const this_instr = next_instr - 10;
(void)this_instr;
_PyStackRef owner;
- _PyStackRef attr;
+ _PyStackRef *attr;
_PyStackRef *self_or_null;
// _SPECIALIZE_LOAD_ATTR
{
@@ -7959,16 +7964,16 @@
/* Skip 8 cache entries */
// _LOAD_ATTR
{
+ attr = &stack_pointer[-1];
self_or_null = &stack_pointer[0];
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
- PyObject *attr_o;
if (oparg & 1) {
- attr_o = NULL;
+ *attr = PyStackRef_NULL;
_PyFrame_SetStackPointer(frame, stack_pointer);
- int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
+ int is_meth = _PyObject_GetMethodStackRef(tstate, PyStackRef_AsPyObjectBorrow(owner), name, attr);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (is_meth) {
- assert(attr_o != NULL);
+ assert(!PyStackRef_IsNull(*attr));
self_or_null[0] = owner;
}
else {
@@ -7977,7 +7982,7 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(owner);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (attr_o == NULL) {
+ if (PyStackRef_IsNull(*attr)) {
JUMP_TO_LABEL(error);
}
self_or_null[0] = PyStackRef_NULL;
@@ -7986,7 +7991,7 @@
}
else {
_PyFrame_SetStackPointer(frame, stack_pointer);
- attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+ PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
stack_pointer = _PyFrame_GetStackPointer(frame);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@@ -7996,11 +8001,10 @@
if (attr_o == NULL) {
JUMP_TO_LABEL(error);
}
+ *attr = PyStackRef_FromPyObjectSteal(attr_o);
stack_pointer += 1;
}
- attr = PyStackRef_FromPyObjectSteal(attr_o);
}
- stack_pointer[-1] = attr;
stack_pointer += (oparg&1);
assert(WITHIN_STACK_BOUNDS());
DISPATCH();
@@ -8640,7 +8644,7 @@
INSTRUCTION_STATS(LOAD_ATTR_PROPERTY);
static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size");
_PyStackRef owner;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -8691,8 +8695,9 @@
JUMP_TO_PREDICTED(LOAD_ATTR);
}
STAT_INC(LOAD_ATTR, hit);
- new_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
- new_frame->localsplus[0] = owner;
+ _PyInterpreterFrame *pushed_frame = _PyFrame_PushUnchecked(tstate, PyStackRef_FromPyObjectNew(fget), 1, frame);
+ pushed_frame->localsplus[0] = owner;
+ new_frame = PyStackRef_Wrap(pushed_frame);
}
// _SAVE_RETURN_OFFSET
{
@@ -8706,11 +8711,11 @@
// _PUSH_FRAME
{
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -8955,63 +8960,9 @@
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(LOAD_CONST);
- PREDICTED_LOAD_CONST:;
- _Py_CODEUNIT* const this_instr = next_instr - 1;
- (void)this_instr;
- _PyStackRef value;
- PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- value = PyStackRef_FromPyObjectNew(obj);
- #if ENABLE_SPECIALIZATION_FT
- #ifdef Py_GIL_DISABLED
- uint8_t expected = LOAD_CONST;
- if (!_Py_atomic_compare_exchange_uint8(
- &this_instr->op.code, &expected,
- _Py_IsImmortal(obj) ? LOAD_CONST_IMMORTAL : LOAD_CONST_MORTAL)) {
- assert(expected >= MIN_INSTRUMENTED_OPCODE);
- }
- #else
- if (this_instr->op.code == LOAD_CONST) {
- this_instr->op.code = _Py_IsImmortal(obj) ? LOAD_CONST_IMMORTAL : LOAD_CONST_MORTAL;
- }
- #endif
- #endif
- stack_pointer[0] = value;
- stack_pointer += 1;
- assert(WITHIN_STACK_BOUNDS());
- DISPATCH();
- }
-
- TARGET(LOAD_CONST_IMMORTAL) {
- #if Py_TAIL_CALL_INTERP
- int opcode = LOAD_CONST_IMMORTAL;
- (void)(opcode);
- #endif
- frame->instr_ptr = next_instr;
- next_instr += 1;
- INSTRUCTION_STATS(LOAD_CONST_IMMORTAL);
- static_assert(0 == 0, "incorrect cache size");
_PyStackRef value;
PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- assert(_Py_IsImmortal(obj));
- value = PyStackRef_FromPyObjectImmortal(obj);
- stack_pointer[0] = value;
- stack_pointer += 1;
- assert(WITHIN_STACK_BOUNDS());
- DISPATCH();
- }
-
- TARGET(LOAD_CONST_MORTAL) {
- #if Py_TAIL_CALL_INTERP
- int opcode = LOAD_CONST_MORTAL;
- (void)(opcode);
- #endif
- frame->instr_ptr = next_instr;
- next_instr += 1;
- INSTRUCTION_STATS(LOAD_CONST_MORTAL);
- static_assert(0 == 0, "incorrect cache size");
- _PyStackRef value;
- PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
- value = PyStackRef_FromPyObjectNewMortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -9546,7 +9497,7 @@
_PyStackRef value;
assert(oparg < _PY_NSMALLPOSINTS);
PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];
- value = PyStackRef_FromPyObjectImmortal(obj);
+ value = PyStackRef_FromPyObjectBorrow(obj);
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -10129,12 +10080,15 @@
frame->instr_ptr = next_instr;
next_instr += 1;
INSTRUCTION_STATS(POP_ITER);
- _PyStackRef value;
- value = stack_pointer[-1];
- stack_pointer += -1;
+ _PyStackRef iter;
+ _PyStackRef index_or_null;
+ index_or_null = stack_pointer[-1];
+ iter = stack_pointer[-2];
+ (void)index_or_null;
+ stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(value);
+ PyStackRef_CLOSE(iter);
stack_pointer = _PyFrame_GetStackPointer(frame);
DISPATCH();
}
@@ -10282,7 +10236,7 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(value);
+ PyStackRef_XCLOSE(value);
stack_pointer = _PyFrame_GetStackPointer(frame);
DISPATCH();
}
@@ -10702,8 +10656,8 @@
static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size");
_PyStackRef receiver;
_PyStackRef v;
- _PyInterpreterFrame *gen_frame;
- _PyInterpreterFrame *new_frame;
+ _PyStackRef gen_frame;
+ _PyStackRef new_frame;
/* Skip 1 cache entry */
// _CHECK_PEP_523
{
@@ -10729,24 +10683,25 @@
JUMP_TO_PREDICTED(SEND);
}
STAT_INC(SEND, hit);
- gen_frame = &gen->gi_iframe;
- _PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
+ _PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
+ _PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert( 2 + oparg <= UINT16_MAX);
frame->return_offset = (uint16_t)( 2 + oparg);
- gen_frame->previous = frame;
+ pushed_frame->previous = frame;
+ gen_frame = PyStackRef_Wrap(pushed_frame);
}
// _PUSH_FRAME
{
new_frame = gen_frame;
assert(tstate->interp->eval_frame == NULL);
- _PyInterpreterFrame *temp = new_frame;
+ _PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
- assert(new_frame->previous == frame || new_frame->previous->previous == frame);
+ assert(temp->previous == frame || temp->previous->previous == frame);
CALL_STAT_INC(inlined_py_calls);
frame = tstate->current_frame = temp;
tstate->py_recursion_remaining--;
@@ -11193,10 +11148,6 @@
INSTRUCTION_STATS(STORE_FAST);
_PyStackRef value;
value = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value)
- );
_PyStackRef tmp = GETLOCAL(oparg);
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -11218,10 +11169,6 @@
_PyStackRef value1;
_PyStackRef value2;
value1 = stack_pointer[-1];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value1)
- );
uint32_t oparg1 = oparg >> 4;
uint32_t oparg2 = oparg & 15;
_PyStackRef tmp = GETLOCAL(oparg1);
@@ -11246,14 +11193,6 @@
_PyStackRef value1;
value1 = stack_pointer[-1];
value2 = stack_pointer[-2];
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value1)
- );
- assert(
- ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_GENERATOR)) == 0) ||
- PyStackRef_IsHeapSafe(value2)
- );
uint32_t oparg1 = oparg >> 4;
uint32_t oparg2 = oparg & 15;
_PyStackRef tmp = GETLOCAL(oparg1);
@@ -11533,7 +11472,7 @@
{
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
- if (!PyLong_CheckExact(value_o)) {
+ if (!_PyLong_CheckExactAndCompact(value_o)) {
UPDATE_MISS_STATS(STORE_SUBSCR);
assert(_PyOpcode_Deopt[opcode] == (STORE_SUBSCR));
JUMP_TO_PREDICTED(STORE_SUBSCR);
@@ -11610,7 +11549,6 @@
_PyStackRef temp = bottom;
bottom = top;
top = temp;
- assert(oparg >= 2);
stack_pointer[-2 - (oparg-2)] = bottom;
stack_pointer[-1] = top;
DISPATCH();
@@ -12424,6 +12362,17 @@ JUMP_TO_LABEL(error);
frame->return_offset = 0;
if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
tstate->current_frame = frame->previous;
+ #if !Py_TAIL_CALL_INTERP
+ assert(frame == &entry.frame);
+ #endif
+ #ifdef _Py_TIER2
+ _PyStackRef executor = frame->localsplus[0];
+ assert(tstate->current_executor == NULL);
+ if (!PyStackRef_IsNull(executor)) {
+ tstate->current_executor = PyStackRef_AsPyObjectBorrow(executor);
+ PyStackRef_CLOSE(executor);
+ }
+ #endif
return NULL;
}
next_instr = frame->instr_ptr;
diff --git a/Python/getopt.c b/Python/getopt.c
index 39a6938dec7..79bea2359ff 100644
--- a/Python/getopt.c
+++ b/Python/getopt.c
@@ -37,7 +37,7 @@ static const wchar_t *opt_ptr = L"";
/* Python command line short and long options */
-#define SHORT_OPTS L"bBc:dEhiIJm:OPqRsStuvVW:xX:?"
+#define SHORT_OPTS L"bBc:dEhiIm:OPqRsStuvVW:xX:?"
static const _PyOS_LongOption longopts[] = {
/* name, has_arg, val (used in switch in initconfig.c) */
@@ -133,13 +133,6 @@ int _PyOS_GetOpt(Py_ssize_t argc, wchar_t * const *argv, int *longindex)
return opt->val;
}
- if (option == 'J') {
- if (_PyOS_opterr) {
- fprintf(stderr, "-J is reserved for Jython\n");
- }
- return '_';
- }
-
if ((ptr = wcschr(SHORT_OPTS, option)) == NULL) {
if (_PyOS_opterr) {
fprintf(stderr, "Unknown option: -%c\n", (char)option);
diff --git a/Python/getversion.c b/Python/getversion.c
index 226b2f999a6..8d8bc6ea700 100644
--- a/Python/getversion.c
+++ b/Python/getversion.c
@@ -15,7 +15,7 @@ void _Py_InitVersion(void)
}
initialized = 1;
#ifdef Py_GIL_DISABLED
- const char *buildinfo_format = "%.80s experimental free-threading build (%.80s) %.80s";
+ const char *buildinfo_format = "%.80s free-threading build (%.80s) %.80s";
#else
const char *buildinfo_format = "%.80s (%.80s) %.80s";
#endif
diff --git a/Python/hamt.c b/Python/hamt.c
index f9bbf63961d..906149cc6cd 100644
--- a/Python/hamt.c
+++ b/Python/hamt.c
@@ -1176,7 +1176,7 @@ hamt_node_bitmap_dump(PyHamtNode_Bitmap *node,
}
if (key_or_null == NULL) {
- if (PyUnicodeWriter_WriteUTF8(writer, "NULL:\n", -1) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, "NULL:\n", 6) < 0) {
goto error;
}
@@ -1194,7 +1194,7 @@ hamt_node_bitmap_dump(PyHamtNode_Bitmap *node,
}
}
- if (PyUnicodeWriter_WriteUTF8(writer, "\n", 1) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, "\n", 1) < 0) {
goto error;
}
}
@@ -1915,7 +1915,7 @@ hamt_node_array_dump(PyHamtNode_Array *node,
goto error;
}
- if (PyUnicodeWriter_WriteUTF8(writer, "\n", 1) < 0) {
+ if (PyUnicodeWriter_WriteASCII(writer, "\n", 1) < 0) {
goto error;
}
}
diff --git a/Python/import.c b/Python/import.c
index a671a08daeb..73b94d0dd2a 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -103,6 +103,15 @@ static struct _inittab *inittab_copy = NULL;
#define FIND_AND_LOAD(interp) \
(interp)->imports.find_and_load
+#define _IMPORT_TIME_HEADER(interp) \
+ do { \
+ if (FIND_AND_LOAD((interp)).header) { \
+ fputs("import time: self [us] | cumulative | imported package\n", \
+ stderr); \
+ FIND_AND_LOAD((interp)).header = 0; \
+ } \
+ } while (0)
+
/*******************/
/* the import lock */
@@ -246,9 +255,13 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n
rc = _PyModuleSpec_IsInitializing(spec);
Py_DECREF(spec);
}
- if (rc <= 0) {
+ if (rc == 0) {
+ goto done;
+ }
+ else if (rc < 0) {
return rc;
}
+
/* Wait until module is done importing. */
PyObject *value = PyObject_CallMethodOneArg(
IMPORTLIB(interp), &_Py_ID(_lock_unlock_module), name);
@@ -256,6 +269,19 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n
return -1;
}
Py_DECREF(value);
+
+done:
+ /* When -X importtime=2, print an import time entry even if an
+ imported module has already been loaded.
+ */
+ if (_PyInterpreterState_GetConfig(interp)->import_time == 2) {
+ _IMPORT_TIME_HEADER(interp);
+#define import_level FIND_AND_LOAD(interp).import_level
+ fprintf(stderr, "import time: cached | cached | %*s\n",
+ import_level*2, PyUnicode_AsUTF8(name));
+#undef import_level
+ }
+
return 0;
}
@@ -3343,11 +3369,11 @@ PyObject *
PyImport_GetImporter(PyObject *path)
{
PyThreadState *tstate = _PyThreadState_GET();
- PyObject *path_importer_cache = _PySys_GetRequiredAttrString("path_importer_cache");
+ PyObject *path_importer_cache = PySys_GetAttrString("path_importer_cache");
if (path_importer_cache == NULL) {
return NULL;
}
- PyObject *path_hooks = _PySys_GetRequiredAttrString("path_hooks");
+ PyObject *path_hooks = PySys_GetAttrString("path_hooks");
if (path_hooks == NULL) {
Py_DECREF(path_importer_cache);
return NULL;
@@ -3408,8 +3434,10 @@ PyImport_ImportModule(const char *name)
* ImportError instead of blocking.
*
* Returns the module object with incremented ref count.
+ *
+ * Removed in 3.15, but kept for stable ABI compatibility.
*/
-PyObject *
+PyAPI_FUNC(PyObject *)
PyImport_ImportModuleNoBlock(const char *name)
{
if (PyErr_WarnEx(PyExc_DeprecationWarning,
@@ -3654,14 +3682,14 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name)
PyTime_t t1 = 0, accumulated_copy = accumulated;
PyObject *sys_path, *sys_meta_path, *sys_path_hooks;
- if (_PySys_GetOptionalAttrString("path", &sys_path) < 0) {
+ if (PySys_GetOptionalAttrString("path", &sys_path) < 0) {
return NULL;
}
- if (_PySys_GetOptionalAttrString("meta_path", &sys_meta_path) < 0) {
+ if (PySys_GetOptionalAttrString("meta_path", &sys_meta_path) < 0) {
Py_XDECREF(sys_path);
return NULL;
}
- if (_PySys_GetOptionalAttrString("path_hooks", &sys_path_hooks) < 0) {
+ if (PySys_GetOptionalAttrString("path_hooks", &sys_path_hooks) < 0) {
Py_XDECREF(sys_meta_path);
Py_XDECREF(sys_path);
return NULL;
@@ -3686,13 +3714,7 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name)
* _PyDict_GetItemIdWithError().
*/
if (import_time) {
-#define header FIND_AND_LOAD(interp).header
- if (header) {
- fputs("import time: self [us] | cumulative | imported package\n",
- stderr);
- header = 0;
- }
-#undef header
+ _IMPORT_TIME_HEADER(interp);
import_level++;
// ignore error: don't block import if reading the clock fails
@@ -3832,15 +3854,17 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
}
final_mod = import_get_module(tstate, to_return);
- Py_DECREF(to_return);
if (final_mod == NULL) {
if (!_PyErr_Occurred(tstate)) {
_PyErr_Format(tstate, PyExc_KeyError,
"%R not in sys.modules as expected",
to_return);
}
+ Py_DECREF(to_return);
goto error;
}
+
+ Py_DECREF(to_return);
}
}
else {
@@ -3936,23 +3960,28 @@ PyImport_Import(PyObject *module_name)
}
/* Get the builtins from current globals */
- globals = PyEval_GetGlobals();
+ globals = PyEval_GetGlobals(); // borrowed
if (globals != NULL) {
Py_INCREF(globals);
+ // XXX Use _PyEval_EnsureBuiltins()?
builtins = PyObject_GetItem(globals, &_Py_ID(__builtins__));
- if (builtins == NULL)
+ if (builtins == NULL) {
+ // XXX Fall back to interp->builtins or sys.modules['builtins']?
goto err;
+ }
+ }
+ else if (_PyErr_Occurred(tstate)) {
+ goto err;
}
else {
/* No globals -- use standard builtins, and fake globals */
- builtins = PyImport_ImportModuleLevel("builtins",
- NULL, NULL, NULL, 0);
- if (builtins == NULL) {
+ globals = PyDict_New();
+ if (globals == NULL) {
goto err;
}
- globals = Py_BuildValue("{OO}", &_Py_ID(__builtins__), builtins);
- if (globals == NULL)
+ if (_PyEval_EnsureBuiltinsWithModule(tstate, globals, &builtins) < 0) {
goto err;
+ }
}
/* Get the __import__ function from the builtins */
@@ -4103,7 +4132,7 @@ _PyImport_FiniCore(PyInterpreterState *interp)
static int
init_zipimport(PyThreadState *tstate, int verbose)
{
- PyObject *path_hooks = _PySys_GetRequiredAttrString("path_hooks");
+ PyObject *path_hooks = PySys_GetAttrString("path_hooks");
if (path_hooks == NULL) {
return -1;
}
diff --git a/Python/index_pool.c b/Python/index_pool.c
index 007c81a0fc1..520a65938ec 100644
--- a/Python/index_pool.c
+++ b/Python/index_pool.c
@@ -172,6 +172,9 @@ _PyIndexPool_AllocIndex(_PyIndexPool *pool)
else {
index = heap_pop(free_indices);
}
+
+ pool->tlbc_generation++;
+
UNLOCK_POOL(pool);
return index;
}
@@ -180,6 +183,7 @@ void
_PyIndexPool_FreeIndex(_PyIndexPool *pool, int32_t index)
{
LOCK_POOL(pool);
+ pool->tlbc_generation++;
heap_add(&pool->free_indices, index);
UNLOCK_POOL(pool);
}
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 7d3043dd5d1..71d7cfed5c4 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -153,7 +153,7 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
SPEC(home, WSTR_OPT, READ_ONLY, NO_SYS),
SPEC(thread_inherit_context, INT, READ_ONLY, NO_SYS),
SPEC(context_aware_warnings, INT, READ_ONLY, NO_SYS),
- SPEC(import_time, BOOL, READ_ONLY, NO_SYS),
+ SPEC(import_time, UINT, READ_ONLY, NO_SYS),
SPEC(install_signal_handlers, BOOL, READ_ONLY, NO_SYS),
SPEC(isolated, BOOL, READ_ONLY, NO_SYS), // sys.flags.isolated
#ifdef MS_WINDOWS
@@ -312,7 +312,8 @@ The following implementation-specific options are available:\n\
"-X gil=[0|1]: enable (1) or disable (0) the GIL; also PYTHON_GIL\n"
#endif
"\
--X importtime: show how long each import takes; also PYTHONPROFILEIMPORTTIME\n\
+-X importtime[=2]: show how long each import takes; use -X importtime=2 to\
+ log imports of already-loaded modules; also PYTHONPROFILEIMPORTTIME\n\
-X int_max_str_digits=N: limit the size of int<->str conversions;\n\
0 disables the limit; also PYTHONINTMAXSTRDIGITS\n\
-X no_debug_ranges: don't include extra location information in code objects;\n\
@@ -1004,6 +1005,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
memset(config, 0, sizeof(*config));
config->_config_init = (int)_PyConfig_INIT_COMPAT;
+ config->import_time = -1;
config->isolated = -1;
config->use_environment = -1;
config->dev_mode = -1;
@@ -2246,6 +2248,38 @@ config_init_run_presite(PyConfig *config)
}
#endif
+static PyStatus
+config_init_import_time(PyConfig *config)
+{
+ int importtime = 0;
+
+ const char *env = config_get_env(config, "PYTHONPROFILEIMPORTTIME");
+ if (env) {
+ if (_Py_str_to_int(env, &importtime) != 0) {
+ importtime = 1;
+ }
+ if (importtime < 0 || importtime > 2) {
+ return _PyStatus_ERR(
+ "PYTHONPROFILEIMPORTTIME: numeric values other than 1 and 2 "
+ "are reserved for future use.");
+ }
+ }
+
+ const wchar_t *x_value = config_get_xoption_value(config, L"importtime");
+ if (x_value) {
+ if (*x_value == 0 || config_wstr_to_int(x_value, &importtime) != 0) {
+ importtime = 1;
+ }
+ if (importtime < 0 || importtime > 2) {
+ return _PyStatus_ERR(
+ "-X importtime: values other than 1 and 2 "
+ "are reserved for future use.");
+ }
+ }
+
+ config->import_time = importtime;
+ return _PyStatus_OK();
+}
static PyStatus
config_read_complex_options(PyConfig *config)
@@ -2257,17 +2291,19 @@ config_read_complex_options(PyConfig *config)
config->faulthandler = 1;
}
}
- if (config_get_env(config, "PYTHONPROFILEIMPORTTIME")
- || config_get_xoption(config, L"importtime")) {
- config->import_time = 1;
- }
-
if (config_get_env(config, "PYTHONNODEBUGRANGES")
|| config_get_xoption(config, L"no_debug_ranges")) {
config->code_debug_ranges = 0;
}
PyStatus status;
+ if (config->import_time < 0) {
+ status = config_init_import_time(config);
+ if (_PyStatus_EXCEPTION(status)) {
+ return status;
+ }
+ }
+
if (config->tracemalloc < 0) {
status = config_init_tracemalloc(config);
if (_PyStatus_EXCEPTION(status)) {
@@ -2926,8 +2962,6 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions,
/* option handled by _PyPreCmdline_Read() */
break;
- /* case 'J': reserved for Jython */
-
case 'O':
config->optimization_level++;
break;
@@ -3613,7 +3647,7 @@ _Py_DumpPathConfig(PyThreadState *tstate)
#define DUMP_SYS(NAME) \
do { \
PySys_FormatStderr(" sys.%s = ", #NAME); \
- if (_PySys_GetOptionalAttrString(#NAME, &obj) < 0) { \
+ if (PySys_GetOptionalAttrString(#NAME, &obj) < 0) { \
PyErr_Clear(); \
} \
if (obj != NULL) { \
@@ -3637,7 +3671,7 @@ _Py_DumpPathConfig(PyThreadState *tstate)
#undef DUMP_SYS
PyObject *sys_path;
- (void) _PySys_GetOptionalAttrString("path", &sys_path);
+ (void) PySys_GetOptionalAttrString("path", &sys_path);
if (sys_path != NULL && PyList_Check(sys_path)) {
PySys_WriteStderr(" sys.path = [\n");
Py_ssize_t len = PyList_GET_SIZE(sys_path);
@@ -4260,7 +4294,7 @@ _PyConfig_CreateXOptionsDict(const PyConfig *config)
static int
config_get_sys_write_bytecode(const PyConfig *config, int *value)
{
- PyObject *attr = _PySys_GetRequiredAttrString("dont_write_bytecode");
+ PyObject *attr = PySys_GetAttrString("dont_write_bytecode");
if (attr == NULL) {
return -1;
}
@@ -4281,7 +4315,7 @@ config_get(const PyConfig *config, const PyConfigSpec *spec,
{
if (use_sys) {
if (spec->sys.attr != NULL) {
- return _PySys_GetRequiredAttrString(spec->sys.attr);
+ return PySys_GetAttrString(spec->sys.attr);
}
if (strcmp(spec->name, "write_bytecode") == 0) {
diff --git a/Python/intrinsics.c b/Python/intrinsics.c
index ff44ba0ee64..8ea920e690c 100644
--- a/Python/intrinsics.c
+++ b/Python/intrinsics.c
@@ -9,7 +9,6 @@
#include "pycore_intrinsics.h" // INTRINSIC_PRINT
#include "pycore_pyerrors.h" // _PyErr_SetString()
#include "pycore_runtime.h" // _Py_ID()
-#include "pycore_sysmodule.h" // _PySys_GetRequiredAttr()
#include "pycore_tuple.h" // _PyTuple_FromArray()
#include "pycore_typevarobject.h" // _Py_make_typevar()
#include "pycore_unicodeobject.h" // _PyUnicode_FromASCII()
@@ -27,7 +26,7 @@ no_intrinsic1(PyThreadState* tstate, PyObject *unused)
static PyObject *
print_expr(PyThreadState* Py_UNUSED(ignored), PyObject *value)
{
- PyObject *hook = _PySys_GetRequiredAttr(&_Py_ID(displayhook));
+ PyObject *hook = PySys_GetAttr(&_Py_ID(displayhook));
if (hook == NULL) {
return NULL;
}
diff --git a/Python/lock.c b/Python/lock.c
index 28a12ad1835..a49d587a168 100644
--- a/Python/lock.c
+++ b/Python/lock.c
@@ -58,7 +58,7 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
return PY_LOCK_ACQUIRED;
}
}
- else if (timeout == 0) {
+ if (timeout == 0) {
return PY_LOCK_FAILURE;
}
@@ -95,6 +95,18 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
if (timeout == 0) {
return PY_LOCK_FAILURE;
}
+ if ((flags & _PY_LOCK_PYTHONLOCK) && Py_IsFinalizing()) {
+ // At this phase of runtime shutdown, only the finalization thread
+ // can have attached thread state; others hang if they try
+ // attaching. And since operations on this lock requires attached
+ // thread state (_PY_LOCK_PYTHONLOCK), the finalization thread is
+ // running this code, and no other thread can unlock.
+ // Raise rather than hang. (_PY_LOCK_PYTHONLOCK allows raising
+ // exceptons.)
+ PyErr_SetString(PyExc_PythonFinalizationError,
+ "cannot acquire lock at interpreter finalization");
+ return PY_LOCK_FAILURE;
+ }
uint8_t newv = v;
if (!(v & _Py_HAS_PARKED)) {
@@ -119,6 +131,9 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
return PY_LOCK_INTR;
}
}
+ else if (ret == Py_PARK_INTR && (flags & _PY_FAIL_IF_INTERRUPTED)) {
+ return PY_LOCK_INTR;
+ }
else if (ret == Py_PARK_TIMEOUT) {
assert(timeout >= 0);
return PY_LOCK_FAILURE;
@@ -619,3 +634,11 @@ PyMutex_Unlock(PyMutex *m)
Py_FatalError("unlocking mutex that is not locked");
}
}
+
+
+#undef PyMutex_IsLocked
+int
+PyMutex_IsLocked(PyMutex *m)
+{
+ return _PyMutex_IsLocked(m);
+}
diff --git a/Python/marshal.c b/Python/marshal.c
index b39c1a5b1ad..15dd25d6268 100644
--- a/Python/marshal.c
+++ b/Python/marshal.c
@@ -38,7 +38,7 @@ module marshal
* On Windows PGO builds, the r_object function overallocates its stack and
* can cause a stack overflow. We reduce the maximum depth for all Windows
* releases to protect against this.
- * #if defined(MS_WINDOWS) && defined(_DEBUG)
+ * #if defined(MS_WINDOWS) && defined(Py_DEBUG)
*/
#if defined(MS_WINDOWS)
# define MAX_MARSHAL_STACK_DEPTH 1000
@@ -1656,6 +1656,9 @@ r_object(RFILE *p)
case TYPE_SLICE:
{
Py_ssize_t idx = r_ref_reserve(flag, p);
+ if (idx < 0) {
+ break;
+ }
PyObject *stop = NULL;
PyObject *step = NULL;
PyObject *start = r_object(p);
diff --git a/Python/modsupport.c b/Python/modsupport.c
index 2caf595949d..437ad412027 100644
--- a/Python/modsupport.c
+++ b/Python/modsupport.c
@@ -669,5 +669,5 @@ Py_PACK_FULL_VERSION(int x, int y, int z, int level, int serial)
uint32_t
Py_PACK_VERSION(int x, int y)
{
- return Py_PACK_FULL_VERSION(x, y, 0, 0, 0);
+ return _Py_PACK_VERSION(x, y);
}
diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h
index 8af445d7d6a..1d6dcddab4b 100644
--- a/Python/opcode_targets.h
+++ b/Python/opcode_targets.h
@@ -190,8 +190,6 @@ static void *opcode_targets[256] = {
&&TARGET_LOAD_ATTR_PROPERTY,
&&TARGET_LOAD_ATTR_SLOT,
&&TARGET_LOAD_ATTR_WITH_HINT,
- &&TARGET_LOAD_CONST_IMMORTAL,
- &&TARGET_LOAD_CONST_MORTAL,
&&TARGET_LOAD_GLOBAL_BUILTIN,
&&TARGET_LOAD_GLOBAL_MODULE,
&&TARGET_LOAD_SUPER_ATTR_ATTR,
@@ -234,6 +232,8 @@ static void *opcode_targets[256] = {
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
+ &&_unknown_opcode,
+ &&_unknown_opcode,
&&TARGET_INSTRUMENTED_END_FOR,
&&TARGET_INSTRUMENTED_POP_ITER,
&&TARGET_INSTRUMENTED_END_SEND,
@@ -410,8 +410,6 @@ Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_ATTR_WITH_HINT(TAIL_CALL_PA
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_BUILD_CLASS(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_COMMON_CONSTANT(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_CONST(TAIL_CALL_PARAMS);
-Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_CONST_IMMORTAL(TAIL_CALL_PARAMS);
-Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_CONST_MORTAL(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_DEREF(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_FAST(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LOAD_FAST_AND_CLEAR(TAIL_CALL_PARAMS);
@@ -649,8 +647,6 @@ static py_tail_call_funcptr INSTRUCTION_TABLE[256] = {
[LOAD_BUILD_CLASS] = _TAIL_CALL_LOAD_BUILD_CLASS,
[LOAD_COMMON_CONSTANT] = _TAIL_CALL_LOAD_COMMON_CONSTANT,
[LOAD_CONST] = _TAIL_CALL_LOAD_CONST,
- [LOAD_CONST_IMMORTAL] = _TAIL_CALL_LOAD_CONST_IMMORTAL,
- [LOAD_CONST_MORTAL] = _TAIL_CALL_LOAD_CONST_MORTAL,
[LOAD_DEREF] = _TAIL_CALL_LOAD_DEREF,
[LOAD_FAST] = _TAIL_CALL_LOAD_FAST,
[LOAD_FAST_AND_CLEAR] = _TAIL_CALL_LOAD_FAST_AND_CLEAR,
@@ -740,6 +736,8 @@ static py_tail_call_funcptr INSTRUCTION_TABLE[256] = {
[125] = _TAIL_CALL_UNKNOWN_OPCODE,
[126] = _TAIL_CALL_UNKNOWN_OPCODE,
[127] = _TAIL_CALL_UNKNOWN_OPCODE,
+ [210] = _TAIL_CALL_UNKNOWN_OPCODE,
+ [211] = _TAIL_CALL_UNKNOWN_OPCODE,
[212] = _TAIL_CALL_UNKNOWN_OPCODE,
[213] = _TAIL_CALL_UNKNOWN_OPCODE,
[214] = _TAIL_CALL_UNKNOWN_OPCODE,
diff --git a/Python/optimizer.c b/Python/optimizer.c
index f8d0aa04b9e..8d01d605ef4 100644
--- a/Python/optimizer.c
+++ b/Python/optimizer.c
@@ -204,16 +204,74 @@ get_oparg(PyObject *self, PyObject *Py_UNUSED(ignored))
static int executor_clear(PyObject *executor);
static void unlink_executor(_PyExecutorObject *executor);
+
+static void
+free_executor(_PyExecutorObject *self)
+{
+#ifdef _Py_JIT
+ _PyJIT_Free(self);
+#endif
+ PyObject_GC_Del(self);
+}
+
+void
+_Py_ClearExecutorDeletionList(PyInterpreterState *interp)
+{
+ _PyRuntimeState *runtime = &_PyRuntime;
+ HEAD_LOCK(runtime);
+ PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
+ HEAD_UNLOCK(runtime);
+ while (ts) {
+ _PyExecutorObject *current = (_PyExecutorObject *)ts->current_executor;
+ if (current != NULL) {
+ /* Anything in this list will be unlinked, so we can reuse the
+ * linked field as a reachability marker. */
+ current->vm_data.linked = 1;
+ }
+ HEAD_LOCK(runtime);
+ ts = PyThreadState_Next(ts);
+ HEAD_UNLOCK(runtime);
+ }
+ _PyExecutorObject **prev_to_next_ptr = &interp->executor_deletion_list_head;
+ _PyExecutorObject *exec = *prev_to_next_ptr;
+ while (exec != NULL) {
+ if (exec->vm_data.linked) {
+ // This executor is currently executing
+ exec->vm_data.linked = 0;
+ prev_to_next_ptr = &exec->vm_data.links.next;
+ }
+ else {
+ *prev_to_next_ptr = exec->vm_data.links.next;
+ free_executor(exec);
+ }
+ exec = *prev_to_next_ptr;
+ }
+ interp->executor_deletion_list_remaining_capacity = EXECUTOR_DELETE_LIST_MAX;
+}
+
+static void
+add_to_pending_deletion_list(_PyExecutorObject *self)
+{
+ PyInterpreterState *interp = PyInterpreterState_Get();
+ self->vm_data.links.next = interp->executor_deletion_list_head;
+ interp->executor_deletion_list_head = self;
+ if (interp->executor_deletion_list_remaining_capacity > 0) {
+ interp->executor_deletion_list_remaining_capacity--;
+ }
+ else {
+ _Py_ClearExecutorDeletionList(interp);
+ }
+}
+
static void
uop_dealloc(PyObject *op) {
_PyExecutorObject *self = _PyExecutorObject_CAST(op);
_PyObject_GC_UNTRACK(self);
assert(self->vm_data.code == NULL);
unlink_executor(self);
-#ifdef _Py_JIT
- _PyJIT_Free(self);
-#endif
- PyObject_GC_Del(self);
+ // Once unlinked it becomes impossible to invalidate an executor, so do it here.
+ self->vm_data.valid = 0;
+ add_to_pending_deletion_list(self);
}
const char *
@@ -1234,8 +1292,8 @@ uop_optimize(
for (int pc = 0; pc < length; pc++) {
int opcode = buffer[pc].opcode;
int oparg = buffer[pc].oparg;
- if (oparg < _PyUop_Replication[opcode]) {
- buffer[pc].opcode = opcode + oparg + 1;
+ if (oparg < _PyUop_Replication[opcode].stop && oparg >= _PyUop_Replication[opcode].start) {
+ buffer[pc].opcode = opcode + oparg + 1 - _PyUop_Replication[opcode].start;
assert(strncmp(_PyOpcode_uop_name[buffer[pc].opcode], _PyOpcode_uop_name[opcode], strlen(_PyOpcode_uop_name[opcode])) == 0);
}
else if (is_terminator(&buffer[pc])) {
diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c
index 8b0bd1e9518..fab6fef5ccd 100644
--- a/Python/optimizer_analysis.c
+++ b/Python/optimizer_analysis.c
@@ -26,6 +26,8 @@
#include "pycore_function.h"
#include "pycore_uop_ids.h"
#include "pycore_range.h"
+#include "pycore_unicodeobject.h"
+#include "pycore_ceval.h"
#include <stdarg.h>
#include <stdbool.h>
@@ -103,6 +105,10 @@ convert_global_to_const(_PyUOpInstruction *inst, PyObject *obj, bool pop)
if ((int)index >= dict->ma_keys->dk_nentries) {
return NULL;
}
+ PyDictKeysObject *keys = dict->ma_keys;
+ if (keys->dk_version != inst->operand0) {
+ return NULL;
+ }
PyObject *res = entries[index].me_value;
if (res == NULL) {
return NULL;
@@ -317,7 +323,10 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer,
/* Shortened forms for convenience, used in optimizer_bytecodes.c */
#define sym_is_not_null _Py_uop_sym_is_not_null
#define sym_is_const _Py_uop_sym_is_const
+#define sym_is_safe_const _Py_uop_sym_is_safe_const
#define sym_get_const _Py_uop_sym_get_const
+#define sym_new_const_steal _Py_uop_sym_new_const_steal
+#define sym_get_const_as_stackref _Py_uop_sym_get_const_as_stackref
#define sym_new_unknown _Py_uop_sym_new_unknown
#define sym_new_not_null _Py_uop_sym_new_not_null
#define sym_new_type _Py_uop_sym_new_type
@@ -333,6 +342,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer,
#define sym_set_type(SYM, TYPE) _Py_uop_sym_set_type(ctx, SYM, TYPE)
#define sym_set_type_version(SYM, VERSION) _Py_uop_sym_set_type_version(ctx, SYM, VERSION)
#define sym_set_const(SYM, CNST) _Py_uop_sym_set_const(ctx, SYM, CNST)
+#define sym_set_compact_int(SYM) _Py_uop_sym_set_compact_int(ctx, SYM)
#define sym_is_bottom _Py_uop_sym_is_bottom
#define sym_truthiness _Py_uop_sym_truthiness
#define frame_new _Py_uop_frame_new
@@ -340,15 +350,19 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer,
#define sym_new_tuple _Py_uop_sym_new_tuple
#define sym_tuple_getitem _Py_uop_sym_tuple_getitem
#define sym_tuple_length _Py_uop_sym_tuple_length
-#define sym_is_immortal _Py_uop_sym_is_immortal
+#define sym_is_immortal _Py_uop_symbol_is_immortal
+#define sym_is_compact_int _Py_uop_sym_is_compact_int
+#define sym_new_compact_int _Py_uop_sym_new_compact_int
#define sym_new_truthiness _Py_uop_sym_new_truthiness
+#define JUMP_TO_LABEL(label) goto label;
+
static int
optimize_to_bool(
_PyUOpInstruction *this_instr,
JitOptContext *ctx,
- JitOptSymbol *value,
- JitOptSymbol **result_ptr)
+ JitOptRef value,
+ JitOptRef *result_ptr)
{
if (sym_matches_type(value, &PyBool_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -375,6 +389,23 @@ eliminate_pop_guard(_PyUOpInstruction *this_instr, bool exit)
}
}
+static JitOptRef
+lookup_attr(JitOptContext *ctx, _PyUOpInstruction *this_instr,
+ PyTypeObject *type, PyObject *name, uint16_t immortal,
+ uint16_t mortal)
+{
+ // The cached value may be dead, so we need to do the lookup again... :(
+ if (type && PyType_Check(type)) {
+ PyObject *lookup = _PyType_Lookup(type, name);
+ if (lookup) {
+ int opcode = _Py_IsImmortal(lookup) ? immortal : mortal;
+ REPLACE_OP(this_instr, opcode, 0, (uintptr_t)lookup);
+ return sym_new_const(ctx, lookup);
+ }
+ }
+ return sym_new_not_null(ctx);
+}
+
/* _PUSH_FRAME/_RETURN_VALUE's operand can be 0, a PyFunctionObject *, or a
* PyCodeObject *. Retrieve the code object if possible.
*/
@@ -423,6 +454,13 @@ get_code_with_logging(_PyUOpInstruction *op)
return co;
}
+// TODO (gh-134584) generate most of this table automatically
+const uint16_t op_without_decref_inputs[MAX_UOP_ID + 1] = {
+ [_BINARY_OP_MULTIPLY_FLOAT] = _BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS,
+ [_BINARY_OP_ADD_FLOAT] = _BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS,
+ [_BINARY_OP_SUBTRACT_FLOAT] = _BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS,
+};
+
/* 1 for success, 0 for not ready, cannot error at the moment. */
static int
optimize_uops(
@@ -460,7 +498,7 @@ optimize_uops(
int oparg = this_instr->oparg;
opcode = this_instr->opcode;
- JitOptSymbol **stack_pointer = ctx->frame->stack_pointer;
+ JitOptRef *stack_pointer = ctx->frame->stack_pointer;
#ifdef Py_DEBUG
if (get_lltrace() >= 3) {
@@ -523,6 +561,45 @@ error:
}
+const uint16_t op_without_push[MAX_UOP_ID + 1] = {
+ [_COPY] = _NOP,
+ [_LOAD_CONST_INLINE] = _NOP,
+ [_LOAD_CONST_INLINE_BORROW] = _NOP,
+ [_LOAD_CONST_UNDER_INLINE] = _POP_TOP_LOAD_CONST_INLINE,
+ [_LOAD_CONST_UNDER_INLINE_BORROW] = _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ [_LOAD_FAST] = _NOP,
+ [_LOAD_FAST_BORROW] = _NOP,
+ [_LOAD_SMALL_INT] = _NOP,
+ [_POP_TOP_LOAD_CONST_INLINE] = _POP_TOP,
+ [_POP_TOP_LOAD_CONST_INLINE_BORROW] = _POP_TOP,
+ [_POP_TWO_LOAD_CONST_INLINE_BORROW] = _POP_TWO,
+ [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = _POP_CALL_TWO,
+};
+
+const bool op_skip[MAX_UOP_ID + 1] = {
+ [_NOP] = true,
+ [_CHECK_VALIDITY] = true,
+ [_CHECK_PERIODIC] = true,
+ [_SET_IP] = true,
+};
+
+const uint16_t op_without_pop[MAX_UOP_ID + 1] = {
+ [_POP_TOP] = _NOP,
+ [_POP_TOP_LOAD_CONST_INLINE] = _LOAD_CONST_INLINE,
+ [_POP_TOP_LOAD_CONST_INLINE_BORROW] = _LOAD_CONST_INLINE_BORROW,
+ [_POP_TWO] = _POP_TOP,
+ [_POP_TWO_LOAD_CONST_INLINE_BORROW] = _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW,
+ [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW] = _POP_CALL_LOAD_CONST_INLINE_BORROW,
+ [_POP_CALL_TWO] = _POP_CALL_ONE,
+ [_POP_CALL_ONE] = _POP_CALL,
+};
+
+const uint16_t op_without_pop_null[MAX_UOP_ID + 1] = {
+ [_POP_CALL] = _POP_TOP,
+ [_POP_CALL_LOAD_CONST_INLINE_BORROW] = _POP_TOP_LOAD_CONST_INLINE_BORROW,
+};
+
static int
remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size)
@@ -551,50 +628,37 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size)
buffer[pc].opcode = _NOP;
}
break;
- case _POP_TOP:
- case _POP_TOP_LOAD_CONST_INLINE:
- case _POP_TOP_LOAD_CONST_INLINE_BORROW:
- case _POP_TWO_LOAD_CONST_INLINE_BORROW:
- optimize_pop_top_again:
+ default:
{
- _PyUOpInstruction *last = &buffer[pc-1];
- while (last->opcode == _NOP) {
- last--;
- }
- switch (last->opcode) {
- case _POP_TWO_LOAD_CONST_INLINE_BORROW:
- last->opcode = _POP_TOP;
- break;
- case _POP_TOP_LOAD_CONST_INLINE:
- case _POP_TOP_LOAD_CONST_INLINE_BORROW:
- last->opcode = _NOP;
- goto optimize_pop_top_again;
- case _COPY:
- case _LOAD_CONST_INLINE:
- case _LOAD_CONST_INLINE_BORROW:
- case _LOAD_FAST:
- case _LOAD_FAST_BORROW:
- case _LOAD_SMALL_INT:
- last->opcode = _NOP;
- if (opcode == _POP_TOP) {
- opcode = buffer[pc].opcode = _NOP;
- }
- else if (opcode == _POP_TOP_LOAD_CONST_INLINE) {
- opcode = buffer[pc].opcode = _LOAD_CONST_INLINE;
- }
- else if (opcode == _POP_TOP_LOAD_CONST_INLINE_BORROW) {
- opcode = buffer[pc].opcode = _LOAD_CONST_INLINE_BORROW;
- }
- else {
- assert(opcode == _POP_TWO_LOAD_CONST_INLINE_BORROW);
- opcode = buffer[pc].opcode = _POP_TOP_LOAD_CONST_INLINE_BORROW;
- goto optimize_pop_top_again;
+ // Cancel out pushes and pops, repeatedly. So:
+ // _LOAD_FAST + _POP_TWO_LOAD_CONST_INLINE_BORROW + _POP_TOP
+ // ...becomes:
+ // _NOP + _POP_TOP + _NOP
+ while (op_without_pop[opcode] || op_without_pop_null[opcode]) {
+ _PyUOpInstruction *last = &buffer[pc - 1];
+ while (op_skip[last->opcode]) {
+ last--;
+ }
+ if (op_without_push[last->opcode] && op_without_pop[opcode]) {
+ last->opcode = op_without_push[last->opcode];
+ opcode = buffer[pc].opcode = op_without_pop[opcode];
+ if (op_without_pop[last->opcode]) {
+ opcode = last->opcode;
+ pc = last - buffer;
}
+ }
+ else if (last->opcode == _PUSH_NULL) {
+ // Handle _POP_CALL and _POP_CALL_LOAD_CONST_INLINE_BORROW separately.
+ // This looks for a preceding _PUSH_NULL instruction and
+ // simplifies to _POP_TOP(_LOAD_CONST_INLINE_BORROW).
+ last->opcode = _NOP;
+ opcode = buffer[pc].opcode = op_without_pop_null[opcode];
+ assert(opcode);
+ }
+ else {
+ break;
+ }
}
- _Py_FALLTHROUGH;
- }
- default:
- {
/* _PUSH_FRAME doesn't escape or error, but it
* does need the IP for the return address */
bool needs_ip = opcode == _PUSH_FRAME;
diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c
index 567caad2255..aeff76affd8 100644
--- a/Python/optimizer_bytecodes.c
+++ b/Python/optimizer_bytecodes.c
@@ -27,13 +27,16 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame;
#define sym_set_type(SYM, TYPE) _Py_uop_sym_set_type(ctx, SYM, TYPE)
#define sym_set_type_version(SYM, VERSION) _Py_uop_sym_set_type_version(ctx, SYM, VERSION)
#define sym_set_const(SYM, CNST) _Py_uop_sym_set_const(ctx, SYM, CNST)
+#define sym_set_compact_int(SYM) _Py_uop_sym_set_compact_int(ctx, SYM)
#define sym_is_bottom _Py_uop_sym_is_bottom
#define frame_new _Py_uop_frame_new
#define frame_pop _Py_uop_frame_pop
#define sym_new_tuple _Py_uop_sym_new_tuple
#define sym_tuple_getitem _Py_uop_sym_tuple_getitem
#define sym_tuple_length _Py_uop_sym_tuple_length
-#define sym_is_immortal _Py_uop_sym_is_immortal
+#define sym_is_immortal _Py_uop_symbol_is_immortal
+#define sym_new_compact_int _Py_uop_sym_new_compact_int
+#define sym_is_compact_int _Py_uop_sym_is_compact_int
#define sym_new_truthiness _Py_uop_sym_new_truthiness
extern int
@@ -87,12 +90,12 @@ dummy_func(void) {
}
op(_LOAD_FAST_BORROW, (-- value)) {
- value = GETLOCAL(oparg);
+ value = PyJitRef_Borrow(GETLOCAL(oparg));
}
op(_LOAD_FAST_AND_CLEAR, (-- value)) {
value = GETLOCAL(oparg);
- JitOptSymbol *temp = sym_new_null(ctx);
+ JitOptRef temp = sym_new_null(ctx);
GETLOCAL(oparg) = temp;
}
@@ -104,18 +107,40 @@ dummy_func(void) {
res = sym_new_null(ctx);
}
- op(_GUARD_TOS_INT, (tos -- tos)) {
- if (sym_matches_type(tos, &PyLong_Type)) {
+ op(_GUARD_TOS_INT, (value -- value)) {
+ if (sym_is_compact_int(value)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(tos, &PyLong_Type);
+ else {
+ if (sym_get_type(value) == &PyLong_Type) {
+ REPLACE_OP(this_instr, _GUARD_TOS_OVERFLOWED, 0, 0);
+ }
+ sym_set_compact_int(value);
+ }
}
- op(_GUARD_NOS_INT, (nos, unused -- nos, unused)) {
- if (sym_matches_type(nos, &PyLong_Type)) {
+ op(_GUARD_NOS_INT, (left, unused -- left, unused)) {
+ if (sym_is_compact_int(left)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(nos, &PyLong_Type);
+ else {
+ if (sym_get_type(left) == &PyLong_Type) {
+ REPLACE_OP(this_instr, _GUARD_NOS_OVERFLOWED, 0, 0);
+ }
+ sym_set_compact_int(left);
+ }
+ }
+
+ op(_CHECK_ATTR_CLASS, (type_version/2, owner -- owner)) {
+ PyObject *type = (PyObject *)_PyType_LookupByVersion(type_version);
+ if (type) {
+ if (type == sym_get_const(ctx, owner)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ else {
+ sym_set_const(owner, type);
+ }
+ }
}
op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner)) {
@@ -141,25 +166,26 @@ dummy_func(void) {
}
}
- op(_GUARD_TOS_FLOAT, (tos -- tos)) {
- if (sym_matches_type(tos, &PyFloat_Type)) {
+ op(_GUARD_TOS_FLOAT, (value -- value)) {
+ if (sym_matches_type(value, &PyFloat_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(tos, &PyFloat_Type);
+ sym_set_type(value, &PyFloat_Type);
}
- op(_GUARD_NOS_FLOAT, (nos, unused -- nos, unused)) {
- if (sym_matches_type(nos, &PyFloat_Type)) {
+ op(_GUARD_NOS_FLOAT, (left, unused -- left, unused)) {
+ if (sym_matches_type(left, &PyFloat_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(nos, &PyFloat_Type);
+ sym_set_type(left, &PyFloat_Type);
}
- op(_BINARY_OP, (left, right -- res)) {
- bool lhs_int = sym_matches_type(left, &PyLong_Type);
- bool rhs_int = sym_matches_type(right, &PyLong_Type);
- bool lhs_float = sym_matches_type(left, &PyFloat_Type);
- bool rhs_float = sym_matches_type(right, &PyFloat_Type);
+ op(_BINARY_OP, (lhs, rhs -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(lhs, rhs);
+ bool lhs_int = sym_matches_type(lhs, &PyLong_Type);
+ bool rhs_int = sym_matches_type(rhs, &PyLong_Type);
+ bool lhs_float = sym_matches_type(lhs, &PyFloat_Type);
+ bool rhs_float = sym_matches_type(rhs, &PyFloat_Type);
if (!((lhs_int || lhs_float) && (rhs_int || rhs_float))) {
// There's something other than an int or float involved:
res = sym_new_unknown(ctx);
@@ -185,11 +211,11 @@ dummy_func(void) {
// Case C:
res = sym_new_type(ctx, &PyFloat_Type);
}
- else if (!sym_is_const(ctx, right)) {
+ else if (!sym_is_const(ctx, rhs)) {
// Case A or B... can't know without the sign of the RHS:
res = sym_new_unknown(ctx);
}
- else if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, right))) {
+ else if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, rhs))) {
// Case B:
res = sym_new_type(ctx, &PyFloat_Type);
}
@@ -210,140 +236,54 @@ dummy_func(void) {
}
op(_BINARY_OP_ADD_INT, (left, right -- res)) {
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyLong_CheckExact(sym_get_const(ctx, left)));
- assert(PyLong_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left),
- (PyLongObject *)sym_get_const(ctx, right));
- if (temp == NULL) {
- goto error;
- }
- res = sym_new_const(ctx, temp);
- Py_DECREF(temp);
- // TODO gh-115506:
- // replace opcode with constant propagated one and add tests!
- }
- else {
- res = sym_new_type(ctx, &PyLong_Type);
- }
+ REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
+ res = sym_new_compact_int(ctx);
}
op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) {
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyLong_CheckExact(sym_get_const(ctx, left)));
- assert(PyLong_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left),
- (PyLongObject *)sym_get_const(ctx, right));
- if (temp == NULL) {
- goto error;
- }
- res = sym_new_const(ctx, temp);
- Py_DECREF(temp);
- // TODO gh-115506:
- // replace opcode with constant propagated one and add tests!
- }
- else {
- res = sym_new_type(ctx, &PyLong_Type);
- }
+ REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
+ res = sym_new_compact_int(ctx);
}
op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) {
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyLong_CheckExact(sym_get_const(ctx, left)));
- assert(PyLong_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left),
- (PyLongObject *)sym_get_const(ctx, right));
- if (temp == NULL) {
- goto error;
- }
- res = sym_new_const(ctx, temp);
- Py_DECREF(temp);
- // TODO gh-115506:
- // replace opcode with constant propagated one and add tests!
- }
- else {
- res = sym_new_type(ctx, &PyLong_Type);
- }
+ REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
+ res = sym_new_compact_int(ctx);
}
op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) {
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyFloat_CheckExact(sym_get_const(ctx, left)));
- assert(PyFloat_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyFloat_FromDouble(
- PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) +
- PyFloat_AS_DOUBLE(sym_get_const(ctx, right)));
- if (temp == NULL) {
- goto error;
- }
- res = sym_new_const(ctx, temp);
- Py_DECREF(temp);
- // TODO gh-115506:
- // replace opcode with constant propagated one and update tests!
- }
- else {
- res = sym_new_type(ctx, &PyFloat_Type);
+ REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
+ res = sym_new_type(ctx, &PyFloat_Type);
+ // TODO (gh-134584): Refactor this to use another uop
+ if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) {
+ REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0);
}
}
op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) {
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyFloat_CheckExact(sym_get_const(ctx, left)));
- assert(PyFloat_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyFloat_FromDouble(
- PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) -
- PyFloat_AS_DOUBLE(sym_get_const(ctx, right)));
- if (temp == NULL) {
- goto error;
- }
- res = sym_new_const(ctx, temp);
- Py_DECREF(temp);
- // TODO gh-115506:
- // replace opcode with constant propagated one and update tests!
- }
- else {
- res = sym_new_type(ctx, &PyFloat_Type);
+ REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
+ res = sym_new_type(ctx, &PyFloat_Type);
+ // TODO (gh-134584): Refactor this to use another uop
+ if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) {
+ REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0);
}
}
op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) {
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyFloat_CheckExact(sym_get_const(ctx, left)));
- assert(PyFloat_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyFloat_FromDouble(
- PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) *
- PyFloat_AS_DOUBLE(sym_get_const(ctx, right)));
- if (temp == NULL) {
- goto error;
- }
- res = sym_new_const(ctx, temp);
- Py_DECREF(temp);
- // TODO gh-115506:
- // replace opcode with constant propagated one and update tests!
- }
- else {
- res = sym_new_type(ctx, &PyFloat_Type);
+ REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
+ res = sym_new_type(ctx, &PyFloat_Type);
+ // TODO (gh-134584): Refactor this to use another uop
+ if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) {
+ REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0);
}
}
op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) {
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyUnicode_CheckExact(sym_get_const(ctx, left)));
- assert(PyUnicode_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right));
- if (temp == NULL) {
- goto error;
- }
- res = sym_new_const(ctx, temp);
- Py_DECREF(temp);
- }
- else {
- res = sym_new_type(ctx, &PyUnicode_Type);
- }
+ REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
+ res = sym_new_type(ctx, &PyUnicode_Type);
}
op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- )) {
- JitOptSymbol *res;
+ JitOptRef res;
if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
assert(PyUnicode_CheckExact(sym_get_const(ctx, left)));
assert(PyUnicode_CheckExact(sym_get_const(ctx, right)));
@@ -361,12 +301,12 @@ dummy_func(void) {
GETLOCAL(this_instr->operand0) = res;
}
- op(_BINARY_OP_SUBSCR_INIT_CALL, (container, sub, getitem -- new_frame: _Py_UOpsAbstractFrame *)) {
- new_frame = NULL;
+ op(_BINARY_OP_SUBSCR_INIT_CALL, (container, sub, getitem -- new_frame)) {
+ new_frame = PyJitRef_NULL;
ctx->done = true;
}
- op(_BINARY_OP_SUBSCR_STR_INT, (left, right -- res)) {
+ op(_BINARY_OP_SUBSCR_STR_INT, (str_st, sub_st -- res)) {
res = sym_new_type(ctx, &PyUnicode_Type);
}
@@ -398,11 +338,11 @@ dummy_func(void) {
}
}
- op(_TO_BOOL_BOOL, (value -- res)) {
- int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
+ op(_TO_BOOL_BOOL, (value -- value)) {
+ int already_bool = optimize_to_bool(this_instr, ctx, value, &value);
if (!already_bool) {
sym_set_type(value, &PyBool_Type);
- res = sym_new_truthiness(ctx, value, true);
+ value = sym_new_truthiness(ctx, value, true);
}
}
@@ -451,10 +391,35 @@ dummy_func(void) {
}
op(_UNARY_NOT, (value -- res)) {
+ REPLACE_OPCODE_IF_EVALUATES_PURE(value);
sym_set_type(value, &PyBool_Type);
res = sym_new_truthiness(ctx, value, false);
}
+ op(_UNARY_NEGATIVE, (value -- res)) {
+ if (sym_is_compact_int(value)) {
+ res = sym_new_compact_int(ctx);
+ }
+ else {
+ PyTypeObject *type = sym_get_type(value);
+ if (type == &PyLong_Type || type == &PyFloat_Type) {
+ res = sym_new_type(ctx, type);
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
+ }
+ }
+
+ op(_UNARY_INVERT, (value -- res)) {
+ if (sym_matches_type(value, &PyLong_Type)) {
+ res = sym_new_type(ctx, &PyLong_Type);
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
+ }
+
op(_COMPARE_OP, (left, right -- res)) {
if (oparg & 16) {
res = sym_new_type(ctx, &PyBool_Type);
@@ -493,45 +458,34 @@ dummy_func(void) {
res = sym_new_type(ctx, &PyBool_Type);
}
- op(_IS_OP, (left, right -- res)) {
- res = sym_new_type(ctx, &PyBool_Type);
+ op(_IS_OP, (left, right -- b)) {
+ b = sym_new_type(ctx, &PyBool_Type);
}
- op(_CONTAINS_OP, (left, right -- res)) {
- res = sym_new_type(ctx, &PyBool_Type);
+ op(_CONTAINS_OP, (left, right -- b)) {
+ b = sym_new_type(ctx, &PyBool_Type);
}
- op(_CONTAINS_OP_SET, (left, right -- res)) {
- res = sym_new_type(ctx, &PyBool_Type);
+ op(_CONTAINS_OP_SET, (left, right -- b)) {
+ b = sym_new_type(ctx, &PyBool_Type);
}
- op(_CONTAINS_OP_DICT, (left, right -- res)) {
- res = sym_new_type(ctx, &PyBool_Type);
+ op(_CONTAINS_OP_DICT, (left, right -- b)) {
+ b = sym_new_type(ctx, &PyBool_Type);
}
op(_LOAD_CONST, (-- value)) {
- PyObject *val = PyTuple_GET_ITEM(co->co_consts, this_instr->oparg);
- int opcode = _Py_IsImmortal(val) ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE;
- REPLACE_OP(this_instr, opcode, 0, (uintptr_t)val);
- value = sym_new_const(ctx, val);
- }
-
- op(_LOAD_CONST_MORTAL, (-- value)) {
- PyObject *val = PyTuple_GET_ITEM(co->co_consts, this_instr->oparg);
- int opcode = _Py_IsImmortal(val) ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE;
- REPLACE_OP(this_instr, opcode, 0, (uintptr_t)val);
- value = sym_new_const(ctx, val);
- }
-
- op(_LOAD_CONST_IMMORTAL, (-- value)) {
- PyObject *val = PyTuple_GET_ITEM(co->co_consts, this_instr->oparg);
+ PyObject *val = PyTuple_GET_ITEM(co->co_consts, oparg);
REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
- value = sym_new_const(ctx, val);
+ value = PyJitRef_Borrow(sym_new_const(ctx, val));
}
op(_LOAD_SMALL_INT, (-- value)) {
- PyObject *val = PyLong_FromLong(this_instr->oparg);
- value = sym_new_const(ctx, val);
+ PyObject *val = PyLong_FromLong(oparg);
+ assert(val);
+ assert(_Py_IsImmortal(val));
+ REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
+ value = PyJitRef_Borrow(sym_new_const(ctx, val));
}
op(_LOAD_CONST_INLINE, (ptr/4 -- value)) {
@@ -539,7 +493,7 @@ dummy_func(void) {
}
op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) {
- value = sym_new_const(ctx, ptr);
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
}
op(_POP_TOP_LOAD_CONST_INLINE, (ptr/4, pop -- value)) {
@@ -547,7 +501,37 @@ dummy_func(void) {
}
op(_POP_TOP_LOAD_CONST_INLINE_BORROW, (ptr/4, pop -- value)) {
- value = sym_new_const(ctx, ptr);
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
+ }
+
+ op(_POP_CALL_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused -- value)) {
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
+ }
+
+ op(_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused, unused -- value)) {
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
+ }
+
+ op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused, unused, unused -- value)) {
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
+ }
+
+ op(_POP_TOP, (value -- )) {
+ PyTypeObject *typ = sym_get_type(value);
+ if (PyJitRef_IsBorrowed(value) ||
+ sym_is_immortal(PyJitRef_Unwrap(value)) ||
+ sym_is_null(value)) {
+ REPLACE_OP(this_instr, _POP_TOP_NOP, 0, 0);
+ }
+ else if (typ == &PyLong_Type) {
+ REPLACE_OP(this_instr, _POP_TOP_INT, 0, 0);
+ }
+ else if (typ == &PyFloat_Type) {
+ REPLACE_OP(this_instr, _POP_TOP_FLOAT, 0, 0);
+ }
+ else if (typ == &PyUnicode_Type) {
+ REPLACE_OP(this_instr, _POP_TOP_UNICODE, 0, 0);
+ }
}
op(_COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) {
@@ -556,7 +540,7 @@ dummy_func(void) {
}
op(_SWAP, (bottom, unused[oparg-2], top -- bottom, unused[oparg-2], top)) {
- JitOptSymbol *temp = bottom;
+ JitOptRef temp = bottom;
bottom = top;
top = temp;
assert(oparg >= 2);
@@ -570,7 +554,7 @@ dummy_func(void) {
op(_LOAD_ATTR_MODULE, (dict_version/2, index/1, owner -- attr)) {
(void)dict_version;
(void)index;
- attr = NULL;
+ attr = PyJitRef_NULL;
if (sym_is_const(ctx, owner)) {
PyModuleObject *mod = (PyModuleObject *)sym_get_const(ctx, owner);
if (PyModule_CheckExact(mod)) {
@@ -580,11 +564,17 @@ dummy_func(void) {
PyDict_Watch(GLOBALS_WATCHER_ID, dict);
_Py_BloomFilter_Add(dependencies, dict);
PyObject *res = convert_global_to_const(this_instr, dict, true);
- attr = sym_new_const(ctx, res);
+ if (res == NULL) {
+ attr = sym_new_not_null(ctx);
+ }
+ else {
+ attr = sym_new_const(ctx, res);
+ }
+
}
}
}
- if (attr == NULL) {
+ if (PyJitRef_IsNull(attr)) {
/* No conversion made. We don't know what `attr` is. */
attr = sym_new_not_null(ctx);
}
@@ -600,10 +590,10 @@ dummy_func(void) {
}
}
- op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) {
+ op(_LOAD_ATTR, (owner -- attr[1], self_or_null[oparg&1])) {
(void)owner;
- attr = sym_new_not_null(ctx);
- if (oparg &1) {
+ *attr = sym_new_not_null(ctx);
+ if (oparg & 1) {
self_or_null[0] = sym_new_unknown(ctx);
}
}
@@ -619,31 +609,65 @@ dummy_func(void) {
}
op(_LOAD_ATTR_CLASS, (descr/4, owner -- attr)) {
- attr = sym_new_not_null(ctx);
(void)descr;
+ PyTypeObject *type = (PyTypeObject *)sym_get_const(ctx, owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ _POP_TOP_LOAD_CONST_INLINE);
+ }
+
+ op(_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, (descr/4, owner -- attr)) {
+ (void)descr;
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ _POP_TOP_LOAD_CONST_INLINE);
+ }
+
+ op(_LOAD_ATTR_NONDESCRIPTOR_NO_DICT, (descr/4, owner -- attr)) {
+ (void)descr;
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ _POP_TOP_LOAD_CONST_INLINE);
}
op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self)) {
(void)descr;
- attr = sym_new_not_null(ctx);
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _LOAD_CONST_UNDER_INLINE_BORROW,
+ _LOAD_CONST_UNDER_INLINE);
self = owner;
}
op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self)) {
(void)descr;
- attr = sym_new_not_null(ctx);
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _LOAD_CONST_UNDER_INLINE_BORROW,
+ _LOAD_CONST_UNDER_INLINE);
self = owner;
}
op(_LOAD_ATTR_METHOD_LAZY_DICT, (descr/4, owner -- attr, self)) {
(void)descr;
- attr = sym_new_not_null(ctx);
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _LOAD_CONST_UNDER_INLINE_BORROW,
+ _LOAD_CONST_UNDER_INLINE);
self = owner;
}
- op(_LOAD_ATTR_PROPERTY_FRAME, (fget/4, owner -- new_frame: _Py_UOpsAbstractFrame *)) {
+ op(_LOAD_ATTR_PROPERTY_FRAME, (fget/4, owner -- new_frame)) {
(void)fget;
- new_frame = NULL;
+ new_frame = PyJitRef_NULL;
ctx->done = true;
}
@@ -661,6 +685,16 @@ dummy_func(void) {
sym_set_type(callable, &PyFunction_Type);
}
+ op(_CHECK_METHOD_VERSION, (func_version/2, callable, null, unused[oparg] -- callable, null, unused[oparg])) {
+ if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyMethod_Type)) {
+ PyMethodObject *method = (PyMethodObject *)sym_get_const(ctx, callable);
+ assert(PyMethod_Check(method));
+ REPLACE_OP(this_instr, _CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
+ this_instr->operand1 = (uintptr_t)method->im_func;
+ }
+ sym_set_type(callable, &PyMethod_Type);
+ }
+
op(_CHECK_FUNCTION_EXACT_ARGS, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
assert(sym_matches_type(callable, &PyFunction_Type));
if (sym_is_const(ctx, callable)) {
@@ -679,7 +713,7 @@ dummy_func(void) {
sym_set_type(callable, &PyMethod_Type);
}
- op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) {
+ op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame)) {
int argcount = oparg;
PyCodeObject *co = NULL;
@@ -691,7 +725,7 @@ dummy_func(void) {
}
- assert(self_or_null != NULL);
+ assert(!PyJitRef_IsNull(self_or_null));
assert(args != NULL);
if (sym_is_not_null(self_or_null)) {
// Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM
@@ -700,20 +734,19 @@ dummy_func(void) {
}
if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) {
- new_frame = frame_new(ctx, co, 0, args, argcount);
+ new_frame = PyJitRef_Wrap((JitOptSymbol *)frame_new(ctx, co, 0, args, argcount));
} else {
- new_frame = frame_new(ctx, co, 0, NULL, 0);
-
+ new_frame = PyJitRef_Wrap((JitOptSymbol *)frame_new(ctx, co, 0, NULL, 0));
}
}
- op(_MAYBE_EXPAND_METHOD, (callable, self_or_null, args[oparg] -- func, maybe_self, args[oparg])) {
+ op(_MAYBE_EXPAND_METHOD, (callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) {
(void)args;
- func = sym_new_not_null(ctx);
- maybe_self = sym_new_not_null(ctx);
+ callable = sym_new_not_null(ctx);
+ self_or_null = sym_new_not_null(ctx);
}
- op(_PY_FRAME_GENERAL, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) {
+ op(_PY_FRAME_GENERAL, (callable, self_or_null, args[oparg] -- new_frame)) {
PyCodeObject *co = NULL;
assert((this_instr + 2)->opcode == _PUSH_FRAME);
co = get_code_with_logging((this_instr + 2));
@@ -722,28 +755,30 @@ dummy_func(void) {
break;
}
- new_frame = frame_new(ctx, co, 0, NULL, 0);
+ new_frame = PyJitRef_Wrap((JitOptSymbol *)frame_new(ctx, co, 0, NULL, 0));
}
- op(_PY_FRAME_KW, (callable, self_or_null, args[oparg], kwnames -- new_frame: _Py_UOpsAbstractFrame *)) {
- new_frame = NULL;
+ op(_PY_FRAME_KW, (callable, self_or_null, args[oparg], kwnames -- new_frame)) {
+ new_frame = PyJitRef_NULL;
ctx->done = true;
}
- op(_CHECK_AND_ALLOCATE_OBJECT, (type_version/2, callable, null, args[oparg] -- self, init, args[oparg])) {
+ op(_CHECK_AND_ALLOCATE_OBJECT, (type_version/2, callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) {
(void)type_version;
(void)args;
- self = sym_new_not_null(ctx);
- init = sym_new_not_null(ctx);
+ callable = sym_new_not_null(ctx);
+ self_or_null = sym_new_not_null(ctx);
}
- op(_CREATE_INIT_FRAME, (self, init, args[oparg] -- init_frame: _Py_UOpsAbstractFrame *)) {
- init_frame = NULL;
+ op(_CREATE_INIT_FRAME, (init, self, args[oparg] -- init_frame)) {
+ init_frame = PyJitRef_NULL;
ctx->done = true;
}
op(_RETURN_VALUE, (retval -- res)) {
- JitOptSymbol *temp = retval;
+ // We wrap and unwrap the value to mimic PyStackRef_MakeHeapSafe
+ // in bytecodes.c
+ JitOptRef temp = PyJitRef_Wrap(PyJitRef_Unwrap(retval));
DEAD(retval);
SAVE_STACK();
ctx->frame->stack_pointer = stack_pointer;
@@ -789,21 +824,34 @@ dummy_func(void) {
}
}
- op(_YIELD_VALUE, (unused -- res)) {
- res = sym_new_unknown(ctx);
+ op(_YIELD_VALUE, (unused -- value)) {
+ value = sym_new_unknown(ctx);
}
- op(_FOR_ITER_GEN_FRAME, ( -- )) {
+ op(_GET_ITER, (iterable -- iter, index_or_null)) {
+ if (sym_matches_type(iterable, &PyTuple_Type) || sym_matches_type(iterable, &PyList_Type)) {
+ iter = iterable;
+ index_or_null = sym_new_not_null(ctx);
+ }
+ else {
+ iter = sym_new_not_null(ctx);
+ index_or_null = sym_new_unknown(ctx);
+ }
+ }
+
+ op(_FOR_ITER_GEN_FRAME, (unused, unused -- unused, unused, gen_frame)) {
+ gen_frame = PyJitRef_NULL;
/* We are about to hit the end of the trace */
ctx->done = true;
}
- op(_SEND_GEN_FRAME, ( -- )) {
+ op(_SEND_GEN_FRAME, (unused, unused -- unused, gen_frame)) {
+ gen_frame = PyJitRef_NULL;
// We are about to hit the end of the trace:
ctx->done = true;
}
- op(_CHECK_STACK_SPACE, ( --)) {
+ op(_CHECK_STACK_SPACE, (unused, unused, unused[oparg] -- unused, unused, unused[oparg])) {
assert(corresponding_check_stack == NULL);
corresponding_check_stack = this_instr;
}
@@ -815,12 +863,12 @@ dummy_func(void) {
Py_UNREACHABLE();
}
- op(_PUSH_FRAME, (new_frame: _Py_UOpsAbstractFrame * -- )) {
+ op(_PUSH_FRAME, (new_frame -- )) {
SYNC_SP();
ctx->frame->stack_pointer = stack_pointer;
- ctx->frame = new_frame;
+ ctx->frame = (_Py_UOpsAbstractFrame *)PyJitRef_Unwrap(new_frame);
ctx->curr_frame_depth++;
- stack_pointer = new_frame->stack_pointer;
+ stack_pointer = ctx->frame->stack_pointer;
co = get_code(this_instr);
if (co == NULL) {
// should be about to _EXIT_TRACE anyway
@@ -848,14 +896,16 @@ dummy_func(void) {
corresponding_check_stack = NULL;
}
- op(_UNPACK_SEQUENCE, (seq -- values[oparg])) {
+ op(_UNPACK_SEQUENCE, (seq -- values[oparg], top[0])) {
+ (void)top;
/* This has to be done manually */
for (int i = 0; i < oparg; i++) {
values[i] = sym_new_unknown(ctx);
}
}
- op(_UNPACK_EX, (seq -- values[oparg & 0xFF], unused, unused[oparg >> 8])) {
+ op(_UNPACK_EX, (seq -- values[oparg & 0xFF], unused, unused[oparg >> 8], top[0])) {
+ (void)top;
/* This has to be done manually */
int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1;
for (int i = 0; i < totalargs; i++) {
@@ -863,13 +913,23 @@ dummy_func(void) {
}
}
- op(_ITER_NEXT_RANGE, (iter -- iter, next)) {
+ op(_ITER_CHECK_TUPLE, (iter, null_or_index -- iter, null_or_index)) {
+ if (sym_matches_type(iter, &PyTuple_Type)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_type(iter, &PyTuple_Type);
+ }
+
+ op(_ITER_NEXT_RANGE, (iter, null_or_index -- iter, null_or_index, next)) {
next = sym_new_type(ctx, &PyLong_Type);
}
op(_CALL_TYPE_1, (unused, unused, arg -- res)) {
- if (sym_has_type(arg)) {
- res = sym_new_const(ctx, (PyObject *)sym_get_type(arg));
+ PyObject* type = (PyObject *)sym_get_type(arg);
+ if (type) {
+ res = sym_new_const(ctx, type);
+ REPLACE_OP(this_instr, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, 0,
+ (uintptr_t)type);
}
else {
res = sym_new_not_null(ctx);
@@ -886,6 +946,26 @@ dummy_func(void) {
}
}
+ op(_CALL_ISINSTANCE, (unused, unused, instance, cls -- res)) {
+ // the result is always a bool, but sometimes we can
+ // narrow it down to True or False
+ res = sym_new_type(ctx, &PyBool_Type);
+ PyTypeObject *inst_type = sym_get_type(instance);
+ PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, cls);
+ if (inst_type && cls_o && sym_matches_type(cls, &PyType_Type)) {
+ // isinstance(inst, cls) where both inst and cls have
+ // known types, meaning we can deduce either True or False
+
+ // The below check is equivalent to PyObject_TypeCheck(inst, cls)
+ PyObject *out = Py_False;
+ if (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o)) {
+ out = Py_True;
+ }
+ sym_set_const(res, out);
+ REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out);
+ }
+ }
+
op(_GUARD_IS_TRUE_POP, (flag -- )) {
if (sym_is_const(ctx, flag)) {
PyObject *value = sym_get_const(ctx, flag);
@@ -904,27 +984,27 @@ dummy_func(void) {
sym_set_const(flag, Py_False);
}
- op(_GUARD_IS_NONE_POP, (flag -- )) {
- if (sym_is_const(ctx, flag)) {
- PyObject *value = sym_get_const(ctx, flag);
+ op(_GUARD_IS_NONE_POP, (val -- )) {
+ if (sym_is_const(ctx, val)) {
+ PyObject *value = sym_get_const(ctx, val);
assert(value != NULL);
eliminate_pop_guard(this_instr, !Py_IsNone(value));
}
- else if (sym_has_type(flag)) {
- assert(!sym_matches_type(flag, &_PyNone_Type));
+ else if (sym_has_type(val)) {
+ assert(!sym_matches_type(val, &_PyNone_Type));
eliminate_pop_guard(this_instr, true);
}
- sym_set_const(flag, Py_None);
+ sym_set_const(val, Py_None);
}
- op(_GUARD_IS_NOT_NONE_POP, (flag -- )) {
- if (sym_is_const(ctx, flag)) {
- PyObject *value = sym_get_const(ctx, flag);
+ op(_GUARD_IS_NOT_NONE_POP, (val -- )) {
+ if (sym_is_const(ctx, val)) {
+ PyObject *value = sym_get_const(ctx, val);
assert(value != NULL);
eliminate_pop_guard(this_instr, Py_IsNone(value));
}
- else if (sym_has_type(flag)) {
- assert(!sym_matches_type(flag, &_PyNone_Type));
+ else if (sym_has_type(val)) {
+ assert(!sym_matches_type(val, &_PyNone_Type));
eliminate_pop_guard(this_instr, false);
}
}
@@ -969,7 +1049,7 @@ dummy_func(void) {
list = sym_new_type(ctx, &PyList_Type);
}
- op(_BUILD_SLICE, (values[oparg] -- slice)) {
+ op(_BUILD_SLICE, (args[oparg] -- slice)) {
slice = sym_new_type(ctx, &PySlice_Type);
}
@@ -977,7 +1057,7 @@ dummy_func(void) {
map = sym_new_type(ctx, &PyDict_Type);
}
- op(_BUILD_STRING, (values[oparg] -- str)) {
+ op(_BUILD_STRING, (pieces[oparg] -- str)) {
str = sym_new_type(ctx, &PyUnicode_Type);
}
@@ -1063,6 +1143,20 @@ dummy_func(void) {
sym_set_null(null);
}
+ op(_GUARD_NOS_NOT_NULL, (nos, unused -- nos, unused)) {
+ if (sym_is_not_null(nos)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_non_null(nos);
+ }
+
+ op(_GUARD_THIRD_NULL, (null, unused, unused -- null, unused, unused)) {
+ if (sym_is_null(null)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_null(null);
+ }
+
op(_GUARD_CALLABLE_TYPE_1, (callable, unused, unused -- callable, unused, unused)) {
if (sym_get_const(ctx, callable) == (PyObject *)&PyType_Type) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -1084,8 +1178,78 @@ dummy_func(void) {
sym_set_const(callable, (PyObject *)&PyUnicode_Type);
}
- op(_CALL_LEN, (callable[1], self_or_null[1], args[oparg] -- res)) {
+ op(_CALL_LEN, (callable, null, arg -- res)) {
res = sym_new_type(ctx, &PyLong_Type);
+ int tuple_length = sym_tuple_length(arg);
+ if (tuple_length >= 0) {
+ PyObject *temp = PyLong_FromLong(tuple_length);
+ if (temp == NULL) {
+ goto error;
+ }
+ if (_Py_IsImmortal(temp)) {
+ REPLACE_OP(this_instr, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW,
+ 0, (uintptr_t)temp);
+ }
+ res = sym_new_const(ctx, temp);
+ Py_DECREF(temp);
+ }
+ }
+
+ op(_GET_LEN, (obj -- obj, len)) {
+ int tuple_length = sym_tuple_length(obj);
+ if (tuple_length == -1) {
+ len = sym_new_type(ctx, &PyLong_Type);
+ }
+ else {
+ assert(tuple_length >= 0);
+ PyObject *temp = PyLong_FromLong(tuple_length);
+ if (temp == NULL) {
+ goto error;
+ }
+ if (_Py_IsImmortal(temp)) {
+ REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp);
+ }
+ len = sym_new_const(ctx, temp);
+ Py_DECREF(temp);
+ }
+ }
+
+ op(_GUARD_CALLABLE_LEN, (callable, unused, unused -- callable, unused, unused)) {
+ PyObject *len = _PyInterpreterState_GET()->callable_cache.len;
+ if (sym_get_const(ctx, callable) == len) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_const(callable, len);
+ }
+
+ op(_GUARD_CALLABLE_ISINSTANCE, (callable, unused, unused, unused -- callable, unused, unused, unused)) {
+ PyObject *isinstance = _PyInterpreterState_GET()->callable_cache.isinstance;
+ if (sym_get_const(ctx, callable) == isinstance) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_const(callable, isinstance);
+ }
+
+ op(_GUARD_CALLABLE_LIST_APPEND, (callable, unused, unused -- callable, unused, unused)) {
+ PyObject *list_append = _PyInterpreterState_GET()->callable_cache.list_append;
+ if (sym_get_const(ctx, callable) == list_append) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_const(callable, list_append);
+ }
+
+ op(_BINARY_SLICE, (container, start, stop -- res)) {
+ // Slicing a string/list/tuple always returns the same type.
+ PyTypeObject *type = sym_get_type(container);
+ if (type == &PyUnicode_Type ||
+ type == &PyList_Type ||
+ type == &PyTuple_Type)
+ {
+ res = sym_new_type(ctx, type);
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
}
// END BYTECODES //
diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h
index 3f91f7eefc7..41402200c16 100644
--- a/Python/optimizer_cases.c.h
+++ b/Python/optimizer_cases.c.h
@@ -26,7 +26,7 @@
/* _MONITOR_RESUME is not a viable micro-op for tier 2 */
case _LOAD_FAST_CHECK: {
- JitOptSymbol *value;
+ JitOptRef value;
value = GETLOCAL(oparg);
if (sym_is_null(value)) {
ctx->done = true;
@@ -38,7 +38,7 @@
}
case _LOAD_FAST: {
- JitOptSymbol *value;
+ JitOptRef value;
value = GETLOCAL(oparg);
stack_pointer[0] = value;
stack_pointer += 1;
@@ -47,8 +47,8 @@
}
case _LOAD_FAST_BORROW: {
- JitOptSymbol *value;
- value = GETLOCAL(oparg);
+ JitOptRef value;
+ value = PyJitRef_Borrow(GETLOCAL(oparg));
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -56,9 +56,9 @@
}
case _LOAD_FAST_AND_CLEAR: {
- JitOptSymbol *value;
+ JitOptRef value;
value = GETLOCAL(oparg);
- JitOptSymbol *temp = sym_new_null(ctx);
+ JitOptRef temp = sym_new_null(ctx);
GETLOCAL(oparg) = temp;
stack_pointer[0] = value;
stack_pointer += 1;
@@ -66,25 +66,11 @@
break;
}
- /* _LOAD_CONST is not a viable micro-op for tier 2 */
-
- case _LOAD_CONST_MORTAL: {
- JitOptSymbol *value;
- PyObject *val = PyTuple_GET_ITEM(co->co_consts, this_instr->oparg);
- int opcode = _Py_IsImmortal(val) ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE;
- REPLACE_OP(this_instr, opcode, 0, (uintptr_t)val);
- value = sym_new_const(ctx, val);
- stack_pointer[0] = value;
- stack_pointer += 1;
- assert(WITHIN_STACK_BOUNDS());
- break;
- }
-
- case _LOAD_CONST_IMMORTAL: {
- JitOptSymbol *value;
- PyObject *val = PyTuple_GET_ITEM(co->co_consts, this_instr->oparg);
+ case _LOAD_CONST: {
+ JitOptRef value;
+ PyObject *val = PyTuple_GET_ITEM(co->co_consts, oparg);
REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
- value = sym_new_const(ctx, val);
+ value = PyJitRef_Borrow(sym_new_const(ctx, val));
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -92,9 +78,12 @@
}
case _LOAD_SMALL_INT: {
- JitOptSymbol *value;
- PyObject *val = PyLong_FromLong(this_instr->oparg);
- value = sym_new_const(ctx, val);
+ JitOptRef value;
+ PyObject *val = PyLong_FromLong(oparg);
+ assert(val);
+ assert(_Py_IsImmortal(val));
+ REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
+ value = PyJitRef_Borrow(sym_new_const(ctx, val));
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -102,7 +91,7 @@
}
case _STORE_FAST: {
- JitOptSymbol *value;
+ JitOptRef value;
value = stack_pointer[-1];
GETLOCAL(oparg) = value;
stack_pointer += -1;
@@ -111,13 +100,60 @@
}
case _POP_TOP: {
+ JitOptRef value;
+ value = stack_pointer[-1];
+ PyTypeObject *typ = sym_get_type(value);
+ if (PyJitRef_IsBorrowed(value) ||
+ sym_is_immortal(PyJitRef_Unwrap(value)) ||
+ sym_is_null(value)) {
+ REPLACE_OP(this_instr, _POP_TOP_NOP, 0, 0);
+ }
+ else if (typ == &PyLong_Type) {
+ REPLACE_OP(this_instr, _POP_TOP_INT, 0, 0);
+ }
+ else if (typ == &PyFloat_Type) {
+ REPLACE_OP(this_instr, _POP_TOP_FLOAT, 0, 0);
+ }
+ else if (typ == &PyUnicode_Type) {
+ REPLACE_OP(this_instr, _POP_TOP_UNICODE, 0, 0);
+ }
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}
+ case _POP_TOP_NOP: {
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TOP_INT: {
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TOP_FLOAT: {
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TOP_UNICODE: {
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_TWO: {
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
case _PUSH_NULL: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_null(ctx);
stack_pointer[0] = res;
stack_pointer += 1;
@@ -131,8 +167,14 @@
break;
}
+ case _POP_ITER: {
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
case _END_SEND: {
- JitOptSymbol *val;
+ JitOptRef val;
val = sym_new_not_null(ctx);
stack_pointer[-2] = val;
stack_pointer += -1;
@@ -141,16 +183,44 @@
}
case _UNARY_NEGATIVE: {
- JitOptSymbol *res;
- res = sym_new_not_null(ctx);
+ JitOptRef value;
+ JitOptRef res;
+ value = stack_pointer[-1];
+ if (sym_is_compact_int(value)) {
+ res = sym_new_compact_int(ctx);
+ }
+ else {
+ PyTypeObject *type = sym_get_type(value);
+ if (type == &PyLong_Type || type == &PyFloat_Type) {
+ res = sym_new_type(ctx, type);
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
+ }
stack_pointer[-1] = res;
break;
}
case _UNARY_NOT: {
- JitOptSymbol *value;
- JitOptSymbol *res;
+ JitOptRef value;
+ JitOptRef res;
value = stack_pointer[-1];
+ if (
+ sym_is_safe_const(ctx, value)
+ ) {
+ JitOptRef value_sym = value;
+ _PyStackRef value = sym_get_const_as_stackref(ctx, value_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ assert(PyStackRef_BoolCheck(value));
+ res_stackref = PyStackRef_IsFalse(value)
+ ? PyStackRef_True : PyStackRef_False;
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-1] = res;
+ break;
+ }
sym_set_type(value, &PyBool_Type);
res = sym_new_truthiness(ctx, value, false);
stack_pointer[-1] = res;
@@ -158,8 +228,8 @@
}
case _TO_BOOL: {
- JitOptSymbol *value;
- JitOptSymbol *res;
+ JitOptRef value;
+ JitOptRef res;
value = stack_pointer[-1];
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
@@ -170,21 +240,20 @@
}
case _TO_BOOL_BOOL: {
- JitOptSymbol *value;
- JitOptSymbol *res;
+ JitOptRef value;
value = stack_pointer[-1];
- int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
+ int already_bool = optimize_to_bool(this_instr, ctx, value, &value);
if (!already_bool) {
sym_set_type(value, &PyBool_Type);
- res = sym_new_truthiness(ctx, value, true);
+ value = sym_new_truthiness(ctx, value, true);
}
- stack_pointer[-1] = res;
+ stack_pointer[-1] = value;
break;
}
case _TO_BOOL_INT: {
- JitOptSymbol *value;
- JitOptSymbol *res;
+ JitOptRef value;
+ JitOptRef res;
value = stack_pointer[-1];
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
@@ -196,7 +265,7 @@
}
case _GUARD_NOS_LIST: {
- JitOptSymbol *nos;
+ JitOptRef nos;
nos = stack_pointer[-2];
if (sym_matches_type(nos, &PyList_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -206,7 +275,7 @@
}
case _GUARD_TOS_LIST: {
- JitOptSymbol *tos;
+ JitOptRef tos;
tos = stack_pointer[-1];
if (sym_matches_type(tos, &PyList_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -220,8 +289,8 @@
}
case _TO_BOOL_LIST: {
- JitOptSymbol *value;
- JitOptSymbol *res;
+ JitOptRef value;
+ JitOptRef res;
value = stack_pointer[-1];
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
@@ -232,8 +301,8 @@
}
case _TO_BOOL_NONE: {
- JitOptSymbol *value;
- JitOptSymbol *res;
+ JitOptRef value;
+ JitOptRef res;
value = stack_pointer[-1];
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
@@ -245,7 +314,7 @@
}
case _GUARD_NOS_UNICODE: {
- JitOptSymbol *nos;
+ JitOptRef nos;
nos = stack_pointer[-2];
if (sym_matches_type(nos, &PyUnicode_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -255,7 +324,7 @@
}
case _GUARD_TOS_UNICODE: {
- JitOptSymbol *value;
+ JitOptRef value;
value = stack_pointer[-1];
if (sym_matches_type(value, &PyUnicode_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -265,8 +334,8 @@
}
case _TO_BOOL_STR: {
- JitOptSymbol *value;
- JitOptSymbol *res;
+ JitOptRef value;
+ JitOptRef res;
value = stack_pointer[-1];
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
@@ -277,7 +346,7 @@
}
case _REPLACE_WITH_TRUE: {
- JitOptSymbol *res;
+ JitOptRef res;
REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)Py_True);
res = sym_new_const(ctx, Py_True);
stack_pointer[-1] = res;
@@ -285,256 +354,416 @@
}
case _UNARY_INVERT: {
- JitOptSymbol *res;
- res = sym_new_not_null(ctx);
+ JitOptRef value;
+ JitOptRef res;
+ value = stack_pointer[-1];
+ if (sym_matches_type(value, &PyLong_Type)) {
+ res = sym_new_type(ctx, &PyLong_Type);
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
stack_pointer[-1] = res;
break;
}
case _GUARD_NOS_INT: {
- JitOptSymbol *nos;
- nos = stack_pointer[-2];
- if (sym_matches_type(nos, &PyLong_Type)) {
+ JitOptRef left;
+ left = stack_pointer[-2];
+ if (sym_is_compact_int(left)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(nos, &PyLong_Type);
+ else {
+ if (sym_get_type(left) == &PyLong_Type) {
+ REPLACE_OP(this_instr, _GUARD_NOS_OVERFLOWED, 0, 0);
+ }
+ sym_set_compact_int(left);
+ }
break;
}
case _GUARD_TOS_INT: {
- JitOptSymbol *tos;
- tos = stack_pointer[-1];
- if (sym_matches_type(tos, &PyLong_Type)) {
+ JitOptRef value;
+ value = stack_pointer[-1];
+ if (sym_is_compact_int(value)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(tos, &PyLong_Type);
+ else {
+ if (sym_get_type(value) == &PyLong_Type) {
+ REPLACE_OP(this_instr, _GUARD_TOS_OVERFLOWED, 0, 0);
+ }
+ sym_set_compact_int(value);
+ }
+ break;
+ }
+
+ case _GUARD_NOS_OVERFLOWED: {
+ break;
+ }
+
+ case _GUARD_TOS_OVERFLOWED: {
break;
}
case _BINARY_OP_MULTIPLY_INT: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyLong_CheckExact(sym_get_const(ctx, left)));
- assert(PyLong_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left),
- (PyLongObject *)sym_get_const(ctx, right));
- if (temp == NULL) {
- goto error;
+ if (
+ sym_is_safe_const(ctx, left) &&
+ sym_is_safe_const(ctx, right)
+ ) {
+ JitOptRef left_sym = left;
+ JitOptRef right_sym = right;
+ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym);
+ _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyLong_CheckExact(left_o));
+ assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
+ STAT_INC(BINARY_OP, hit);
+ res_stackref = _PyCompactLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res_stackref )) {
+ ctx->done = true;
+ break;
}
- res = sym_new_const(ctx, temp);
+ PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
+ PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
- Py_DECREF(temp);
- }
- else {
- res = sym_new_type(ctx, &PyLong_Type);
- stack_pointer += -1;
+ break;
}
- stack_pointer[-1] = res;
+ res = sym_new_compact_int(ctx);
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _BINARY_OP_ADD_INT: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyLong_CheckExact(sym_get_const(ctx, left)));
- assert(PyLong_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left),
- (PyLongObject *)sym_get_const(ctx, right));
- if (temp == NULL) {
- goto error;
+ if (
+ sym_is_safe_const(ctx, left) &&
+ sym_is_safe_const(ctx, right)
+ ) {
+ JitOptRef left_sym = left;
+ JitOptRef right_sym = right;
+ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym);
+ _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyLong_CheckExact(left_o));
+ assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
+ STAT_INC(BINARY_OP, hit);
+ res_stackref = _PyCompactLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res_stackref )) {
+ ctx->done = true;
+ break;
}
- res = sym_new_const(ctx, temp);
+ PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
+ PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
- Py_DECREF(temp);
- }
- else {
- res = sym_new_type(ctx, &PyLong_Type);
- stack_pointer += -1;
+ break;
}
- stack_pointer[-1] = res;
+ res = sym_new_compact_int(ctx);
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _BINARY_OP_SUBTRACT_INT: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyLong_CheckExact(sym_get_const(ctx, left)));
- assert(PyLong_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left),
- (PyLongObject *)sym_get_const(ctx, right));
- if (temp == NULL) {
- goto error;
+ if (
+ sym_is_safe_const(ctx, left) &&
+ sym_is_safe_const(ctx, right)
+ ) {
+ JitOptRef left_sym = left;
+ JitOptRef right_sym = right;
+ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym);
+ _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyLong_CheckExact(left_o));
+ assert(PyLong_CheckExact(right_o));
+ assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
+ STAT_INC(BINARY_OP, hit);
+ res_stackref = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
+ if (PyStackRef_IsNull(res_stackref )) {
+ ctx->done = true;
+ break;
}
- res = sym_new_const(ctx, temp);
+ PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
+ PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
- Py_DECREF(temp);
- }
- else {
- res = sym_new_type(ctx, &PyLong_Type);
- stack_pointer += -1;
+ break;
}
- stack_pointer[-1] = res;
+ res = sym_new_compact_int(ctx);
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _GUARD_NOS_FLOAT: {
- JitOptSymbol *nos;
- nos = stack_pointer[-2];
- if (sym_matches_type(nos, &PyFloat_Type)) {
+ JitOptRef left;
+ left = stack_pointer[-2];
+ if (sym_matches_type(left, &PyFloat_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(nos, &PyFloat_Type);
+ sym_set_type(left, &PyFloat_Type);
break;
}
case _GUARD_TOS_FLOAT: {
- JitOptSymbol *tos;
- tos = stack_pointer[-1];
- if (sym_matches_type(tos, &PyFloat_Type)) {
+ JitOptRef value;
+ value = stack_pointer[-1];
+ if (sym_matches_type(value, &PyFloat_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
- sym_set_type(tos, &PyFloat_Type);
+ sym_set_type(value, &PyFloat_Type);
break;
}
case _BINARY_OP_MULTIPLY_FLOAT: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyFloat_CheckExact(sym_get_const(ctx, left)));
- assert(PyFloat_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyFloat_FromDouble(
- PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) *
- PyFloat_AS_DOUBLE(sym_get_const(ctx, right)));
- if (temp == NULL) {
+ if (
+ sym_is_safe_const(ctx, left) &&
+ sym_is_safe_const(ctx, right)
+ ) {
+ JitOptRef left_sym = left;
+ JitOptRef right_sym = right;
+ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym);
+ _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval *
+ ((PyFloatObject *)right_o)->ob_fval;
+ res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+ if (PyStackRef_IsNull(res_stackref )) {
goto error;
}
- res = sym_new_const(ctx, temp);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
- Py_DECREF(temp);
+ break;
}
- else {
- res = sym_new_type(ctx, &PyFloat_Type);
- stack_pointer += -1;
+ res = sym_new_type(ctx, &PyFloat_Type);
+ if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) {
+ REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0);
}
- stack_pointer[-1] = res;
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _BINARY_OP_ADD_FLOAT: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyFloat_CheckExact(sym_get_const(ctx, left)));
- assert(PyFloat_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyFloat_FromDouble(
- PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) +
- PyFloat_AS_DOUBLE(sym_get_const(ctx, right)));
- if (temp == NULL) {
+ if (
+ sym_is_safe_const(ctx, left) &&
+ sym_is_safe_const(ctx, right)
+ ) {
+ JitOptRef left_sym = left;
+ JitOptRef right_sym = right;
+ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym);
+ _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval +
+ ((PyFloatObject *)right_o)->ob_fval;
+ res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+ if (PyStackRef_IsNull(res_stackref )) {
goto error;
}
- res = sym_new_const(ctx, temp);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
- Py_DECREF(temp);
+ break;
}
- else {
- res = sym_new_type(ctx, &PyFloat_Type);
- stack_pointer += -1;
+ res = sym_new_type(ctx, &PyFloat_Type);
+ if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) {
+ REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0);
}
- stack_pointer[-1] = res;
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _BINARY_OP_SUBTRACT_FLOAT: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyFloat_CheckExact(sym_get_const(ctx, left)));
- assert(PyFloat_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyFloat_FromDouble(
- PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) -
- PyFloat_AS_DOUBLE(sym_get_const(ctx, right)));
- if (temp == NULL) {
+ if (
+ sym_is_safe_const(ctx, left) &&
+ sym_is_safe_const(ctx, right)
+ ) {
+ JitOptRef left_sym = left;
+ JitOptRef right_sym = right;
+ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym);
+ _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyFloat_CheckExact(left_o));
+ assert(PyFloat_CheckExact(right_o));
+ STAT_INC(BINARY_OP, hit);
+ double dres =
+ ((PyFloatObject *)left_o)->ob_fval -
+ ((PyFloatObject *)right_o)->ob_fval;
+ res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+ if (PyStackRef_IsNull(res_stackref )) {
goto error;
}
- res = sym_new_const(ctx, temp);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
- Py_DECREF(temp);
+ break;
}
- else {
- res = sym_new_type(ctx, &PyFloat_Type);
- stack_pointer += -1;
+ res = sym_new_type(ctx, &PyFloat_Type);
+ if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) {
+ REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0);
}
- stack_pointer[-1] = res;
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _BINARY_OP_MULTIPLY_FLOAT__NO_DECREF_INPUTS: {
+ JitOptRef res;
+ res = sym_new_not_null(ctx);
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _BINARY_OP_ADD_FLOAT__NO_DECREF_INPUTS: {
+ JitOptRef res;
+ res = sym_new_not_null(ctx);
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _BINARY_OP_SUBTRACT_FLOAT__NO_DECREF_INPUTS: {
+ JitOptRef res;
+ res = sym_new_not_null(ctx);
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _BINARY_OP_ADD_UNICODE: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
- if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
- assert(PyUnicode_CheckExact(sym_get_const(ctx, left)));
- assert(PyUnicode_CheckExact(sym_get_const(ctx, right)));
- PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right));
- if (temp == NULL) {
+ if (
+ sym_is_safe_const(ctx, left) &&
+ sym_is_safe_const(ctx, right)
+ ) {
+ JitOptRef left_sym = left;
+ JitOptRef right_sym = right;
+ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym);
+ _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+ PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
+ assert(PyUnicode_CheckExact(left_o));
+ assert(PyUnicode_CheckExact(right_o));
+ STAT_INC(BINARY_OP, hit);
+ PyObject *res_o = PyUnicode_Concat(left_o, right_o);
+ PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
+ PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
+ if (res_o == NULL) {
goto error;
}
- res = sym_new_const(ctx, temp);
+ res_stackref = PyStackRef_FromPyObjectSteal(res_o);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
- Py_DECREF(temp);
- }
- else {
- res = sym_new_type(ctx, &PyUnicode_Type);
- stack_pointer += -1;
+ break;
}
- stack_pointer[-1] = res;
+ res = sym_new_type(ctx, &PyUnicode_Type);
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _BINARY_OP_INPLACE_ADD_UNICODE: {
- JitOptSymbol *right;
- JitOptSymbol *left;
+ JitOptRef right;
+ JitOptRef left;
right = stack_pointer[-1];
left = stack_pointer[-2];
- JitOptSymbol *res;
+ JitOptRef res;
if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
assert(PyUnicode_CheckExact(sym_get_const(ctx, left)));
assert(PyUnicode_CheckExact(sym_get_const(ctx, right)));
@@ -559,7 +788,7 @@
}
case _BINARY_OP_EXTEND: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -568,8 +797,19 @@
}
case _BINARY_SLICE: {
- JitOptSymbol *res;
- res = sym_new_not_null(ctx);
+ JitOptRef container;
+ JitOptRef res;
+ container = stack_pointer[-3];
+ PyTypeObject *type = sym_get_type(container);
+ if (type == &PyUnicode_Type ||
+ type == &PyList_Type ||
+ type == &PyTuple_Type)
+ {
+ res = sym_new_type(ctx, type);
+ }
+ else {
+ res = sym_new_not_null(ctx);
+ }
stack_pointer[-3] = res;
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
@@ -583,7 +823,7 @@
}
case _BINARY_OP_SUBSCR_LIST_INT: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -592,7 +832,7 @@
}
case _BINARY_OP_SUBSCR_LIST_SLICE: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -601,7 +841,7 @@
}
case _BINARY_OP_SUBSCR_STR_INT: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_type(ctx, &PyUnicode_Type);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -610,7 +850,7 @@
}
case _GUARD_NOS_TUPLE: {
- JitOptSymbol *nos;
+ JitOptRef nos;
nos = stack_pointer[-2];
if (sym_matches_type(nos, &PyTuple_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -620,7 +860,7 @@
}
case _GUARD_TOS_TUPLE: {
- JitOptSymbol *tos;
+ JitOptRef tos;
tos = stack_pointer[-1];
if (sym_matches_type(tos, &PyTuple_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -630,9 +870,9 @@
}
case _BINARY_OP_SUBSCR_TUPLE_INT: {
- JitOptSymbol *sub_st;
- JitOptSymbol *tuple_st;
- JitOptSymbol *res;
+ JitOptRef sub_st;
+ JitOptRef tuple_st;
+ JitOptRef res;
sub_st = stack_pointer[-1];
tuple_st = stack_pointer[-2];
assert(sym_matches_type(tuple_st, &PyTuple_Type));
@@ -659,7 +899,7 @@
}
case _GUARD_NOS_DICT: {
- JitOptSymbol *nos;
+ JitOptRef nos;
nos = stack_pointer[-2];
if (sym_matches_type(nos, &PyDict_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -669,7 +909,7 @@
}
case _GUARD_TOS_DICT: {
- JitOptSymbol *tos;
+ JitOptRef tos;
tos = stack_pointer[-1];
if (sym_matches_type(tos, &PyDict_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -679,7 +919,7 @@
}
case _BINARY_OP_SUBSCR_DICT: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -688,7 +928,7 @@
}
case _BINARY_OP_SUBSCR_CHECK_FUNC: {
- JitOptSymbol *getitem;
+ JitOptRef getitem;
getitem = sym_new_not_null(ctx);
stack_pointer[0] = getitem;
stack_pointer += 1;
@@ -697,10 +937,10 @@
}
case _BINARY_OP_SUBSCR_INIT_CALL: {
- _Py_UOpsAbstractFrame *new_frame;
- new_frame = NULL;
+ JitOptRef new_frame;
+ new_frame = PyJitRef_NULL;
ctx->done = true;
- stack_pointer[-3] = (JitOptSymbol *)new_frame;
+ stack_pointer[-3] = new_frame;
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -743,14 +983,14 @@
}
case _CALL_INTRINSIC_1: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-1] = res;
break;
}
case _CALL_INTRINSIC_2: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -759,10 +999,10 @@
}
case _RETURN_VALUE: {
- JitOptSymbol *retval;
- JitOptSymbol *res;
+ JitOptRef retval;
+ JitOptRef res;
retval = stack_pointer[-1];
- JitOptSymbol *temp = retval;
+ JitOptRef temp = PyJitRef_Wrap(PyJitRef_Unwrap(retval));
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
ctx->frame->stack_pointer = stack_pointer;
@@ -786,14 +1026,14 @@
}
case _GET_AITER: {
- JitOptSymbol *iter;
+ JitOptRef iter;
iter = sym_new_not_null(ctx);
stack_pointer[-1] = iter;
break;
}
case _GET_ANEXT: {
- JitOptSymbol *awaitable;
+ JitOptRef awaitable;
awaitable = sym_new_not_null(ctx);
stack_pointer[0] = awaitable;
stack_pointer += 1;
@@ -802,7 +1042,7 @@
}
case _GET_AWAITABLE: {
- JitOptSymbol *iter;
+ JitOptRef iter;
iter = sym_new_not_null(ctx);
stack_pointer[-1] = iter;
break;
@@ -811,14 +1051,17 @@
/* _SEND is not a viable micro-op for tier 2 */
case _SEND_GEN_FRAME: {
+ JitOptRef gen_frame;
+ gen_frame = PyJitRef_NULL;
ctx->done = true;
+ stack_pointer[-1] = gen_frame;
break;
}
case _YIELD_VALUE: {
- JitOptSymbol *res;
- res = sym_new_unknown(ctx);
- stack_pointer[-1] = res;
+ JitOptRef value;
+ value = sym_new_unknown(ctx);
+ stack_pointer[-1] = value;
break;
}
@@ -829,7 +1072,7 @@
}
case _LOAD_COMMON_CONSTANT: {
- JitOptSymbol *value;
+ JitOptRef value;
value = sym_new_not_null(ctx);
stack_pointer[0] = value;
stack_pointer += 1;
@@ -838,7 +1081,7 @@
}
case _LOAD_BUILD_CLASS: {
- JitOptSymbol *bc;
+ JitOptRef bc;
bc = sym_new_not_null(ctx);
stack_pointer[0] = bc;
stack_pointer += 1;
@@ -857,8 +1100,11 @@
}
case _UNPACK_SEQUENCE: {
- JitOptSymbol **values;
+ JitOptRef *values;
+ JitOptRef *top;
values = &stack_pointer[-1];
+ top = &stack_pointer[-1 + oparg];
+ (void)top;
for (int i = 0; i < oparg; i++) {
values[i] = sym_new_unknown(ctx);
}
@@ -868,9 +1114,9 @@
}
case _UNPACK_SEQUENCE_TWO_TUPLE: {
- JitOptSymbol *seq;
- JitOptSymbol *val1;
- JitOptSymbol *val0;
+ JitOptRef seq;
+ JitOptRef val1;
+ JitOptRef val0;
seq = stack_pointer[-1];
val0 = sym_tuple_getitem(ctx, seq, 0);
val1 = sym_tuple_getitem(ctx, seq, 1);
@@ -882,8 +1128,8 @@
}
case _UNPACK_SEQUENCE_TUPLE: {
- JitOptSymbol *seq;
- JitOptSymbol **values;
+ JitOptRef seq;
+ JitOptRef *values;
seq = stack_pointer[-1];
values = &stack_pointer[-1];
for (int i = 0; i < oparg; i++) {
@@ -895,7 +1141,7 @@
}
case _UNPACK_SEQUENCE_LIST: {
- JitOptSymbol **values;
+ JitOptRef *values;
values = &stack_pointer[-1];
for (int _i = oparg; --_i >= 0;) {
values[_i] = sym_new_not_null(ctx);
@@ -906,8 +1152,11 @@
}
case _UNPACK_EX: {
- JitOptSymbol **values;
+ JitOptRef *values;
+ JitOptRef *top;
values = &stack_pointer[-1];
+ top = &stack_pointer[(oparg & 0xFF) + (oparg >> 8)];
+ (void)top;
int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1;
for (int i = 0; i < totalargs; i++) {
values[i] = sym_new_unknown(ctx);
@@ -940,7 +1189,7 @@
}
case _LOAD_LOCALS: {
- JitOptSymbol *locals;
+ JitOptRef locals;
locals = sym_new_not_null(ctx);
stack_pointer[0] = locals;
stack_pointer += 1;
@@ -951,7 +1200,7 @@
/* _LOAD_FROM_DICT_OR_GLOBALS is not a viable micro-op for tier 2 */
case _LOAD_NAME: {
- JitOptSymbol *v;
+ JitOptRef v;
v = sym_new_not_null(ctx);
stack_pointer[0] = v;
stack_pointer += 1;
@@ -960,7 +1209,7 @@
}
case _LOAD_GLOBAL: {
- JitOptSymbol **res;
+ JitOptRef *res;
res = &stack_pointer[0];
res[0] = sym_new_not_null(ctx);
stack_pointer += 1;
@@ -969,7 +1218,7 @@
}
case _PUSH_NULL_CONDITIONAL: {
- JitOptSymbol **null;
+ JitOptRef *null;
null = &stack_pointer[0];
if (oparg & 1) {
REPLACE_OP(this_instr, _PUSH_NULL, 0, 0);
@@ -988,7 +1237,7 @@
}
case _LOAD_GLOBAL_MODULE: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[0] = res;
stack_pointer += 1;
@@ -997,7 +1246,7 @@
}
case _LOAD_GLOBAL_BUILTINS: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[0] = res;
stack_pointer += 1;
@@ -1018,14 +1267,14 @@
}
case _LOAD_FROM_DICT_OR_DEREF: {
- JitOptSymbol *value;
+ JitOptRef value;
value = sym_new_not_null(ctx);
stack_pointer[-1] = value;
break;
}
case _LOAD_DEREF: {
- JitOptSymbol *value;
+ JitOptRef value;
value = sym_new_not_null(ctx);
stack_pointer[0] = value;
stack_pointer += 1;
@@ -1044,7 +1293,7 @@
}
case _BUILD_STRING: {
- JitOptSymbol *str;
+ JitOptRef str;
str = sym_new_type(ctx, &PyUnicode_Type);
stack_pointer[-oparg] = str;
stack_pointer += 1 - oparg;
@@ -1053,7 +1302,7 @@
}
case _BUILD_INTERPOLATION: {
- JitOptSymbol *interpolation;
+ JitOptRef interpolation;
interpolation = sym_new_not_null(ctx);
stack_pointer[-2 - (oparg & 1)] = interpolation;
stack_pointer += -1 - (oparg & 1);
@@ -1062,7 +1311,7 @@
}
case _BUILD_TEMPLATE: {
- JitOptSymbol *template;
+ JitOptRef template;
template = sym_new_not_null(ctx);
stack_pointer[-2] = template;
stack_pointer += -1;
@@ -1071,8 +1320,8 @@
}
case _BUILD_TUPLE: {
- JitOptSymbol **values;
- JitOptSymbol *tup;
+ JitOptRef *values;
+ JitOptRef tup;
values = &stack_pointer[-oparg];
tup = sym_new_tuple(ctx, oparg, values);
stack_pointer[-oparg] = tup;
@@ -1082,7 +1331,7 @@
}
case _BUILD_LIST: {
- JitOptSymbol *list;
+ JitOptRef list;
list = sym_new_type(ctx, &PyList_Type);
stack_pointer[-oparg] = list;
stack_pointer += 1 - oparg;
@@ -1103,7 +1352,7 @@
}
case _BUILD_SET: {
- JitOptSymbol *set;
+ JitOptRef set;
set = sym_new_type(ctx, &PySet_Type);
stack_pointer[-oparg] = set;
stack_pointer += 1 - oparg;
@@ -1112,7 +1361,7 @@
}
case _BUILD_MAP: {
- JitOptSymbol *map;
+ JitOptRef map;
map = sym_new_type(ctx, &PyDict_Type);
stack_pointer[-oparg*2] = map;
stack_pointer += 1 - oparg*2;
@@ -1143,7 +1392,7 @@
}
case _LOAD_SUPER_ATTR_ATTR: {
- JitOptSymbol *attr_st;
+ JitOptRef attr_st;
attr_st = sym_new_not_null(ctx);
stack_pointer[-3] = attr_st;
stack_pointer += -2;
@@ -1152,8 +1401,8 @@
}
case _LOAD_SUPER_ATTR_METHOD: {
- JitOptSymbol *attr;
- JitOptSymbol *self_or_null;
+ JitOptRef attr;
+ JitOptRef self_or_null;
attr = sym_new_not_null(ctx);
self_or_null = sym_new_not_null(ctx);
stack_pointer[-3] = attr;
@@ -1164,24 +1413,24 @@
}
case _LOAD_ATTR: {
- JitOptSymbol *owner;
- JitOptSymbol *attr;
- JitOptSymbol **self_or_null;
+ JitOptRef owner;
+ JitOptRef *attr;
+ JitOptRef *self_or_null;
owner = stack_pointer[-1];
+ attr = &stack_pointer[-1];
self_or_null = &stack_pointer[0];
(void)owner;
- attr = sym_new_not_null(ctx);
- if (oparg &1) {
+ *attr = sym_new_not_null(ctx);
+ if (oparg & 1) {
self_or_null[0] = sym_new_unknown(ctx);
}
- stack_pointer[-1] = attr;
stack_pointer += (oparg&1);
assert(WITHIN_STACK_BOUNDS());
break;
}
case _GUARD_TYPE_VERSION: {
- JitOptSymbol *owner;
+ JitOptRef owner;
owner = stack_pointer[-1];
uint32_t type_version = (uint32_t)this_instr->operand0;
assert(type_version);
@@ -1208,7 +1457,7 @@
}
case _LOAD_ATTR_INSTANCE_VALUE: {
- JitOptSymbol *attr;
+ JitOptRef attr;
uint16_t offset = (uint16_t)this_instr->operand0;
attr = sym_new_not_null(ctx);
(void)offset;
@@ -1217,14 +1466,14 @@
}
case _LOAD_ATTR_MODULE: {
- JitOptSymbol *owner;
- JitOptSymbol *attr;
+ JitOptRef owner;
+ JitOptRef attr;
owner = stack_pointer[-1];
uint32_t dict_version = (uint32_t)this_instr->operand0;
uint16_t index = (uint16_t)this_instr->operand0;
(void)dict_version;
(void)index;
- attr = NULL;
+ attr = PyJitRef_NULL;
if (sym_is_const(ctx, owner)) {
PyModuleObject *mod = (PyModuleObject *)sym_get_const(ctx, owner);
if (PyModule_CheckExact(mod)) {
@@ -1235,11 +1484,16 @@
PyDict_Watch(GLOBALS_WATCHER_ID, dict);
_Py_BloomFilter_Add(dependencies, dict);
PyObject *res = convert_global_to_const(this_instr, dict, true);
- attr = sym_new_const(ctx, res);
+ if (res == NULL) {
+ attr = sym_new_not_null(ctx);
+ }
+ else {
+ attr = sym_new_const(ctx, res);
+ }
}
}
}
- if (attr == NULL) {
+ if (PyJitRef_IsNull(attr)) {
attr = sym_new_not_null(ctx);
}
stack_pointer[-1] = attr;
@@ -1247,7 +1501,7 @@
}
case _LOAD_ATTR_WITH_HINT: {
- JitOptSymbol *attr;
+ JitOptRef attr;
uint16_t hint = (uint16_t)this_instr->operand0;
attr = sym_new_not_null(ctx);
(void)hint;
@@ -1256,7 +1510,7 @@
}
case _LOAD_ATTR_SLOT: {
- JitOptSymbol *attr;
+ JitOptRef attr;
uint16_t index = (uint16_t)this_instr->operand0;
attr = sym_new_not_null(ctx);
(void)index;
@@ -1265,25 +1519,43 @@
}
case _CHECK_ATTR_CLASS: {
+ JitOptRef owner;
+ owner = stack_pointer[-1];
+ uint32_t type_version = (uint32_t)this_instr->operand0;
+ PyObject *type = (PyObject *)_PyType_LookupByVersion(type_version);
+ if (type) {
+ if (type == sym_get_const(ctx, owner)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ else {
+ sym_set_const(owner, type);
+ }
+ }
break;
}
case _LOAD_ATTR_CLASS: {
- JitOptSymbol *attr;
+ JitOptRef owner;
+ JitOptRef attr;
+ owner = stack_pointer[-1];
PyObject *descr = (PyObject *)this_instr->operand0;
- attr = sym_new_not_null(ctx);
(void)descr;
+ PyTypeObject *type = (PyTypeObject *)sym_get_const(ctx, owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ _POP_TOP_LOAD_CONST_INLINE);
stack_pointer[-1] = attr;
break;
}
case _LOAD_ATTR_PROPERTY_FRAME: {
- _Py_UOpsAbstractFrame *new_frame;
+ JitOptRef new_frame;
PyObject *fget = (PyObject *)this_instr->operand0;
(void)fget;
- new_frame = NULL;
+ new_frame = PyJitRef_NULL;
ctx->done = true;
- stack_pointer[-1] = (JitOptSymbol *)new_frame;
+ stack_pointer[-1] = new_frame;
break;
}
@@ -1312,7 +1584,7 @@
}
case _COMPARE_OP: {
- JitOptSymbol *res;
+ JitOptRef res;
if (oparg & 16) {
res = sym_new_type(ctx, &PyBool_Type);
}
@@ -1326,7 +1598,7 @@
}
case _COMPARE_OP_FLOAT: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_type(ctx, &PyBool_Type);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -1335,9 +1607,9 @@
}
case _COMPARE_OP_INT: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
+ JitOptRef right;
+ JitOptRef left;
+ JitOptRef res;
right = stack_pointer[-1];
left = stack_pointer[-2];
if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
@@ -1367,7 +1639,7 @@
}
case _COMPARE_OP_STR: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_type(ctx, &PyBool_Type);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -1376,25 +1648,25 @@
}
case _IS_OP: {
- JitOptSymbol *res;
- res = sym_new_type(ctx, &PyBool_Type);
- stack_pointer[-2] = res;
+ JitOptRef b;
+ b = sym_new_type(ctx, &PyBool_Type);
+ stack_pointer[-2] = b;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _CONTAINS_OP: {
- JitOptSymbol *res;
- res = sym_new_type(ctx, &PyBool_Type);
- stack_pointer[-2] = res;
+ JitOptRef b;
+ b = sym_new_type(ctx, &PyBool_Type);
+ stack_pointer[-2] = b;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _GUARD_TOS_ANY_SET: {
- JitOptSymbol *tos;
+ JitOptRef tos;
tos = stack_pointer[-1];
if (sym_matches_type(tos, &PySet_Type) ||
sym_matches_type(tos, &PyFrozenSet_Type))
@@ -1405,26 +1677,26 @@
}
case _CONTAINS_OP_SET: {
- JitOptSymbol *res;
- res = sym_new_type(ctx, &PyBool_Type);
- stack_pointer[-2] = res;
+ JitOptRef b;
+ b = sym_new_type(ctx, &PyBool_Type);
+ stack_pointer[-2] = b;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _CONTAINS_OP_DICT: {
- JitOptSymbol *res;
- res = sym_new_type(ctx, &PyBool_Type);
- stack_pointer[-2] = res;
+ JitOptRef b;
+ b = sym_new_type(ctx, &PyBool_Type);
+ stack_pointer[-2] = b;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _CHECK_EG_MATCH: {
- JitOptSymbol *rest;
- JitOptSymbol *match;
+ JitOptRef rest;
+ JitOptRef match;
rest = sym_new_not_null(ctx);
match = sym_new_not_null(ctx);
stack_pointer[-2] = rest;
@@ -1433,14 +1705,14 @@
}
case _CHECK_EXC_MATCH: {
- JitOptSymbol *b;
+ JitOptRef b;
b = sym_new_not_null(ctx);
stack_pointer[-1] = b;
break;
}
case _IMPORT_NAME: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -1449,7 +1721,7 @@
}
case _IMPORT_FROM: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[0] = res;
stack_pointer += 1;
@@ -1462,15 +1734,36 @@
/* _POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */
case _IS_NONE: {
- JitOptSymbol *b;
+ JitOptRef b;
b = sym_new_not_null(ctx);
stack_pointer[-1] = b;
break;
}
case _GET_LEN: {
- JitOptSymbol *len;
- len = sym_new_not_null(ctx);
+ JitOptRef obj;
+ JitOptRef len;
+ obj = stack_pointer[-1];
+ int tuple_length = sym_tuple_length(obj);
+ if (tuple_length == -1) {
+ len = sym_new_type(ctx, &PyLong_Type);
+ }
+ else {
+ assert(tuple_length >= 0);
+ PyObject *temp = PyLong_FromLong(tuple_length);
+ if (temp == NULL) {
+ goto error;
+ }
+ if (_Py_IsImmortal(temp)) {
+ REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp);
+ }
+ len = sym_new_const(ctx, temp);
+ stack_pointer[0] = len;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ Py_DECREF(temp);
+ stack_pointer += -1;
+ }
stack_pointer[0] = len;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@@ -1478,7 +1771,7 @@
}
case _MATCH_CLASS: {
- JitOptSymbol *attrs;
+ JitOptRef attrs;
attrs = sym_new_not_null(ctx);
stack_pointer[-3] = attrs;
stack_pointer += -2;
@@ -1487,7 +1780,7 @@
}
case _MATCH_MAPPING: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[0] = res;
stack_pointer += 1;
@@ -1496,7 +1789,7 @@
}
case _MATCH_SEQUENCE: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[0] = res;
stack_pointer += 1;
@@ -1505,7 +1798,7 @@
}
case _MATCH_KEYS: {
- JitOptSymbol *values_or_none;
+ JitOptRef values_or_none;
values_or_none = sym_new_not_null(ctx);
stack_pointer[0] = values_or_none;
stack_pointer += 1;
@@ -1514,14 +1807,27 @@
}
case _GET_ITER: {
- JitOptSymbol *iter;
- iter = sym_new_not_null(ctx);
+ JitOptRef iterable;
+ JitOptRef iter;
+ JitOptRef index_or_null;
+ iterable = stack_pointer[-1];
+ if (sym_matches_type(iterable, &PyTuple_Type) || sym_matches_type(iterable, &PyList_Type)) {
+ iter = iterable;
+ index_or_null = sym_new_not_null(ctx);
+ }
+ else {
+ iter = sym_new_not_null(ctx);
+ index_or_null = sym_new_unknown(ctx);
+ }
stack_pointer[-1] = iter;
+ stack_pointer[0] = index_or_null;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _GET_YIELD_FROM_ITER: {
- JitOptSymbol *iter;
+ JitOptRef iter;
iter = sym_new_not_null(ctx);
stack_pointer[-1] = iter;
break;
@@ -1530,7 +1836,7 @@
/* _FOR_ITER is not a viable micro-op for tier 2 */
case _FOR_ITER_TIER_TWO: {
- JitOptSymbol *next;
+ JitOptRef next;
next = sym_new_not_null(ctx);
stack_pointer[0] = next;
stack_pointer += 1;
@@ -1553,7 +1859,7 @@
/* _ITER_NEXT_LIST is not a viable micro-op for tier 2 */
case _ITER_NEXT_LIST_TIER_TWO: {
- JitOptSymbol *next;
+ JitOptRef next;
next = sym_new_not_null(ctx);
stack_pointer[0] = next;
stack_pointer += 1;
@@ -1562,6 +1868,12 @@
}
case _ITER_CHECK_TUPLE: {
+ JitOptRef iter;
+ iter = stack_pointer[-2];
+ if (sym_matches_type(iter, &PyTuple_Type)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_type(iter, &PyTuple_Type);
break;
}
@@ -1572,7 +1884,7 @@
}
case _ITER_NEXT_TUPLE: {
- JitOptSymbol *next;
+ JitOptRef next;
next = sym_new_not_null(ctx);
stack_pointer[0] = next;
stack_pointer += 1;
@@ -1591,7 +1903,7 @@
}
case _ITER_NEXT_RANGE: {
- JitOptSymbol *next;
+ JitOptRef next;
next = sym_new_type(ctx, &PyLong_Type);
stack_pointer[0] = next;
stack_pointer += 1;
@@ -1600,13 +1912,18 @@
}
case _FOR_ITER_GEN_FRAME: {
+ JitOptRef gen_frame;
+ gen_frame = PyJitRef_NULL;
ctx->done = true;
+ stack_pointer[0] = gen_frame;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
break;
}
case _INSERT_NULL: {
- JitOptSymbol *self;
- JitOptSymbol **method_and_self;
+ JitOptRef self;
+ JitOptRef *method_and_self;
self = stack_pointer[-1];
method_and_self = &stack_pointer[-1];
method_and_self[0] = sym_new_null(ctx);
@@ -1617,7 +1934,7 @@
}
case _LOAD_SPECIAL: {
- JitOptSymbol **method_and_self;
+ JitOptRef *method_and_self;
method_and_self = &stack_pointer[-2];
method_and_self[0] = sym_new_not_null(ctx);
method_and_self[1] = sym_new_unknown(ctx);
@@ -1625,7 +1942,7 @@
}
case _WITH_EXCEPT_START: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[0] = res;
stack_pointer += 1;
@@ -1634,8 +1951,8 @@
}
case _PUSH_EXC_INFO: {
- JitOptSymbol *prev_exc;
- JitOptSymbol *new_exc;
+ JitOptRef prev_exc;
+ JitOptRef new_exc;
prev_exc = sym_new_not_null(ctx);
new_exc = sym_new_not_null(ctx);
stack_pointer[-1] = prev_exc;
@@ -1654,13 +1971,17 @@
}
case _LOAD_ATTR_METHOD_WITH_VALUES: {
- JitOptSymbol *owner;
- JitOptSymbol *attr;
- JitOptSymbol *self;
+ JitOptRef owner;
+ JitOptRef attr;
+ JitOptRef self;
owner = stack_pointer[-1];
PyObject *descr = (PyObject *)this_instr->operand0;
(void)descr;
- attr = sym_new_not_null(ctx);
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _LOAD_CONST_UNDER_INLINE_BORROW,
+ _LOAD_CONST_UNDER_INLINE);
self = owner;
stack_pointer[-1] = attr;
stack_pointer[0] = self;
@@ -1670,13 +1991,17 @@
}
case _LOAD_ATTR_METHOD_NO_DICT: {
- JitOptSymbol *owner;
- JitOptSymbol *attr;
- JitOptSymbol *self;
+ JitOptRef owner;
+ JitOptRef attr;
+ JitOptRef self;
owner = stack_pointer[-1];
PyObject *descr = (PyObject *)this_instr->operand0;
(void)descr;
- attr = sym_new_not_null(ctx);
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _LOAD_CONST_UNDER_INLINE_BORROW,
+ _LOAD_CONST_UNDER_INLINE);
self = owner;
stack_pointer[-1] = attr;
stack_pointer[0] = self;
@@ -1686,15 +2011,31 @@
}
case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: {
- JitOptSymbol *attr;
- attr = sym_new_not_null(ctx);
+ JitOptRef owner;
+ JitOptRef attr;
+ owner = stack_pointer[-1];
+ PyObject *descr = (PyObject *)this_instr->operand0;
+ (void)descr;
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ _POP_TOP_LOAD_CONST_INLINE);
stack_pointer[-1] = attr;
break;
}
case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: {
- JitOptSymbol *attr;
- attr = sym_new_not_null(ctx);
+ JitOptRef owner;
+ JitOptRef attr;
+ owner = stack_pointer[-1];
+ PyObject *descr = (PyObject *)this_instr->operand0;
+ (void)descr;
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _POP_TOP_LOAD_CONST_INLINE_BORROW,
+ _POP_TOP_LOAD_CONST_INLINE);
stack_pointer[-1] = attr;
break;
}
@@ -1704,13 +2045,17 @@
}
case _LOAD_ATTR_METHOD_LAZY_DICT: {
- JitOptSymbol *owner;
- JitOptSymbol *attr;
- JitOptSymbol *self;
+ JitOptRef owner;
+ JitOptRef attr;
+ JitOptRef self;
owner = stack_pointer[-1];
PyObject *descr = (PyObject *)this_instr->operand0;
(void)descr;
- attr = sym_new_not_null(ctx);
+ PyTypeObject *type = sym_get_type(owner);
+ PyObject *name = PyTuple_GET_ITEM(co->co_names, oparg >> 1);
+ attr = lookup_attr(ctx, this_instr, type, name,
+ _LOAD_CONST_UNDER_INLINE_BORROW,
+ _LOAD_CONST_UNDER_INLINE);
self = owner;
stack_pointer[-1] = attr;
stack_pointer[0] = self;
@@ -1720,16 +2065,17 @@
}
case _MAYBE_EXPAND_METHOD: {
- JitOptSymbol **args;
- JitOptSymbol *func;
- JitOptSymbol *maybe_self;
- args = &stack_pointer[-oparg];
+ JitOptRef *args;
+ JitOptRef self_or_null;
+ JitOptRef callable;
args = &stack_pointer[-oparg];
+ self_or_null = stack_pointer[-1 - oparg];
+ callable = stack_pointer[-2 - oparg];
(void)args;
- func = sym_new_not_null(ctx);
- maybe_self = sym_new_not_null(ctx);
- stack_pointer[-2 - oparg] = func;
- stack_pointer[-1 - oparg] = maybe_self;
+ callable = sym_new_not_null(ctx);
+ self_or_null = sym_new_not_null(ctx);
+ stack_pointer[-2 - oparg] = callable;
+ stack_pointer[-1 - oparg] = self_or_null;
break;
}
@@ -1738,7 +2084,7 @@
/* _MONITOR_CALL is not a viable micro-op for tier 2 */
case _PY_FRAME_GENERAL: {
- _Py_UOpsAbstractFrame *new_frame;
+ JitOptRef new_frame;
PyCodeObject *co = NULL;
assert((this_instr + 2)->opcode == _PUSH_FRAME);
co = get_code_with_logging((this_instr + 2));
@@ -1746,15 +2092,15 @@
ctx->done = true;
break;
}
- new_frame = frame_new(ctx, co, 0, NULL, 0);
- stack_pointer[-2 - oparg] = (JitOptSymbol *)new_frame;
+ new_frame = PyJitRef_Wrap((JitOptSymbol *)frame_new(ctx, co, 0, NULL, 0));
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _CHECK_FUNCTION_VERSION: {
- JitOptSymbol *callable;
+ JitOptRef callable;
callable = stack_pointer[-2 - oparg];
uint32_t func_version = (uint32_t)this_instr->operand0;
if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) {
@@ -1771,6 +2117,16 @@
}
case _CHECK_METHOD_VERSION: {
+ JitOptRef callable;
+ callable = stack_pointer[-2 - oparg];
+ uint32_t func_version = (uint32_t)this_instr->operand0;
+ if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyMethod_Type)) {
+ PyMethodObject *method = (PyMethodObject *)sym_get_const(ctx, callable);
+ assert(PyMethod_Check(method));
+ REPLACE_OP(this_instr, _CHECK_FUNCTION_VERSION_INLINE, 0, func_version);
+ this_instr->operand1 = (uintptr_t)method->im_func;
+ }
+ sym_set_type(callable, &PyMethod_Type);
break;
}
@@ -1783,7 +2139,7 @@
}
case _CALL_NON_PY_GENERAL: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -1792,8 +2148,8 @@
}
case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: {
- JitOptSymbol *null;
- JitOptSymbol *callable;
+ JitOptRef null;
+ JitOptRef callable;
null = stack_pointer[-1 - oparg];
callable = stack_pointer[-2 - oparg];
sym_set_null(null);
@@ -1802,8 +2158,8 @@
}
case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: {
- JitOptSymbol *self_or_null;
- JitOptSymbol *callable;
+ JitOptRef self_or_null;
+ JitOptRef callable;
self_or_null = stack_pointer[-1 - oparg];
callable = stack_pointer[-2 - oparg];
callable = sym_new_not_null(ctx);
@@ -1821,8 +2177,8 @@
}
case _CHECK_FUNCTION_EXACT_ARGS: {
- JitOptSymbol *self_or_null;
- JitOptSymbol *callable;
+ JitOptRef self_or_null;
+ JitOptRef callable;
self_or_null = stack_pointer[-1 - oparg];
callable = stack_pointer[-2 - oparg];
assert(sym_matches_type(callable, &PyFunction_Type));
@@ -1849,9 +2205,9 @@
}
case _INIT_CALL_PY_EXACT_ARGS: {
- JitOptSymbol **args;
- JitOptSymbol *self_or_null;
- _Py_UOpsAbstractFrame *new_frame;
+ JitOptRef *args;
+ JitOptRef self_or_null;
+ JitOptRef new_frame;
args = &stack_pointer[-oparg];
self_or_null = stack_pointer[-1 - oparg];
int argcount = oparg;
@@ -1862,32 +2218,32 @@
ctx->done = true;
break;
}
- assert(self_or_null != NULL);
+ assert(!PyJitRef_IsNull(self_or_null));
assert(args != NULL);
if (sym_is_not_null(self_or_null)) {
args--;
argcount++;
}
if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) {
- new_frame = frame_new(ctx, co, 0, args, argcount);
+ new_frame = PyJitRef_Wrap((JitOptSymbol *)frame_new(ctx, co, 0, args, argcount));
} else {
- new_frame = frame_new(ctx, co, 0, NULL, 0);
+ new_frame = PyJitRef_Wrap((JitOptSymbol *)frame_new(ctx, co, 0, NULL, 0));
}
- stack_pointer[-2 - oparg] = (JitOptSymbol *)new_frame;
+ stack_pointer[-2 - oparg] = new_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _PUSH_FRAME: {
- _Py_UOpsAbstractFrame *new_frame;
- new_frame = (_Py_UOpsAbstractFrame *)stack_pointer[-1];
+ JitOptRef new_frame;
+ new_frame = stack_pointer[-1];
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
ctx->frame->stack_pointer = stack_pointer;
- ctx->frame = new_frame;
+ ctx->frame = (_Py_UOpsAbstractFrame *)PyJitRef_Unwrap(new_frame);
ctx->curr_frame_depth++;
- stack_pointer = new_frame->stack_pointer;
+ stack_pointer = ctx->frame->stack_pointer;
co = get_code(this_instr);
if (co == NULL) {
ctx->done = true;
@@ -1912,7 +2268,7 @@
}
case _GUARD_NOS_NULL: {
- JitOptSymbol *null;
+ JitOptRef null;
null = stack_pointer[-2];
if (sym_is_null(null)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -1921,8 +2277,28 @@
break;
}
+ case _GUARD_NOS_NOT_NULL: {
+ JitOptRef nos;
+ nos = stack_pointer[-2];
+ if (sym_is_not_null(nos)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_non_null(nos);
+ break;
+ }
+
+ case _GUARD_THIRD_NULL: {
+ JitOptRef null;
+ null = stack_pointer[-3];
+ if (sym_is_null(null)) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_null(null);
+ break;
+ }
+
case _GUARD_CALLABLE_TYPE_1: {
- JitOptSymbol *callable;
+ JitOptRef callable;
callable = stack_pointer[-3];
if (sym_get_const(ctx, callable) == (PyObject *)&PyType_Type) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -1932,11 +2308,14 @@
}
case _CALL_TYPE_1: {
- JitOptSymbol *arg;
- JitOptSymbol *res;
+ JitOptRef arg;
+ JitOptRef res;
arg = stack_pointer[-1];
- if (sym_has_type(arg)) {
- res = sym_new_const(ctx, (PyObject *)sym_get_type(arg));
+ PyObject* type = (PyObject *)sym_get_type(arg);
+ if (type) {
+ res = sym_new_const(ctx, type);
+ REPLACE_OP(this_instr, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, 0,
+ (uintptr_t)type);
}
else {
res = sym_new_not_null(ctx);
@@ -1948,7 +2327,7 @@
}
case _GUARD_CALLABLE_STR_1: {
- JitOptSymbol *callable;
+ JitOptRef callable;
callable = stack_pointer[-3];
if (sym_get_const(ctx, callable) == (PyObject *)&PyUnicode_Type) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -1958,8 +2337,8 @@
}
case _CALL_STR_1: {
- JitOptSymbol *arg;
- JitOptSymbol *res;
+ JitOptRef arg;
+ JitOptRef res;
arg = stack_pointer[-1];
if (sym_matches_type(arg, &PyUnicode_Type)) {
res = arg;
@@ -1974,7 +2353,7 @@
}
case _GUARD_CALLABLE_TUPLE_1: {
- JitOptSymbol *callable;
+ JitOptRef callable;
callable = stack_pointer[-3];
if (sym_get_const(ctx, callable) == (PyObject *)&PyTuple_Type) {
REPLACE_OP(this_instr, _NOP, 0, 0);
@@ -1984,8 +2363,8 @@
}
case _CALL_TUPLE_1: {
- JitOptSymbol *arg;
- JitOptSymbol *res;
+ JitOptRef arg;
+ JitOptRef res;
arg = stack_pointer[-1];
if (sym_matches_type(arg, &PyTuple_Type)) {
res = arg;
@@ -2000,26 +2379,27 @@
}
case _CHECK_AND_ALLOCATE_OBJECT: {
- JitOptSymbol **args;
- JitOptSymbol *self;
- JitOptSymbol *init;
- args = &stack_pointer[-oparg];
+ JitOptRef *args;
+ JitOptRef self_or_null;
+ JitOptRef callable;
args = &stack_pointer[-oparg];
+ self_or_null = stack_pointer[-1 - oparg];
+ callable = stack_pointer[-2 - oparg];
uint32_t type_version = (uint32_t)this_instr->operand0;
(void)type_version;
(void)args;
- self = sym_new_not_null(ctx);
- init = sym_new_not_null(ctx);
- stack_pointer[-2 - oparg] = self;
- stack_pointer[-1 - oparg] = init;
+ callable = sym_new_not_null(ctx);
+ self_or_null = sym_new_not_null(ctx);
+ stack_pointer[-2 - oparg] = callable;
+ stack_pointer[-1 - oparg] = self_or_null;
break;
}
case _CREATE_INIT_FRAME: {
- _Py_UOpsAbstractFrame *init_frame;
- init_frame = NULL;
+ JitOptRef init_frame;
+ init_frame = PyJitRef_NULL;
ctx->done = true;
- stack_pointer[-2 - oparg] = (JitOptSymbol *)init_frame;
+ stack_pointer[-2 - oparg] = init_frame;
stack_pointer += -1 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -2032,7 +2412,7 @@
}
case _CALL_BUILTIN_CLASS: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2041,7 +2421,7 @@
}
case _CALL_BUILTIN_O: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2050,7 +2430,7 @@
}
case _CALL_BUILTIN_FAST: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2059,7 +2439,7 @@
}
case _CALL_BUILTIN_FAST_WITH_KEYWORDS: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2067,24 +2447,90 @@
break;
}
+ case _GUARD_CALLABLE_LEN: {
+ JitOptRef callable;
+ callable = stack_pointer[-3];
+ PyObject *len = _PyInterpreterState_GET()->callable_cache.len;
+ if (sym_get_const(ctx, callable) == len) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_const(callable, len);
+ break;
+ }
+
case _CALL_LEN: {
- JitOptSymbol *res;
+ JitOptRef arg;
+ JitOptRef res;
+ arg = stack_pointer[-1];
res = sym_new_type(ctx, &PyLong_Type);
- stack_pointer[-2 - oparg] = res;
- stack_pointer += -1 - oparg;
+ int tuple_length = sym_tuple_length(arg);
+ if (tuple_length >= 0) {
+ PyObject *temp = PyLong_FromLong(tuple_length);
+ if (temp == NULL) {
+ goto error;
+ }
+ if (_Py_IsImmortal(temp)) {
+ REPLACE_OP(this_instr, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW,
+ 0, (uintptr_t)temp);
+ }
+ res = sym_new_const(ctx, temp);
+ stack_pointer[-3] = res;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ Py_DECREF(temp);
+ stack_pointer += 2;
+ }
+ stack_pointer[-3] = res;
+ stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
break;
}
+ case _GUARD_CALLABLE_ISINSTANCE: {
+ JitOptRef callable;
+ callable = stack_pointer[-4];
+ PyObject *isinstance = _PyInterpreterState_GET()->callable_cache.isinstance;
+ if (sym_get_const(ctx, callable) == isinstance) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_const(callable, isinstance);
+ break;
+ }
+
case _CALL_ISINSTANCE: {
- JitOptSymbol *res;
- res = sym_new_not_null(ctx);
- stack_pointer[-2 - oparg] = res;
- stack_pointer += -1 - oparg;
+ JitOptRef cls;
+ JitOptRef instance;
+ JitOptRef res;
+ cls = stack_pointer[-1];
+ instance = stack_pointer[-2];
+ res = sym_new_type(ctx, &PyBool_Type);
+ PyTypeObject *inst_type = sym_get_type(instance);
+ PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, cls);
+ if (inst_type && cls_o && sym_matches_type(cls, &PyType_Type)) {
+ PyObject *out = Py_False;
+ if (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o)) {
+ out = Py_True;
+ }
+ sym_set_const(res, out);
+ REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out);
+ }
+ stack_pointer[-4] = res;
+ stack_pointer += -3;
assert(WITHIN_STACK_BOUNDS());
break;
}
+ case _GUARD_CALLABLE_LIST_APPEND: {
+ JitOptRef callable;
+ callable = stack_pointer[-3];
+ PyObject *list_append = _PyInterpreterState_GET()->callable_cache.list_append;
+ if (sym_get_const(ctx, callable) == list_append) {
+ REPLACE_OP(this_instr, _NOP, 0, 0);
+ }
+ sym_set_const(callable, list_append);
+ break;
+ }
+
case _CALL_LIST_APPEND: {
stack_pointer += -3;
assert(WITHIN_STACK_BOUNDS());
@@ -2092,7 +2538,7 @@
}
case _CALL_METHOD_DESCRIPTOR_O: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2101,7 +2547,7 @@
}
case _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2110,7 +2556,7 @@
}
case _CALL_METHOD_DESCRIPTOR_NOARGS: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2119,7 +2565,7 @@
}
case _CALL_METHOD_DESCRIPTOR_FAST: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2 - oparg] = res;
stack_pointer += -1 - oparg;
@@ -2136,10 +2582,10 @@
/* _DO_CALL_KW is not a viable micro-op for tier 2 */
case _PY_FRAME_KW: {
- _Py_UOpsAbstractFrame *new_frame;
- new_frame = NULL;
+ JitOptRef new_frame;
+ new_frame = PyJitRef_NULL;
ctx->done = true;
- stack_pointer[-3 - oparg] = (JitOptSymbol *)new_frame;
+ stack_pointer[-3 - oparg] = new_frame;
stack_pointer += -2 - oparg;
assert(WITHIN_STACK_BOUNDS());
break;
@@ -2162,7 +2608,7 @@
}
case _CALL_KW_NON_PY: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-3 - oparg] = res;
stack_pointer += -2 - oparg;
@@ -2177,14 +2623,14 @@
/* _DO_CALL_FUNCTION_EX is not a viable micro-op for tier 2 */
case _MAKE_FUNCTION: {
- JitOptSymbol *func;
+ JitOptRef func;
func = sym_new_not_null(ctx);
stack_pointer[-1] = func;
break;
}
case _SET_FUNCTION_ATTRIBUTE: {
- JitOptSymbol *func_out;
+ JitOptRef func_out;
func_out = sym_new_not_null(ctx);
stack_pointer[-2] = func_out;
stack_pointer += -1;
@@ -2193,7 +2639,7 @@
}
case _RETURN_GENERATOR: {
- JitOptSymbol *res;
+ JitOptRef res;
ctx->frame->stack_pointer = stack_pointer;
frame_pop(ctx);
stack_pointer = ctx->frame->stack_pointer;
@@ -2215,7 +2661,7 @@
}
case _BUILD_SLICE: {
- JitOptSymbol *slice;
+ JitOptRef slice;
slice = sym_new_type(ctx, &PySlice_Type);
stack_pointer[-oparg] = slice;
stack_pointer += 1 - oparg;
@@ -2224,21 +2670,21 @@
}
case _CONVERT_VALUE: {
- JitOptSymbol *result;
+ JitOptRef result;
result = sym_new_not_null(ctx);
stack_pointer[-1] = result;
break;
}
case _FORMAT_SIMPLE: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-1] = res;
break;
}
case _FORMAT_WITH_SPEC: {
- JitOptSymbol *res;
+ JitOptRef res;
res = sym_new_not_null(ctx);
stack_pointer[-2] = res;
stack_pointer += -1;
@@ -2247,8 +2693,8 @@
}
case _COPY: {
- JitOptSymbol *bottom;
- JitOptSymbol *top;
+ JitOptRef bottom;
+ JitOptRef top;
bottom = stack_pointer[-1 - (oparg-1)];
assert(oparg > 0);
top = bottom;
@@ -2259,15 +2705,40 @@
}
case _BINARY_OP: {
- JitOptSymbol *right;
- JitOptSymbol *left;
- JitOptSymbol *res;
- right = stack_pointer[-1];
- left = stack_pointer[-2];
- bool lhs_int = sym_matches_type(left, &PyLong_Type);
- bool rhs_int = sym_matches_type(right, &PyLong_Type);
- bool lhs_float = sym_matches_type(left, &PyFloat_Type);
- bool rhs_float = sym_matches_type(right, &PyFloat_Type);
+ JitOptRef rhs;
+ JitOptRef lhs;
+ JitOptRef res;
+ rhs = stack_pointer[-1];
+ lhs = stack_pointer[-2];
+ if (
+ sym_is_safe_const(ctx, lhs) &&
+ sym_is_safe_const(ctx, rhs)
+ ) {
+ JitOptRef lhs_sym = lhs;
+ JitOptRef rhs_sym = rhs;
+ _PyStackRef lhs = sym_get_const_as_stackref(ctx, lhs_sym);
+ _PyStackRef rhs = sym_get_const_as_stackref(ctx, rhs_sym);
+ _PyStackRef res_stackref;
+ /* Start of uop copied from bytecodes for constant evaluation */
+ PyObject *lhs_o = PyStackRef_AsPyObjectBorrow(lhs);
+ PyObject *rhs_o = PyStackRef_AsPyObjectBorrow(rhs);
+ assert(_PyEval_BinaryOps[oparg]);
+ PyObject *res_o = _PyEval_BinaryOps[oparg](lhs_o, rhs_o);
+ if (res_o == NULL) {
+ JUMP_TO_LABEL(error);
+ }
+ res_stackref = PyStackRef_FromPyObjectSteal(res_o);
+ /* End of uop copied from bytecodes for constant evaluation */
+ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
+ stack_pointer[-2] = res;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+ bool lhs_int = sym_matches_type(lhs, &PyLong_Type);
+ bool rhs_int = sym_matches_type(rhs, &PyLong_Type);
+ bool lhs_float = sym_matches_type(lhs, &PyFloat_Type);
+ bool rhs_float = sym_matches_type(rhs, &PyFloat_Type);
if (!((lhs_int || lhs_float) && (rhs_int || rhs_float))) {
res = sym_new_unknown(ctx);
}
@@ -2278,10 +2749,10 @@
else if (lhs_float) {
res = sym_new_type(ctx, &PyFloat_Type);
}
- else if (!sym_is_const(ctx, right)) {
+ else if (!sym_is_const(ctx, rhs)) {
res = sym_new_unknown(ctx);
}
- else if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, right))) {
+ else if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, rhs))) {
res = sym_new_type(ctx, &PyFloat_Type);
}
else {
@@ -2304,11 +2775,11 @@
}
case _SWAP: {
- JitOptSymbol *top;
- JitOptSymbol *bottom;
+ JitOptRef top;
+ JitOptRef bottom;
top = stack_pointer[-1];
bottom = stack_pointer[-2 - (oparg-2)];
- JitOptSymbol *temp = bottom;
+ JitOptRef temp = bottom;
bottom = top;
top = temp;
assert(oparg >= 2);
@@ -2336,7 +2807,7 @@
/* _INSTRUMENTED_POP_JUMP_IF_NOT_NONE is not a viable micro-op for tier 2 */
case _GUARD_IS_TRUE_POP: {
- JitOptSymbol *flag;
+ JitOptRef flag;
flag = stack_pointer[-1];
if (sym_is_const(ctx, flag)) {
PyObject *value = sym_get_const(ctx, flag);
@@ -2350,7 +2821,7 @@
}
case _GUARD_IS_FALSE_POP: {
- JitOptSymbol *flag;
+ JitOptRef flag;
flag = stack_pointer[-1];
if (sym_is_const(ctx, flag)) {
PyObject *value = sym_get_const(ctx, flag);
@@ -2364,33 +2835,33 @@
}
case _GUARD_IS_NONE_POP: {
- JitOptSymbol *flag;
- flag = stack_pointer[-1];
- if (sym_is_const(ctx, flag)) {
- PyObject *value = sym_get_const(ctx, flag);
+ JitOptRef val;
+ val = stack_pointer[-1];
+ if (sym_is_const(ctx, val)) {
+ PyObject *value = sym_get_const(ctx, val);
assert(value != NULL);
eliminate_pop_guard(this_instr, !Py_IsNone(value));
}
- else if (sym_has_type(flag)) {
- assert(!sym_matches_type(flag, &_PyNone_Type));
+ else if (sym_has_type(val)) {
+ assert(!sym_matches_type(val, &_PyNone_Type));
eliminate_pop_guard(this_instr, true);
}
- sym_set_const(flag, Py_None);
+ sym_set_const(val, Py_None);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}
case _GUARD_IS_NOT_NONE_POP: {
- JitOptSymbol *flag;
- flag = stack_pointer[-1];
- if (sym_is_const(ctx, flag)) {
- PyObject *value = sym_get_const(ctx, flag);
+ JitOptRef val;
+ val = stack_pointer[-1];
+ if (sym_is_const(ctx, val)) {
+ PyObject *value = sym_get_const(ctx, val);
assert(value != NULL);
eliminate_pop_guard(this_instr, Py_IsNone(value));
}
- else if (sym_has_type(flag)) {
- assert(!sym_matches_type(flag, &_PyNone_Type));
+ else if (sym_has_type(val)) {
+ assert(!sym_matches_type(val, &_PyNone_Type));
eliminate_pop_guard(this_instr, false);
}
stack_pointer += -1;
@@ -2430,7 +2901,7 @@
}
case _LOAD_CONST_INLINE: {
- JitOptSymbol *value;
+ JitOptRef value;
PyObject *ptr = (PyObject *)this_instr->operand0;
value = sym_new_const(ctx, ptr);
stack_pointer[0] = value;
@@ -2440,7 +2911,7 @@
}
case _POP_TOP_LOAD_CONST_INLINE: {
- JitOptSymbol *value;
+ JitOptRef value;
PyObject *ptr = (PyObject *)this_instr->operand0;
value = sym_new_const(ctx, ptr);
stack_pointer[-1] = value;
@@ -2448,25 +2919,43 @@
}
case _LOAD_CONST_INLINE_BORROW: {
- JitOptSymbol *value;
+ JitOptRef value;
PyObject *ptr = (PyObject *)this_instr->operand0;
- value = sym_new_const(ctx, ptr);
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
stack_pointer[0] = value;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
}
+ case _POP_CALL: {
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_CALL_ONE: {
+ stack_pointer += -3;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_CALL_TWO: {
+ stack_pointer += -4;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
case _POP_TOP_LOAD_CONST_INLINE_BORROW: {
- JitOptSymbol *value;
+ JitOptRef value;
PyObject *ptr = (PyObject *)this_instr->operand0;
- value = sym_new_const(ctx, ptr);
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
stack_pointer[-1] = value;
break;
}
case _POP_TWO_LOAD_CONST_INLINE_BORROW: {
- JitOptSymbol *value;
+ JitOptRef value;
value = sym_new_not_null(ctx);
stack_pointer[-2] = value;
stack_pointer += -1;
@@ -2474,6 +2963,60 @@
break;
}
+ case _POP_CALL_LOAD_CONST_INLINE_BORROW: {
+ JitOptRef value;
+ PyObject *ptr = (PyObject *)this_instr->operand0;
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
+ stack_pointer[-2] = value;
+ stack_pointer += -1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW: {
+ JitOptRef value;
+ PyObject *ptr = (PyObject *)this_instr->operand0;
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
+ stack_pointer[-3] = value;
+ stack_pointer += -2;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW: {
+ JitOptRef value;
+ PyObject *ptr = (PyObject *)this_instr->operand0;
+ value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
+ stack_pointer[-4] = value;
+ stack_pointer += -3;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _LOAD_CONST_UNDER_INLINE: {
+ JitOptRef value;
+ JitOptRef new;
+ value = sym_new_not_null(ctx);
+ new = sym_new_not_null(ctx);
+ stack_pointer[-1] = value;
+ stack_pointer[0] = new;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
+ case _LOAD_CONST_UNDER_INLINE_BORROW: {
+ JitOptRef value;
+ JitOptRef new;
+ value = sym_new_not_null(ctx);
+ new = sym_new_not_null(ctx);
+ stack_pointer[-1] = value;
+ stack_pointer[0] = new;
+ stack_pointer += 1;
+ assert(WITHIN_STACK_BOUNDS());
+ break;
+ }
+
case _CHECK_FUNCTION: {
break;
}
diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c
index e8a4f87031b..8a3df236c80 100644
--- a/Python/optimizer_symbols.c
+++ b/Python/optimizer_symbols.c
@@ -13,22 +13,50 @@
#include <stdint.h>
#include <stddef.h>
-/* Symbols
- =======
-
- See the diagram at
- https://github.com/faster-cpython/ideas/blob/main/3.13/redundancy_eliminator.md
-
- We represent the nodes in the diagram as follows
- (the flag bits are only defined in optimizer_symbols.c):
- - Top: no flag bits, typ and const_val are NULL.
- - NULL: IS_NULL flag set, type and const_val NULL.
- - Not NULL: NOT_NULL flag set, type and const_val NULL.
- - None/not None: not used. (None could be represented as any other constant.)
- - Known type: NOT_NULL flag set and typ set; const_val is NULL.
- - Known constant: NOT_NULL flag set, type set, const_val set.
- - Bottom: IS_NULL and NOT_NULL flags set, type and const_val NULL.
- */
+/*
+
+Symbols
+=======
+
+https://github.com/faster-cpython/ideas/blob/main/3.13/redundancy_eliminator.md
+
+Logically, all symbols begin as UNKNOWN, and can transition downwards along the
+edges of the lattice, but *never* upwards (see the diagram below). The UNKNOWN
+state represents no information, and the BOTTOM state represents contradictory
+information. Though symbols logically progress through all intermediate nodes,
+we often skip in-between states for convenience:
+
+ UNKNOWN
+ | |
+NULL |
+| | <- Anything below this level is an object.
+| NON_NULL-+
+| | | <- Anything below this level has a known type version.
+| TYPE_VERSION |
+| | | <- Anything below this level has a known type.
+| KNOWN_CLASS |
+| | | | | |
+| | | INT* | |
+| | | | | | <- Anything below this level has a known truthiness.
+| | | | | TRUTHINESS
+| | | | | |
+| TUPLE | | | |
+| | | | | | <- Anything below this level is a known constant.
+| KNOWN_VALUE--+
+| | <- Anything below this level is unreachable.
+BOTTOM
+
+For example, after guarding that the type of an UNKNOWN local is int, we can
+narrow the symbol to KNOWN_CLASS (logically progressing though NON_NULL and
+TYPE_VERSION to get there). Later, we may learn that it is falsey based on the
+result of a truth test, which would allow us to narrow the symbol to KNOWN_VALUE
+(with a value of integer zero). If at any point we encounter a float guard on
+the same symbol, that would be a contradiction, and the symbol would be set to
+BOTTOM (indicating that the code is unreachable).
+
+INT* is a limited range int, currently a "compact" int.
+
+*/
#ifdef Py_DEBUG
static inline int get_lltrace(void) {
@@ -64,6 +92,12 @@ out_of_space(JitOptContext *ctx)
return &NO_SPACE_SYMBOL;
}
+JitOptRef
+out_of_space_ref(JitOptContext *ctx)
+{
+ return PyJitRef_Wrap(out_of_space(ctx));
+}
+
static JitOptSymbol *
sym_new(JitOptContext *ctx)
{
@@ -74,7 +108,7 @@ sym_new(JitOptContext *ctx)
return NULL;
}
ctx->t_arena.ty_curr_number++;
- self->tag = JIT_SYM_UNKNOWN_TAG;
+ self->tag = JIT_SYM_UNKNOWN_TAG;;
return self;
}
@@ -93,25 +127,28 @@ sym_set_bottom(JitOptContext *ctx, JitOptSymbol *sym)
}
bool
-_Py_uop_sym_is_bottom(JitOptSymbol *sym)
+_Py_uop_sym_is_bottom(JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
return sym->tag == JIT_SYM_BOTTOM_TAG;
}
bool
-_Py_uop_sym_is_not_null(JitOptSymbol *sym) {
+_Py_uop_sym_is_not_null(JitOptRef ref) {
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
return sym->tag == JIT_SYM_NON_NULL_TAG || sym->tag > JIT_SYM_BOTTOM_TAG;
}
bool
-_Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym)
+_Py_uop_sym_is_const(JitOptContext *ctx, JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
return true;
}
if (sym->tag == JIT_SYM_TRUTHINESS_TAG) {
JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value;
- int truthiness = _Py_uop_sym_truthiness(ctx, value);
+ int truthiness = _Py_uop_sym_truthiness(ctx, PyJitRef_Wrap(value));
if (truthiness < 0) {
return false;
}
@@ -122,21 +159,22 @@ _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym)
}
bool
-_Py_uop_sym_is_null(JitOptSymbol *sym)
+_Py_uop_sym_is_null(JitOptRef ref)
{
- return sym->tag == JIT_SYM_NULL_TAG;
+ return PyJitRef_Unwrap(ref)->tag == JIT_SYM_NULL_TAG;
}
PyObject *
-_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym)
+_Py_uop_sym_get_const(JitOptContext *ctx, JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
return sym->value.value;
}
if (sym->tag == JIT_SYM_TRUTHINESS_TAG) {
JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value;
- int truthiness = _Py_uop_sym_truthiness(ctx, value);
+ int truthiness = _Py_uop_sym_truthiness(ctx, PyJitRef_Wrap(value));
if (truthiness < 0) {
return NULL;
}
@@ -147,9 +185,41 @@ _Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym)
return NULL;
}
+_PyStackRef
+_Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptRef sym)
+{
+ PyObject *const_val = _Py_uop_sym_get_const(ctx, sym);
+ if (const_val == NULL) {
+ return PyStackRef_NULL;
+ }
+ return PyStackRef_FromPyObjectBorrow(const_val);
+}
+
+/*
+ Indicates whether the constant is safe to constant evaluate
+ (without side effects).
+ */
+bool
+_Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptRef sym)
+{
+ PyObject *const_val = _Py_uop_sym_get_const(ctx, sym);
+ if (const_val == NULL) {
+ return false;
+ }
+ if (_PyLong_CheckExactAndCompact(const_val)) {
+ return true;
+ }
+ PyTypeObject *typ = Py_TYPE(const_val);
+ return (typ == &PyUnicode_Type) ||
+ (typ == &PyFloat_Type) ||
+ (typ == &_PyNone_Type) ||
+ (typ == &PyBool_Type);
+}
+
void
-_Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeObject *typ)
+_Py_uop_sym_set_type(JitOptContext *ctx, JitOptRef ref, PyTypeObject *typ)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
JitSymType tag = sym->tag;
switch(tag) {
case JIT_SYM_NULL_TAG:
@@ -194,12 +264,22 @@ _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeObject *typ)
sym_set_bottom(ctx, sym);
}
return;
+ case JIT_SYM_COMPACT_INT:
+ if (typ != &PyLong_Type) {
+ sym_set_bottom(ctx, sym);
+ }
+ return;
}
}
bool
-_Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int version)
+_Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptRef ref, unsigned int version)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
+ PyTypeObject *type = _PyType_LookupByVersion(version);
+ if (type) {
+ _Py_uop_sym_set_type(ctx, ref, type);
+ }
JitSymType tag = sym->tag;
switch(tag) {
case JIT_SYM_NULL_TAG:
@@ -215,18 +295,24 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int
return true;
}
case JIT_SYM_KNOWN_VALUE_TAG:
- Py_CLEAR(sym->value.value);
- sym_set_bottom(ctx, sym);
- return false;
+ if (Py_TYPE(sym->value.value)->tp_version_tag != version) {
+ Py_CLEAR(sym->value.value);
+ sym_set_bottom(ctx, sym);
+ return false;
+ };
+ return true;
case JIT_SYM_TUPLE_TAG:
- sym_set_bottom(ctx, sym);
- return false;
+ if (PyTuple_Type.tp_version_tag != version) {
+ sym_set_bottom(ctx, sym);
+ return false;
+ };
+ return true;
case JIT_SYM_TYPE_VERSION_TAG:
- if (sym->version.version == version) {
- return true;
+ if (sym->version.version != version) {
+ sym_set_bottom(ctx, sym);
+ return false;
}
- sym_set_bottom(ctx, sym);
- return false;
+ return true;
case JIT_SYM_BOTTOM_TAG:
return false;
case JIT_SYM_NON_NULL_TAG:
@@ -240,13 +326,20 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int
return false;
}
return true;
+ case JIT_SYM_COMPACT_INT:
+ if (version != PyLong_Type.tp_version_tag) {
+ sym_set_bottom(ctx, sym);
+ return false;
+ }
+ return true;
}
Py_UNREACHABLE();
}
void
-_Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val)
+_Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef ref, PyObject *const_val)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
JitSymType tag = sym->tag;
switch(tag) {
case JIT_SYM_NULL_TAG:
@@ -266,6 +359,18 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val
}
return;
case JIT_SYM_TUPLE_TAG:
+ if (PyTuple_CheckExact(const_val)) {
+ Py_ssize_t len = _Py_uop_sym_tuple_length(ref);
+ if (len == PyTuple_GET_SIZE(const_val)) {
+ for (Py_ssize_t i = 0; i < len; i++) {
+ JitOptRef sym_item = _Py_uop_sym_tuple_getitem(ctx, ref, i);
+ PyObject *item = PyTuple_GET_ITEM(const_val, i);
+ _Py_uop_sym_set_const(ctx, sym_item, item);
+ }
+ make_const(sym, const_val);
+ return;
+ }
+ }
sym_set_bottom(ctx, sym);
return;
case JIT_SYM_TYPE_VERSION_TAG:
@@ -283,13 +388,14 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val
return;
case JIT_SYM_TRUTHINESS_TAG:
if (!PyBool_Check(const_val) ||
- (_Py_uop_sym_is_const(ctx, sym) &&
- _Py_uop_sym_get_const(ctx, sym) != const_val))
+ (_Py_uop_sym_is_const(ctx, ref) &&
+ _Py_uop_sym_get_const(ctx, ref) != const_val))
{
sym_set_bottom(ctx, sym);
return;
}
- JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value;
+ JitOptRef value = PyJitRef_Wrap(
+ allocation_base(ctx) + sym->truthiness.value);
PyTypeObject *type = _Py_uop_sym_get_type(value);
if (const_val == (sym->truthiness.invert ? Py_False : Py_True)) {
// value is truthy. This is only useful for bool:
@@ -310,12 +416,21 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val
// TODO: More types (GH-130415)!
make_const(sym, const_val);
return;
+ case JIT_SYM_COMPACT_INT:
+ if (_PyLong_CheckExactAndCompact(const_val)) {
+ make_const(sym, const_val);
+ }
+ else {
+ sym_set_bottom(ctx, sym);
+ }
+ return;
}
}
void
-_Py_uop_sym_set_null(JitOptContext *ctx, JitOptSymbol *sym)
+_Py_uop_sym_set_null(JitOptContext *ctx, JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
if (sym->tag == JIT_SYM_UNKNOWN_TAG) {
sym->tag = JIT_SYM_NULL_TAG;
}
@@ -325,8 +440,9 @@ _Py_uop_sym_set_null(JitOptContext *ctx, JitOptSymbol *sym)
}
void
-_Py_uop_sym_set_non_null(JitOptContext *ctx, JitOptSymbol *sym)
+_Py_uop_sym_set_non_null(JitOptContext *ctx, JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
if (sym->tag == JIT_SYM_UNKNOWN_TAG) {
sym->tag = JIT_SYM_NON_NULL_TAG;
}
@@ -335,70 +451,82 @@ _Py_uop_sym_set_non_null(JitOptContext *ctx, JitOptSymbol *sym)
}
}
-
-JitOptSymbol *
+JitOptRef
_Py_uop_sym_new_unknown(JitOptContext *ctx)
{
JitOptSymbol *res = sym_new(ctx);
if (res == NULL) {
- return out_of_space(ctx);
+ return out_of_space_ref(ctx);
}
- return res;
+ return PyJitRef_Wrap(res);
}
-JitOptSymbol *
+JitOptRef
_Py_uop_sym_new_not_null(JitOptContext *ctx)
{
JitOptSymbol *res = sym_new(ctx);
if (res == NULL) {
- return out_of_space(ctx);
+ return out_of_space_ref(ctx);
}
res->tag = JIT_SYM_NON_NULL_TAG;
- return res;
+ return PyJitRef_Wrap(res);
}
-JitOptSymbol *
+JitOptRef
_Py_uop_sym_new_type(JitOptContext *ctx, PyTypeObject *typ)
{
JitOptSymbol *res = sym_new(ctx);
if (res == NULL) {
- return out_of_space(ctx);
+ return out_of_space_ref(ctx);
}
- _Py_uop_sym_set_type(ctx, res, typ);
- return res;
+ JitOptRef ref = PyJitRef_Wrap(res);
+ _Py_uop_sym_set_type(ctx, ref, typ);
+ return ref;
}
// Adds a new reference to const_val, owned by the symbol.
-JitOptSymbol *
+JitOptRef
_Py_uop_sym_new_const(JitOptContext *ctx, PyObject *const_val)
{
assert(const_val != NULL);
JitOptSymbol *res = sym_new(ctx);
if (res == NULL) {
- return out_of_space(ctx);
+ return out_of_space_ref(ctx);
}
- _Py_uop_sym_set_const(ctx, res, const_val);
+ JitOptRef ref = PyJitRef_Wrap(res);
+ _Py_uop_sym_set_const(ctx, ref, const_val);
+ return ref;
+}
+
+JitOptRef
+_Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val)
+{
+ assert(const_val != NULL);
+ JitOptRef res = _Py_uop_sym_new_const(ctx, const_val);
+ // Decref once because sym_new_const increfs it.
+ Py_DECREF(const_val);
return res;
}
-JitOptSymbol *
+JitOptRef
_Py_uop_sym_new_null(JitOptContext *ctx)
{
JitOptSymbol *null_sym = sym_new(ctx);
if (null_sym == NULL) {
- return out_of_space(ctx);
+ return out_of_space_ref(ctx);
}
- _Py_uop_sym_set_null(ctx, null_sym);
- return null_sym;
+ JitOptRef ref = PyJitRef_Wrap(null_sym);
+ _Py_uop_sym_set_null(ctx, ref);
+ return ref;
}
PyTypeObject *
-_Py_uop_sym_get_type(JitOptSymbol *sym)
+_Py_uop_sym_get_type(JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
JitSymType tag = sym->tag;
switch(tag) {
case JIT_SYM_NULL_TAG:
- case JIT_SYM_TYPE_VERSION_TAG:
case JIT_SYM_BOTTOM_TAG:
case JIT_SYM_NON_NULL_TAG:
case JIT_SYM_UNKNOWN_TAG:
@@ -407,17 +535,23 @@ _Py_uop_sym_get_type(JitOptSymbol *sym)
return sym->cls.type;
case JIT_SYM_KNOWN_VALUE_TAG:
return Py_TYPE(sym->value.value);
+ case JIT_SYM_TYPE_VERSION_TAG:
+ return _PyType_LookupByVersion(sym->version.version);
case JIT_SYM_TUPLE_TAG:
return &PyTuple_Type;
case JIT_SYM_TRUTHINESS_TAG:
return &PyBool_Type;
+ case JIT_SYM_COMPACT_INT:
+ return &PyLong_Type;
+
}
Py_UNREACHABLE();
}
unsigned int
-_Py_uop_sym_get_type_version(JitOptSymbol *sym)
+_Py_uop_sym_get_type_version(JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
JitSymType tag = sym->tag;
switch(tag) {
case JIT_SYM_NULL_TAG:
@@ -435,52 +569,42 @@ _Py_uop_sym_get_type_version(JitOptSymbol *sym)
return PyTuple_Type.tp_version_tag;
case JIT_SYM_TRUTHINESS_TAG:
return PyBool_Type.tp_version_tag;
+ case JIT_SYM_COMPACT_INT:
+ return PyLong_Type.tp_version_tag;
}
Py_UNREACHABLE();
}
bool
-_Py_uop_sym_has_type(JitOptSymbol *sym)
+_Py_uop_sym_has_type(JitOptRef sym)
{
- JitSymType tag = sym->tag;
- switch(tag) {
- case JIT_SYM_NULL_TAG:
- case JIT_SYM_TYPE_VERSION_TAG:
- case JIT_SYM_BOTTOM_TAG:
- case JIT_SYM_NON_NULL_TAG:
- case JIT_SYM_UNKNOWN_TAG:
- return false;
- case JIT_SYM_KNOWN_CLASS_TAG:
- case JIT_SYM_KNOWN_VALUE_TAG:
- case JIT_SYM_TUPLE_TAG:
- case JIT_SYM_TRUTHINESS_TAG:
- return true;
- }
- Py_UNREACHABLE();
+ return _Py_uop_sym_get_type(sym) != NULL;
}
bool
-_Py_uop_sym_matches_type(JitOptSymbol *sym, PyTypeObject *typ)
+_Py_uop_sym_matches_type(JitOptRef sym, PyTypeObject *typ)
{
assert(typ != NULL && PyType_Check(typ));
return _Py_uop_sym_get_type(sym) == typ;
}
bool
-_Py_uop_sym_matches_type_version(JitOptSymbol *sym, unsigned int version)
+_Py_uop_sym_matches_type_version(JitOptRef sym, unsigned int version)
{
return _Py_uop_sym_get_type_version(sym) == version;
}
int
-_Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym)
+_Py_uop_sym_truthiness(JitOptContext *ctx, JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
switch(sym->tag) {
case JIT_SYM_NULL_TAG:
case JIT_SYM_TYPE_VERSION_TAG:
case JIT_SYM_BOTTOM_TAG:
case JIT_SYM_NON_NULL_TAG:
case JIT_SYM_UNKNOWN_TAG:
+ case JIT_SYM_COMPACT_INT:
return -1;
case JIT_SYM_KNOWN_CLASS_TAG:
/* TODO :
@@ -494,7 +618,8 @@ _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym)
case JIT_SYM_TRUTHINESS_TAG:
;
JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value;
- int truthiness = _Py_uop_sym_truthiness(ctx, value);
+ int truthiness = _Py_uop_sym_truthiness(ctx,
+ PyJitRef_Wrap(value));
if (truthiness < 0) {
return truthiness;
}
@@ -520,12 +645,12 @@ _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym)
return -1;
}
-JitOptSymbol *
-_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args)
+JitOptRef
+_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptRef *args)
{
JitOptSymbol *res = sym_new(ctx);
if (res == NULL) {
- return out_of_space(ctx);
+ return out_of_space_ref(ctx);
}
if (size > MAX_SYMBOLIC_TUPLE_SIZE) {
res->tag = JIT_SYM_KNOWN_CLASS_TAG;
@@ -535,15 +660,16 @@ _Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args)
res->tag = JIT_SYM_TUPLE_TAG;
res->tuple.length = size;
for (int i = 0; i < size; i++) {
- res->tuple.items[i] = (uint16_t)(args[i] - allocation_base(ctx));
+ res->tuple.items[i] = (uint16_t)(PyJitRef_Unwrap(args[i]) - allocation_base(ctx));
}
}
- return res;
+ return PyJitRef_Wrap(res);
}
-JitOptSymbol *
-_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptSymbol *sym, int item)
+JitOptRef
+_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptRef ref, int item)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
assert(item >= 0);
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
PyObject *tuple = sym->value.value;
@@ -552,14 +678,15 @@ _Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptSymbol *sym, int item)
}
}
else if (sym->tag == JIT_SYM_TUPLE_TAG && item < sym->tuple.length) {
- return allocation_base(ctx) + sym->tuple.items[item];
+ return PyJitRef_Wrap(allocation_base(ctx) + sym->tuple.items[item]);
}
- return _Py_uop_sym_new_unknown(ctx);
+ return _Py_uop_sym_new_not_null(ctx);
}
int
-_Py_uop_sym_tuple_length(JitOptSymbol *sym)
+_Py_uop_sym_tuple_length(JitOptRef ref)
{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
PyObject *tuple = sym->value.value;
if (PyTuple_CheckExact(tuple)) {
@@ -574,7 +701,7 @@ _Py_uop_sym_tuple_length(JitOptSymbol *sym)
// Return true if known to be immortal.
bool
-_Py_uop_sym_is_immortal(JitOptSymbol *sym)
+_Py_uop_symbol_is_immortal(JitOptSymbol *sym)
{
if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
return _Py_IsImmortal(sym->value.value);
@@ -582,25 +709,84 @@ _Py_uop_sym_is_immortal(JitOptSymbol *sym)
if (sym->tag == JIT_SYM_KNOWN_CLASS_TAG) {
return sym->cls.type == &PyBool_Type;
}
- if (sym->tag == JIT_SYM_TRUTHINESS_TAG) {
- return true;
- }
return false;
}
-JitOptSymbol *
-_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy)
+bool
+_Py_uop_sym_is_compact_int(JitOptRef ref)
+{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
+ if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) {
+ return (bool)_PyLong_CheckExactAndCompact(sym->value.value);
+ }
+ return sym->tag == JIT_SYM_COMPACT_INT;
+}
+
+bool
+_Py_uop_sym_is_immortal(JitOptRef ref)
+{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
+ return _Py_uop_symbol_is_immortal(sym);
+}
+
+void
+_Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef ref)
+{
+ JitOptSymbol *sym = PyJitRef_Unwrap(ref);
+ JitSymType tag = sym->tag;
+ switch(tag) {
+ case JIT_SYM_NULL_TAG:
+ sym_set_bottom(ctx, sym);
+ return;
+ case JIT_SYM_KNOWN_CLASS_TAG:
+ if (sym->cls.type == &PyLong_Type) {
+ sym->tag = JIT_SYM_COMPACT_INT;
+ } else {
+ sym_set_bottom(ctx, sym);
+ }
+ return;
+ case JIT_SYM_TYPE_VERSION_TAG:
+ if (sym->version.version == PyLong_Type.tp_version_tag) {
+ sym->tag = JIT_SYM_COMPACT_INT;
+ }
+ else {
+ sym_set_bottom(ctx, sym);
+ }
+ return;
+ case JIT_SYM_KNOWN_VALUE_TAG:
+ if (!_PyLong_CheckExactAndCompact(sym->value.value)) {
+ Py_CLEAR(sym->value.value);
+ sym_set_bottom(ctx, sym);
+ }
+ return;
+ case JIT_SYM_TUPLE_TAG:
+ case JIT_SYM_TRUTHINESS_TAG:
+ sym_set_bottom(ctx, sym);
+ return;
+ case JIT_SYM_BOTTOM_TAG:
+ case JIT_SYM_COMPACT_INT:
+ return;
+ case JIT_SYM_NON_NULL_TAG:
+ case JIT_SYM_UNKNOWN_TAG:
+ sym->tag = JIT_SYM_COMPACT_INT;
+ return;
+ }
+}
+
+JitOptRef
+_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptRef ref, bool truthy)
{
+ JitOptSymbol *value = PyJitRef_Unwrap(ref);
// It's clearer to invert this in the signature:
bool invert = !truthy;
if (value->tag == JIT_SYM_TRUTHINESS_TAG && value->truthiness.invert == invert) {
- return value;
+ return ref;
}
JitOptSymbol *res = sym_new(ctx);
if (res == NULL) {
- return out_of_space(ctx);
+ return out_of_space_ref(ctx);
}
- int truthiness = _Py_uop_sym_truthiness(ctx, value);
+ int truthiness = _Py_uop_sym_truthiness(ctx, ref);
if (truthiness < 0) {
res->tag = JIT_SYM_TRUTHINESS_TAG;
res->truthiness.invert = invert;
@@ -609,7 +795,18 @@ _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy)
else {
make_const(res, (truthiness ^ invert) ? Py_True : Py_False);
}
- return res;
+ return PyJitRef_Wrap(res);
+}
+
+JitOptRef
+_Py_uop_sym_new_compact_int(JitOptContext *ctx)
+{
+ JitOptSymbol *sym = sym_new(ctx);
+ if (sym == NULL) {
+ return out_of_space_ref(ctx);
+ }
+ sym->tag = JIT_SYM_COMPACT_INT;
+ return PyJitRef_Wrap(sym);
}
// 0 on success, -1 on error.
@@ -618,7 +815,7 @@ _Py_uop_frame_new(
JitOptContext *ctx,
PyCodeObject *co,
int curr_stackentries,
- JitOptSymbol **args,
+ JitOptRef *args,
int arg_len)
{
assert(ctx->curr_frame_depth < MAX_ABSTRACT_FRAME_DEPTH);
@@ -643,14 +840,14 @@ _Py_uop_frame_new(
}
for (int i = arg_len; i < co->co_nlocalsplus; i++) {
- JitOptSymbol *local = _Py_uop_sym_new_unknown(ctx);
+ JitOptRef local = _Py_uop_sym_new_unknown(ctx);
frame->locals[i] = local;
}
// Initialize the stack as well
for (int i = 0; i < curr_stackentries; i++) {
- JitOptSymbol *stackvar = _Py_uop_sym_new_unknown(ctx);
+ JitOptRef stackvar = _Py_uop_sym_new_unknown(ctx);
frame->stack[i] = stackvar;
}
@@ -676,12 +873,12 @@ _Py_uop_abstractcontext_fini(JitOptContext *ctx)
void
_Py_uop_abstractcontext_init(JitOptContext *ctx)
{
- static_assert(sizeof(JitOptSymbol) <= 2 * sizeof(uint64_t), "JitOptSymbol has grown");
+ static_assert(sizeof(JitOptSymbol) <= 3 * sizeof(uint64_t), "JitOptSymbol has grown");
ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE;
ctx->n_consumed = ctx->locals_and_stack;
#ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter.
for (int i = 0 ; i < MAX_ABSTRACT_INTERP_SIZE; i++) {
- ctx->locals_and_stack[i] = NULL;
+ ctx->locals_and_stack[i] = PyJitRef_NULL;
}
#endif
@@ -731,47 +928,48 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored))
_Py_uop_abstractcontext_init(ctx);
PyObject *val_42 = NULL;
PyObject *val_43 = NULL;
+ PyObject *val_big = NULL;
PyObject *tuple = NULL;
// Use a single 'sym' variable so copy-pasting tests is easier.
- JitOptSymbol *sym = _Py_uop_sym_new_unknown(ctx);
- if (sym == NULL) {
+ JitOptRef ref = _Py_uop_sym_new_unknown(ctx);
+ if (PyJitRef_IsNull(ref)) {
goto fail;
}
- TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "top is NULL");
- TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "top is not NULL");
- TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "top matches a type");
- TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, sym), "top is a constant");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "top as constant is not NULL");
- TEST_PREDICATE(!_Py_uop_sym_is_bottom(sym), "top is bottom");
-
- sym = make_bottom(ctx);
- if (sym == NULL) {
+ TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "top is NULL");
+ TEST_PREDICATE(!_Py_uop_sym_is_not_null(ref), "top is not NULL");
+ TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyLong_Type), "top matches a type");
+ TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, ref), "top is a constant");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "top as constant is not NULL");
+ TEST_PREDICATE(!_Py_uop_sym_is_bottom(ref), "top is bottom");
+
+ ref = PyJitRef_Wrap(make_bottom(ctx));
+ if (PyJitRef_IsNull(ref)) {
goto fail;
}
- TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "bottom is NULL is not false");
- TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "bottom is not NULL is not false");
- TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "bottom matches a type");
- TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, sym), "bottom is a constant is not false");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "bottom as constant is not NULL");
- TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "bottom isn't bottom");
-
- sym = _Py_uop_sym_new_type(ctx, &PyLong_Type);
- if (sym == NULL) {
+ TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "bottom is NULL is not false");
+ TEST_PREDICATE(!_Py_uop_sym_is_not_null(ref), "bottom is not NULL is not false");
+ TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyLong_Type), "bottom matches a type");
+ TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, ref), "bottom is a constant is not false");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "bottom as constant is not NULL");
+ TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "bottom isn't bottom");
+
+ ref = _Py_uop_sym_new_type(ctx, &PyLong_Type);
+ if (PyJitRef_IsNull(ref)) {
goto fail;
}
- TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "int is NULL");
- TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "int isn't not NULL");
- TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "int isn't int");
- TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "int matches float");
- TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, sym), "int is a constant");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "int as constant is not NULL");
+ TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "int is NULL");
+ TEST_PREDICATE(_Py_uop_sym_is_not_null(ref), "int isn't not NULL");
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "int isn't int");
+ TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyFloat_Type), "int matches float");
+ TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, ref), "int is a constant");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "int as constant is not NULL");
- _Py_uop_sym_set_type(ctx, sym, &PyLong_Type); // Should be a no-op
- TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(int and int) isn't int");
+ _Py_uop_sym_set_type(ctx, ref, &PyLong_Type); // Should be a no-op
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "(int and int) isn't int");
- _Py_uop_sym_set_type(ctx, sym, &PyFloat_Type); // Should make it bottom
- TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(int and float) isn't bottom");
+ _Py_uop_sym_set_type(ctx, ref, &PyFloat_Type); // Should make it bottom
+ TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "(int and float) isn't bottom");
val_42 = PyLong_FromLong(42);
assert(val_42 != NULL);
@@ -781,84 +979,118 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored))
assert(val_43 != NULL);
assert(_Py_IsImmortal(val_43));
- sym = _Py_uop_sym_new_type(ctx, &PyLong_Type);
- if (sym == NULL) {
+ ref = _Py_uop_sym_new_type(ctx, &PyLong_Type);
+ if (PyJitRef_IsNull(ref)) {
goto fail;
}
- _Py_uop_sym_set_const(ctx, sym, val_42);
- TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 1, "bool(42) is not True");
- TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "42 is NULL");
- TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "42 isn't not NULL");
- TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "42 isn't an int");
- TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "42 matches float");
- TEST_PREDICATE(_Py_uop_sym_is_const(ctx, sym), "42 is not a constant");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) != NULL, "42 as constant is NULL");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == val_42, "42 as constant isn't 42");
- TEST_PREDICATE(_Py_uop_sym_is_immortal(sym), "42 is not immortal");
-
- _Py_uop_sym_set_type(ctx, sym, &PyLong_Type); // Should be a no-op
- TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(42 and 42) isn't an int");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == val_42, "(42 and 42) as constant isn't 42");
-
- _Py_uop_sym_set_type(ctx, sym, &PyFloat_Type); // Should make it bottom
- TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and float) isn't bottom");
-
- sym = _Py_uop_sym_new_type(ctx, &PyBool_Type);
- TEST_PREDICATE(_Py_uop_sym_is_immortal(sym), "a bool is not immortal");
-
- sym = _Py_uop_sym_new_type(ctx, &PyLong_Type);
- if (sym == NULL) {
+ _Py_uop_sym_set_const(ctx, ref, val_42);
+ TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 1, "bool(42) is not True");
+ TEST_PREDICATE(!_Py_uop_sym_is_null(ref), "42 is NULL");
+ TEST_PREDICATE(_Py_uop_sym_is_not_null(ref), "42 isn't not NULL");
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "42 isn't an int");
+ TEST_PREDICATE(!_Py_uop_sym_matches_type(ref, &PyFloat_Type), "42 matches float");
+ TEST_PREDICATE(_Py_uop_sym_is_const(ctx, ref), "42 is not a constant");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) != NULL, "42 as constant is NULL");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == val_42, "42 as constant isn't 42");
+ TEST_PREDICATE(_Py_uop_sym_is_immortal(ref), "42 is not immortal");
+
+ _Py_uop_sym_set_type(ctx, ref, &PyLong_Type); // Should be a no-op
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyLong_Type), "(42 and 42) isn't an int");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == val_42, "(42 and 42) as constant isn't 42");
+
+ _Py_uop_sym_set_type(ctx, ref, &PyFloat_Type); // Should make it bottom
+ TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "(42 and float) isn't bottom");
+
+ ref = _Py_uop_sym_new_type(ctx, &PyBool_Type);
+ TEST_PREDICATE(_Py_uop_sym_is_immortal(ref), "a bool is not immortal");
+
+ ref = _Py_uop_sym_new_type(ctx, &PyLong_Type);
+ if (PyJitRef_IsNull(ref)) {
goto fail;
}
- _Py_uop_sym_set_const(ctx, sym, val_42);
- _Py_uop_sym_set_const(ctx, sym, val_43); // Should make it bottom
- TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and 43) isn't bottom");
-
-
- sym = _Py_uop_sym_new_const(ctx, Py_None);
- TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "bool(None) is not False");
- sym = _Py_uop_sym_new_const(ctx, Py_False);
- TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "bool(False) is not False");
- sym = _Py_uop_sym_new_const(ctx, PyLong_FromLong(0));
- TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "bool(0) is not False");
-
- JitOptSymbol *i1 = _Py_uop_sym_new_type(ctx, &PyFloat_Type);
- JitOptSymbol *i2 = _Py_uop_sym_new_const(ctx, val_43);
- JitOptSymbol *array[2] = { i1, i2 };
- sym = _Py_uop_sym_new_tuple(ctx, 2, array);
+ _Py_uop_sym_set_const(ctx, ref, val_42);
+ _Py_uop_sym_set_const(ctx, ref, val_43); // Should make it bottom
+ TEST_PREDICATE(_Py_uop_sym_is_bottom(ref), "(42 and 43) isn't bottom");
+
+
+ ref = _Py_uop_sym_new_const(ctx, Py_None);
+ TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "bool(None) is not False");
+ ref = _Py_uop_sym_new_const(ctx, Py_False);
+ TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "bool(False) is not False");
+ ref = _Py_uop_sym_new_const(ctx, PyLong_FromLong(0));
+ TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "bool(0) is not False");
+
+ JitOptRef i1 = _Py_uop_sym_new_type(ctx, &PyFloat_Type);
+ JitOptRef i2 = _Py_uop_sym_new_const(ctx, val_43);
+ JitOptRef array[2] = { i1, i2 };
+ ref = _Py_uop_sym_new_tuple(ctx, 2, array);
TEST_PREDICATE(
- _Py_uop_sym_matches_type(_Py_uop_sym_tuple_getitem(ctx, sym, 0), &PyFloat_Type),
+ _Py_uop_sym_matches_type(_Py_uop_sym_tuple_getitem(ctx, ref, 0), &PyFloat_Type),
"tuple item does not match value used to create tuple"
);
TEST_PREDICATE(
- _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43,
+ _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, ref, 1)) == val_43,
"tuple item does not match value used to create tuple"
);
PyObject *pair[2] = { val_42, val_43 };
tuple = _PyTuple_FromArray(pair, 2);
- sym = _Py_uop_sym_new_const(ctx, tuple);
+ ref = _Py_uop_sym_new_const(ctx, tuple);
TEST_PREDICATE(
- _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43,
+ _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, ref, 1)) == val_43,
"tuple item does not match value used to create tuple"
);
- JitOptSymbol *value = _Py_uop_sym_new_type(ctx, &PyBool_Type);
- sym = _Py_uop_sym_new_truthiness(ctx, value, false);
- TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyBool_Type), "truthiness is not boolean");
- TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == -1, "truthiness is not unknown");
- TEST_PREDICATE(_Py_uop_sym_is_const(ctx, sym) == false, "truthiness is constant");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "truthiness is not NULL");
+ ref = _Py_uop_sym_new_type(ctx, &PyTuple_Type);
+ TEST_PREDICATE(
+ _Py_uop_sym_is_not_null(_Py_uop_sym_tuple_getitem(ctx, ref, 42)),
+ "Unknown tuple item is not narrowed to non-NULL"
+ );
+ JitOptRef value = _Py_uop_sym_new_type(ctx, &PyBool_Type);
+ ref = _Py_uop_sym_new_truthiness(ctx, value, false);
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyBool_Type), "truthiness is not boolean");
+ TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == -1, "truthiness is not unknown");
+ TEST_PREDICATE(_Py_uop_sym_is_const(ctx, ref) == false, "truthiness is constant");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == NULL, "truthiness is not NULL");
TEST_PREDICATE(_Py_uop_sym_is_const(ctx, value) == false, "value is constant");
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, value) == NULL, "value is not NULL");
- _Py_uop_sym_set_const(ctx, sym, Py_False);
- TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyBool_Type), "truthiness is not boolean");
- TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "truthiness is not True");
- TEST_PREDICATE(_Py_uop_sym_is_const(ctx, sym) == true, "truthiness is not constant");
- TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == Py_False, "truthiness is not False");
+ _Py_uop_sym_set_const(ctx, ref, Py_False);
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref, &PyBool_Type), "truthiness is not boolean");
+ TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, ref) == 0, "truthiness is not True");
+ TEST_PREDICATE(_Py_uop_sym_is_const(ctx, ref) == true, "truthiness is not constant");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref) == Py_False, "truthiness is not False");
TEST_PREDICATE(_Py_uop_sym_is_const(ctx, value) == true, "value is not constant");
TEST_PREDICATE(_Py_uop_sym_get_const(ctx, value) == Py_True, "value is not True");
+
+
+ val_big = PyNumber_Lshift(_PyLong_GetOne(), PyLong_FromLong(66));
+ if (val_big == NULL) {
+ goto fail;
+ }
+
+ JitOptRef ref_42 = _Py_uop_sym_new_const(ctx, val_42);
+ JitOptRef ref_big = _Py_uop_sym_new_const(ctx, val_big);
+ JitOptRef ref_int = _Py_uop_sym_new_compact_int(ctx);
+ TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_42), "42 is not a compact int");
+ TEST_PREDICATE(!_Py_uop_sym_is_compact_int(ref_big), "(1 << 66) is a compact int");
+ TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_int), "compact int is not a compact int");
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "compact int is not an int");
+
+ _Py_uop_sym_set_type(ctx, ref_int, &PyLong_Type); // Should have no effect
+ TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_int), "compact int is not a compact int after cast");
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "compact int is not an int after cast");
+
+ _Py_uop_sym_set_type(ctx, ref_int, &PyFloat_Type); // Should make it bottom
+ TEST_PREDICATE(_Py_uop_sym_is_bottom(ref_int), "compact int cast to float isn't bottom");
+
+ ref_int = _Py_uop_sym_new_compact_int(ctx);
+ _Py_uop_sym_set_const(ctx, ref_int, val_43);
+ TEST_PREDICATE(_Py_uop_sym_is_compact_int(ref_int), "43 is not a compact int");
+ TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "43 is not an int");
+ TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref_int) == val_43, "43 isn't 43");
+
_Py_uop_abstractcontext_fini(ctx);
Py_DECREF(val_42);
Py_DECREF(val_43);
+ Py_DECREF(val_big);
Py_DECREF(tuple);
Py_RETURN_NONE;
@@ -866,6 +1098,7 @@ fail:
_Py_uop_abstractcontext_fini(ctx);
Py_XDECREF(val_42);
Py_XDECREF(val_43);
+ Py_XDECREF(val_big);
Py_DECREF(tuple);
return NULL;
}
diff --git a/Python/parking_lot.c b/Python/parking_lot.c
index 8edf4323594..e896dea0271 100644
--- a/Python/parking_lot.c
+++ b/Python/parking_lot.c
@@ -112,17 +112,27 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout)
}
}
- // NOTE: we wait on the sigint event even in non-main threads to match the
- // behavior of the other platforms. Non-main threads will ignore the
- // Py_PARK_INTR result.
- HANDLE sigint_event = _PyOS_SigintEvent();
- HANDLE handles[2] = { sema->platform_sem, sigint_event };
- DWORD count = sigint_event != NULL ? 2 : 1;
+ HANDLE handles[2] = { sema->platform_sem, NULL };
+ HANDLE sigint_event = NULL;
+ DWORD count = 1;
+ if (_Py_IsMainThread()) {
+ // gh-135099: Wait on the SIGINT event only in the main thread. Other
+ // threads would ignore the result anyways, and accessing
+ // `_PyOS_SigintEvent()` from non-main threads may race with
+ // interpreter shutdown, which closes the event handle. Note that
+ // non-main interpreters will ignore the result.
+ sigint_event = _PyOS_SigintEvent();
+ if (sigint_event != NULL) {
+ handles[1] = sigint_event;
+ count = 2;
+ }
+ }
wait = WaitForMultipleObjects(count, handles, FALSE, millis);
if (wait == WAIT_OBJECT_0) {
res = Py_PARK_OK;
}
else if (wait == WAIT_OBJECT_0 + 1) {
+ assert(sigint_event != NULL);
ResetEvent(sigint_event);
res = Py_PARK_INTR;
}
diff --git a/Python/pathconfig.c b/Python/pathconfig.c
index 92360c1bb02..d034562c43f 100644
--- a/Python/pathconfig.c
+++ b/Python/pathconfig.c
@@ -272,7 +272,8 @@ Py_SetProgramName(const wchar_t *program_name)
}
-wchar_t *
+/* removed in 3.15, but kept for stable ABI compatibility */
+PyAPI_FUNC(wchar_t *)
Py_GetPath(void)
{
/* If the user has provided a path, return that */
@@ -284,7 +285,7 @@ Py_GetPath(void)
}
-wchar_t *
+PyAPI_FUNC(wchar_t *)
_Py_GetStdlibDir(void)
{
wchar_t *stdlib_dir = _Py_path_config.stdlib_dir;
@@ -295,35 +296,40 @@ _Py_GetStdlibDir(void)
}
-wchar_t *
+/* removed in 3.15, but kept for stable ABI compatibility */
+PyAPI_FUNC(wchar_t *)
Py_GetPrefix(void)
{
return _Py_path_config.prefix;
}
-wchar_t *
+/* removed in 3.15, but kept for stable ABI compatibility */
+PyAPI_FUNC(wchar_t *)
Py_GetExecPrefix(void)
{
return _Py_path_config.exec_prefix;
}
-wchar_t *
+/* removed in 3.15, but kept for stable ABI compatibility */
+PyAPI_FUNC(wchar_t *)
Py_GetProgramFullPath(void)
{
return _Py_path_config.program_full_path;
}
-wchar_t*
+/* removed in 3.15, but kept for stable ABI compatibility */
+PyAPI_FUNC(wchar_t *)
Py_GetPythonHome(void)
{
return _Py_path_config.home;
}
-wchar_t *
+/* removed in 3.15, but kept for stable ABI compatibility */
+PyAPI_FUNC(wchar_t *)
Py_GetProgramName(void)
{
return _Py_path_config.program_name;
diff --git a/Python/perf_jit_trampoline.c b/Python/perf_jit_trampoline.c
index 1211e0e9f11..2ca18c23593 100644
--- a/Python/perf_jit_trampoline.c
+++ b/Python/perf_jit_trampoline.c
@@ -1,241 +1,354 @@
+/*
+ * Python Perf Trampoline Support - JIT Dump Implementation
+ *
+ * This file implements the perf jitdump API for Python's performance profiling
+ * integration. It allows perf (Linux performance analysis tool) to understand
+ * and profile dynamically generated Python bytecode by creating JIT dump files
+ * that perf can inject into its analysis.
+ *
+ *
+ * IMPORTANT: This file exports specific callback functions that are part of
+ * Python's internal API. Do not modify the function signatures or behavior
+ * of exported functions without coordinating with the Python core team.
+ *
+ * Usually the binary and libraries are mapped in separate region like below:
+ *
+ * address ->
+ * --+---------------------+--//--+---------------------+--
+ * | .text | .data | ... | | .text | .data | ... |
+ * --+---------------------+--//--+---------------------+--
+ * myprog libc.so
+ *
+ * So it'd be easy and straight-forward to find a mapped binary or library from an
+ * address.
+ *
+ * But for JIT code, the code arena only cares about the code section. But the
+ * resulting DSOs (which is generated by perf inject -j) contain ELF headers and
+ * unwind info too. Then it'd generate following address space with synthesized
+ * MMAP events. Let's say it has a sample between address B and C.
+ *
+ * sample
+ * |
+ * address -> A B v C
+ * ---------------------------------------------------------------------------------------------------
+ * /tmp/jitted-PID-0.so | (headers) | .text | unwind info |
+ * /tmp/jitted-PID-1.so | (headers) | .text | unwind info |
+ * /tmp/jitted-PID-2.so | (headers) | .text | unwind info |
+ * ...
+ * ---------------------------------------------------------------------------------------------------
+ *
+ * If it only maps the .text section, it'd find the jitted-PID-1.so but cannot see
+ * the unwind info. If it maps both .text section and unwind sections, the sample
+ * could be mapped to either jitted-PID-0.so or jitted-PID-1.so and it's confusing
+ * which one is right. So to make perf happy we have non-overlapping ranges for each
+ * DSO:
+ *
+ * address ->
+ * -------------------------------------------------------------------------------------------------------
+ * /tmp/jitted-PID-0.so | (headers) | .text | unwind info |
+ * /tmp/jitted-PID-1.so | (headers) | .text | unwind info |
+ * /tmp/jitted-PID-2.so | (headers) | .text | unwind info |
+ * ...
+ * -------------------------------------------------------------------------------------------------------
+ *
+ * As the trampolines are constant, we add a constant padding but in general the padding needs to have the
+ * size of the unwind info rounded to 16 bytes. In general, for our trampolines this is 0x50
+ */
+
+
+
#include "Python.h"
#include "pycore_ceval.h" // _PyPerf_Callbacks
#include "pycore_frame.h"
#include "pycore_interp.h"
#include "pycore_runtime.h" // _PyRuntime
-
#ifdef PY_HAVE_PERF_TRAMPOLINE
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/mman.h> // mmap()
-#include <sys/types.h>
-#include <unistd.h> // sysconf()
-#include <sys/time.h> // gettimeofday()
-#include <sys/syscall.h>
-
-// ----------------------------------
-// Perf jitdump API
-// ----------------------------------
-
-typedef struct {
- FILE* perf_map;
- PyThread_type_lock map_lock;
- void* mapped_buffer;
- size_t mapped_size;
- int code_id;
-} PerfMapJitState;
-
-static PerfMapJitState perf_jit_map_state;
+/* Standard library includes for perf jitdump implementation */
+#include <elf.h> // ELF architecture constants
+#include <fcntl.h> // File control operations
+#include <stdio.h> // Standard I/O operations
+#include <stdlib.h> // Standard library functions
+#include <sys/mman.h> // Memory mapping functions (mmap)
+#include <sys/types.h> // System data types
+#include <unistd.h> // System calls (sysconf, getpid)
+#include <sys/time.h> // Time functions (gettimeofday)
+#include <sys/syscall.h> // System call interface
+
+// =============================================================================
+// CONSTANTS AND CONFIGURATION
+// =============================================================================
/*
-Usually the binary and libraries are mapped in separate region like below:
-
- address ->
- --+---------------------+--//--+---------------------+--
- | .text | .data | ... | | .text | .data | ... |
- --+---------------------+--//--+---------------------+--
- myprog libc.so
-
-So it'd be easy and straight-forward to find a mapped binary or library from an
-address.
-
-But for JIT code, the code arena only cares about the code section. But the
-resulting DSOs (which is generated by perf inject -j) contain ELF headers and
-unwind info too. Then it'd generate following address space with synthesized
-MMAP events. Let's say it has a sample between address B and C.
-
- sample
- |
- address -> A B v C
- ---------------------------------------------------------------------------------------------------
- /tmp/jitted-PID-0.so | (headers) | .text | unwind info |
- /tmp/jitted-PID-1.so | (headers) | .text | unwind info |
- /tmp/jitted-PID-2.so | (headers) | .text | unwind info |
- ...
- ---------------------------------------------------------------------------------------------------
-
-If it only maps the .text section, it'd find the jitted-PID-1.so but cannot see
-the unwind info. If it maps both .text section and unwind sections, the sample
-could be mapped to either jitted-PID-0.so or jitted-PID-1.so and it's confusing
-which one is right. So to make perf happy we have non-overlapping ranges for each
-DSO:
-
- address ->
- -------------------------------------------------------------------------------------------------------
- /tmp/jitted-PID-0.so | (headers) | .text | unwind info |
- /tmp/jitted-PID-1.so | (headers) | .text | unwind info |
- /tmp/jitted-PID-2.so | (headers) | .text | unwind info |
- ...
- -------------------------------------------------------------------------------------------------------
-
-As the trampolines are constant, we add a constant padding but in general the padding needs to have the
-size of the unwind info rounded to 16 bytes. In general, for our trampolines this is 0x50
+ * Memory layout considerations for perf jitdump:
+ *
+ * Perf expects non-overlapping memory regions for each JIT-compiled function.
+ * When perf processes the jitdump file, it creates synthetic DSO (Dynamic
+ * Shared Object) files that contain:
+ * - ELF headers
+ * - .text section (actual machine code)
+ * - Unwind information (for stack traces)
+ *
+ * To ensure proper address space layout, we add padding between code regions.
+ * This prevents address conflicts when perf maps the synthesized DSOs.
+ *
+ * Memory layout example:
+ * /tmp/jitted-PID-0.so: [headers][.text][unwind_info][padding]
+ * /tmp/jitted-PID-1.so: [headers][.text][unwind_info][padding]
+ *
+ * The padding size (0x100) is chosen to accommodate typical unwind info sizes
+ * while maintaining 16-byte alignment requirements.
*/
-
#define PERF_JIT_CODE_PADDING 0x100
-#define trampoline_api _PyRuntime.ceval.perf.trampoline_api
-
-typedef uint64_t uword;
-typedef const char* CodeComments;
-#define Pd "d"
-#define MB (1024 * 1024)
-
-#define EM_386 3
-#define EM_X86_64 62
-#define EM_ARM 40
-#define EM_AARCH64 183
-#define EM_RISCV 243
+/* Convenient access to the global trampoline API state */
+#define trampoline_api _PyRuntime.ceval.perf.trampoline_api
-#define TARGET_ARCH_IA32 0
-#define TARGET_ARCH_X64 0
-#define TARGET_ARCH_ARM 0
-#define TARGET_ARCH_ARM64 0
-#define TARGET_ARCH_RISCV32 0
-#define TARGET_ARCH_RISCV64 0
+/* Type aliases for clarity and portability */
+typedef uint64_t uword; // Word-sized unsigned integer
+typedef const char* CodeComments; // Code comment strings
-#define FLAG_generate_perf_jitdump 0
-#define FLAG_write_protect_code 0
-#define FLAG_write_protect_vm_isolate 0
-#define FLAG_code_comments 0
+/* Memory size constants */
+#define MB (1024 * 1024) // 1 Megabyte for buffer sizing
-#define UNREACHABLE()
+// =============================================================================
+// ARCHITECTURE-SPECIFIC DEFINITIONS
+// =============================================================================
-static uword GetElfMachineArchitecture(void) {
-#if TARGET_ARCH_IA32
- return EM_386;
-#elif TARGET_ARCH_X64
+/*
+ * Returns the ELF machine architecture constant for the current platform.
+ * This is required for the jitdump header to correctly identify the target
+ * architecture for perf processing.
+ *
+ */
+static uint64_t GetElfMachineArchitecture(void) {
+#if defined(__x86_64__) || defined(_M_X64)
return EM_X86_64;
-#elif TARGET_ARCH_ARM
- return EM_ARM;
-#elif TARGET_ARCH_ARM64
+#elif defined(__i386__) || defined(_M_IX86)
+ return EM_386;
+#elif defined(__aarch64__)
return EM_AARCH64;
-#elif TARGET_ARCH_RISCV32 || TARGET_ARCH_RISCV64
+#elif defined(__arm__) || defined(_M_ARM)
+ return EM_ARM;
+#elif defined(__riscv)
return EM_RISCV;
#else
- UNREACHABLE();
+ Py_UNREACHABLE(); // Unsupported architecture - should never reach here
return 0;
#endif
}
+// =============================================================================
+// PERF JITDUMP DATA STRUCTURES
+// =============================================================================
+
+/*
+ * Perf jitdump file format structures
+ *
+ * These structures define the binary format that perf expects for JIT dump files.
+ * The format is documented in the Linux perf tools source code and must match
+ * exactly for proper perf integration.
+ */
+
+/*
+ * Jitdump file header - written once at the beginning of each jitdump file
+ * Contains metadata about the process and jitdump format version
+ */
typedef struct {
- uint32_t magic;
- uint32_t version;
- uint32_t size;
- uint32_t elf_mach_target;
- uint32_t reserved;
- uint32_t process_id;
- uint64_t time_stamp;
- uint64_t flags;
+ uint32_t magic; // Magic number (0x4A695444 = "JiTD")
+ uint32_t version; // Jitdump format version (currently 1)
+ uint32_t size; // Size of this header structure
+ uint32_t elf_mach_target; // Target architecture (from GetElfMachineArchitecture)
+ uint32_t reserved; // Reserved field (must be 0)
+ uint32_t process_id; // Process ID of the JIT compiler
+ uint64_t time_stamp; // Timestamp when jitdump was created
+ uint64_t flags; // Feature flags (currently unused)
} Header;
- enum PerfEvent {
- PerfLoad = 0,
- PerfMove = 1,
- PerfDebugInfo = 2,
- PerfClose = 3,
- PerfUnwindingInfo = 4
+/*
+ * Perf event types supported by the jitdump format
+ * Each event type has a corresponding structure format
+ */
+enum PerfEvent {
+ PerfLoad = 0, // Code load event (new JIT function)
+ PerfMove = 1, // Code move event (function relocated)
+ PerfDebugInfo = 2, // Debug information event
+ PerfClose = 3, // JIT session close event
+ PerfUnwindingInfo = 4 // Stack unwinding information event
};
+/*
+ * Base event structure - common header for all perf events
+ * Every event in the jitdump file starts with this structure
+ */
struct BaseEvent {
- uint32_t event;
- uint32_t size;
- uint64_t time_stamp;
- };
+ uint32_t event; // Event type (from PerfEvent enum)
+ uint32_t size; // Total size of this event including payload
+ uint64_t time_stamp; // Timestamp when event occurred
+};
+/*
+ * Code load event - indicates a new JIT-compiled function is available
+ * This is the most important event type for Python profiling
+ */
typedef struct {
- struct BaseEvent base;
- uint32_t process_id;
- uint32_t thread_id;
- uint64_t vma;
- uint64_t code_address;
- uint64_t code_size;
- uint64_t code_id;
+ struct BaseEvent base; // Common event header
+ uint32_t process_id; // Process ID where code was generated
+ uint32_t thread_id; // Thread ID where code was generated
+ uint64_t vma; // Virtual memory address where code is loaded
+ uint64_t code_address; // Address of the actual machine code
+ uint64_t code_size; // Size of the machine code in bytes
+ uint64_t code_id; // Unique identifier for this code region
+ /* Followed by:
+ * - null-terminated function name string
+ * - raw machine code bytes
+ */
} CodeLoadEvent;
+/*
+ * Code unwinding information event - provides DWARF data for stack traces
+ * Essential for proper stack unwinding during profiling
+ */
typedef struct {
- struct BaseEvent base;
- uint64_t unwind_data_size;
- uint64_t eh_frame_hdr_size;
- uint64_t mapped_size;
+ struct BaseEvent base; // Common event header
+ uint64_t unwind_data_size; // Size of the unwinding data
+ uint64_t eh_frame_hdr_size; // Size of the EH frame header
+ uint64_t mapped_size; // Total mapped size (with padding)
+ /* Followed by:
+ * - EH frame header
+ * - DWARF unwinding information
+ * - Padding to alignment boundary
+ */
} CodeUnwindingInfoEvent;
-static const intptr_t nanoseconds_per_second = 1000000000;
-
-// Dwarf encoding constants
+// =============================================================================
+// GLOBAL STATE MANAGEMENT
+// =============================================================================
-static const uint8_t DwarfUData4 = 0x03;
-static const uint8_t DwarfSData4 = 0x0b;
-static const uint8_t DwarfPcRel = 0x10;
-static const uint8_t DwarfDataRel = 0x30;
-// static uint8_t DwarfOmit = 0xff;
+/*
+ * Global state for the perf jitdump implementation
+ *
+ * This structure maintains all the state needed for generating jitdump files.
+ * It's designed as a singleton since there's typically only one jitdump file
+ * per Python process.
+ */
typedef struct {
- unsigned char version;
- unsigned char eh_frame_ptr_enc;
- unsigned char fde_count_enc;
- unsigned char table_enc;
- int32_t eh_frame_ptr;
- int32_t eh_fde_count;
- int32_t from;
- int32_t to;
-} EhFrameHeader;
+ FILE* perf_map; // File handle for the jitdump file
+ PyThread_type_lock map_lock; // Thread synchronization lock
+ void* mapped_buffer; // Memory-mapped region (signals perf we're active)
+ size_t mapped_size; // Size of the mapped region
+ int code_id; // Counter for unique code region identifiers
+} PerfMapJitState;
+
+/* Global singleton instance */
+static PerfMapJitState perf_jit_map_state;
+
+// =============================================================================
+// TIME UTILITIES
+// =============================================================================
+/* Time conversion constant */
+static const intptr_t nanoseconds_per_second = 1000000000;
+
+/*
+ * Get current monotonic time in nanoseconds
+ *
+ * Monotonic time is preferred for event timestamps because it's not affected
+ * by system clock adjustments. This ensures consistent timing relationships
+ * between events even if the system clock is changed.
+ *
+ * Returns: Current monotonic time in nanoseconds since an arbitrary epoch
+ */
static int64_t get_current_monotonic_ticks(void) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
- UNREACHABLE();
+ Py_UNREACHABLE(); // Should never fail on supported systems
return 0;
}
- // Convert to nanoseconds.
+
+ /* Convert to nanoseconds for maximum precision */
int64_t result = ts.tv_sec;
result *= nanoseconds_per_second;
result += ts.tv_nsec;
return result;
}
+/*
+ * Get current wall clock time in microseconds
+ *
+ * Used for the jitdump file header timestamp. Unlike monotonic time,
+ * this represents actual wall clock time that can be correlated with
+ * other system events.
+ *
+ * Returns: Current time in microseconds since Unix epoch
+ */
static int64_t get_current_time_microseconds(void) {
- // gettimeofday has microsecond resolution.
- struct timeval tv;
- if (gettimeofday(&tv, NULL) < 0) {
- UNREACHABLE();
- return 0;
- }
- return ((int64_t)(tv.tv_sec) * 1000000) + tv.tv_usec;
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) < 0) {
+ Py_UNREACHABLE(); // Should never fail on supported systems
+ return 0;
+ }
+ return ((int64_t)(tv.tv_sec) * 1000000) + tv.tv_usec;
}
+// =============================================================================
+// UTILITY FUNCTIONS
+// =============================================================================
+/*
+ * Round up a value to the next multiple of a given number
+ *
+ * This is essential for maintaining proper alignment requirements in the
+ * jitdump format. Many structures need to be aligned to specific boundaries
+ * (typically 8 or 16 bytes) for efficient processing by perf.
+ *
+ * Args:
+ * value: The value to round up
+ * multiple: The multiple to round up to
+ *
+ * Returns: The smallest value >= input that is a multiple of 'multiple'
+ */
static size_t round_up(int64_t value, int64_t multiple) {
if (multiple == 0) {
- // Avoid division by zero
- return value;
+ return value; // Avoid division by zero
}
int64_t remainder = value % multiple;
if (remainder == 0) {
- // Value is already a multiple of 'multiple'
- return value;
+ return value; // Already aligned
}
- // Calculate the difference to the next multiple
+ /* Calculate how much to add to reach the next multiple */
int64_t difference = multiple - remainder;
-
- // Add the difference to the value
int64_t rounded_up_value = value + difference;
return rounded_up_value;
}
+// =============================================================================
+// FILE I/O UTILITIES
+// =============================================================================
+/*
+ * Write data to the jitdump file with error handling
+ *
+ * This function ensures that all data is written to the file, handling
+ * partial writes that can occur with large buffers or when the system
+ * is under load.
+ *
+ * Args:
+ * buffer: Pointer to data to write
+ * size: Number of bytes to write
+ */
static void perf_map_jit_write_fully(const void* buffer, size_t size) {
FILE* out_file = perf_jit_map_state.perf_map;
const char* ptr = (const char*)(buffer);
+
while (size > 0) {
const size_t written = fwrite(ptr, 1, size, out_file);
if (written == 0) {
- UNREACHABLE();
+ Py_UNREACHABLE(); // Write failure - should be very rare
break;
}
size -= written;
@@ -243,284 +356,724 @@ static void perf_map_jit_write_fully(const void* buffer, size_t size) {
}
}
+/*
+ * Write the jitdump file header
+ *
+ * The header must be written exactly once at the beginning of each jitdump
+ * file. It provides metadata that perf uses to parse the rest of the file.
+ *
+ * Args:
+ * pid: Process ID to include in the header
+ * out_file: File handle to write to (currently unused, uses global state)
+ */
static void perf_map_jit_write_header(int pid, FILE* out_file) {
Header header;
- header.magic = 0x4A695444;
- header.version = 1;
- header.size = sizeof(Header);
- header.elf_mach_target = GetElfMachineArchitecture();
- header.process_id = pid;
- header.time_stamp = get_current_time_microseconds();
- header.flags = 0;
- perf_map_jit_write_fully(&header, sizeof(header));
-}
-static void* perf_map_jit_init(void) {
- char filename[100];
- int pid = getpid();
- snprintf(filename, sizeof(filename) - 1, "/tmp/jit-%d.dump", pid);
- const int fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0666);
- if (fd == -1) {
- return NULL;
- }
+ /* Initialize header with required values */
+ header.magic = 0x4A695444; // "JiTD" magic number
+ header.version = 1; // Current jitdump version
+ header.size = sizeof(Header); // Header size for validation
+ header.elf_mach_target = GetElfMachineArchitecture(); // Target architecture
+ header.process_id = pid; // Process identifier
+ header.time_stamp = get_current_time_microseconds(); // Creation time
+ header.flags = 0; // No special flags currently used
- const long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
- if (page_size == -1) {
- close(fd);
- return NULL;
- }
-
- // The perf jit interface forces us to map the first page of the file
- // to signal that we are using the interface.
- perf_jit_map_state.mapped_buffer = mmap(NULL, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
- if (perf_jit_map_state.mapped_buffer == NULL) {
- close(fd);
- return NULL;
- }
- perf_jit_map_state.mapped_size = page_size;
- perf_jit_map_state.perf_map = fdopen(fd, "w+");
- if (perf_jit_map_state.perf_map == NULL) {
- close(fd);
- return NULL;
- }
- setvbuf(perf_jit_map_state.perf_map, NULL, _IOFBF, 2 * MB);
- perf_map_jit_write_header(pid, perf_jit_map_state.perf_map);
-
- perf_jit_map_state.map_lock = PyThread_allocate_lock();
- if (perf_jit_map_state.map_lock == NULL) {
- fclose(perf_jit_map_state.perf_map);
- return NULL;
- }
- perf_jit_map_state.code_id = 0;
-
- trampoline_api.code_padding = PERF_JIT_CODE_PADDING;
- return &perf_jit_map_state;
+ perf_map_jit_write_fully(&header, sizeof(header));
}
-/* DWARF definitions. */
+// =============================================================================
+// DWARF CONSTANTS AND UTILITIES
+// =============================================================================
+
+/*
+ * DWARF (Debug With Arbitrary Record Formats) constants
+ *
+ * DWARF is a debugging data format used to provide stack unwinding information.
+ * These constants define the various encoding types and opcodes used in
+ * DWARF Call Frame Information (CFI) records.
+ */
+/* DWARF Call Frame Information version */
#define DWRF_CIE_VERSION 1
+/* DWARF CFA (Call Frame Address) opcodes */
enum {
- DWRF_CFA_nop = 0x0,
- DWRF_CFA_offset_extended = 0x5,
- DWRF_CFA_def_cfa = 0xc,
- DWRF_CFA_def_cfa_offset = 0xe,
- DWRF_CFA_offset_extended_sf = 0x11,
- DWRF_CFA_advance_loc = 0x40,
- DWRF_CFA_offset = 0x80
+ DWRF_CFA_nop = 0x0, // No operation
+ DWRF_CFA_offset_extended = 0x5, // Extended offset instruction
+ DWRF_CFA_def_cfa = 0xc, // Define CFA rule
+ DWRF_CFA_def_cfa_offset = 0xe, // Define CFA offset
+ DWRF_CFA_offset_extended_sf = 0x11, // Extended signed offset
+ DWRF_CFA_advance_loc = 0x40, // Advance location counter
+ DWRF_CFA_offset = 0x80 // Simple offset instruction
};
-enum
- {
- DWRF_EH_PE_absptr = 0x00,
- DWRF_EH_PE_omit = 0xff,
-
- /* FDE data encoding. */
- 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,
-
- /* FDE flags. */
- 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
- };
+/* DWARF Exception Handling pointer encodings */
+enum {
+ DWRF_EH_PE_absptr = 0x00, // Absolute pointer
+ DWRF_EH_PE_omit = 0xff, // Omitted value
+
+ /* Data type encodings */
+ DWRF_EH_PE_uleb128 = 0x01, // Unsigned LEB128
+ DWRF_EH_PE_udata2 = 0x02, // Unsigned 2-byte
+ DWRF_EH_PE_udata4 = 0x03, // Unsigned 4-byte
+ DWRF_EH_PE_udata8 = 0x04, // Unsigned 8-byte
+ DWRF_EH_PE_sleb128 = 0x09, // Signed LEB128
+ DWRF_EH_PE_sdata2 = 0x0a, // Signed 2-byte
+ DWRF_EH_PE_sdata4 = 0x0b, // Signed 4-byte
+ DWRF_EH_PE_sdata8 = 0x0c, // Signed 8-byte
+ DWRF_EH_PE_signed = 0x08, // Signed flag
+
+ /* Reference type encodings */
+ DWRF_EH_PE_pcrel = 0x10, // PC-relative
+ DWRF_EH_PE_textrel = 0x20, // Text-relative
+ DWRF_EH_PE_datarel = 0x30, // Data-relative
+ DWRF_EH_PE_funcrel = 0x40, // Function-relative
+ DWRF_EH_PE_aligned = 0x50, // Aligned
+ DWRF_EH_PE_indirect = 0x80 // Indirect
+};
+/* Additional DWARF constants for debug information */
enum { DWRF_TAG_compile_unit = 0x11 };
-
enum { DWRF_children_no = 0, DWRF_children_yes = 1 };
+enum {
+ DWRF_AT_name = 0x03, // Name attribute
+ DWRF_AT_stmt_list = 0x10, // Statement list
+ DWRF_AT_low_pc = 0x11, // Low PC address
+ DWRF_AT_high_pc = 0x12 // High PC address
+};
+enum {
+ DWRF_FORM_addr = 0x01, // Address form
+ DWRF_FORM_data4 = 0x06, // 4-byte data
+ DWRF_FORM_string = 0x08 // String form
+};
-enum { DWRF_AT_name = 0x03, DWRF_AT_stmt_list = 0x10, DWRF_AT_low_pc = 0x11, DWRF_AT_high_pc = 0x12 };
-
-enum { DWRF_FORM_addr = 0x01, DWRF_FORM_data4 = 0x06, DWRF_FORM_string = 0x08 };
-
-enum { DWRF_LNS_extended_op = 0, DWRF_LNS_copy = 1, DWRF_LNS_advance_pc = 2, DWRF_LNS_advance_line = 3 };
+/* Line number program opcodes */
+enum {
+ DWRF_LNS_extended_op = 0, // Extended opcode
+ DWRF_LNS_copy = 1, // Copy operation
+ DWRF_LNS_advance_pc = 2, // Advance program counter
+ DWRF_LNS_advance_line = 3 // Advance line number
+};
-enum { DWRF_LNE_end_sequence = 1, DWRF_LNE_set_address = 2 };
+/* Line number extended opcodes */
+enum {
+ DWRF_LNE_end_sequence = 1, // End of sequence
+ DWRF_LNE_set_address = 2 // Set address
+};
+/*
+ * Architecture-specific DWARF register numbers
+ *
+ * These constants define the register numbering scheme used by DWARF
+ * for each supported architecture. The numbers must match the ABI
+ * specification for proper stack unwinding.
+ */
enum {
#ifdef __x86_64__
- /* Yes, the order is strange, but correct. */
- DWRF_REG_AX,
- DWRF_REG_DX,
- DWRF_REG_CX,
- DWRF_REG_BX,
- DWRF_REG_SI,
- DWRF_REG_DI,
- DWRF_REG_BP,
- DWRF_REG_SP,
- DWRF_REG_8,
- DWRF_REG_9,
- DWRF_REG_10,
- DWRF_REG_11,
- DWRF_REG_12,
- DWRF_REG_13,
- DWRF_REG_14,
- DWRF_REG_15,
- DWRF_REG_RA,
+ /* x86_64 register numbering (note: order is defined by x86_64 ABI) */
+ DWRF_REG_AX, // RAX
+ DWRF_REG_DX, // RDX
+ DWRF_REG_CX, // RCX
+ DWRF_REG_BX, // RBX
+ DWRF_REG_SI, // RSI
+ DWRF_REG_DI, // RDI
+ DWRF_REG_BP, // RBP
+ DWRF_REG_SP, // RSP
+ DWRF_REG_8, // R8
+ DWRF_REG_9, // R9
+ DWRF_REG_10, // R10
+ DWRF_REG_11, // R11
+ DWRF_REG_12, // R12
+ DWRF_REG_13, // R13
+ DWRF_REG_14, // R14
+ DWRF_REG_15, // R15
+ DWRF_REG_RA, // Return address (RIP)
#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__)
- DWRF_REG_SP = 31,
- DWRF_REG_RA = 30,
+ /* AArch64 register numbering */
+ DWRF_REG_FP = 29, // Frame Pointer
+ DWRF_REG_RA = 30, // Link register (return address)
+ DWRF_REG_SP = 31, // Stack pointer
#else
# error "Unsupported target architecture"
#endif
};
-typedef struct ELFObjectContext
-{
- uint8_t* p; /* Pointer to next address in obj.space. */
- uint8_t* startp; /* Pointer to start address in obj.space. */
- uint8_t* eh_frame_p; /* Pointer to start address in obj.space. */
- uint32_t code_size; /* Size of machine code. */
+/* DWARF encoding constants used in EH frame headers */
+static const uint8_t DwarfUData4 = 0x03; // Unsigned 4-byte data
+static const uint8_t DwarfSData4 = 0x0b; // Signed 4-byte data
+static const uint8_t DwarfPcRel = 0x10; // PC-relative encoding
+static const uint8_t DwarfDataRel = 0x30; // Data-relative encoding
+
+// =============================================================================
+// ELF OBJECT CONTEXT
+// =============================================================================
+
+/*
+ * Context for building ELF/DWARF structures
+ *
+ * This structure maintains state while constructing DWARF unwind information.
+ * It acts as a simple buffer manager with pointers to track current position
+ * and important landmarks within the buffer.
+ */
+typedef struct ELFObjectContext {
+ uint8_t* p; // Current write position in buffer
+ uint8_t* startp; // Start of buffer (for offset calculations)
+ uint8_t* eh_frame_p; // Start of EH frame data (for relative offsets)
+ uint32_t code_size; // Size of the code being described
} ELFObjectContext;
-/* Append a null-terminated string. */
-static uint32_t
-elfctx_append_string(ELFObjectContext* ctx, const char* str)
-{
+/*
+ * EH Frame Header structure for DWARF unwinding
+ *
+ * This structure provides metadata about the DWARF unwinding information
+ * that follows. It's required by the perf jitdump format to enable proper
+ * stack unwinding during profiling.
+ */
+typedef struct {
+ unsigned char version; // EH frame version (always 1)
+ unsigned char eh_frame_ptr_enc; // Encoding of EH frame pointer
+ unsigned char fde_count_enc; // Encoding of FDE count
+ unsigned char table_enc; // Encoding of table entries
+ int32_t eh_frame_ptr; // Pointer to EH frame data
+ int32_t eh_fde_count; // Number of FDEs (Frame Description Entries)
+ int32_t from; // Start address of code range
+ int32_t to; // End address of code range
+} EhFrameHeader;
+
+// =============================================================================
+// DWARF GENERATION UTILITIES
+// =============================================================================
+
+/*
+ * Append a null-terminated string to the ELF context buffer
+ *
+ * Args:
+ * ctx: ELF object context
+ * str: String to append (must be null-terminated)
+ *
+ * Returns: Offset from start of buffer where string was written
+ */
+static uint32_t elfctx_append_string(ELFObjectContext* ctx, const char* str) {
uint8_t* p = ctx->p;
uint32_t ofs = (uint32_t)(p - ctx->startp);
+
+ /* Copy string including null terminator */
do {
*p++ = (uint8_t)*str;
} while (*str++);
+
ctx->p = p;
return ofs;
}
-/* Append a SLEB128 value. */
-static void
-elfctx_append_sleb128(ELFObjectContext* ctx, int32_t v)
-{
+/*
+ * Append a SLEB128 (Signed Little Endian Base 128) value
+ *
+ * SLEB128 is a variable-length encoding used extensively in DWARF.
+ * It efficiently encodes small numbers in fewer bytes.
+ *
+ * Args:
+ * ctx: ELF object context
+ * v: Signed value to encode
+ */
+static void elfctx_append_sleb128(ELFObjectContext* ctx, int32_t v) {
uint8_t* p = ctx->p;
+
+ /* Encode 7 bits at a time, with continuation bit in MSB */
for (; (uint32_t)(v + 0x40) >= 0x80; v >>= 7) {
- *p++ = (uint8_t)((v & 0x7f) | 0x80);
+ *p++ = (uint8_t)((v & 0x7f) | 0x80); // Set continuation bit
}
- *p++ = (uint8_t)(v & 0x7f);
+ *p++ = (uint8_t)(v & 0x7f); // Final byte without continuation bit
+
ctx->p = p;
}
-/* Append a ULEB128 to buffer. */
-static void
-elfctx_append_uleb128(ELFObjectContext* ctx, uint32_t v)
-{
+/*
+ * Append a ULEB128 (Unsigned Little Endian Base 128) value
+ *
+ * Similar to SLEB128 but for unsigned values.
+ *
+ * Args:
+ * ctx: ELF object context
+ * v: Unsigned value to encode
+ */
+static void elfctx_append_uleb128(ELFObjectContext* ctx, uint32_t v) {
uint8_t* p = ctx->p;
+
+ /* Encode 7 bits at a time, with continuation bit in MSB */
for (; v >= 0x80; v >>= 7) {
- *p++ = (char)((v & 0x7f) | 0x80);
+ *p++ = (char)((v & 0x7f) | 0x80); // Set continuation bit
}
- *p++ = (char)v;
+ *p++ = (char)v; // Final byte without continuation bit
+
ctx->p = p;
}
-/* Shortcuts to generate DWARF structures. */
-#define DWRF_U8(x) (*p++ = (x))
-#define DWRF_I8(x) (*(int8_t*)p = (x), p++)
-#define DWRF_U16(x) (*(uint16_t*)p = (x), p += 2)
-#define DWRF_U32(x) (*(uint32_t*)p = (x), p += 4)
-#define DWRF_ADDR(x) (*(uintptr_t*)p = (x), p += sizeof(uintptr_t))
-#define DWRF_UV(x) (ctx->p = p, elfctx_append_uleb128(ctx, (x)), p = ctx->p)
-#define DWRF_SV(x) (ctx->p = p, elfctx_append_sleb128(ctx, (x)), p = ctx->p)
-#define DWRF_STR(str) (ctx->p = p, elfctx_append_string(ctx, (str)), p = ctx->p)
-#define DWRF_ALIGNNOP(s) \
- while ((uintptr_t)p & ((s)-1)) { \
- *p++ = DWRF_CFA_nop; \
+/*
+ * Macros for generating DWARF structures
+ *
+ * These macros provide a convenient way to write various data types
+ * to the DWARF buffer while automatically advancing the pointer.
+ */
+#define DWRF_U8(x) (*p++ = (x)) // Write unsigned 8-bit
+#define DWRF_I8(x) (*(int8_t*)p = (x), p++) // Write signed 8-bit
+#define DWRF_U16(x) (*(uint16_t*)p = (x), p += 2) // Write unsigned 16-bit
+#define DWRF_U32(x) (*(uint32_t*)p = (x), p += 4) // Write unsigned 32-bit
+#define DWRF_ADDR(x) (*(uintptr_t*)p = (x), p += sizeof(uintptr_t)) // Write address
+#define DWRF_UV(x) (ctx->p = p, elfctx_append_uleb128(ctx, (x)), p = ctx->p) // Write ULEB128
+#define DWRF_SV(x) (ctx->p = p, elfctx_append_sleb128(ctx, (x)), p = ctx->p) // Write SLEB128
+#define DWRF_STR(str) (ctx->p = p, elfctx_append_string(ctx, (str)), p = ctx->p) // Write string
+
+/* Align to specified boundary with NOP instructions */
+#define DWRF_ALIGNNOP(s) \
+ while ((uintptr_t)p & ((s)-1)) { \
+ *p++ = DWRF_CFA_nop; \
}
-#define DWRF_SECTION(name, stmt) \
- { \
- uint32_t* szp_##name = (uint32_t*)p; \
- p += 4; \
- stmt; \
- *szp_##name = (uint32_t)((p - (uint8_t*)szp_##name) - 4); \
+
+/* Write a DWARF section with automatic size calculation */
+#define DWRF_SECTION(name, stmt) \
+ { \
+ uint32_t* szp_##name = (uint32_t*)p; \
+ p += 4; \
+ stmt; \
+ *szp_##name = (uint32_t)((p - (uint8_t*)szp_##name) - 4); \
}
-/* Initialize .eh_frame section. */
-static void
-elf_init_ehframe(ELFObjectContext* ctx)
-{
+// =============================================================================
+// DWARF EH FRAME GENERATION
+// =============================================================================
+
+/*
+ * Initialize DWARF .eh_frame section for a code region
+ *
+ * The .eh_frame section contains Call Frame Information (CFI) that describes
+ * how to unwind the stack at any point in the code. This is essential for
+ * proper profiling as it allows perf to generate accurate call graphs.
+ *
+ * The function generates two main components:
+ * 1. CIE (Common Information Entry) - describes calling conventions
+ * 2. FDE (Frame Description Entry) - describes specific function unwinding
+ *
+ * Args:
+ * ctx: ELF object context containing code size and buffer pointers
+ */
+static void elf_init_ehframe(ELFObjectContext* ctx) {
uint8_t* p = ctx->p;
- uint8_t* framep = p;
-
- /* Emit DWARF EH CIE. */
- DWRF_SECTION(CIE, DWRF_U32(0); /* Offset to CIE itself. */
- DWRF_U8(DWRF_CIE_VERSION);
- DWRF_STR("zR"); /* Augmentation. */
- DWRF_UV(1); /* Code alignment factor. */
- DWRF_SV(-(int64_t)sizeof(uintptr_t)); /* Data alignment factor. */
- DWRF_U8(DWRF_REG_RA); /* Return address register. */
- DWRF_UV(1);
- DWRF_U8(DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4); /* Augmentation data. */
- DWRF_U8(DWRF_CFA_def_cfa); DWRF_UV(DWRF_REG_SP); DWRF_UV(sizeof(uintptr_t));
- DWRF_U8(DWRF_CFA_offset|DWRF_REG_RA); DWRF_UV(1);
- DWRF_ALIGNNOP(sizeof(uintptr_t));
+ uint8_t* framep = p; // Remember start of frame data
+
+ /*
+ * DWARF Unwind Table for Trampoline Function
+ *
+ * This section defines DWARF Call Frame Information (CFI) using encoded macros
+ * like `DWRF_U8`, `DWRF_UV`, and `DWRF_SECTION` to describe how the trampoline function
+ * preserves and restores registers. This is used by profiling tools (e.g., `perf`)
+ * and debuggers for stack unwinding in JIT-compiled code.
+ *
+ * -------------------------------------------------
+ * TO REGENERATE THIS TABLE FROM GCC OBJECTS:
+ * -------------------------------------------------
+ *
+ * 1. Create a trampoline source file (e.g., `trampoline.c`):
+ *
+ * #include <Python.h>
+ * typedef PyObject* (*py_evaluator)(void*, void*, int);
+ * PyObject* trampoline(void *ts, void *f, int throwflag, py_evaluator evaluator) {
+ * return evaluator(ts, f, throwflag);
+ * }
+ *
+ * 2. Compile to an object file with frame pointer preservation:
+ *
+ * gcc trampoline.c -I. -I./Include -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -c
+ *
+ * 3. Extract DWARF unwind info from the object file:
+ *
+ * readelf -w trampoline.o
+ *
+ * Example output from `.eh_frame`:
+ *
+ * 00000000 CIE
+ * Version: 1
+ * Augmentation: "zR"
+ * Code alignment factor: 4
+ * Data alignment factor: -8
+ * Return address column: 30
+ * DW_CFA_def_cfa: r31 (sp) ofs 0
+ *
+ * 00000014 FDE cie=00000000 pc=0..14
+ * DW_CFA_advance_loc: 4
+ * DW_CFA_def_cfa_offset: 16
+ * DW_CFA_offset: r29 at cfa-16
+ * DW_CFA_offset: r30 at cfa-8
+ * DW_CFA_advance_loc: 12
+ * DW_CFA_restore: r30
+ * DW_CFA_restore: r29
+ * DW_CFA_def_cfa_offset: 0
+ *
+ * -- These values can be verified by comparing with `readelf -w` or `llvm-dwarfdump --eh-frame`.
+ *
+ * ----------------------------------
+ * HOW TO TRANSLATE TO DWRF_* MACROS:
+ * ----------------------------------
+ *
+ * After compiling your trampoline with:
+ *
+ * gcc trampoline.c -I. -I./Include -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -c
+ *
+ * run:
+ *
+ * readelf -w trampoline.o
+ *
+ * to inspect the generated `.eh_frame` data. You will see two main components:
+ *
+ * 1. A CIE (Common Information Entry): shared configuration used by all FDEs.
+ * 2. An FDE (Frame Description Entry): function-specific unwind instructions.
+ *
+ * ---------------------
+ * Translating the CIE:
+ * ---------------------
+ * From `readelf -w`, you might see:
+ *
+ * 00000000 0000000000000010 00000000 CIE
+ * Version: 1
+ * Augmentation: "zR"
+ * Code alignment factor: 4
+ * Data alignment factor: -8
+ * Return address column: 30
+ * Augmentation data: 1b
+ * DW_CFA_def_cfa: r31 (sp) ofs 0
+ *
+ * Map this to:
+ *
+ * DWRF_SECTION(CIE,
+ * DWRF_U32(0); // CIE ID (always 0 for CIEs)
+ * DWRF_U8(DWRF_CIE_VERSION); // Version: 1
+ * DWRF_STR("zR"); // Augmentation string "zR"
+ * DWRF_UV(4); // Code alignment factor = 4
+ * DWRF_SV(-8); // Data alignment factor = -8
+ * DWRF_U8(DWRF_REG_RA); // Return address register (e.g., x30 = 30)
+ * DWRF_UV(1); // Augmentation data length = 1
+ * DWRF_U8(DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4); // Encoding for FDE pointers
+ *
+ * DWRF_U8(DWRF_CFA_def_cfa); // DW_CFA_def_cfa
+ * DWRF_UV(DWRF_REG_SP); // Register: SP (r31)
+ * DWRF_UV(0); // Offset = 0
+ *
+ * DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer size boundary
+ * )
+ *
+ * Notes:
+ * - Use `DWRF_UV` for unsigned LEB128, `DWRF_SV` for signed LEB128.
+ * - `DWRF_REG_RA` and `DWRF_REG_SP` are architecture-defined constants.
+ *
+ * ---------------------
+ * Translating the FDE:
+ * ---------------------
+ * From `readelf -w`:
+ *
+ * 00000014 0000000000000020 00000018 FDE cie=00000000 pc=0000000000000000..0000000000000014
+ * DW_CFA_advance_loc: 4
+ * DW_CFA_def_cfa_offset: 16
+ * DW_CFA_offset: r29 at cfa-16
+ * DW_CFA_offset: r30 at cfa-8
+ * DW_CFA_advance_loc: 12
+ * DW_CFA_restore: r30
+ * DW_CFA_restore: r29
+ * DW_CFA_def_cfa_offset: 0
+ *
+ * Map the FDE header and instructions to:
+ *
+ * DWRF_SECTION(FDE,
+ * DWRF_U32((uint32_t)(p - framep)); // Offset to CIE (relative from here)
+ * DWRF_U32(-0x30); // Initial PC-relative location of the code
+ * DWRF_U32(ctx->code_size); // Code range covered by this FDE
+ * DWRF_U8(0); // Augmentation data length (none)
+ *
+ * DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance location by 1 unit (1 * 4 = 4 bytes)
+ * DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP + 16
+ * DWRF_UV(16);
+ *
+ * DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Save x29 (frame pointer)
+ * DWRF_UV(2); // At offset 2 * 8 = 16 bytes
+ *
+ * DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Save x30 (return address)
+ * DWRF_UV(1); // At offset 1 * 8 = 8 bytes
+ *
+ * DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance location by 3 units (3 * 4 = 12 bytes)
+ *
+ * DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Restore x30
+ * DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Restore x29
+ *
+ * DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP
+ * DWRF_UV(0);
+ * )
+ *
+ * To regenerate:
+ * 1. Get the `code alignment factor`, `data alignment factor`, and `RA column` from the CIE.
+ * 2. Note the range of the function from the FDE's `pc=...` line and map it to the JIT code as
+ * the code is in a different address space every time.
+ * 3. For each `DW_CFA_*` entry, use the corresponding `DWRF_*` macro:
+ * - `DW_CFA_def_cfa_offset` → DWRF_U8(DWRF_CFA_def_cfa_offset), DWRF_UV(value)
+ * - `DW_CFA_offset: rX` → DWRF_U8(DWRF_CFA_offset | reg), DWRF_UV(offset)
+ * - `DW_CFA_restore: rX` → DWRF_U8(DWRF_CFA_offset | reg) // restore is same as reusing offset
+ * - `DW_CFA_advance_loc: N` → DWRF_U8(DWRF_CFA_advance_loc | (N / code_alignment_factor))
+ * 4. Use `DWRF_REG_FP`, `DWRF_REG_RA`, etc., for register numbers.
+ * 5. Use `sizeof(uintptr_t)` (typically 8) for pointer size calculations and alignment.
+ */
+
+ /*
+ * Emit DWARF EH CIE (Common Information Entry)
+ *
+ * The CIE describes the calling conventions and basic unwinding rules
+ * that apply to all functions in this compilation unit.
+ */
+ DWRF_SECTION(CIE,
+ DWRF_U32(0); // CIE ID (0 indicates this is a CIE)
+ DWRF_U8(DWRF_CIE_VERSION); // CIE version (1)
+ DWRF_STR("zR"); // Augmentation string ("zR" = has LSDA)
+ DWRF_UV(1); // Code alignment factor
+ DWRF_SV(-(int64_t)sizeof(uintptr_t)); // Data alignment factor (negative)
+ DWRF_U8(DWRF_REG_RA); // Return address register number
+ DWRF_UV(1); // Augmentation data length
+ DWRF_U8(DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4); // FDE pointer encoding
+
+ /* Initial CFI instructions - describe default calling convention */
+ DWRF_U8(DWRF_CFA_def_cfa); // Define CFA (Call Frame Address)
+ DWRF_UV(DWRF_REG_SP); // CFA = SP register
+ DWRF_UV(sizeof(uintptr_t)); // CFA = SP + pointer_size
+ DWRF_U8(DWRF_CFA_offset|DWRF_REG_RA); // Return address is saved
+ DWRF_UV(1); // At offset 1 from CFA
+
+ DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer boundary
)
- ctx->eh_frame_p = p;
-
- /* Emit DWARF EH FDE. */
- DWRF_SECTION(FDE, DWRF_U32((uint32_t)(p - framep)); /* Offset to CIE. */
- DWRF_U32(-0x30); /* Machine code offset relative to .text. */
- DWRF_U32(ctx->code_size); /* Machine code length. */
- DWRF_U8(0); /* Augmentation data. */
- /* Registers saved in CFRAME. */
+ ctx->eh_frame_p = p; // Remember start of FDE data
+
+ /*
+ * Emit DWARF EH FDE (Frame Description Entry)
+ *
+ * The FDE describes unwinding information specific to this function.
+ * It references the CIE and provides function-specific CFI instructions.
+ */
+ DWRF_SECTION(FDE,
+ DWRF_U32((uint32_t)(p - framep)); // Offset to CIE (backwards reference)
+ DWRF_U32(-0x30); // Machine code offset relative to .text
+ DWRF_U32(ctx->code_size); // Address range covered by this FDE (code lenght)
+ DWRF_U8(0); // Augmentation data length (none)
+
+ /*
+ * Architecture-specific CFI instructions
+ *
+ * These instructions describe how registers are saved and restored
+ * during function calls. Each architecture has different calling
+ * conventions and register usage patterns.
+ */
#ifdef __x86_64__
- DWRF_U8(DWRF_CFA_advance_loc | 4);
- DWRF_U8(DWRF_CFA_def_cfa_offset); DWRF_UV(16);
- DWRF_U8(DWRF_CFA_advance_loc | 6);
- DWRF_U8(DWRF_CFA_def_cfa_offset); DWRF_UV(8);
- /* Extra registers saved for JIT-compiled code. */
+ /* x86_64 calling convention unwinding rules */
+# if defined(__CET__) && (__CET__ & 1)
+ DWRF_U8(DWRF_CFA_advance_loc | 8); // Advance location by 8 bytes when CET protection is enabled
+# else
+ DWRF_U8(DWRF_CFA_advance_loc | 4); // Advance location by 4 bytes
+# endif
+ DWRF_U8(DWRF_CFA_def_cfa_offset); // Redefine CFA offset
+ DWRF_UV(16); // New offset: SP + 16
+ DWRF_U8(DWRF_CFA_advance_loc | 6); // Advance location by 6 bytes
+ DWRF_U8(DWRF_CFA_def_cfa_offset); // Redefine CFA offset
+ DWRF_UV(8); // New offset: SP + 8
#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__)
- DWRF_U8(DWRF_CFA_advance_loc | 1);
- DWRF_U8(DWRF_CFA_def_cfa_offset); DWRF_UV(16);
- DWRF_U8(DWRF_CFA_offset | 29); DWRF_UV(2);
- DWRF_U8(DWRF_CFA_offset | 30); DWRF_UV(1);
- DWRF_U8(DWRF_CFA_advance_loc | 3);
- DWRF_U8(DWRF_CFA_offset | -(64 - 29));
- DWRF_U8(DWRF_CFA_offset | -(64 - 30));
- DWRF_U8(DWRF_CFA_def_cfa_offset);
- DWRF_UV(0);
+ /* AArch64 calling convention unwinding rules */
+ DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance location by 1 instruction (stp x29, x30)
+ DWRF_U8(DWRF_CFA_def_cfa_offset); // Redefine CFA offset
+ DWRF_UV(16); // CFA = SP + 16 (stack pointer after push)
+ DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Frame pointer (x29) saved
+ DWRF_UV(2); // At offset 2 from CFA (2 * 8 = 16 bytes)
+ DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Link register (x30) saved
+ DWRF_UV(1); // At offset 1 from CFA (1 * 8 = 8 bytes)
+ DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance by 3 instructions (mov x16, x3; mov x29, sp; ldp...)
+ DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Restore frame pointer (x29)
+ DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Restore link register (x30)
+ DWRF_U8(DWRF_CFA_def_cfa_offset); // Final CFA adjustment
+ DWRF_UV(0); // CFA = SP + 0 (stack restored)
+
#else
# error "Unsupported target architecture"
#endif
- DWRF_ALIGNNOP(sizeof(uintptr_t));)
- ctx->p = p;
+ DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer boundary
+ )
+
+ ctx->p = p; // Update context pointer to end of generated data
+}
+
+// =============================================================================
+// JITDUMP INITIALIZATION
+// =============================================================================
+
+/*
+ * Initialize the perf jitdump interface
+ *
+ * This function sets up everything needed to generate jitdump files:
+ * 1. Creates the jitdump file with a unique name
+ * 2. Maps the first page to signal perf that we're using the interface
+ * 3. Writes the jitdump header
+ * 4. Initializes synchronization primitives
+ *
+ * The memory mapping is crucial - perf detects jitdump files by scanning
+ * for processes that have mapped files matching the pattern /tmp/jit-*.dump
+ *
+ * Returns: Pointer to initialized state, or NULL on failure
+ */
+static void* perf_map_jit_init(void) {
+ char filename[100];
+ int pid = getpid();
+
+ /* Create unique filename based on process ID */
+ snprintf(filename, sizeof(filename) - 1, "/tmp/jit-%d.dump", pid);
+
+ /* Create/open the jitdump file with appropriate permissions */
+ const int fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0666);
+ if (fd == -1) {
+ return NULL; // Failed to create file
+ }
+
+ /* Get system page size for memory mapping */
+ const long page_size = sysconf(_SC_PAGESIZE);
+ if (page_size == -1) {
+ close(fd);
+ return NULL; // Failed to get page size
+ }
+
+ /*
+ * Map the first page of the jitdump file
+ *
+ * This memory mapping serves as a signal to perf that this process
+ * is generating JIT code. Perf scans /proc/.../maps looking for mapped
+ * files that match the jitdump naming pattern.
+ *
+ * The mapping must be PROT_READ | PROT_EXEC to be detected by perf.
+ */
+ perf_jit_map_state.mapped_buffer = mmap(
+ NULL, // Let kernel choose address
+ page_size, // Map one page
+ PROT_READ | PROT_EXEC, // Read and execute permissions (required by perf)
+ MAP_PRIVATE, // Private mapping
+ fd, // File descriptor
+ 0 // Offset 0 (first page)
+ );
+
+ if (perf_jit_map_state.mapped_buffer == NULL) {
+ close(fd);
+ return NULL; // Memory mapping failed
+ }
+
+ perf_jit_map_state.mapped_size = page_size;
+
+ /* Convert file descriptor to FILE* for easier I/O operations */
+ perf_jit_map_state.perf_map = fdopen(fd, "w+");
+ if (perf_jit_map_state.perf_map == NULL) {
+ close(fd);
+ return NULL; // Failed to create FILE*
+ }
+
+ /*
+ * Set up file buffering for better performance
+ *
+ * We use a large buffer (2MB) because jitdump files can be written
+ * frequently during program execution. Buffering reduces system call
+ * overhead and improves overall performance.
+ */
+ setvbuf(perf_jit_map_state.perf_map, NULL, _IOFBF, 2 * MB);
+
+ /* Write the jitdump file header */
+ perf_map_jit_write_header(pid, perf_jit_map_state.perf_map);
+
+ /*
+ * Initialize thread synchronization lock
+ *
+ * Multiple threads may attempt to write to the jitdump file
+ * simultaneously. This lock ensures thread-safe access to the
+ * global jitdump state.
+ */
+ perf_jit_map_state.map_lock = PyThread_allocate_lock();
+ if (perf_jit_map_state.map_lock == NULL) {
+ fclose(perf_jit_map_state.perf_map);
+ return NULL; // Failed to create lock
+ }
+
+ /* Initialize code ID counter */
+ perf_jit_map_state.code_id = 0;
+
+ /* Configure trampoline API with padding information */
+ trampoline_api.code_padding = PERF_JIT_CODE_PADDING;
+
+ return &perf_jit_map_state;
}
+// =============================================================================
+// MAIN JITDUMP ENTRY WRITING
+// =============================================================================
+
+/*
+ * Write a complete jitdump entry for a Python function
+ *
+ * This is the main function called by Python's trampoline system whenever
+ * a new piece of JIT-compiled code needs to be recorded. It writes both
+ * the unwinding information and the code load event to the jitdump file.
+ *
+ * The function performs these steps:
+ * 1. Initialize jitdump system if not already done
+ * 2. Extract function name and filename from Python code object
+ * 3. Generate DWARF unwinding information
+ * 4. Write unwinding info event to jitdump file
+ * 5. Write code load event to jitdump file
+ *
+ * Args:
+ * state: Jitdump state (currently unused, uses global state)
+ * code_addr: Address where the compiled code resides
+ * code_size: Size of the compiled code in bytes
+ * co: Python code object containing metadata
+ *
+ * IMPORTANT: This function signature is part of Python's internal API
+ * and must not be changed without coordinating with core Python development.
+ */
static void perf_map_jit_write_entry(void *state, const void *code_addr,
- unsigned int code_size, PyCodeObject *co)
+ unsigned int code_size, PyCodeObject *co)
{
-
+ /* Initialize jitdump system on first use */
if (perf_jit_map_state.perf_map == NULL) {
void* ret = perf_map_jit_init();
if(ret == NULL){
- return;
+ return; // Initialization failed, silently abort
}
}
+ /*
+ * Extract function information from Python code object
+ *
+ * We create a human-readable function name by combining the qualified
+ * name (includes class/module context) with the filename. This helps
+ * developers identify functions in perf reports.
+ */
const char *entry = "";
if (co->co_qualname != NULL) {
entry = PyUnicode_AsUTF8(co->co_qualname);
}
+
const char *filename = "";
if (co->co_filename != NULL) {
filename = PyUnicode_AsUTF8(co->co_filename);
}
-
+ /*
+ * Create formatted function name for perf display
+ *
+ * Format: "py::<function_name>:<filename>"
+ * The "py::" prefix helps identify Python functions in mixed-language
+ * profiles (e.g., when profiling C extensions alongside Python code).
+ */
size_t perf_map_entry_size = snprintf(NULL, 0, "py::%s:%s", entry, filename) + 1;
char* perf_map_entry = (char*) PyMem_RawMalloc(perf_map_entry_size);
if (perf_map_entry == NULL) {
- return;
+ return; // Memory allocation failed
}
snprintf(perf_map_entry, perf_map_entry_size, "py::%s:%s", entry, filename);
@@ -528,90 +1081,185 @@ static void perf_map_jit_write_entry(void *state, const void *code_addr,
uword base = (uword)code_addr;
uword size = code_size;
- // Write the code unwinding info event.
-
- // Create unwinding information (eh frame)
+ /*
+ * Generate DWARF unwinding information
+ *
+ * DWARF data is essential for proper stack unwinding during profiling.
+ * Without it, perf cannot generate accurate call graphs, especially
+ * in optimized code where frame pointers may be omitted.
+ */
ELFObjectContext ctx;
- char buffer[1024];
+ char buffer[1024]; // Buffer for DWARF data (1KB should be sufficient)
ctx.code_size = code_size;
ctx.startp = ctx.p = (uint8_t*)buffer;
+
+ /* Generate EH frame (Exception Handling frame) data */
elf_init_ehframe(&ctx);
int eh_frame_size = ctx.p - ctx.startp;
- // Populate the unwind info event for perf
+ /*
+ * Write Code Unwinding Information Event
+ *
+ * This event must be written before the code load event to ensure
+ * perf has the unwinding information available when it processes
+ * the code region.
+ */
CodeUnwindingInfoEvent ev2;
ev2.base.event = PerfUnwindingInfo;
ev2.base.time_stamp = get_current_monotonic_ticks();
ev2.unwind_data_size = sizeof(EhFrameHeader) + eh_frame_size;
- // Ensure we have enough space between DSOs when perf maps them
+
+ /* Verify we don't exceed our padding budget */
assert(ev2.unwind_data_size <= PERF_JIT_CODE_PADDING);
+
ev2.eh_frame_hdr_size = sizeof(EhFrameHeader);
- ev2.mapped_size = round_up(ev2.unwind_data_size, 16);
+ ev2.mapped_size = round_up(ev2.unwind_data_size, 16); // 16-byte alignment
+
+ /* Calculate total event size with padding */
int content_size = sizeof(ev2) + sizeof(EhFrameHeader) + eh_frame_size;
- int padding_size = round_up(content_size, 8) - content_size;
+ int padding_size = round_up(content_size, 8) - content_size; // 8-byte align
ev2.base.size = content_size + padding_size;
- perf_map_jit_write_fully(&ev2, sizeof(ev2));
+ /* Write the unwinding info event header */
+ perf_map_jit_write_fully(&ev2, sizeof(ev2));
- // Populate the eh Frame header
+ /*
+ * Write EH Frame Header
+ *
+ * The EH frame header provides metadata about the DWARF unwinding
+ * information that follows. It includes pointers and counts that
+ * help perf navigate the unwinding data efficiently.
+ */
EhFrameHeader f;
f.version = 1;
- f.eh_frame_ptr_enc = DwarfSData4 | DwarfPcRel;
- f.fde_count_enc = DwarfUData4;
- f.table_enc = DwarfSData4 | DwarfDataRel;
+ f.eh_frame_ptr_enc = DwarfSData4 | DwarfPcRel; // PC-relative signed 4-byte
+ f.fde_count_enc = DwarfUData4; // Unsigned 4-byte count
+ f.table_enc = DwarfSData4 | DwarfDataRel; // Data-relative signed 4-byte
+
+ /* Calculate relative offsets for EH frame navigation */
f.eh_frame_ptr = -(eh_frame_size + 4 * sizeof(unsigned char));
- f.eh_fde_count = 1;
+ f.eh_fde_count = 1; // We generate exactly one FDE per function
f.from = -(round_up(code_size, 8) + eh_frame_size);
+
int cie_size = ctx.eh_frame_p - ctx.startp;
f.to = -(eh_frame_size - cie_size);
+ /* Write EH frame data and header */
perf_map_jit_write_fully(ctx.startp, eh_frame_size);
perf_map_jit_write_fully(&f, sizeof(f));
+ /* Write padding to maintain alignment */
char padding_bytes[] = "\0\0\0\0\0\0\0\0";
perf_map_jit_write_fully(&padding_bytes, padding_size);
- // Write the code load event.
+ /*
+ * Write Code Load Event
+ *
+ * This event tells perf about the new code region. It includes:
+ * - Memory addresses and sizes
+ * - Process and thread identification
+ * - Function name for symbol resolution
+ * - The actual machine code bytes
+ */
CodeLoadEvent ev;
ev.base.event = PerfLoad;
ev.base.size = sizeof(ev) + (name_length+1) + size;
ev.base.time_stamp = get_current_monotonic_ticks();
ev.process_id = getpid();
- ev.thread_id = syscall(SYS_gettid);
- ev.vma = base;
- ev.code_address = base;
+ ev.thread_id = syscall(SYS_gettid); // Get thread ID via system call
+ ev.vma = base; // Virtual memory address
+ ev.code_address = base; // Same as VMA for our use case
ev.code_size = size;
+
+ /* Assign unique code ID and increment counter */
perf_jit_map_state.code_id += 1;
ev.code_id = perf_jit_map_state.code_id;
+ /* Write code load event and associated data */
perf_map_jit_write_fully(&ev, sizeof(ev));
- perf_map_jit_write_fully(perf_map_entry, name_length+1);
- perf_map_jit_write_fully((void*)(base), size);
- return;
+ perf_map_jit_write_fully(perf_map_entry, name_length+1); // Include null terminator
+ perf_map_jit_write_fully((void*)(base), size); // Copy actual machine code
+
+ /* Clean up allocated memory */
+ PyMem_RawFree(perf_map_entry);
}
+// =============================================================================
+// CLEANUP AND FINALIZATION
+// =============================================================================
+
+/*
+ * Finalize and cleanup the perf jitdump system
+ *
+ * This function is called when Python is shutting down or when the
+ * perf trampoline system is being disabled. It ensures all resources
+ * are properly released and all buffered data is flushed to disk.
+ *
+ * Args:
+ * state: Jitdump state (currently unused, uses global state)
+ *
+ * Returns: 0 on success
+ *
+ * IMPORTANT: This function signature is part of Python's internal API
+ * and must not be changed without coordinating with core Python development.
+ */
static int perf_map_jit_fini(void* state) {
+ /*
+ * Close jitdump file with proper synchronization
+ *
+ * We need to acquire the lock to ensure no other threads are
+ * writing to the file when we close it. This prevents corruption
+ * and ensures all data is properly flushed.
+ */
if (perf_jit_map_state.perf_map != NULL) {
- // close the file
PyThread_acquire_lock(perf_jit_map_state.map_lock, 1);
- fclose(perf_jit_map_state.perf_map);
+ fclose(perf_jit_map_state.perf_map); // This also flushes buffers
PyThread_release_lock(perf_jit_map_state.map_lock);
- // clean up the lock and state
+ /* Clean up synchronization primitive */
PyThread_free_lock(perf_jit_map_state.map_lock);
perf_jit_map_state.perf_map = NULL;
}
+
+ /*
+ * Unmap the memory region
+ *
+ * This removes the signal to perf that we were generating JIT code.
+ * After this point, perf will no longer detect this process as
+ * having JIT capabilities.
+ */
if (perf_jit_map_state.mapped_buffer != NULL) {
munmap(perf_jit_map_state.mapped_buffer, perf_jit_map_state.mapped_size);
+ perf_jit_map_state.mapped_buffer = NULL;
}
+
+ /* Clear global state reference */
trampoline_api.state = NULL;
- return 0;
+
+ return 0; // Success
}
+// =============================================================================
+// PUBLIC API EXPORT
+// =============================================================================
+
+/*
+ * Python Perf Callbacks Structure
+ *
+ * This structure defines the callback interface that Python's trampoline
+ * system uses to integrate with perf profiling. It contains function
+ * pointers for initialization, event writing, and cleanup.
+ *
+ * CRITICAL: This structure and its contents are part of Python's internal
+ * API. The function signatures and behavior must remain stable to maintain
+ * compatibility with the Python interpreter's perf integration system.
+ *
+ * Used by: Python's _PyPerf_Callbacks system in pycore_ceval.h
+ */
_PyPerf_Callbacks _Py_perfmap_jit_callbacks = {
- &perf_map_jit_init,
- &perf_map_jit_write_entry,
- &perf_map_jit_fini,
+ &perf_map_jit_init, // Initialization function
+ &perf_map_jit_write_entry, // Event writing function
+ &perf_map_jit_fini, // Cleanup function
};
-#endif
+#endif /* PY_HAVE_PERF_TRAMPOLINE */ \ No newline at end of file
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index c4c1d9fd9e1..724fda63511 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -1283,7 +1283,7 @@ init_interp_main(PyThreadState *tstate)
if (is_main_interp) {
/* Initialize warnings. */
PyObject *warnoptions;
- if (_PySys_GetOptionalAttrString("warnoptions", &warnoptions) < 0) {
+ if (PySys_GetOptionalAttrString("warnoptions", &warnoptions) < 0) {
return _PyStatus_ERR("can't initialize warnings");
}
if (warnoptions != NULL && PyList_Check(warnoptions) &&
@@ -1806,7 +1806,7 @@ flush_std_files(void)
PyObject *file;
int status = 0;
- if (_PySys_GetOptionalAttr(&_Py_ID(stdout), &file) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stdout), &file) < 0) {
status = -1;
}
else if (file != NULL && file != Py_None && !file_is_closed(file)) {
@@ -1819,7 +1819,7 @@ flush_std_files(void)
}
Py_XDECREF(file);
- if (_PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
PyErr_Clear();
status = -1;
}
@@ -3046,7 +3046,7 @@ _Py_FatalError_PrintExc(PyThreadState *tstate)
}
PyObject *ferr;
- if (_PySys_GetOptionalAttr(&_Py_ID(stderr), &ferr) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stderr), &ferr) < 0) {
_PyErr_Clear(tstate);
}
if (ferr == NULL || ferr == Py_None) {
@@ -3144,7 +3144,7 @@ static inline void _Py_NO_RETURN
fatal_error_exit(int status)
{
if (status < 0) {
-#if defined(MS_WINDOWS) && defined(_DEBUG)
+#if defined(MS_WINDOWS) && defined(Py_DEBUG)
DebugBreak();
#endif
abort();
diff --git a/Python/pystate.c b/Python/pystate.c
index 5685957b160..0d4c26f92ce 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -69,7 +69,12 @@ to avoid the expense of doing their own locking).
#ifdef HAVE_THREAD_LOCAL
+/* The attached thread state for the current thread. */
_Py_thread_local PyThreadState *_Py_tss_tstate = NULL;
+
+/* The "bound" thread state used by PyGILState_Ensure(),
+ also known as a "gilstate." */
+_Py_thread_local PyThreadState *_Py_tss_gilstate = NULL;
#endif
static inline PyThreadState *
@@ -118,79 +123,9 @@ _PyThreadState_GetCurrent(void)
}
-//------------------------------------------------
-// the thread state bound to the current OS thread
-//------------------------------------------------
-
-static inline int
-tstate_tss_initialized(Py_tss_t *key)
-{
- return PyThread_tss_is_created(key);
-}
-
-static inline int
-tstate_tss_init(Py_tss_t *key)
-{
- assert(!tstate_tss_initialized(key));
- return PyThread_tss_create(key);
-}
-
-static inline void
-tstate_tss_fini(Py_tss_t *key)
-{
- assert(tstate_tss_initialized(key));
- PyThread_tss_delete(key);
-}
-
-static inline PyThreadState *
-tstate_tss_get(Py_tss_t *key)
-{
- assert(tstate_tss_initialized(key));
- return (PyThreadState *)PyThread_tss_get(key);
-}
-
-static inline int
-tstate_tss_set(Py_tss_t *key, PyThreadState *tstate)
-{
- assert(tstate != NULL);
- assert(tstate_tss_initialized(key));
- return PyThread_tss_set(key, (void *)tstate);
-}
-
-static inline int
-tstate_tss_clear(Py_tss_t *key)
-{
- assert(tstate_tss_initialized(key));
- return PyThread_tss_set(key, (void *)NULL);
-}
-
-#ifdef HAVE_FORK
-/* Reset the TSS key - called by PyOS_AfterFork_Child().
- * This should not be necessary, but some - buggy - pthread implementations
- * don't reset TSS upon fork(), see issue #10517.
- */
-static PyStatus
-tstate_tss_reinit(Py_tss_t *key)
-{
- if (!tstate_tss_initialized(key)) {
- return _PyStatus_OK();
- }
- PyThreadState *tstate = tstate_tss_get(key);
-
- tstate_tss_fini(key);
- if (tstate_tss_init(key) != 0) {
- return _PyStatus_NO_MEMORY();
- }
-
- /* If the thread had an associated auto thread state, reassociate it with
- * the new key. */
- if (tstate && tstate_tss_set(key, tstate) != 0) {
- return _PyStatus_ERR("failed to re-set autoTSSkey");
- }
- return _PyStatus_OK();
-}
-#endif
-
+//---------------------------------------------
+// The thread state used by PyGILState_Ensure()
+//---------------------------------------------
/*
The stored thread state is set by bind_tstate() (AKA PyThreadState_Bind().
@@ -198,36 +133,23 @@ tstate_tss_reinit(Py_tss_t *key)
The GIL does no need to be held for these.
*/
-#define gilstate_tss_initialized(runtime) \
- tstate_tss_initialized(&(runtime)->autoTSSkey)
-#define gilstate_tss_init(runtime) \
- tstate_tss_init(&(runtime)->autoTSSkey)
-#define gilstate_tss_fini(runtime) \
- tstate_tss_fini(&(runtime)->autoTSSkey)
-#define gilstate_tss_get(runtime) \
- tstate_tss_get(&(runtime)->autoTSSkey)
-#define _gilstate_tss_set(runtime, tstate) \
- tstate_tss_set(&(runtime)->autoTSSkey, tstate)
-#define _gilstate_tss_clear(runtime) \
- tstate_tss_clear(&(runtime)->autoTSSkey)
-#define gilstate_tss_reinit(runtime) \
- tstate_tss_reinit(&(runtime)->autoTSSkey)
+static inline PyThreadState *
+gilstate_get(void)
+{
+ return _Py_tss_gilstate;
+}
static inline void
-gilstate_tss_set(_PyRuntimeState *runtime, PyThreadState *tstate)
+gilstate_set(PyThreadState *tstate)
{
- assert(tstate != NULL && tstate->interp->runtime == runtime);
- if (_gilstate_tss_set(runtime, tstate) != 0) {
- Py_FatalError("failed to set current tstate (TSS)");
- }
+ assert(tstate != NULL);
+ _Py_tss_gilstate = tstate;
}
static inline void
-gilstate_tss_clear(_PyRuntimeState *runtime)
+gilstate_clear(void)
{
- if (_gilstate_tss_clear(runtime) != 0) {
- Py_FatalError("failed to clear current tstate (TSS)");
- }
+ _Py_tss_gilstate = NULL;
}
@@ -253,7 +175,7 @@ bind_tstate(PyThreadState *tstate)
assert(tstate_is_alive(tstate) && !tstate->_status.bound);
assert(!tstate->_status.unbound); // just in case
assert(!tstate->_status.bound_gilstate);
- assert(tstate != gilstate_tss_get(tstate->interp->runtime));
+ assert(tstate != gilstate_get());
assert(!tstate->_status.active);
assert(tstate->thread_id == 0);
assert(tstate->native_thread_id == 0);
@@ -328,14 +250,13 @@ bind_gilstate_tstate(PyThreadState *tstate)
// XXX assert(!tstate->_status.active);
assert(!tstate->_status.bound_gilstate);
- _PyRuntimeState *runtime = tstate->interp->runtime;
- PyThreadState *tcur = gilstate_tss_get(runtime);
+ PyThreadState *tcur = gilstate_get();
assert(tstate != tcur);
if (tcur != NULL) {
tcur->_status.bound_gilstate = 0;
}
- gilstate_tss_set(runtime, tstate);
+ gilstate_set(tstate);
tstate->_status.bound_gilstate = 1;
}
@@ -347,9 +268,8 @@ unbind_gilstate_tstate(PyThreadState *tstate)
assert(tstate_is_bound(tstate));
// XXX assert(!tstate->_status.active);
assert(tstate->_status.bound_gilstate);
- assert(tstate == gilstate_tss_get(tstate->interp->runtime));
-
- gilstate_tss_clear(tstate->interp->runtime);
+ assert(tstate == gilstate_get());
+ gilstate_clear();
tstate->_status.bound_gilstate = 0;
}
@@ -373,7 +293,7 @@ holds_gil(PyThreadState *tstate)
// (and tstate->interp->runtime->ceval.gil.locked).
assert(tstate != NULL);
/* Must be the tstate for this thread */
- assert(tstate == gilstate_tss_get(tstate->interp->runtime));
+ assert(tstate == gilstate_get());
return tstate == current_fast_get();
}
@@ -469,16 +389,6 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
return status;
}
- if (gilstate_tss_init(runtime) != 0) {
- _PyRuntimeState_Fini(runtime);
- return _PyStatus_NO_MEMORY();
- }
-
- if (PyThread_tss_create(&runtime->trashTSSkey) != 0) {
- _PyRuntimeState_Fini(runtime);
- return _PyStatus_NO_MEMORY();
- }
-
init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head,
unicode_next_index);
@@ -492,14 +402,7 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
/* The count is cleared by _Py_FinalizeRefTotal(). */
assert(runtime->object_state.interpreter_leaks == 0);
#endif
-
- if (gilstate_tss_initialized(runtime)) {
- gilstate_tss_fini(runtime);
- }
-
- if (PyThread_tss_is_created(&runtime->trashTSSkey)) {
- PyThread_tss_delete(&runtime->trashTSSkey);
- }
+ gilstate_clear();
}
#ifdef HAVE_FORK
@@ -532,18 +435,6 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
_PyTypes_AfterFork();
- PyStatus status = gilstate_tss_reinit(runtime);
- if (_PyStatus_EXCEPTION(status)) {
- return status;
- }
-
- if (PyThread_tss_is_created(&runtime->trashTSSkey)) {
- PyThread_tss_delete(&runtime->trashTSSkey);
- }
- if (PyThread_tss_create(&runtime->trashTSSkey) != 0) {
- return _PyStatus_NO_MEMORY();
- }
-
_PyThread_AfterFork(&runtime->threads);
return _PyStatus_OK();
@@ -676,8 +567,11 @@ init_interpreter(PyInterpreterState *interp,
}
interp->sys_profile_initialized = false;
interp->sys_trace_initialized = false;
+ interp->_code_object_generation = 0;
interp->jit = false;
interp->executor_list_head = NULL;
+ interp->executor_deletion_list_head = NULL;
+ interp->executor_deletion_list_remaining_capacity = 0;
interp->trace_run_counter = JIT_CLEANUP_THRESHOLD;
if (interp != &runtime->_main_interpreter) {
/* Fix the self-referential, statically initialized fields. */
@@ -884,6 +778,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
for (int t = 0; t < PY_MONITORING_TOOL_IDS; t++) {
Py_CLEAR(interp->monitoring_tool_names[t]);
}
+ interp->_code_object_generation = 0;
+#ifdef Py_GIL_DISABLED
+ interp->tlbc_indices.tlbc_generation = 0;
+#endif
PyConfig_Clear(&interp->config);
_PyCodec_Fini(interp);
@@ -902,6 +800,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->after_forkers_child);
#endif
+
+#ifdef _Py_TIER2
+ _Py_ClearExecutorDeletionList(interp);
+#endif
_PyAST_Fini(interp);
_PyWarnings_Fini(interp);
_PyAtExit_Fini(interp);
@@ -1240,6 +1142,7 @@ _Py_CheckMainModule(PyObject *module)
PyObject *msg = PyUnicode_FromString("invalid __main__ module");
if (msg != NULL) {
(void)PyErr_SetImportError(msg, &_Py_ID(__main__), NULL);
+ Py_DECREF(msg);
}
return -1;
}
@@ -1387,10 +1290,8 @@ interp_look_up_id(_PyRuntimeState *runtime, int64_t requested_id)
{
PyInterpreterState *interp = runtime->interpreters.head;
while (interp != NULL) {
- int64_t id = PyInterpreterState_GetID(interp);
- if (id < 0) {
- return NULL;
- }
+ int64_t id = interp->id;
+ assert(id >= 0);
if (requested_id == id) {
return interp;
}
@@ -1451,9 +1352,6 @@ tstate_is_alive(PyThreadState *tstate)
// lifecycle
//----------
-/* Minimum size of data stack chunk */
-#define DATA_STACK_CHUNK_SIZE (16*1024)
-
static _PyStackChunk*
allocate_chunk(int size_in_bytes, _PyStackChunk* previous)
{
@@ -1570,7 +1468,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
tstate->datastack_top = NULL;
tstate->datastack_limit = NULL;
tstate->what_event = -1;
- tstate->previous_executor = NULL;
+ tstate->current_executor = NULL;
tstate->dict_global_version = 0;
_tstate->c_stack_soft_limit = UINTPTR_MAX;
@@ -1665,7 +1563,7 @@ _PyThreadState_NewBound(PyInterpreterState *interp, int whence)
bind_tstate(tstate);
// This makes sure there's a gilstate tstate bound
// as soon as possible.
- if (gilstate_tss_get(tstate->interp->runtime) == NULL) {
+ if (gilstate_get() == NULL) {
bind_gilstate_tstate(tstate);
}
}
@@ -1902,9 +1800,14 @@ tstate_delete_common(PyThreadState *tstate, int release_gil)
static void
zapthreads(PyInterpreterState *interp)
{
+ PyThreadState *tstate;
/* No need to lock the mutex here because this should only happen
- when the threads are all really dead (XXX famous last words). */
- _Py_FOR_EACH_TSTATE_UNLOCKED(interp, tstate) {
+ when the threads are all really dead (XXX famous last words).
+
+ Cannot use _Py_FOR_EACH_TSTATE_UNLOCKED because we are freeing
+ the thread states here.
+ */
+ while ((tstate = interp->threads.head) != NULL) {
tstate_verify_not_active(tstate);
tstate_delete_common(tstate, 0);
free_threadstate((_PyThreadStateImpl *)tstate);
@@ -2086,7 +1989,7 @@ tstate_activate(PyThreadState *tstate)
assert(!tstate->_status.active);
assert(!tstate->_status.bound_gilstate ||
- tstate == gilstate_tss_get((tstate->interp->runtime)));
+ tstate == gilstate_get());
if (!tstate->_status.bound_gilstate) {
bind_gilstate_tstate(tstate);
}
@@ -2554,7 +2457,7 @@ _PyThreadState_Bind(PyThreadState *tstate)
bind_tstate(tstate);
// This makes sure there's a gilstate tstate bound
// as soon as possible.
- if (gilstate_tss_get(tstate->interp->runtime) == NULL) {
+ if (gilstate_get() == NULL) {
bind_gilstate_tstate(tstate);
}
}
@@ -2756,7 +2659,7 @@ _PyGILState_Init(PyInterpreterState *interp)
return _PyStatus_OK();
}
_PyRuntimeState *runtime = interp->runtime;
- assert(gilstate_tss_get(runtime) == NULL);
+ assert(gilstate_get() == NULL);
assert(runtime->gilstate.autoInterpreterState == NULL);
runtime->gilstate.autoInterpreterState = interp;
return _PyStatus_OK();
@@ -2792,7 +2695,7 @@ _PyGILState_SetTstate(PyThreadState *tstate)
_PyRuntimeState *runtime = tstate->interp->runtime;
assert(runtime->gilstate.autoInterpreterState == tstate->interp);
- assert(gilstate_tss_get(runtime) == tstate);
+ assert(gilstate_get() == tstate);
assert(tstate->gilstate_counter == 1);
#endif
}
@@ -2808,11 +2711,7 @@ _PyGILState_GetInterpreterStateUnsafe(void)
PyThreadState *
PyGILState_GetThisThreadState(void)
{
- _PyRuntimeState *runtime = &_PyRuntime;
- if (!gilstate_tss_initialized(runtime)) {
- return NULL;
- }
- return gilstate_tss_get(runtime);
+ return gilstate_get();
}
int
@@ -2823,16 +2722,12 @@ PyGILState_Check(void)
return 1;
}
- if (!gilstate_tss_initialized(runtime)) {
- return 1;
- }
-
PyThreadState *tstate = current_fast_get();
if (tstate == NULL) {
return 0;
}
- PyThreadState *tcur = gilstate_tss_get(runtime);
+ PyThreadState *tcur = gilstate_get();
return (tstate == tcur);
}
@@ -2847,12 +2742,17 @@ PyGILState_Ensure(void)
called Py_Initialize(). */
/* Ensure that _PyEval_InitThreads() and _PyGILState_Init() have been
- called by Py_Initialize() */
- assert(_PyEval_ThreadsInitialized());
- assert(gilstate_tss_initialized(runtime));
- assert(runtime->gilstate.autoInterpreterState != NULL);
+ called by Py_Initialize()
- PyThreadState *tcur = gilstate_tss_get(runtime);
+ TODO: This isn't thread-safe. There's no protection here against
+ concurrent finalization of the interpreter; it's simply a guard
+ for *after* the interpreter has finalized.
+ */
+ if (!_PyEval_ThreadsInitialized() || runtime->gilstate.autoInterpreterState == NULL) {
+ PyThread_hang_thread();
+ }
+
+ PyThreadState *tcur = gilstate_get();
int has_gil;
if (tcur == NULL) {
/* Create a new Python thread state for this thread */
@@ -2892,8 +2792,7 @@ PyGILState_Ensure(void)
void
PyGILState_Release(PyGILState_STATE oldstate)
{
- _PyRuntimeState *runtime = &_PyRuntime;
- PyThreadState *tstate = gilstate_tss_get(runtime);
+ PyThreadState *tstate = gilstate_get();
if (tstate == NULL) {
Py_FatalError("auto-releasing thread-state, "
"but no thread-state for this thread");
@@ -3001,7 +2900,7 @@ _PyInterpreterState_HasFeature(PyInterpreterState *interp, unsigned long feature
static PyObject **
push_chunk(PyThreadState *tstate, int size)
{
- int allocate_size = DATA_STACK_CHUNK_SIZE;
+ int allocate_size = _PY_DATA_STACK_CHUNK_SIZE;
while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
allocate_size *= 2;
}
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index 23af8b6f6be..8f1c78bf831 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -114,7 +114,7 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag
}
PyObject *v;
- if (_PySys_GetOptionalAttr(&_Py_ID(ps1), &v) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(ps1), &v) < 0) {
PyErr_Print();
return -1;
}
@@ -128,7 +128,7 @@ _PyRun_InteractiveLoopObject(FILE *fp, PyObject *filename, PyCompilerFlags *flag
}
}
Py_XDECREF(v);
- if (_PySys_GetOptionalAttr(&_Py_ID(ps2), &v) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(ps2), &v) < 0) {
PyErr_Print();
return -1;
}
@@ -206,7 +206,7 @@ pyrun_one_parse_ast(FILE *fp, PyObject *filename,
PyObject *encoding_obj = NULL;
const char *encoding = NULL;
if (fp == stdin) {
- if (_PySys_GetOptionalAttr(&_Py_ID(stdin), &attr) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stdin), &attr) < 0) {
PyErr_Clear();
}
else if (attr != NULL && attr != Py_None) {
@@ -226,7 +226,7 @@ pyrun_one_parse_ast(FILE *fp, PyObject *filename,
// Get sys.ps1 (as UTF-8)
PyObject *ps1_obj = NULL;
const char *ps1 = "";
- if (_PySys_GetOptionalAttr(&_Py_ID(ps1), &attr) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(ps1), &attr) < 0) {
PyErr_Clear();
}
else if (attr != NULL) {
@@ -247,7 +247,7 @@ pyrun_one_parse_ast(FILE *fp, PyObject *filename,
// Get sys.ps2 (as UTF-8)
PyObject *ps2_obj = NULL;
const char *ps2 = "";
- if (_PySys_GetOptionalAttr(&_Py_ID(ps2), &attr) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(ps2), &attr) < 0) {
PyErr_Clear();
}
else if (attr != NULL) {
@@ -658,7 +658,7 @@ _Py_HandleSystemExitAndKeyboardInterrupt(int *exitcode_p)
}
PyObject *sys_stderr;
- if (_PySys_GetOptionalAttr(&_Py_ID(stderr), &sys_stderr) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stderr), &sys_stderr) < 0) {
PyErr_Clear();
}
else if (sys_stderr != NULL && sys_stderr != Py_None) {
@@ -722,7 +722,7 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars)
_PyErr_Clear(tstate);
}
}
- if (_PySys_GetOptionalAttr(&_Py_ID(excepthook), &hook) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(excepthook), &hook) < 0) {
PyErr_Clear();
}
if (_PySys_Audit(tstate, "sys.excepthook", "OOOO", hook ? hook : Py_None,
@@ -1197,7 +1197,7 @@ void
PyErr_Display(PyObject *unused, PyObject *value, PyObject *tb)
{
PyObject *file;
- if (_PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
PyObject *exc = PyErr_GetRaisedException();
_PyObject_Dump(value);
fprintf(stderr, "lost sys.stderr\n");
@@ -1321,7 +1321,7 @@ static void
flush_io_stream(PyThreadState *tstate, PyObject *name)
{
PyObject *f;
- if (_PySys_GetOptionalAttr(name, &f) < 0) {
+ if (PySys_GetOptionalAttr(name, &f) < 0) {
PyErr_Clear();
}
if (f != NULL) {
@@ -1498,7 +1498,7 @@ Py_CompileStringObject(const char *str, PyObject *filename, int start,
}
if (flags && (flags->cf_flags & PyCF_ONLY_AST)) {
int syntax_check_only = ((flags->cf_flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST); /* unoptiomized AST */
- if (_PyCompile_AstOptimize(mod, filename, flags, optimize, arena, syntax_check_only) < 0) {
+ if (_PyCompile_AstPreprocess(mod, filename, flags, optimize, arena, syntax_check_only) < 0) {
_PyArena_Free(arena);
return NULL;
}
@@ -1524,6 +1524,26 @@ Py_CompileStringExFlags(const char *str, const char *filename_str, int start,
return co;
}
+int
+_PyObject_SupportedAsScript(PyObject *cmd)
+{
+ if (PyUnicode_Check(cmd)) {
+ return 1;
+ }
+ else if (PyBytes_Check(cmd)) {
+ return 1;
+ }
+ else if (PyByteArray_Check(cmd)) {
+ return 1;
+ }
+ else if (PyObject_CheckBuffer(cmd)) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
const char *
_Py_SourceAsString(PyObject *cmd, const char *funcname, const char *what, PyCompilerFlags *cf, PyObject **cmd_copy)
{
diff --git a/Python/qsbr.c b/Python/qsbr.c
index bf34fb2523d..c992c285cb1 100644
--- a/Python/qsbr.c
+++ b/Python/qsbr.c
@@ -1,6 +1,6 @@
/*
* Implementation of safe memory reclamation scheme using
- * quiescent states.
+ * quiescent states. See InternalDocs/qsbr.md.
*
* This is derived from the "GUS" safe memory reclamation technique
* in FreeBSD written by Jeffrey Roberson. It is heavily modified. Any bugs
@@ -41,10 +41,6 @@
// Starting size of the array of qsbr thread states
#define MIN_ARRAY_SIZE 8
-// For _Py_qsbr_deferred_advance(): the number of deferrals before advancing
-// the write sequence.
-#define QSBR_DEFERRED_LIMIT 10
-
// Allocate a QSBR thread state from the freelist
static struct _qsbr_thread_state *
qsbr_allocate(struct _qsbr_shared *shared)
@@ -117,13 +113,9 @@ _Py_qsbr_advance(struct _qsbr_shared *shared)
}
uint64_t
-_Py_qsbr_deferred_advance(struct _qsbr_thread_state *qsbr)
+_Py_qsbr_shared_next(struct _qsbr_shared *shared)
{
- if (++qsbr->deferrals < QSBR_DEFERRED_LIMIT) {
- return _Py_qsbr_shared_current(qsbr->shared) + QSBR_INCR;
- }
- qsbr->deferrals = 0;
- return _Py_qsbr_advance(qsbr->shared);
+ return _Py_qsbr_shared_current(shared) + QSBR_INCR;
}
static uint64_t
diff --git a/Python/remote_debug.h b/Python/remote_debug.h
index edc77c30291..d1fcb478d2b 100644
--- a/Python/remote_debug.h
+++ b/Python/remote_debug.h
@@ -13,6 +13,16 @@ If you need to add a new function ensure that is declared 'static'.
extern "C" {
#endif
+#ifdef __clang__
+ #define UNUSED __attribute__((unused))
+#elif defined(__GNUC__)
+ #define UNUSED __attribute__((unused))
+#elif defined(_MSC_VER)
+ #define UNUSED __pragma(warning(suppress: 4505))
+#else
+ #define UNUSED
+#endif
+
#if !defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
# error "this header requires Py_BUILD_CORE or Py_BUILD_CORE_MODULE define"
#endif
@@ -35,7 +45,7 @@ extern "C" {
# include <sys/mman.h>
#endif
-#if defined(__APPLE__) && TARGET_OS_OSX
+#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
# include <libproc.h>
# include <mach-o/fat.h>
# include <mach-o/loader.h>
@@ -73,27 +83,75 @@ extern "C" {
# define HAVE_PROCESS_VM_READV 0
#endif
+#define _set_debug_exception_cause(exception, format, ...) \
+ do { \
+ if (!PyErr_ExceptionMatches(PyExc_PermissionError)) { \
+ PyThreadState *tstate = _PyThreadState_GET(); \
+ if (!_PyErr_Occurred(tstate)) { \
+ _PyErr_Format(tstate, exception, format, ##__VA_ARGS__); \
+ } else { \
+ _PyErr_FormatFromCause(exception, format, ##__VA_ARGS__); \
+ } \
+ } \
+ } while (0)
+
+static inline size_t
+get_page_size(void) {
+ size_t page_size = 0;
+ if (page_size == 0) {
+#ifdef MS_WINDOWS
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ page_size = si.dwPageSize;
+#else
+ page_size = (size_t)getpagesize();
+#endif
+ }
+ return page_size;
+}
+
+
// Define a platform-independent process handle structure
typedef struct {
pid_t pid;
-#ifdef MS_WINDOWS
+#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
+ mach_port_t task;
+#elif defined(MS_WINDOWS)
HANDLE hProcess;
+#elif defined(__linux__)
+ int memfd;
#endif
+ Py_ssize_t page_size;
} proc_handle_t;
+
+#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
+static mach_port_t pid_to_task(pid_t pid);
+#endif
+
// Initialize the process handle
static int
_Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) {
handle->pid = pid;
-#ifdef MS_WINDOWS
+#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
+ handle->task = pid_to_task(handle->pid);
+ if (handle->task == 0) {
+ _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize macOS process handle");
+ return -1;
+ }
+#elif defined(MS_WINDOWS)
handle->hProcess = OpenProcess(
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,
FALSE, pid);
if (handle->hProcess == NULL) {
PyErr_SetFromWindowsErr(0);
+ _set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize Windows process handle");
return -1;
}
+#elif defined(__linux__)
+ handle->memfd = -1;
#endif
+ handle->page_size = get_page_size();
return 0;
}
@@ -105,11 +163,16 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) {
CloseHandle(handle->hProcess);
handle->hProcess = NULL;
}
+#elif defined(__linux__)
+ if (handle->memfd != -1) {
+ close(handle->memfd);
+ handle->memfd = -1;
+ }
#endif
handle->pid = 0;
}
-#if defined(__APPLE__) && TARGET_OS_OSX
+#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
static uintptr_t
return_section_address64(
@@ -148,8 +211,10 @@ return_section_address64(
&object_name
);
if (ret != KERN_SUCCESS) {
- PyErr_SetString(
- PyExc_RuntimeError, "Cannot get any more VM maps.\n");
+ PyErr_Format(PyExc_RuntimeError,
+ "mach_vm_region failed while parsing 64-bit Mach-O binary "
+ "at base address 0x%lx (kern_return_t: %d)",
+ base, ret);
return 0;
}
}
@@ -169,9 +234,6 @@ return_section_address64(
cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize);
}
- // We should not be here, but if we are there, we should say about this
- PyErr_SetString(
- PyExc_RuntimeError, "Cannot find section address.\n");
return 0;
}
@@ -212,8 +274,10 @@ return_section_address32(
&object_name
);
if (ret != KERN_SUCCESS) {
- PyErr_SetString(
- PyExc_RuntimeError, "Cannot get any more VM maps.\n");
+ PyErr_Format(PyExc_RuntimeError,
+ "mach_vm_region failed while parsing 32-bit Mach-O binary "
+ "at base address 0x%lx (kern_return_t: %d)",
+ base, ret);
return 0;
}
}
@@ -233,9 +297,6 @@ return_section_address32(
cmd = (struct segment_command*)((void*)cmd + cmd->cmdsize);
}
- // We should not be here, but if we are there, we should say about this
- PyErr_SetString(
- PyExc_RuntimeError, "Cannot find section address.\n");
return 0;
}
@@ -253,8 +314,20 @@ return_section_address_fat(
int is_abi64;
size_t cpu_size = sizeof(cpu), abi64_size = sizeof(is_abi64);
- sysctlbyname("hw.cputype", &cpu, &cpu_size, NULL, 0);
- sysctlbyname("hw.cpu64bit_capable", &is_abi64, &abi64_size, NULL, 0);
+ if (sysctlbyname("hw.cputype", &cpu, &cpu_size, NULL, 0) != 0) {
+ PyErr_Format(PyExc_OSError,
+ "Failed to determine CPU type via sysctlbyname "
+ "for fat binary analysis at 0x%lx: %s",
+ base, strerror(errno));
+ return 0;
+ }
+ if (sysctlbyname("hw.cpu64bit_capable", &is_abi64, &abi64_size, NULL, 0) != 0) {
+ PyErr_Format(PyExc_OSError,
+ "Failed to determine CPU ABI capability via sysctlbyname "
+ "for fat binary analysis at 0x%lx: %s",
+ base, strerror(errno));
+ return 0;
+ }
cpu |= is_abi64 * CPU_ARCH_ABI64;
@@ -285,13 +358,18 @@ return_section_address_fat(
return return_section_address64(section, proc_ref, base, (void*)hdr);
default:
- PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic in fat binary.\n");
+ PyErr_Format(PyExc_RuntimeError,
+ "Unknown Mach-O magic number 0x%x in fat binary architecture %u at base 0x%lx",
+ hdr->magic, i, base);
return 0;
}
}
}
- PyErr_SetString(PyExc_RuntimeError, "No matching architecture found in fat binary.\n");
+ PyErr_Format(PyExc_RuntimeError,
+ "No matching architecture found for CPU type 0x%x "
+ "in fat binary at base 0x%lx (%u architectures examined)",
+ cpu, base, nfat_arch);
return 0;
}
@@ -300,20 +378,26 @@ search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_
{
int fd = open(path, O_RDONLY);
if (fd == -1) {
- PyErr_Format(PyExc_RuntimeError, "Cannot open binary %s\n", path);
+ PyErr_Format(PyExc_OSError,
+ "Cannot open binary file '%s' for section '%s' search: %s",
+ path, secname, strerror(errno));
return 0;
}
struct stat fs;
if (fstat(fd, &fs) == -1) {
- PyErr_Format(PyExc_RuntimeError, "Cannot get size of binary %s\n", path);
+ PyErr_Format(PyExc_OSError,
+ "Cannot get file size for binary '%s' during section '%s' search: %s",
+ path, secname, strerror(errno));
close(fd);
return 0;
}
void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
- PyErr_Format(PyExc_RuntimeError, "Cannot map binary %s\n", path);
+ PyErr_Format(PyExc_OSError,
+ "Cannot memory map binary file '%s' (size: %lld bytes) for section '%s' search: %s",
+ path, (long long)fs.st_size, secname, strerror(errno));
close(fd);
return 0;
}
@@ -335,13 +419,22 @@ search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_
result = return_section_address_fat(secname, proc_ref, base, map);
break;
default:
- PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic");
+ PyErr_Format(PyExc_RuntimeError,
+ "Unrecognized Mach-O magic number 0x%x in binary file '%s' for section '%s' search",
+ magic, path, secname);
break;
}
- munmap(map, fs.st_size);
+ if (munmap(map, fs.st_size) != 0) {
+ PyErr_Format(PyExc_OSError,
+ "Failed to unmap binary file '%s' (size: %lld bytes): %s",
+ path, (long long)fs.st_size, strerror(errno));
+ result = 0;
+ }
if (close(fd) != 0) {
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_Format(PyExc_OSError,
+ "Failed to close binary file '%s': %s",
+ path, strerror(errno));
result = 0;
}
return result;
@@ -356,7 +449,10 @@ pid_to_task(pid_t pid)
result = task_for_pid(mach_task_self(), pid, &task);
if (result != KERN_SUCCESS) {
- PyErr_Format(PyExc_PermissionError, "Cannot get task for PID %d", pid);
+ PyErr_Format(PyExc_PermissionError,
+ "Cannot get task port for PID %d (kern_return_t: %d). "
+ "This typically requires running as root or having the 'com.apple.system-task-ports' entitlement.",
+ pid, result);
return 0;
}
return task;
@@ -373,13 +469,15 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s
mach_port_t proc_ref = pid_to_task(handle->pid);
if (proc_ref == 0) {
if (!PyErr_Occurred()) {
- PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID");
+ PyErr_Format(PyExc_PermissionError,
+ "Cannot get task port for PID %d during section search",
+ handle->pid);
}
return 0;
}
- int match_found = 0;
char map_filename[MAXPATHLEN + 1];
+
while (mach_vm_region(
proc_ref,
&address,
@@ -389,6 +487,7 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s
&count,
&object_name) == KERN_SUCCESS)
{
+
if ((region_info.protection & VM_PROT_READ) == 0
|| (region_info.protection & VM_PROT_EXECUTE) == 0) {
address += size;
@@ -409,21 +508,21 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s
filename = map_filename; // No path, use the whole string
}
- if (!match_found && strncmp(filename, substr, strlen(substr)) == 0) {
- match_found = 1;
- return search_section_in_file(
+ if (strncmp(filename, substr, strlen(substr)) == 0) {
+ uintptr_t result = search_section_in_file(
secname, map_filename, address, size, proc_ref);
+ if (result != 0) {
+ return result;
+ }
}
address += size;
}
- PyErr_SetString(PyExc_RuntimeError,
- "mach_vm_region failed to find the section");
return 0;
}
-#endif // (__APPLE__ && TARGET_OS_OSX)
+#endif // (__APPLE__ && defined(TARGET_OS_OSX) && TARGET_OS_OSX)
#if defined(__linux__) && HAVE_PROCESS_VM_READV
static uintptr_t
@@ -442,24 +541,38 @@ search_elf_file_for_section(
int fd = open(elf_file, O_RDONLY);
if (fd < 0) {
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_Format(PyExc_OSError,
+ "Cannot open ELF file '%s' for section '%s' search: %s",
+ elf_file, secname, strerror(errno));
goto exit;
}
struct stat file_stats;
if (fstat(fd, &file_stats) != 0) {
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_Format(PyExc_OSError,
+ "Cannot get file size for ELF file '%s' during section '%s' search: %s",
+ elf_file, secname, strerror(errno));
goto exit;
}
file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (file_memory == MAP_FAILED) {
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_Format(PyExc_OSError,
+ "Cannot memory map ELF file '%s' (size: %lld bytes) for section '%s' search: %s",
+ elf_file, (long long)file_stats.st_size, secname, strerror(errno));
goto exit;
}
Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory;
+ // Validate ELF header
+ if (elf_header->e_shstrndx >= elf_header->e_shnum) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Invalid ELF file '%s': string table index %u >= section count %u",
+ elf_file, elf_header->e_shstrndx, elf_header->e_shnum);
+ goto exit;
+ }
+
Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff);
Elf_Shdr* shstrtab_section = &section_header_table[elf_header->e_shstrndx];
@@ -476,6 +589,10 @@ search_elf_file_for_section(
}
}
+ if (section == NULL) {
+ goto exit;
+ }
+
Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff);
// Find the first PT_LOAD segment
Elf_Phdr* first_load_segment = NULL;
@@ -486,18 +603,25 @@ search_elf_file_for_section(
}
}
- if (section != NULL && first_load_segment != NULL) {
- uintptr_t elf_load_addr = first_load_segment->p_vaddr
- - (first_load_segment->p_vaddr % first_load_segment->p_align);
- result = start_address + (uintptr_t)section->sh_addr - elf_load_addr;
+ if (first_load_segment == NULL) {
+ PyErr_Format(PyExc_RuntimeError,
+ "No PT_LOAD segment found in ELF file '%s' (%u program headers examined)",
+ elf_file, elf_header->e_phnum);
+ goto exit;
}
+ uintptr_t elf_load_addr = first_load_segment->p_vaddr
+ - (first_load_segment->p_vaddr % first_load_segment->p_align);
+ result = start_address + (uintptr_t)section->sh_addr - elf_load_addr;
+
exit:
if (file_memory != NULL) {
munmap(file_memory, file_stats.st_size);
}
if (fd >= 0 && close(fd) != 0) {
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_Format(PyExc_OSError,
+ "Failed to close ELF file '%s': %s",
+ elf_file, strerror(errno));
result = 0;
}
return result;
@@ -511,7 +635,9 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c
FILE* maps_file = fopen(maps_file_path, "r");
if (maps_file == NULL) {
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_Format(PyExc_OSError,
+ "Cannot open process memory map file '%s' for PID %d section search: %s",
+ maps_file_path, handle->pid, strerror(errno));
return 0;
}
@@ -520,11 +646,14 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c
char *line = PyMem_Malloc(linesz);
if (!line) {
fclose(maps_file);
- PyErr_NoMemory();
+ _set_debug_exception_cause(PyExc_MemoryError,
+ "Cannot allocate memory for reading process map file '%s'",
+ maps_file_path);
return 0;
}
uintptr_t retval = 0;
+
while (fgets(line + linelen, linesz - linelen, maps_file) != NULL) {
linelen = strlen(line);
if (line[linelen - 1] != '\n') {
@@ -535,7 +664,9 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c
if (!biggerline) {
PyMem_Free(line);
fclose(maps_file);
- PyErr_NoMemory();
+ _set_debug_exception_cause(PyExc_MemoryError,
+ "Cannot reallocate memory while reading process map file '%s' (attempted size: %zu)",
+ maps_file_path, linesz);
return 0;
}
line = biggerline;
@@ -575,7 +706,9 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c
PyMem_Free(line);
if (fclose(maps_file) != 0) {
- PyErr_SetFromErrno(PyExc_OSError);
+ PyErr_Format(PyExc_OSError,
+ "Failed to close process map file '%s': %s",
+ maps_file_path, strerror(errno));
retval = 0;
}
@@ -591,11 +724,20 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char*
HANDLE hFile = CreateFileW(mod_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
PyErr_SetFromWindowsErr(0);
+ DWORD error = GetLastError();
+ PyErr_Format(PyExc_OSError,
+ "Cannot open PE file for section '%s' analysis (error %lu)",
+ secname, error);
return NULL;
}
+
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0);
if (!hMap) {
PyErr_SetFromWindowsErr(0);
+ DWORD error = GetLastError();
+ PyErr_Format(PyExc_OSError,
+ "Cannot create file mapping for PE file section '%s' analysis (error %lu)",
+ secname, error);
CloseHandle(hFile);
return NULL;
}
@@ -603,6 +745,10 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char*
BYTE* mapView = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if (!mapView) {
PyErr_SetFromWindowsErr(0);
+ DWORD error = GetLastError();
+ PyErr_Format(PyExc_OSError,
+ "Cannot map view of PE file for section '%s' analysis (error %lu)",
+ secname, error);
CloseHandle(hMap);
CloseHandle(hFile);
return NULL;
@@ -610,7 +756,9 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char*
IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)mapView;
if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) {
- PyErr_SetString(PyExc_RuntimeError, "Invalid DOS signature.");
+ PyErr_Format(PyExc_RuntimeError,
+ "Invalid DOS signature (0x%x) in PE file for section '%s' analysis (expected 0x%x)",
+ pDOSHeader->e_magic, secname, IMAGE_DOS_SIGNATURE);
UnmapViewOfFile(mapView);
CloseHandle(hMap);
CloseHandle(hFile);
@@ -619,7 +767,9 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char*
IMAGE_NT_HEADERS* pNTHeaders = (IMAGE_NT_HEADERS*)(mapView + pDOSHeader->e_lfanew);
if (pNTHeaders->Signature != IMAGE_NT_SIGNATURE) {
- PyErr_SetString(PyExc_RuntimeError, "Invalid NT signature.");
+ PyErr_Format(PyExc_RuntimeError,
+ "Invalid NT signature (0x%lx) in PE file for section '%s' analysis (expected 0x%lx)",
+ pNTHeaders->Signature, secname, IMAGE_NT_SIGNATURE);
UnmapViewOfFile(mapView);
CloseHandle(hMap);
CloseHandle(hFile);
@@ -653,7 +803,12 @@ search_windows_map_for_section(proc_handle_t* handle, const char* secname, const
} while (hProcSnap == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);
if (hProcSnap == INVALID_HANDLE_VALUE) {
- PyErr_SetString(PyExc_PermissionError, "Unable to create module snapshot. Check permissions or PID.");
+ PyErr_SetFromWindowsErr(0);
+ DWORD error = GetLastError();
+ PyErr_Format(PyExc_PermissionError,
+ "Unable to create module snapshot for PID %d section '%s' "
+ "search (error %lu). Check permissions or PID validity",
+ handle->pid, secname, error);
return 0;
}
@@ -672,6 +827,7 @@ search_windows_map_for_section(proc_handle_t* handle, const char* secname, const
}
CloseHandle(hProcSnap);
+
return (uintptr_t)runtime_addr;
}
@@ -689,7 +845,9 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
if (address == 0) {
// Error out: 'python' substring covers both executable and DLL
PyObject *exc = PyErr_GetRaisedException();
- PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to find the PyRuntime section in process %d on Windows platform",
+ handle->pid);
_PyErr_ChainExceptions1(exc);
}
#elif defined(__linux__)
@@ -698,16 +856,28 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
if (address == 0) {
// Error out: 'python' substring covers both executable and DLL
PyObject *exc = PyErr_GetRaisedException();
- PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to find the PyRuntime section in process %d on Linux platform",
+ handle->pid);
_PyErr_ChainExceptions1(exc);
}
-#elif defined(__APPLE__) && TARGET_OS_OSX
+#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
// On macOS, try libpython first, then fall back to python
- address = search_map_for_section(handle, "PyRuntime", "libpython");
- if (address == 0) {
- // TODO: Differentiate between not found and error
+ const char* candidates[] = {"libpython", "python", "Python", NULL};
+ for (const char** candidate = candidates; *candidate; candidate++) {
PyErr_Clear();
- address = search_map_for_section(handle, "PyRuntime", "python");
+ address = search_map_for_section(handle, "PyRuntime", *candidate);
+ if (address != 0) {
+ break;
+ }
+ }
+ if (address == 0) {
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to find the PyRuntime section in process %d "
+ "on macOS platform (tried both libpython and python)",
+ handle->pid);
+ _PyErr_ChainExceptions1(exc);
}
#else
Py_UNREACHABLE();
@@ -716,6 +886,61 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
return address;
}
+#if defined(__linux__) && HAVE_PROCESS_VM_READV
+
+static int
+open_proc_mem_fd(proc_handle_t *handle)
+{
+ char mem_file_path[64];
+ sprintf(mem_file_path, "/proc/%d/mem", handle->pid);
+
+ handle->memfd = open(mem_file_path, O_RDWR);
+ if (handle->memfd == -1) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ _set_debug_exception_cause(PyExc_OSError,
+ "failed to open file %s: %s", mem_file_path, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+// Why is pwritev not guarded? Except on Android API level 23 (no longer
+// supported), HAVE_PROCESS_VM_READV is sufficient.
+static int
+read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
+{
+ if (handle->memfd == -1) {
+ if (open_proc_mem_fd(handle) < 0) {
+ return -1;
+ }
+ }
+
+ struct iovec local[1];
+ Py_ssize_t result = 0;
+ Py_ssize_t read_bytes = 0;
+
+ do {
+ local[0].iov_base = (char*)dst + result;
+ local[0].iov_len = len - result;
+ off_t offset = remote_address + result;
+
+ read_bytes = preadv(handle->memfd, local, 1, offset);
+ if (read_bytes < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ _set_debug_exception_cause(PyExc_OSError,
+ "preadv failed for PID %d at address 0x%lx "
+ "(size %zu, partial read %zd bytes): %s",
+ handle->pid, remote_address + result, len - result, result, strerror(errno));
+ return -1;
+ }
+
+ result += read_bytes;
+ } while ((size_t)read_bytes != local[0].iov_len);
+ return 0;
+}
+
+#endif // __linux__
+
// Platform-independent memory read function
static int
_Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
@@ -726,12 +951,20 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
do {
if (!ReadProcessMemory(handle->hProcess, (LPCVOID)(remote_address + result), (char*)dst + result, len - result, &read_bytes)) {
PyErr_SetFromWindowsErr(0);
+ DWORD error = GetLastError();
+ _set_debug_exception_cause(PyExc_OSError,
+ "ReadProcessMemory failed for PID %d at address 0x%lx "
+ "(size %zu, partial read %zu bytes): Windows error %lu",
+ handle->pid, remote_address + result, len - result, result, error);
return -1;
}
result += read_bytes;
} while (result < len);
return 0;
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
+ if (handle->memfd != -1) {
+ return read_remote_memory_fallback(handle, remote_address, len, dst);
+ }
struct iovec local[1];
struct iovec remote[1];
Py_ssize_t result = 0;
@@ -745,17 +978,24 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
if (read_bytes < 0) {
+ if (errno == ENOSYS) {
+ return read_remote_memory_fallback(handle, remote_address, len, dst);
+ }
PyErr_SetFromErrno(PyExc_OSError);
+ _set_debug_exception_cause(PyExc_OSError,
+ "process_vm_readv failed for PID %d at address 0x%lx "
+ "(size %zu, partial read %zd bytes): %s",
+ handle->pid, remote_address + result, len - result, result, strerror(errno));
return -1;
}
result += read_bytes;
} while ((size_t)read_bytes != local[0].iov_len);
return 0;
-#elif defined(__APPLE__) && TARGET_OS_OSX
+#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
Py_ssize_t result = -1;
kern_return_t kr = mach_vm_read_overwrite(
- pid_to_task(handle->pid),
+ handle->task,
(mach_vm_address_t)remote_address,
len,
(mach_vm_address_t)dst,
@@ -764,13 +1004,22 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
if (kr != KERN_SUCCESS) {
switch (kr) {
case KERN_PROTECTION_FAILURE:
- PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory");
+ PyErr_Format(PyExc_PermissionError,
+ "Memory protection failure reading from PID %d at address "
+ "0x%lx (size %zu): insufficient permissions",
+ handle->pid, remote_address, len);
break;
case KERN_INVALID_ARGUMENT:
- PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite");
+ PyErr_Format(PyExc_ValueError,
+ "Invalid argument to mach_vm_read_overwrite for PID %d at "
+ "address 0x%lx (size %zu)",
+ handle->pid, remote_address, len);
break;
default:
- PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory");
+ PyErr_Format(PyExc_RuntimeError,
+ "mach_vm_read_overwrite failed for PID %d at address 0x%lx "
+ "(size %zu): kern_return_t %d",
+ handle->pid, remote_address, len, kr);
}
return -1;
}
@@ -780,6 +1029,15 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
#endif
}
+UNUSED static int
+_Py_RemoteDebug_PagedReadRemoteMemory(proc_handle_t *handle,
+ uintptr_t addr,
+ size_t size,
+ void *out)
+{
+ return _Py_RemoteDebug_ReadRemoteMemory(handle, addr, size, out);
+}
+
static int
_Py_RemoteDebug_ReadDebugOffsets(
proc_handle_t *handle,
@@ -789,13 +1047,16 @@ _Py_RemoteDebug_ReadDebugOffsets(
*runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
if (!*runtime_start_address) {
if (!PyErr_Occurred()) {
- PyErr_SetString(
- PyExc_RuntimeError, "Failed to get PyRuntime address");
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to locate PyRuntime address for PID %d",
+ handle->pid);
}
+ _set_debug_exception_cause(PyExc_RuntimeError, "PyRuntime address lookup failed during debug offsets initialization");
return -1;
}
size_t size = sizeof(struct _Py_DebugOffsets);
if (0 != _Py_RemoteDebug_ReadRemoteMemory(handle, *runtime_start_address, size, debug_offsets)) {
+ _set_debug_exception_cause(PyExc_RuntimeError, "Failed to read debug offsets structure from remote process");
return -1;
}
return 0;
diff --git a/Python/remote_debugging.c b/Python/remote_debugging.c
index dd55b7812d4..7aee87ef05a 100644
--- a/Python/remote_debugging.c
+++ b/Python/remote_debugging.c
@@ -24,6 +24,39 @@ read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* ds
return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst);
}
+// Why is pwritev not guarded? Except on Android API level 23 (no longer
+// supported), HAVE_PROCESS_VM_READV is sufficient.
+#if defined(__linux__) && HAVE_PROCESS_VM_READV
+static int
+write_memory_fallback(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
+{
+ if (handle->memfd == -1) {
+ if (open_proc_mem_fd(handle) < 0) {
+ return -1;
+ }
+ }
+
+ struct iovec local[1];
+ Py_ssize_t result = 0;
+ Py_ssize_t written = 0;
+
+ do {
+ local[0].iov_base = (char*)src + result;
+ local[0].iov_len = len - result;
+ off_t offset = remote_address + result;
+
+ written = pwritev(handle->memfd, local, 1, offset);
+ if (written < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+
+ result += written;
+ } while ((size_t)written != local[0].iov_len);
+ return 0;
+}
+#endif // __linux__
+
static int
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
{
@@ -39,6 +72,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const
} while (result < len);
return 0;
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
+ if (handle->memfd != -1) {
+ return write_memory_fallback(handle, remote_address, len, src);
+ }
struct iovec local[1];
struct iovec remote[1];
Py_ssize_t result = 0;
@@ -52,6 +88,9 @@ write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const
written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
if (written < 0) {
+ if (errno == ENOSYS) {
+ return write_memory_fallback(handle, remote_address, len, src);
+ }
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
diff --git a/Python/specialize.c b/Python/specialize.c
index 59ec9a4cad6..fe8d04cf344 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -118,6 +118,7 @@ _Py_GetSpecializationStats(void) {
err += add_stat_dict(stats, LOAD_GLOBAL, "load_global");
err += add_stat_dict(stats, STORE_SUBSCR, "store_subscr");
err += add_stat_dict(stats, STORE_ATTR, "store_attr");
+ err += add_stat_dict(stats, JUMP_BACKWARD, "jump_backward");
err += add_stat_dict(stats, CALL, "call");
err += add_stat_dict(stats, CALL_KW, "call_kw");
err += add_stat_dict(stats, BINARY_OP, "binary_op");
@@ -2147,7 +2148,7 @@ specialize_c_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs)
}
/* len(o) */
PyInterpreterState *interp = _PyInterpreterState_GET();
- if (callable == interp->callable_cache.len) {
+ if (callable == interp->callable_cache.len && instr->op.arg == 1) {
specialize(instr, CALL_LEN);
return 0;
}
@@ -2158,7 +2159,7 @@ specialize_c_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs)
if (nargs == 2) {
/* isinstance(o1, o2) */
PyInterpreterState *interp = _PyInterpreterState_GET();
- if (callable == interp->callable_cache.isinstance) {
+ if (callable == interp->callable_cache.isinstance && instr->op.arg == 2) {
specialize(instr, CALL_ISINSTANCE);
return 0;
}
@@ -2904,53 +2905,57 @@ int
#endif // Py_STATS
Py_NO_INLINE void
-_Py_Specialize_ForIter(_PyStackRef iter, _Py_CODEUNIT *instr, int oparg)
+_Py_Specialize_ForIter(_PyStackRef iter, _PyStackRef null_or_index, _Py_CODEUNIT *instr, int oparg)
{
assert(ENABLE_SPECIALIZATION_FT);
assert(_PyOpcode_Caches[FOR_ITER] == INLINE_CACHE_ENTRIES_FOR_ITER);
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
PyTypeObject *tp = Py_TYPE(iter_o);
+
+ if (PyStackRef_IsNull(null_or_index)) {
#ifdef Py_GIL_DISABLED
- // Only specialize for uniquely referenced iterators, so that we know
- // they're only referenced by this one thread. This is more limiting
- // than we need (even `it = iter(mylist); for item in it:` won't get
- // specialized) but we don't have a way to check whether we're the only
- // _thread_ who has access to the object.
- if (!_PyObject_IsUniquelyReferenced(iter_o))
- goto failure;
-#endif
- if (tp == &PyListIter_Type) {
-#ifdef Py_GIL_DISABLED
- _PyListIterObject *it = (_PyListIterObject *)iter_o;
- if (!_Py_IsOwnedByCurrentThread((PyObject *)it->it_seq) &&
- !_PyObject_GC_IS_SHARED(it->it_seq)) {
- // Maybe this should just set GC_IS_SHARED in a critical
- // section, instead of leaving it to the first iteration?
+ // Only specialize for uniquely referenced iterators, so that we know
+ // they're only referenced by this one thread. This is more limiting
+ // than we need (even `it = iter(mylist); for item in it:` won't get
+ // specialized) but we don't have a way to check whether we're the only
+ // _thread_ who has access to the object.
+ if (!_PyObject_IsUniquelyReferenced(iter_o)) {
goto failure;
}
#endif
- specialize(instr, FOR_ITER_LIST);
- return;
- }
- else if (tp == &PyTupleIter_Type) {
- specialize(instr, FOR_ITER_TUPLE);
- return;
- }
- else if (tp == &PyRangeIter_Type) {
- specialize(instr, FOR_ITER_RANGE);
- return;
+ if (tp == &PyRangeIter_Type) {
+ specialize(instr, FOR_ITER_RANGE);
+ return;
+ }
+ else if (tp == &PyGen_Type && oparg <= SHRT_MAX) {
+ // Generators are very much not thread-safe, so don't worry about
+ // the specialization not being thread-safe.
+ assert(instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == END_FOR ||
+ instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == INSTRUMENTED_END_FOR
+ );
+ /* Don't specialize if PEP 523 is active */
+ if (_PyInterpreterState_GET()->eval_frame) {
+ goto failure;
+ }
+ specialize(instr, FOR_ITER_GEN);
+ return;
+ }
}
- else if (tp == &PyGen_Type && oparg <= SHRT_MAX) {
- // Generators are very much not thread-safe, so don't worry about
- // the specialization not being thread-safe.
- assert(instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == END_FOR ||
- instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == INSTRUMENTED_END_FOR
- );
- /* Don't specialize if PEP 523 is active */
- if (_PyInterpreterState_GET()->eval_frame)
- goto failure;
- specialize(instr, FOR_ITER_GEN);
- return;
+ else {
+ if (tp == &PyList_Type) {
+#ifdef Py_GIL_DISABLED
+ // Only specialize for lists owned by this thread or shared
+ if (!_Py_IsOwnedByCurrentThread(iter_o) && !_PyObject_GC_IS_SHARED(iter_o)) {
+ goto failure;
+ }
+#endif
+ specialize(instr, FOR_ITER_LIST);
+ return;
+ }
+ else if (tp == &PyTuple_Type) {
+ specialize(instr, FOR_ITER_TUPLE);
+ return;
+ }
}
failure:
SPECIALIZATION_FAIL(FOR_ITER,
diff --git a/Python/stackrefs.c b/Python/stackrefs.c
index 979a6b1c628..ecc0012ef17 100644
--- a/Python/stackrefs.c
+++ b/Python/stackrefs.c
@@ -1,4 +1,3 @@
-
#include "Python.h"
#include "pycore_object.h"
@@ -34,12 +33,14 @@ make_table_entry(PyObject *obj, const char *filename, int linenumber)
result->filename = filename;
result->linenumber = linenumber;
result->filename_borrow = NULL;
+ result->linenumber_borrow = 0;
return result;
}
PyObject *
_Py_stackref_get_object(_PyStackRef ref)
{
+ assert(!PyStackRef_IsError(ref));
if (ref.index == 0) {
return NULL;
}
@@ -64,6 +65,7 @@ PyStackRef_Is(_PyStackRef a, _PyStackRef b)
PyObject *
_Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber)
{
+ assert(!PyStackRef_IsError(ref));
PyInterpreterState *interp = PyInterpreterState_Get();
if (ref.index >= interp->next_stackref) {
_Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", (void *)ref.index, filename, linenumber);
@@ -128,6 +130,7 @@ _Py_stackref_create(PyObject *obj, const char *filename, int linenumber)
void
_Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber)
{
+ assert(!PyStackRef_IsError(ref));
if (ref.index < INITIAL_STACKREF_INDEX) {
return;
}
@@ -152,6 +155,7 @@ _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber
void
_Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref)
{
+ assert(!PyStackRef_IsError(ref));
assert(ref.index < INITIAL_STACKREF_INDEX);
TableEntry *entry = make_table_entry(obj, "builtin-object", 0);
if (entry == NULL) {
@@ -216,4 +220,12 @@ PyStackRef_IsNullOrInt(_PyStackRef ref)
return PyStackRef_IsNull(ref) || PyStackRef_IsTaggedInt(ref);
}
+_PyStackRef
+PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref)
+{
+ assert(ref.index <= INT_MAX - 2); // No overflow
+ return (_PyStackRef){ .index = ref.index + 2 };
+}
+
+
#endif
diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h
index fcef7419bd3..63e4599c31e 100644
--- a/Python/stdlib_module_names.h
+++ b/Python/stdlib_module_names.h
@@ -71,6 +71,7 @@ static const char* _Py_stdlib_module_names[] = {
"_pyrepl",
"_queue",
"_random",
+"_remote_debugging",
"_scproxy",
"_sha1",
"_sha2",
@@ -103,6 +104,7 @@ static const char* _Py_stdlib_module_names[] = {
"_winapi",
"_wmi",
"_zoneinfo",
+"_zstd",
"abc",
"annotationlib",
"antigravity",
@@ -243,9 +245,6 @@ static const char* _Py_stdlib_module_names[] = {
"socket",
"socketserver",
"sqlite3",
-"sre_compile",
-"sre_constants",
-"sre_parse",
"ssl",
"stat",
"statistics",
diff --git a/Python/symtable.c b/Python/symtable.c
index f633e281019..a3d0fff80d2 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -380,8 +380,8 @@ static void dump_symtable(PySTEntryObject* ste)
}
#endif
-#define DUPLICATE_ARGUMENT \
-"duplicate argument '%U' in function definition"
+#define DUPLICATE_PARAMETER \
+"duplicate parameter '%U' in function definition"
static struct symtable *
symtable_new(void)
@@ -1494,7 +1494,7 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s
}
if ((flag & DEF_PARAM) && (val & DEF_PARAM)) {
/* Is it better to use 'mangled' or 'name' here? */
- PyErr_Format(PyExc_SyntaxError, DUPLICATE_ARGUMENT, name);
+ PyErr_Format(PyExc_SyntaxError, DUPLICATE_PARAMETER, name);
SET_ERROR_LOCATION(st->st_filename, loc);
goto error;
}
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index e650444108e..ae6cf306735 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -76,12 +76,12 @@ module sys
PyObject *
-_PySys_GetRequiredAttr(PyObject *name)
+PySys_GetAttr(PyObject *name)
{
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
- "attribute name must be string, not '%.200s'",
- Py_TYPE(name)->tp_name);
+ "attribute name must be string, not '%T'",
+ name);
return NULL;
}
PyThreadState *tstate = _PyThreadState_GET();
@@ -98,7 +98,7 @@ _PySys_GetRequiredAttr(PyObject *name)
}
PyObject *
-_PySys_GetRequiredAttrString(const char *name)
+PySys_GetAttrString(const char *name)
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *sysdict = tstate->interp->sysdict;
@@ -114,12 +114,12 @@ _PySys_GetRequiredAttrString(const char *name)
}
int
-_PySys_GetOptionalAttr(PyObject *name, PyObject **value)
+PySys_GetOptionalAttr(PyObject *name, PyObject **value)
{
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
- "attribute name must be string, not '%.200s'",
- Py_TYPE(name)->tp_name);
+ "attribute name must be string, not '%T'",
+ name);
*value = NULL;
return -1;
}
@@ -133,7 +133,7 @@ _PySys_GetOptionalAttr(PyObject *name, PyObject **value)
}
int
-_PySys_GetOptionalAttrString(const char *name, PyObject **value)
+PySys_GetOptionalAttrString(const char *name, PyObject **value)
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *sysdict = tstate->interp->sysdict;
@@ -773,7 +773,7 @@ sys_displayhook(PyObject *module, PyObject *o)
}
if (PyObject_SetAttr(builtins, _Py_LATIN1_CHR('_'), Py_None) != 0)
return NULL;
- outf = _PySys_GetRequiredAttr(&_Py_ID(stdout));
+ outf = PySys_GetAttr(&_Py_ID(stdout));
if (outf == NULL) {
return NULL;
}
@@ -1643,6 +1643,7 @@ static PyObject *
_sys_getwindowsversion_from_kernel32(void)
{
#ifndef MS_WINDOWS_DESKTOP
+ PyErr_SetString(PyExc_OSError, "cannot read version info on this platform");
return NULL;
#else
HANDLE hKernel32;
@@ -1670,6 +1671,9 @@ _sys_getwindowsversion_from_kernel32(void)
!GetFileVersionInfoW(kernel32_path, 0, verblock_size, verblock) ||
!VerQueryValueW(verblock, L"", (LPVOID)&ffi, &ffi_len)) {
PyErr_SetFromWindowsErr(0);
+ if (verblock) {
+ PyMem_RawFree(verblock);
+ }
return NULL;
}
@@ -2448,26 +2452,63 @@ sys_is_remote_debug_enabled_impl(PyObject *module)
#endif
}
+/*[clinic input]
+sys.remote_exec
+
+ pid: int
+ script: object
+
+Executes a file containing Python code in a given remote Python process.
+
+This function returns immediately, and the code will be executed by the
+target process's main thread at the next available opportunity, similarly
+to how signals are handled. There is no interface to determine when the
+code has been executed. The caller is responsible for making sure that
+the file still exists whenever the remote process tries to read it and that
+it hasn't been overwritten.
+
+The remote process must be running a CPython interpreter of the same major
+and minor version as the local process. If either the local or remote
+interpreter is pre-release (alpha, beta, or release candidate) then the
+local and remote interpreters must be the same exact version.
+
+Args:
+ pid (int): The process ID of the target Python process.
+ script (str|bytes): The path to a file containing
+ the Python code to be executed.
+[clinic start generated code]*/
+
static PyObject *
-sys_remote_exec_unicode_path(PyObject *module, int pid, PyObject *script)
+sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
+/*[clinic end generated code: output=7d94c56afe4a52c0 input=39908ca2c5fe1eb0]*/
{
- const char *debugger_script_path = PyUnicode_AsUTF8(script);
- if (debugger_script_path == NULL) {
+ PyObject *path;
+ const char *debugger_script_path;
+
+ if (PyUnicode_FSConverter(script, &path) == 0) {
+ return NULL;
+ }
+
+ if (PySys_Audit("sys.remote_exec", "iO", pid, script) < 0) {
return NULL;
}
+ debugger_script_path = PyBytes_AS_STRING(path);
#ifdef MS_WINDOWS
+ PyObject *unicode_path;
+ if (PyUnicode_FSDecoder(path, &unicode_path) < 0) {
+ goto error;
+ }
// Use UTF-16 (wide char) version of the path for permission checks
- wchar_t *debugger_script_path_w = PyUnicode_AsWideCharString(script, NULL);
+ wchar_t *debugger_script_path_w = PyUnicode_AsWideCharString(unicode_path, NULL);
+ Py_DECREF(unicode_path);
if (debugger_script_path_w == NULL) {
- return NULL;
+ goto error;
}
-
- // Check file attributes using wide character version (W) instead of ANSI (A)
DWORD attr = GetFileAttributesW(debugger_script_path_w);
- PyMem_Free(debugger_script_path_w);
if (attr == INVALID_FILE_ATTRIBUTES) {
DWORD err = GetLastError();
+ PyMem_Free(debugger_script_path_w);
if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
PyErr_SetString(PyExc_FileNotFoundError, "Script file does not exist");
}
@@ -2475,11 +2516,12 @@ sys_remote_exec_unicode_path(PyObject *module, int pid, PyObject *script)
PyErr_SetString(PyExc_PermissionError, "Script file cannot be read");
}
else {
- PyErr_SetFromWindowsErr(0);
+ PyErr_SetFromWindowsErr(err);
}
- return NULL;
+ goto error;
}
-#else
+ PyMem_Free(debugger_script_path_w);
+#else // MS_WINDOWS
if (access(debugger_script_path, F_OK | R_OK) != 0) {
switch (errno) {
case ENOENT:
@@ -2491,54 +2533,19 @@ sys_remote_exec_unicode_path(PyObject *module, int pid, PyObject *script)
default:
PyErr_SetFromErrno(PyExc_OSError);
}
- return NULL;
+ goto error;
}
-#endif
-
+#endif // MS_WINDOWS
if (_PySysRemoteDebug_SendExec(pid, 0, debugger_script_path) < 0) {
- return NULL;
+ goto error;
}
+ Py_DECREF(path);
Py_RETURN_NONE;
-}
-
-/*[clinic input]
-sys.remote_exec
-
- pid: int
- script: object
-
-Executes a file containing Python code in a given remote Python process.
-
-This function returns immediately, and the code will be executed by the
-target process's main thread at the next available opportunity, similarly
-to how signals are handled. There is no interface to determine when the
-code has been executed. The caller is responsible for making sure that
-the file still exists whenever the remote process tries to read it and that
-it hasn't been overwritten.
-The remote process must be running a CPython interpreter of the same major
-and minor version as the local process. If either the local or remote
-interpreter is pre-release (alpha, beta, or release candidate) then the
-local and remote interpreters must be the same exact version.
-
-Args:
- pid (int): The process ID of the target Python process.
- script (str|bytes): The path to a file containing
- the Python code to be executed.
-[clinic start generated code]*/
-
-static PyObject *
-sys_remote_exec_impl(PyObject *module, int pid, PyObject *script)
-/*[clinic end generated code: output=7d94c56afe4a52c0 input=39908ca2c5fe1eb0]*/
-{
- PyObject *ret = NULL;
- PyObject *path;
- if (PyUnicode_FSDecoder(script, &path)) {
- ret = sys_remote_exec_unicode_path(module, pid, path);
- Py_DECREF(path);
- }
- return ret;
+error:
+ Py_DECREF(path);
+ return NULL;
}
@@ -3003,7 +3010,7 @@ static PyObject *
get_warnoptions(PyThreadState *tstate)
{
PyObject *warnoptions;
- if (_PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) {
return NULL;
}
if (warnoptions == NULL || !PyList_Check(warnoptions)) {
@@ -3040,7 +3047,7 @@ PySys_ResetWarnOptions(void)
}
PyObject *warnoptions;
- if (_PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) {
PyErr_Clear();
return;
}
@@ -3104,7 +3111,7 @@ PyAPI_FUNC(int)
PySys_HasWarnOptions(void)
{
PyObject *warnoptions;
- if (_PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(warnoptions), &warnoptions) < 0) {
PyErr_Clear();
return 0;
}
@@ -3118,7 +3125,7 @@ static PyObject *
get_xoptions(PyThreadState *tstate)
{
PyObject *xoptions;
- if (_PySys_GetOptionalAttr(&_Py_ID(_xoptions), &xoptions) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(_xoptions), &xoptions) < 0) {
return NULL;
}
if (xoptions == NULL || !PyDict_Check(xoptions)) {
@@ -3371,7 +3378,7 @@ sys_set_flag(PyObject *flags, Py_ssize_t pos, PyObject *value)
int
_PySys_SetFlagObj(Py_ssize_t pos, PyObject *value)
{
- PyObject *flags = _PySys_GetRequiredAttrString("flags");
+ PyObject *flags = PySys_GetAttrString("flags");
if (flags == NULL) {
return -1;
}
@@ -3600,6 +3607,18 @@ make_impl_info(PyObject *version_info)
goto error;
#endif
+ // PEP-734
+#if defined(__wasi__) || defined(__EMSCRIPTEN__)
+ // It is not enabled on WASM builds just yet
+ value = Py_False;
+#else
+ value = Py_True;
+#endif
+ res = PyDict_SetItemString(impl_info, "supports_isolated_interpreters", value);
+ if (res < 0) {
+ goto error;
+ }
+
/* dict ready */
ns = _PyNamespace_New(impl_info);
@@ -3933,7 +3952,7 @@ _PySys_UpdateConfig(PyThreadState *tstate)
#undef COPY_WSTR
// sys.flags
- PyObject *flags = _PySys_GetRequiredAttrString("flags");
+ PyObject *flags = PySys_GetAttrString("flags");
if (flags == NULL) {
return -1;
}
@@ -3986,6 +4005,71 @@ error:
PyObject *_Py_CreateMonitoringObject(void);
+/*[clinic input]
+module _jit
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=10952f74d7bbd972]*/
+
+PyDoc_STRVAR(_jit_doc, "Utilities for observing just-in-time compilation.");
+
+/*[clinic input]
+_jit.is_available -> bool
+Return True if the current Python executable supports JIT compilation, and False otherwise.
+[clinic start generated code]*/
+
+static int
+_jit_is_available_impl(PyObject *module)
+/*[clinic end generated code: output=6849a9cd2ff4aac9 input=03add84aa8347cf1]*/
+{
+ (void)module;
+#ifdef _Py_TIER2
+ return true;
+#else
+ return false;
+#endif
+}
+
+/*[clinic input]
+_jit.is_enabled -> bool
+Return True if JIT compilation is enabled for the current Python process (implies sys._jit.is_available()), and False otherwise.
+[clinic start generated code]*/
+
+static int
+_jit_is_enabled_impl(PyObject *module)
+/*[clinic end generated code: output=55865f8de993fe42 input=02439394da8e873f]*/
+{
+ (void)module;
+ return _PyInterpreterState_GET()->jit;
+}
+
+/*[clinic input]
+_jit.is_active -> bool
+Return True if the topmost Python frame is currently executing JIT code (implies sys._jit.is_enabled()), and False otherwise.
+[clinic start generated code]*/
+
+static int
+_jit_is_active_impl(PyObject *module)
+/*[clinic end generated code: output=7facca06b10064d4 input=be2fcd8a269d9b72]*/
+{
+ (void)module;
+ return _PyThreadState_GET()->current_executor != NULL;
+}
+
+static PyMethodDef _jit_methods[] = {
+ _JIT_IS_AVAILABLE_METHODDEF
+ _JIT_IS_ENABLED_METHODDEF
+ _JIT_IS_ACTIVE_METHODDEF
+ {NULL}
+};
+
+static struct PyModuleDef _jit_module = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "sys._jit",
+ .m_doc = _jit_doc,
+ .m_size = -1,
+ .m_methods = _jit_methods,
+};
+
/* Create sys module without all attributes.
_PySys_UpdateConfig() should be called later to add remaining attributes. */
PyStatus
@@ -4047,6 +4131,16 @@ _PySys_Create(PyThreadState *tstate, PyObject **sysmod_p)
goto error;
}
+ PyObject *_jit = _PyModule_CreateInitialized(&_jit_module, PYTHON_API_VERSION);
+ if (_jit == NULL) {
+ goto error;
+ }
+ err = PyDict_SetItemString(sysdict, "_jit", _jit);
+ Py_DECREF(_jit);
+ if (err) {
+ goto error;
+ }
+
assert(!_PyErr_Occurred(tstate));
*sysmod_p = sysmod;
@@ -4174,7 +4268,7 @@ PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath)
}
PyObject *sys_path;
- if (_PySys_GetOptionalAttr(&_Py_ID(path), &sys_path) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(path), &sys_path) < 0) {
Py_FatalError("can't get sys.path");
}
else if (sys_path != NULL) {
@@ -4270,7 +4364,7 @@ sys_write(PyObject *key, FILE *fp, const char *format, va_list va)
PyObject *exc = _PyErr_GetRaisedException(tstate);
written = PyOS_vsnprintf(buffer, sizeof(buffer), format, va);
- file = _PySys_GetRequiredAttr(key);
+ file = PySys_GetAttr(key);
if (sys_pyfile_write(buffer, file) != 0) {
_PyErr_Clear(tstate);
fputs(buffer, fp);
@@ -4314,7 +4408,7 @@ sys_format(PyObject *key, FILE *fp, const char *format, va_list va)
PyObject *exc = _PyErr_GetRaisedException(tstate);
message = PyUnicode_FromFormatV(format, va);
if (message != NULL) {
- file = _PySys_GetRequiredAttr(key);
+ file = PySys_GetAttr(key);
if (sys_pyfile_write_unicode(message, file) != 0) {
_PyErr_Clear(tstate);
utf8 = PyUnicode_AsUTF8(message);
diff --git a/Python/thread.c b/Python/thread.c
index 4ff5f11a348..18c4af7f634 100644
--- a/Python/thread.c
+++ b/Python/thread.c
@@ -39,7 +39,8 @@
const long long PY_TIMEOUT_MAX = PY_TIMEOUT_MAX_VALUE;
-static void PyThread__init_thread(void); /* Forward */
+/* Forward declaration */
+static void PyThread__init_thread(void);
#define initialized _PyRuntime.threads.initialized
@@ -71,6 +72,79 @@ PyThread_init_thread(void)
#endif
+/*
+ * Lock support.
+ */
+
+PyThread_type_lock
+PyThread_allocate_lock(void)
+{
+ if (!initialized) {
+ PyThread_init_thread();
+ }
+
+ PyMutex *lock = (PyMutex *)PyMem_RawMalloc(sizeof(PyMutex));
+ if (lock) {
+ *lock = (PyMutex){0};
+ }
+
+ return (PyThread_type_lock)lock;
+}
+
+void
+PyThread_free_lock(PyThread_type_lock lock)
+{
+ PyMem_RawFree(lock);
+}
+
+PyLockStatus
+PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
+ int intr_flag)
+{
+ PyTime_t timeout; // relative timeout
+ if (microseconds >= 0) {
+ // bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
+ // overflow to the caller, so clamp the timeout to
+ // [PyTime_MIN, PyTime_MAX].
+ //
+ // PyTime_MAX nanoseconds is around 292.3 years.
+ //
+ // _thread.Lock.acquire() and _thread.RLock.acquire() raise an
+ // OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
+ timeout = _PyTime_FromMicrosecondsClamp(microseconds);
+ }
+ else {
+ timeout = -1;
+ }
+
+ _PyLockFlags flags = _Py_LOCK_DONT_DETACH;
+ if (intr_flag) {
+ flags |= _PY_FAIL_IF_INTERRUPTED;
+ }
+
+ return _PyMutex_LockTimed((PyMutex *)lock, timeout, flags);
+}
+
+void
+PyThread_release_lock(PyThread_type_lock lock)
+{
+ PyMutex_Unlock((PyMutex *)lock);
+}
+
+int
+_PyThread_at_fork_reinit(PyThread_type_lock *lock)
+{
+ _PyMutex_at_fork_reinit((PyMutex *)lock);
+ return 0;
+}
+
+int
+PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
+{
+ return PyThread_acquire_lock_timed(lock, waitflag ? -1 : 0, /*intr_flag=*/0);
+}
+
+
/* return the current thread stack size */
size_t
PyThread_get_stacksize(void)
@@ -261,11 +335,7 @@ PyThread_GetInfo(void)
#ifdef HAVE_PTHREAD_STUBS
value = Py_NewRef(Py_None);
#elif defined(_POSIX_THREADS)
-#ifdef USE_SEMAPHORES
- value = PyUnicode_FromString("semaphore");
-#else
- value = PyUnicode_FromString("mutex+cond");
-#endif
+ value = PyUnicode_FromString("pymutex");
if (value == NULL) {
Py_DECREF(threadinfo);
return NULL;
diff --git a/Python/thread_nt.h b/Python/thread_nt.h
index e078b98be3c..9a29d14ef67 100644
--- a/Python/thread_nt.h
+++ b/Python/thread_nt.h
@@ -300,98 +300,6 @@ PyThread_hang_thread(void)
}
}
-/*
- * Lock support. It has to be implemented as semaphores.
- * I [Dag] tried to implement it with mutex but I could find a way to
- * tell whether a thread already own the lock or not.
- */
-PyThread_type_lock
-PyThread_allocate_lock(void)
-{
- PNRMUTEX mutex;
-
- if (!initialized)
- PyThread_init_thread();
-
- mutex = AllocNonRecursiveMutex() ;
-
- PyThread_type_lock aLock = (PyThread_type_lock) mutex;
- assert(aLock);
-
- return aLock;
-}
-
-void
-PyThread_free_lock(PyThread_type_lock aLock)
-{
- FreeNonRecursiveMutex(aLock) ;
-}
-
-// WaitForSingleObject() accepts timeout in milliseconds in the range
-// [0; 0xFFFFFFFE] (DWORD type). INFINITE value (0xFFFFFFFF) means no
-// timeout. 0xFFFFFFFE milliseconds is around 49.7 days.
-const DWORD TIMEOUT_MS_MAX = 0xFFFFFFFE;
-
-/*
- * Return 1 on success if the lock was acquired
- *
- * and 0 if the lock was not acquired. This means a 0 is returned
- * if the lock has already been acquired by this thread!
- */
-PyLockStatus
-PyThread_acquire_lock_timed(PyThread_type_lock aLock,
- PY_TIMEOUT_T microseconds, int intr_flag)
-{
- assert(aLock);
-
- /* Fow now, intr_flag does nothing on Windows, and lock acquires are
- * uninterruptible. */
- PyLockStatus success;
- PY_TIMEOUT_T milliseconds;
-
- if (microseconds >= 0) {
- milliseconds = microseconds / 1000;
- // Round milliseconds away from zero
- if (microseconds % 1000 > 0) {
- milliseconds++;
- }
- if (milliseconds > (PY_TIMEOUT_T)TIMEOUT_MS_MAX) {
- // bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
- // overflow to the caller, so clamp the timeout to
- // [0, TIMEOUT_MS_MAX] milliseconds.
- //
- // _thread.Lock.acquire() and _thread.RLock.acquire() raise an
- // OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
- milliseconds = TIMEOUT_MS_MAX;
- }
- assert(milliseconds != INFINITE);
- }
- else {
- milliseconds = INFINITE;
- }
-
- if (EnterNonRecursiveMutex((PNRMUTEX)aLock,
- (DWORD)milliseconds) == WAIT_OBJECT_0) {
- success = PY_LOCK_ACQUIRED;
- }
- else {
- success = PY_LOCK_FAILURE;
- }
-
- return success;
-}
-int
-PyThread_acquire_lock(PyThread_type_lock aLock, int waitflag)
-{
- return PyThread_acquire_lock_timed(aLock, waitflag ? -1 : 0, 0);
-}
-
-void
-PyThread_release_lock(PyThread_type_lock aLock)
-{
- assert(aLock);
- (void)LeaveNonRecursiveMutex((PNRMUTEX) aLock);
-}
/* minimum/maximum thread stack sizes supported */
#define THREAD_MIN_STACKSIZE 0x8000 /* 32 KiB */
diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h
index da405824244..13992f95723 100644
--- a/Python/thread_pthread.h
+++ b/Python/thread_pthread.h
@@ -99,16 +99,6 @@
#undef HAVE_SEM_CLOCKWAIT
#endif
-/* Whether or not to use semaphores directly rather than emulating them with
- * mutexes and condition variables:
- */
-#if (defined(_POSIX_SEMAPHORES) && !defined(HAVE_BROKEN_POSIX_SEMAPHORES) && \
- (defined(HAVE_SEM_TIMEDWAIT) || defined(HAVE_SEM_CLOCKWAIT)))
-# define USE_SEMAPHORES
-#else
-# undef USE_SEMAPHORES
-#endif
-
/* On platforms that don't use standard POSIX threads pthread_sigmask()
* isn't present. DEC threads uses sigprocmask() instead as do most
@@ -442,388 +432,6 @@ PyThread_hang_thread(void)
}
}
-#ifdef USE_SEMAPHORES
-
-/*
- * Lock support.
- */
-
-PyThread_type_lock
-PyThread_allocate_lock(void)
-{
- sem_t *lock;
- int status, error = 0;
-
- if (!initialized)
- PyThread_init_thread();
-
- lock = (sem_t *)PyMem_RawMalloc(sizeof(sem_t));
-
- if (lock) {
- status = sem_init(lock,0,1);
- CHECK_STATUS("sem_init");
-
- if (error) {
- PyMem_RawFree((void *)lock);
- lock = NULL;
- }
- }
-
- return (PyThread_type_lock)lock;
-}
-
-void
-PyThread_free_lock(PyThread_type_lock lock)
-{
- sem_t *thelock = (sem_t *)lock;
- int status, error = 0;
-
- (void) error; /* silence unused-but-set-variable warning */
-
- if (!thelock)
- return;
-
- status = sem_destroy(thelock);
- CHECK_STATUS("sem_destroy");
-
- PyMem_RawFree((void *)thelock);
-}
-
-/*
- * As of February 2002, Cygwin thread implementations mistakenly report error
- * codes in the return value of the sem_ calls (like the pthread_ functions).
- * Correct implementations return -1 and put the code in errno. This supports
- * either.
- */
-static int
-fix_status(int status)
-{
- return (status == -1) ? errno : status;
-}
-
-PyLockStatus
-PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
- int intr_flag)
-{
- PyLockStatus success;
- sem_t *thelock = (sem_t *)lock;
- int status, error = 0;
-
- (void) error; /* silence unused-but-set-variable warning */
-
- PyTime_t timeout; // relative timeout
- if (microseconds >= 0) {
- // bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
- // overflow to the caller, so clamp the timeout to
- // [PyTime_MIN, PyTime_MAX].
- //
- // PyTime_MAX nanoseconds is around 292.3 years.
- //
- // _thread.Lock.acquire() and _thread.RLock.acquire() raise an
- // OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
- timeout = _PyTime_FromMicrosecondsClamp(microseconds);
- }
- else {
- timeout = -1;
- }
-
-#ifdef HAVE_SEM_CLOCKWAIT
- struct timespec abs_timeout;
- // Local scope for deadline
- {
- PyTime_t now;
- // silently ignore error: cannot report error to the caller
- (void)PyTime_MonotonicRaw(&now);
- PyTime_t deadline = _PyTime_Add(now, timeout);
- _PyTime_AsTimespec_clamp(deadline, &abs_timeout);
- }
-#else
- PyTime_t deadline = 0;
- if (timeout > 0 && !intr_flag) {
- deadline = _PyDeadline_Init(timeout);
- }
-#endif
-
- while (1) {
- if (timeout > 0) {
-#ifdef HAVE_SEM_CLOCKWAIT
- status = fix_status(sem_clockwait(thelock, CLOCK_MONOTONIC,
- &abs_timeout));
-#else
- PyTime_t now;
- // silently ignore error: cannot report error to the caller
- (void)PyTime_TimeRaw(&now);
- PyTime_t abs_time = _PyTime_Add(now, timeout);
-
- struct timespec ts;
- _PyTime_AsTimespec_clamp(abs_time, &ts);
- status = fix_status(sem_timedwait(thelock, &ts));
-#endif
- }
- else if (timeout == 0) {
- status = fix_status(sem_trywait(thelock));
- }
- else {
- status = fix_status(sem_wait(thelock));
- }
-
- /* Retry if interrupted by a signal, unless the caller wants to be
- notified. */
- if (intr_flag || status != EINTR) {
- break;
- }
-
- // sem_clockwait() uses an absolute timeout, there is no need
- // to recompute the relative timeout.
-#ifndef HAVE_SEM_CLOCKWAIT
- if (timeout > 0) {
- /* wait interrupted by a signal (EINTR): recompute the timeout */
- timeout = _PyDeadline_Get(deadline);
- if (timeout < 0) {
- status = ETIMEDOUT;
- break;
- }
- }
-#endif
- }
-
- /* Don't check the status if we're stopping because of an interrupt. */
- if (!(intr_flag && status == EINTR)) {
- if (timeout > 0) {
- if (status != ETIMEDOUT) {
-#ifdef HAVE_SEM_CLOCKWAIT
- CHECK_STATUS("sem_clockwait");
-#else
- CHECK_STATUS("sem_timedwait");
-#endif
- }
- }
- else if (timeout == 0) {
- if (status != EAGAIN) {
- CHECK_STATUS("sem_trywait");
- }
- }
- else {
- CHECK_STATUS("sem_wait");
- }
- }
-
- if (status == 0) {
- success = PY_LOCK_ACQUIRED;
- } else if (intr_flag && status == EINTR) {
- success = PY_LOCK_INTR;
- } else {
- success = PY_LOCK_FAILURE;
- }
-
- return success;
-}
-
-void
-PyThread_release_lock(PyThread_type_lock lock)
-{
- sem_t *thelock = (sem_t *)lock;
- int status, error = 0;
-
- (void) error; /* silence unused-but-set-variable warning */
-
- status = sem_post(thelock);
- CHECK_STATUS("sem_post");
-}
-
-#else /* USE_SEMAPHORES */
-
-/*
- * Lock support.
- */
-PyThread_type_lock
-PyThread_allocate_lock(void)
-{
- pthread_lock *lock;
- int status, error = 0;
-
- if (!initialized)
- PyThread_init_thread();
-
- lock = (pthread_lock *) PyMem_RawCalloc(1, sizeof(pthread_lock));
- if (lock) {
- lock->locked = 0;
-
- status = pthread_mutex_init(&lock->mut, NULL);
- CHECK_STATUS_PTHREAD("pthread_mutex_init");
- /* Mark the pthread mutex underlying a Python mutex as
- pure happens-before. We can't simply mark the
- Python-level mutex as a mutex because it can be
- acquired and released in different threads, which
- will cause errors. */
- _Py_ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(&lock->mut);
-
- status = _PyThread_cond_init(&lock->lock_released);
- CHECK_STATUS_PTHREAD("pthread_cond_init");
-
- if (error) {
- PyMem_RawFree((void *)lock);
- lock = 0;
- }
- }
-
- return (PyThread_type_lock) lock;
-}
-
-void
-PyThread_free_lock(PyThread_type_lock lock)
-{
- pthread_lock *thelock = (pthread_lock *)lock;
- int status, error = 0;
-
- (void) error; /* silence unused-but-set-variable warning */
-
- /* some pthread-like implementations tie the mutex to the cond
- * and must have the cond destroyed first.
- */
- status = pthread_cond_destroy( &thelock->lock_released );
- CHECK_STATUS_PTHREAD("pthread_cond_destroy");
-
- status = pthread_mutex_destroy( &thelock->mut );
- CHECK_STATUS_PTHREAD("pthread_mutex_destroy");
-
- PyMem_RawFree((void *)thelock);
-}
-
-PyLockStatus
-PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
- int intr_flag)
-{
- PyLockStatus success = PY_LOCK_FAILURE;
- pthread_lock *thelock = (pthread_lock *)lock;
- int status, error = 0;
-
- if (microseconds == 0) {
- status = pthread_mutex_trylock( &thelock->mut );
- if (status != EBUSY) {
- CHECK_STATUS_PTHREAD("pthread_mutex_trylock[1]");
- }
- }
- else {
- status = pthread_mutex_lock( &thelock->mut );
- CHECK_STATUS_PTHREAD("pthread_mutex_lock[1]");
- }
- if (status != 0) {
- goto done;
- }
-
- if (thelock->locked == 0) {
- success = PY_LOCK_ACQUIRED;
- goto unlock;
- }
- if (microseconds == 0) {
- goto unlock;
- }
-
- struct timespec abs_timeout;
- if (microseconds > 0) {
- _PyThread_cond_after(microseconds, &abs_timeout);
- }
- // Continue trying until we get the lock
-
- // mut must be locked by me -- part of the condition protocol
- while (1) {
- if (microseconds > 0) {
- status = pthread_cond_timedwait(&thelock->lock_released,
- &thelock->mut, &abs_timeout);
- if (status == 1) {
- break;
- }
- if (status == ETIMEDOUT) {
- break;
- }
- CHECK_STATUS_PTHREAD("pthread_cond_timedwait");
- }
- else {
- status = pthread_cond_wait(
- &thelock->lock_released,
- &thelock->mut);
- CHECK_STATUS_PTHREAD("pthread_cond_wait");
- }
-
- if (intr_flag && status == 0 && thelock->locked) {
- // We were woken up, but didn't get the lock. We probably received
- // a signal. Return PY_LOCK_INTR to allow the caller to handle
- // it and retry.
- success = PY_LOCK_INTR;
- break;
- }
-
- if (status == 0 && !thelock->locked) {
- success = PY_LOCK_ACQUIRED;
- break;
- }
-
- // Wait got interrupted by a signal: retry
- }
-
-unlock:
- if (success == PY_LOCK_ACQUIRED) {
- thelock->locked = 1;
- }
- status = pthread_mutex_unlock( &thelock->mut );
- CHECK_STATUS_PTHREAD("pthread_mutex_unlock[1]");
-
-done:
- if (error) {
- success = PY_LOCK_FAILURE;
- }
- return success;
-}
-
-void
-PyThread_release_lock(PyThread_type_lock lock)
-{
- pthread_lock *thelock = (pthread_lock *)lock;
- int status, error = 0;
-
- (void) error; /* silence unused-but-set-variable warning */
-
- status = pthread_mutex_lock( &thelock->mut );
- CHECK_STATUS_PTHREAD("pthread_mutex_lock[3]");
-
- thelock->locked = 0;
-
- /* wake up someone (anyone, if any) waiting on the lock */
- status = pthread_cond_signal( &thelock->lock_released );
- CHECK_STATUS_PTHREAD("pthread_cond_signal");
-
- status = pthread_mutex_unlock( &thelock->mut );
- CHECK_STATUS_PTHREAD("pthread_mutex_unlock[3]");
-}
-
-#endif /* USE_SEMAPHORES */
-
-int
-_PyThread_at_fork_reinit(PyThread_type_lock *lock)
-{
- PyThread_type_lock new_lock = PyThread_allocate_lock();
- if (new_lock == NULL) {
- return -1;
- }
-
- /* bpo-6721, bpo-40089: The old lock can be in an inconsistent state.
- fork() can be called in the middle of an operation on the lock done by
- another thread. So don't call PyThread_free_lock(*lock).
-
- Leak memory on purpose. Don't release the memory either since the
- address of a mutex is relevant. Putting two mutexes at the same address
- can lead to problems. */
-
- *lock = new_lock;
- return 0;
-}
-
-int
-PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
-{
- return PyThread_acquire_lock_timed(lock, waitflag ? -1 : 0, /*intr_flag=*/0);
-}
/* set the thread stack size.
* Return 0 if size is valid, -1 if size is invalid,
diff --git a/Python/traceback.c b/Python/traceback.c
index c06cb1a5908..4f674eaf557 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -9,7 +9,6 @@
#include "pycore_interpframe.h" // _PyFrame_GetCode()
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
#include "pycore_pystate.h" // _PyThreadState_GET()
-#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
#include "pycore_traceback.h" // EXCEPTION_TB_HEADER
#include "frameobject.h" // PyFrame_New()
@@ -399,7 +398,7 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject *
taillen = strlen(tail);
PyThreadState *tstate = _PyThreadState_GET();
- if (_PySys_GetOptionalAttr(&_Py_ID(path), &syspath) < 0) {
+ if (PySys_GetOptionalAttr(&_Py_ID(path), &syspath) < 0) {
PyErr_Clear();
goto error;
}
@@ -777,7 +776,7 @@ _PyTraceBack_Print(PyObject *v, const char *header, PyObject *f)
PyErr_BadInternalCall();
return -1;
}
- if (_PySys_GetOptionalAttrString("tracebacklimit", &limitv) < 0) {
+ if (PySys_GetOptionalAttrString("tracebacklimit", &limitv) < 0) {
return -1;
}
else if (limitv != NULL && PyLong_Check(limitv)) {
diff --git a/README.rst b/README.rst
index 6492acda3d7..baea5e0978d 100644
--- a/README.rst
+++ b/README.rst
@@ -1,4 +1,4 @@
-This is Python version 3.14.0 alpha 7
+This is Python version 3.15.0 alpha 0
=====================================
.. image:: https://github.com/python/cpython/actions/workflows/build.yml/badge.svg?branch=main&event=push
@@ -135,8 +135,8 @@ libraries for additional performance gains.
What's New
----------
-We have a comprehensive overview of the changes in the `What's New in Python
-3.14 <https://docs.python.org/3.14/whatsnew/3.14.html>`_ document. For a more
+We have a comprehensive overview of the changes in the `What's new in Python
+3.15 <https://docs.python.org/3.15/whatsnew/3.15.html>`_ document. For a more
detailed change log, read `Misc/NEWS
<https://github.com/python/cpython/tree/main/Misc/NEWS.d>`_, but a full
accounting of changes can only be gleaned from the `commit history
@@ -149,7 +149,7 @@ entitled "Installing multiple versions".
Documentation
-------------
-`Documentation for Python 3.14 <https://docs.python.org/3.14/>`_ is online,
+`Documentation for Python 3.15 <https://docs.python.org/3.15/>`_ is online,
updated daily.
It can also be downloaded in many formats for faster access. The documentation
@@ -200,15 +200,15 @@ intend to install multiple versions using the same prefix you must decide which
version (if any) is your "primary" version. Install that version using
``make install``. Install all other versions using ``make altinstall``.
-For example, if you want to install Python 2.7, 3.6, and 3.14 with 3.14 being the
-primary version, you would execute ``make install`` in your 3.14 build directory
+For example, if you want to install Python 2.7, 3.6, and 3.15 with 3.15 being the
+primary version, you would execute ``make install`` in your 3.15 build directory
and ``make altinstall`` in the others.
Release Schedule
----------------
-See `PEP 745 <https://peps.python.org/pep-0745/>`__ for Python 3.14 release details.
+See `PEP 790 <https://peps.python.org/pep-0790/>`__ for Python 3.15 release details.
Copyright and License Information
diff --git a/Tools/build/.ruff.toml b/Tools/build/.ruff.toml
index fa7689d45db..dcbf2936290 100644
--- a/Tools/build/.ruff.toml
+++ b/Tools/build/.ruff.toml
@@ -1,7 +1,7 @@
extend = "../../.ruff.toml" # Inherit the project-wide settings
[per-file-target-version]
-"deepfreeze.py" = "py310"
+"deepfreeze.py" = "py311" # requires `code.co_exceptiontable`
"stable_abi.py" = "py311" # requires 'tomllib'
[format]
diff --git a/Tools/build/compute-changes.py b/Tools/build/compute-changes.py
index b3be7df2dba..b5993d29b92 100644
--- a/Tools/build/compute-changes.py
+++ b/Tools/build/compute-changes.py
@@ -56,12 +56,10 @@ class Outputs:
def compute_changes() -> None:
- target_branch, head_branch = git_branches()
- if target_branch and head_branch:
+ target_branch, head_ref = git_refs()
+ if os.environ.get("GITHUB_EVENT_NAME", "") == "pull_request":
# Getting changed files only makes sense on a pull request
- files = get_changed_files(
- f"origin/{target_branch}", f"origin/{head_branch}"
- )
+ files = get_changed_files(target_branch, head_ref)
outputs = process_changed_files(files)
else:
# Otherwise, just run the tests
@@ -89,15 +87,15 @@ def compute_changes() -> None:
write_github_output(outputs)
-def git_branches() -> tuple[str, str]:
- target_branch = os.environ.get("GITHUB_BASE_REF", "")
- target_branch = target_branch.removeprefix("refs/heads/")
- print(f"target branch: {target_branch!r}")
+def git_refs() -> tuple[str, str]:
+ target_ref = os.environ.get("CCF_TARGET_REF", "")
+ target_ref = target_ref.removeprefix("refs/heads/")
+ print(f"target ref: {target_ref!r}")
- head_branch = os.environ.get("GITHUB_HEAD_REF", "")
- head_branch = head_branch.removeprefix("refs/heads/")
- print(f"head branch: {head_branch!r}")
- return target_branch, head_branch
+ head_ref = os.environ.get("CCF_HEAD_REF", "")
+ head_ref = head_ref.removeprefix("refs/heads/")
+ print(f"head ref: {head_ref!r}")
+ return f"origin/{target_ref}", head_ref
def get_changed_files(
diff --git a/Tools/build/deepfreeze.py b/Tools/build/deepfreeze.py
index 23f58447937..2b9f03aebb6 100644
--- a/Tools/build/deepfreeze.py
+++ b/Tools/build/deepfreeze.py
@@ -2,9 +2,12 @@
The script may be executed by _bootstrap_python interpreter.
Shared library extension modules are not available in that case.
-On Windows, and in cross-compilation cases, it is executed
-by Python 3.10, and 3.11 features are not available.
+Requires 3.11+ to be executed,
+because relies on `code.co_qualname` and `code.co_exceptiontable`.
"""
+
+from __future__ import annotations
+
import argparse
import builtins
import collections
@@ -13,10 +16,14 @@ import os
import re
import time
import types
-from typing import TextIO
import umarshal
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+ from typing import Any, TextIO
+
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
verbose = False
@@ -45,8 +52,8 @@ CO_FAST_FREE = 0x80
next_code_version = 1
-def get_localsplus(code: types.CodeType):
- a = collections.defaultdict(int)
+def get_localsplus(code: types.CodeType) -> tuple[tuple[str, ...], bytes]:
+ a: collections.defaultdict[str, int] = collections.defaultdict(int)
for name in code.co_varnames:
a[name] |= CO_FAST_LOCAL
for name in code.co_cellvars:
@@ -136,7 +143,7 @@ class Printer:
return identifiers, strings
@contextlib.contextmanager
- def indent(self) -> None:
+ def indent(self) -> Iterator[None]:
save_level = self.level
try:
self.level += 1
@@ -148,7 +155,7 @@ class Printer:
self.file.writelines((" "*self.level, arg, "\n"))
@contextlib.contextmanager
- def block(self, prefix: str, suffix: str = "") -> None:
+ def block(self, prefix: str, suffix: str = "") -> Iterator[None]:
self.write(prefix + " {")
with self.indent():
yield
@@ -250,9 +257,17 @@ class Printer:
co_names = self.generate(name + "_names", code.co_names)
co_filename = self.generate(name + "_filename", code.co_filename)
co_name = self.generate(name + "_name", code.co_name)
- co_qualname = self.generate(name + "_qualname", code.co_qualname)
co_linetable = self.generate(name + "_linetable", code.co_linetable)
- co_exceptiontable = self.generate(name + "_exceptiontable", code.co_exceptiontable)
+ # We use 3.10 for type checking, but this module requires 3.11
+ # TODO: bump python version for this script.
+ co_qualname = self.generate(
+ name + "_qualname",
+ code.co_qualname, # type: ignore[attr-defined]
+ )
+ co_exceptiontable = self.generate(
+ name + "_exceptiontable",
+ code.co_exceptiontable, # type: ignore[attr-defined]
+ )
# These fields are not directly accessible
localsplusnames, localspluskinds = get_localsplus(code)
co_localsplusnames = self.generate(name + "_localsplusnames", localsplusnames)
@@ -379,13 +394,13 @@ class Printer:
self.write(f".cval = {{ {z.real}, {z.imag} }},")
return f"&{name}.ob_base"
- def generate_frozenset(self, name: str, fs: frozenset[object]) -> str:
+ def generate_frozenset(self, name: str, fs: frozenset[Any]) -> str:
try:
- fs = sorted(fs)
+ fs_sorted = sorted(fs)
except TypeError:
# frozen set with incompatible types, fallback to repr()
- fs = sorted(fs, key=repr)
- ret = self.generate_tuple(name, tuple(fs))
+ fs_sorted = sorted(fs, key=repr)
+ ret = self.generate_tuple(name, tuple(fs_sorted))
self.write("// TODO: The above tuple should be a frozenset")
return ret
@@ -402,7 +417,7 @@ class Printer:
# print(f"Cache hit {key!r:.40}: {self.cache[key]!r:.40}")
return self.cache[key]
self.misses += 1
- if isinstance(obj, (types.CodeType, umarshal.Code)) :
+ if isinstance(obj, types.CodeType) :
val = self.generate_code(name, obj)
elif isinstance(obj, tuple):
val = self.generate_tuple(name, obj)
@@ -458,7 +473,7 @@ def decode_frozen_data(source: str) -> types.CodeType:
if re.match(FROZEN_DATA_LINE, line):
values.extend([int(x) for x in line.split(",") if x.strip()])
data = bytes(values)
- return umarshal.loads(data)
+ return umarshal.loads(data) # type: ignore[no-any-return]
def generate(args: list[str], output: TextIO) -> None:
@@ -494,12 +509,12 @@ group.add_argument('args', nargs="*", default=(),
help="Input file and module name (required) in file:modname format")
@contextlib.contextmanager
-def report_time(label: str):
- t0 = time.time()
+def report_time(label: str) -> Iterator[None]:
+ t0 = time.perf_counter()
try:
yield
finally:
- t1 = time.time()
+ t1 = time.perf_counter()
if verbose:
print(f"{label}: {t1-t0:.3f} sec")
diff --git a/Tools/build/generate-build-details.py b/Tools/build/generate-build-details.py
index 0da6c2948d6..8cd23e2f54f 100644
--- a/Tools/build/generate-build-details.py
+++ b/Tools/build/generate-build-details.py
@@ -3,6 +3,8 @@
# Script initially imported from:
# https://github.com/FFY00/python-instrospection/blob/main/python_introspection/scripts/generate-build-details.py
+from __future__ import annotations
+
import argparse
import collections
import importlib.machinery
@@ -11,19 +13,23 @@ import os
import sys
import sysconfig
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from typing import Any
+
-def version_info_to_dict(obj): # (object) -> dict[str, Any]
+def version_info_to_dict(obj: sys._version_info) -> dict[str, Any]:
field_names = ('major', 'minor', 'micro', 'releaselevel', 'serial')
return {field: getattr(obj, field) for field in field_names}
-def get_dict_key(container, key): # (dict[str, Any], str) -> dict[str, Any]
+def get_dict_key(container: dict[str, Any], key: str) -> dict[str, Any]:
for part in key.split('.'):
container = container[part]
return container
-def generate_data(schema_version):
+def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
"""Generate the build-details.json data (PEP 739).
:param schema_version: The schema version of the data we want to generate.
@@ -32,7 +38,9 @@ def generate_data(schema_version):
if schema_version != '1.0':
raise ValueError(f'Unsupported schema_version: {schema_version}')
- data = collections.defaultdict(lambda: collections.defaultdict(dict))
+ data: collections.defaultdict[str, Any] = collections.defaultdict(
+ lambda: collections.defaultdict(dict),
+ )
data['schema_version'] = schema_version
@@ -67,7 +75,7 @@ def generate_data(schema_version):
PY3LIBRARY = sysconfig.get_config_var('PY3LIBRARY')
LIBPYTHON = sysconfig.get_config_var('LIBPYTHON')
LIBPC = sysconfig.get_config_var('LIBPC')
- INCLUDEDIR = sysconfig.get_config_var('INCLUDEDIR')
+ INCLUDEPY = sysconfig.get_config_var('INCLUDEPY')
if os.name == 'posix':
# On POSIX, LIBRARY is always the static library, while LDLIBRARY is the
@@ -115,14 +123,14 @@ def generate_data(schema_version):
if has_static_library:
data['libpython']['static'] = os.path.join(LIBDIR, LIBRARY)
- data['c_api']['include'] = INCLUDEDIR
+ data['c_api']['headers'] = INCLUDEPY
if LIBPC:
data['c_api']['pkgconfig_path'] = LIBPC
return data
-def make_paths_relative(data, config_path=None): # (dict[str, Any], str | None) -> None
+def make_paths_relative(data: dict[str, Any], config_path: str | None = None) -> None:
# Make base_prefix relative to the config_path directory
if config_path:
data['base_prefix'] = os.path.relpath(data['base_prefix'], os.path.dirname(config_path))
@@ -152,7 +160,7 @@ def make_paths_relative(data, config_path=None): # (dict[str, Any], str | None)
container[child] = new_path
-def main(): # () -> None
+def main() -> None:
parser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument('location')
parser.add_argument(
diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py
index db01426e972..968397728b2 100644
--- a/Tools/build/generate_sbom.py
+++ b/Tools/build/generate_sbom.py
@@ -4,10 +4,13 @@ import glob
import hashlib
import json
import os
+import random
import re
import subprocess
import sys
+import time
import typing
+import urllib.error
import urllib.request
from pathlib import Path, PurePosixPath, PureWindowsPath
@@ -161,6 +164,23 @@ def get_externals() -> list[str]:
return externals
+def download_with_retries(download_location: str,
+ max_retries: int = 7,
+ base_delay: float = 2.25,
+ max_jitter: float = 1.0) -> typing.Any:
+ """Download a file with exponential backoff retry."""
+ for attempt in range(max_retries + 1):
+ try:
+ resp = urllib.request.urlopen(download_location)
+ except (urllib.error.URLError, ConnectionError) as ex:
+ if attempt == max_retries:
+ msg = f"Download from {download_location} failed."
+ raise OSError(msg) from ex
+ time.sleep(base_delay**attempt + random.uniform(0, max_jitter))
+ else:
+ return resp
+
+
def check_sbom_packages(sbom_data: dict[str, typing.Any]) -> None:
"""Make a bunch of assertions about the SBOM package data to ensure it's consistent."""
@@ -175,7 +195,7 @@ def check_sbom_packages(sbom_data: dict[str, typing.Any]) -> None:
# and that the download URL is valid.
if "checksums" not in package or "CI" in os.environ:
download_location = package["downloadLocation"]
- resp = urllib.request.urlopen(download_location)
+ resp = download_with_retries(download_location)
error_if(resp.status != 200, f"Couldn't access URL: {download_location}'")
package["checksums"] = [{
diff --git a/Tools/build/generate_stdlib_module_names.py b/Tools/build/generate_stdlib_module_names.py
index 9873890837f..88414cdbb37 100644
--- a/Tools/build/generate_stdlib_module_names.py
+++ b/Tools/build/generate_stdlib_module_names.py
@@ -34,7 +34,6 @@ IGNORE = {
'_testlimitedcapi',
'_testmultiphase',
'_testsinglephase',
- '_testexternalinspection',
'_xxtestfuzz',
'idlelib.idle_test',
'test',
diff --git a/Tools/build/mypy.ini b/Tools/build/mypy.ini
index 06224163884..123dc895f90 100644
--- a/Tools/build/mypy.ini
+++ b/Tools/build/mypy.ini
@@ -1,7 +1,16 @@
[mypy]
+
+# Please, when adding new files here, also add them to:
+# .github/workflows/mypy.yml
files =
Tools/build/compute-changes.py,
- Tools/build/generate_sbom.py
+ Tools/build/deepfreeze.py,
+ Tools/build/generate-build-details.py,
+ Tools/build/generate_sbom.py,
+ Tools/build/verify_ensurepip_wheels.py,
+ Tools/build/update_file.py,
+ Tools/build/umarshal.py
+
pretty = True
# Make sure Python can still be built
@@ -10,6 +19,8 @@ python_version = 3.10
# ...And be strict:
strict = True
+strict_bytes = True
+local_partial_types = True
extra_checks = True
enable_error_code = ignore-without-code,redundant-expr,truthy-bool,possibly-undefined
warn_unreachable = True
diff --git a/Tools/build/umarshal.py b/Tools/build/umarshal.py
index 679fa7caf9f..865cffc2440 100644
--- a/Tools/build/umarshal.py
+++ b/Tools/build/umarshal.py
@@ -145,12 +145,12 @@ class Reader:
def r_float_bin(self) -> float:
buf = self.r_string(8)
import struct # Lazy import to avoid breaking UNIX build
- return struct.unpack("d", buf)[0]
+ return struct.unpack("d", buf)[0] # type: ignore[no-any-return]
def r_float_str(self) -> float:
n = self.r_byte()
buf = self.r_string(n)
- return ast.literal_eval(buf.decode("ascii"))
+ return ast.literal_eval(buf.decode("ascii")) # type: ignore[no-any-return]
def r_ref_reserve(self, flag: int) -> int:
if flag:
@@ -306,7 +306,7 @@ def loads(data: bytes) -> Any:
return r.r_object()
-def main():
+def main() -> None:
# Test
import marshal
import pprint
@@ -314,8 +314,9 @@ def main():
data = marshal.dumps(sample)
retval = loads(data)
assert retval == sample, retval
- sample = main.__code__
- data = marshal.dumps(sample)
+
+ sample2 = main.__code__
+ data = marshal.dumps(sample2)
retval = loads(data)
assert isinstance(retval, Code), retval
pprint.pprint(retval.__dict__)
diff --git a/Tools/build/update_file.py b/Tools/build/update_file.py
index b4182c1d0cb..b4a5fb6e778 100644
--- a/Tools/build/update_file.py
+++ b/Tools/build/update_file.py
@@ -6,14 +6,27 @@ This avoids wholesale rebuilds when a code (re)generation phase does not
actually change the in-tree generated code.
"""
+from __future__ import annotations
+
import contextlib
import os
import os.path
import sys
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ import typing
+ from collections.abc import Iterator
+ from io import TextIOWrapper
+
+ _Outcome: typing.TypeAlias = typing.Literal['created', 'updated', 'same']
+
@contextlib.contextmanager
-def updating_file_with_tmpfile(filename, tmpfile=None):
+def updating_file_with_tmpfile(
+ filename: str,
+ tmpfile: str | None = None,
+) -> Iterator[tuple[TextIOWrapper, TextIOWrapper]]:
"""A context manager for updating a file via a temp file.
The context manager provides two open files: the source file open
@@ -46,13 +59,18 @@ def updating_file_with_tmpfile(filename, tmpfile=None):
update_file_with_tmpfile(filename, tmpfile)
-def update_file_with_tmpfile(filename, tmpfile, *, create=False):
+def update_file_with_tmpfile(
+ filename: str,
+ tmpfile: str,
+ *,
+ create: bool = False,
+) -> _Outcome:
try:
targetfile = open(filename, 'rb')
except FileNotFoundError:
if not create:
raise # re-raise
- outcome = 'created'
+ outcome: _Outcome = 'created'
os.replace(tmpfile, filename)
else:
with targetfile:
diff --git a/Tools/build/verify_ensurepip_wheels.py b/Tools/build/verify_ensurepip_wheels.py
index a37da2f7075..46c42916d93 100755
--- a/Tools/build/verify_ensurepip_wheels.py
+++ b/Tools/build/verify_ensurepip_wheels.py
@@ -20,13 +20,13 @@ ENSURE_PIP_INIT_PY_TEXT = (ENSURE_PIP_ROOT / "__init__.py").read_text(encoding="
GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true"
-def print_notice(file_path: str, message: str) -> None:
+def print_notice(file_path: str | Path, message: str) -> None:
if GITHUB_ACTIONS:
message = f"::notice file={file_path}::{message}"
print(message, end="\n\n")
-def print_error(file_path: str, message: str) -> None:
+def print_error(file_path: str | Path, message: str) -> None:
if GITHUB_ACTIONS:
message = f"::error file={file_path}::{message}"
print(message, end="\n\n")
@@ -67,6 +67,7 @@ def verify_wheel(package_name: str) -> bool:
return False
release_files = json.loads(raw_text)["releases"][package_version]
+ expected_digest = ""
for release_info in release_files:
if package_path.name != release_info["filename"]:
continue
@@ -95,6 +96,7 @@ def verify_wheel(package_name: str) -> bool:
return True
+
if __name__ == "__main__":
exit_status = int(not verify_wheel("pip"))
raise SystemExit(exit_status)
diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv
index a33619b1b34..15b18f5286b 100644
--- a/Tools/c-analyzer/cpython/ignored.tsv
+++ b/Tools/c-analyzer/cpython/ignored.tsv
@@ -191,6 +191,7 @@ Python/pyfpe.c - PyFPE_counter -
Python/import.c - pkgcontext -
Python/pystate.c - _Py_tss_tstate -
+Python/pystate.c - _Py_tss_gilstate -
##-----------------------
## should be const
@@ -349,7 +350,6 @@ Objects/unicodeobject.c unicode_translate_call_errorhandler argparse -
Parser/parser.c - reserved_keywords -
Parser/parser.c - soft_keywords -
Parser/lexer/lexer.c - type_comment_prefix -
-Python/ast_opt.c fold_unaryop ops -
Python/ceval.c - _PyEval_BinaryOps -
Python/ceval.c - _Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS -
Python/codecs.c - Py_hexdigits -
@@ -748,6 +748,7 @@ Modules/expat/xmlrole.c - error -
## other
Modules/_io/_iomodule.c - _PyIO_Module -
Modules/_sqlite/module.c - _sqlite3module -
+Modules/_zstd/_zstdmodule.c - _zstdmodule -
Modules/clinic/md5module.c.h _md5_md5 _keywords -
Modules/clinic/grpmodule.c.h grp_getgrgid _keywords -
Modules/clinic/grpmodule.c.h grp_getgrnam _keywords -
diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py
index 2b3a90c7db9..6466d2615cd 100644
--- a/Tools/cases_generator/analyzer.py
+++ b/Tools/cases_generator/analyzer.py
@@ -135,15 +135,13 @@ class Flush:
@dataclass
class StackItem:
name: str
- type: str | None
size: str
peek: bool = False
used: bool = False
def __str__(self) -> str:
size = f"[{self.size}]" if self.size else ""
- type = "" if self.type is None else f"{self.type} "
- return f"{type}{self.name}{size} {self.peek}"
+ return f"{self.name}{size} {self.peek}"
def is_array(self) -> bool:
return self.size != ""
@@ -182,7 +180,7 @@ class Uop:
properties: Properties
_size: int = -1
implicitly_created: bool = False
- replicated = 0
+ replicated = range(0)
replicates: "Uop | None" = None
# Size of the instruction(s), only set for uops containing the INSTRUCTION_SIZE macro
instruction_size: int | None = None
@@ -345,7 +343,7 @@ def override_error(
def convert_stack_item(
item: parser.StackEffect, replace_op_arg_1: str | None
) -> StackItem:
- return StackItem(item.name, item.type, item.size)
+ return StackItem(item.name, item.size)
def check_unused(stack: list[StackItem], input_names: dict[str, lexer.Token]) -> None:
"Unused items cannot be on the stack above used, non-peek items"
@@ -585,7 +583,7 @@ NON_ESCAPING_FUNCTIONS = (
"PyStackRef_CLOSE_SPECIALIZED",
"PyStackRef_DUP",
"PyStackRef_False",
- "PyStackRef_FromPyObjectImmortal",
+ "PyStackRef_FromPyObjectBorrow",
"PyStackRef_FromPyObjectNew",
"PyStackRef_FromPyObjectSteal",
"PyStackRef_IsExactly",
@@ -598,6 +596,7 @@ NON_ESCAPING_FUNCTIONS = (
"PyStackRef_IsNull",
"PyStackRef_MakeHeapSafe",
"PyStackRef_None",
+ "PyStackRef_RefcountOnObject",
"PyStackRef_TYPE",
"PyStackRef_True",
"PyTuple_GET_ITEM",
@@ -637,6 +636,10 @@ NON_ESCAPING_FUNCTIONS = (
"_PyLong_IsNegative",
"_PyLong_IsNonNegativeCompact",
"_PyLong_IsZero",
+ "_PyLong_BothAreCompact",
+ "_PyCompactLong_Add",
+ "_PyCompactLong_Multiply",
+ "_PyCompactLong_Subtract",
"_PyManagedDictPointer_IsValues",
"_PyObject_GC_IS_SHARED",
"_PyObject_GC_IS_TRACKED",
@@ -679,8 +682,16 @@ NON_ESCAPING_FUNCTIONS = (
"PyStackRef_IsTaggedInt",
"PyStackRef_TagInt",
"PyStackRef_UntagInt",
+ "PyStackRef_IncrementTaggedIntNoOverflow",
+ "PyStackRef_IsNullOrInt",
+ "PyStackRef_IsError",
+ "PyStackRef_IsValid",
+ "PyStackRef_Wrap",
+ "PyStackRef_Unwrap",
+ "_PyLong_CheckExactAndCompact",
)
+
def check_escaping_calls(instr: parser.CodeDef, escapes: dict[SimpleStmt, EscapingCall]) -> None:
error: lexer.Token | None = None
calls = {e.call for e in escapes.values()}
@@ -732,7 +743,7 @@ def find_escaping_api_calls(instr: parser.CodeDef) -> dict[SimpleStmt, EscapingC
continue
#if not tkn.text.startswith(("Py", "_Py", "monitor")):
# continue
- if tkn.text.startswith(("sym_", "optimize_")):
+ if tkn.text.startswith(("sym_", "optimize_", "PyJitRef")):
# Optimize functions
continue
if tkn.text.endswith("Check"):
@@ -806,7 +817,7 @@ def stack_effect_only_peeks(instr: parser.InstDef) -> bool:
if len(stack_inputs) == 0:
return False
return all(
- (s.name == other.name and s.type == other.type and s.size == other.size)
+ (s.name == other.name and s.size == other.size)
for s, other in zip(stack_inputs, instr.outputs)
)
@@ -832,7 +843,7 @@ def compute_properties(op: parser.CodeDef) -> Properties:
)
error_with_pop = has_error_with_pop(op)
error_without_pop = has_error_without_pop(op)
- escapes = bool(escaping_calls)
+ escapes = bool(escaping_calls) or variable_used(op, "DECREF_INPUTS")
pure = False if isinstance(op, parser.LabelDef) else "pure" in op.annotations
no_save_ip = False if isinstance(op, parser.LabelDef) else "no_save_ip" in op.annotations
return Properties(
@@ -859,6 +870,28 @@ def compute_properties(op: parser.CodeDef) -> Properties:
needs_prev=variable_used(op, "prev_instr"),
)
+def expand(items: list[StackItem], oparg: int) -> list[StackItem]:
+ # Only replace array item with scalar if no more than one item is an array
+ index = -1
+ for i, item in enumerate(items):
+ if "oparg" in item.size:
+ if index >= 0:
+ return items
+ index = i
+ if index < 0:
+ return items
+ try:
+ count = int(eval(items[index].size.replace("oparg", str(oparg))))
+ except ValueError:
+ return items
+ return items[:index] + [
+ StackItem(items[index].name + f"_{i}", "", items[index].peek, items[index].used) for i in range(count)
+ ] + items[index+1:]
+
+def scalarize_stack(stack: StackEffect, oparg: int) -> StackEffect:
+ stack.inputs = expand(stack.inputs, oparg)
+ stack.outputs = expand(stack.outputs, oparg)
+ return stack
def make_uop(
name: str,
@@ -878,20 +911,26 @@ def make_uop(
)
for anno in op.annotations:
if anno.startswith("replicate"):
- result.replicated = int(anno[10:-1])
+ text = anno[10:-1]
+ start, stop = text.split(":")
+ result.replicated = range(int(start), int(stop))
break
else:
return result
- for oparg in range(result.replicated):
+ for oparg in result.replicated:
name_x = name + "_" + str(oparg)
properties = compute_properties(op)
properties.oparg = False
- properties.const_oparg = oparg
+ stack = analyze_stack(op)
+ if not variable_used(op, "oparg"):
+ stack = scalarize_stack(stack, oparg)
+ else:
+ properties.const_oparg = oparg
rep = Uop(
name=name_x,
context=op.context,
annotations=op.annotations,
- stack=analyze_stack(op),
+ stack=stack,
caches=analyze_caches(inputs),
local_stores=find_variable_stores(op),
body=op.block,
diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py
index 9e60d219a71..4c210fbf8d2 100644
--- a/Tools/cases_generator/generators_common.py
+++ b/Tools/cases_generator/generators_common.py
@@ -56,9 +56,7 @@ def root_relative_path(filename: str) -> str:
def type_and_null(var: StackItem) -> tuple[str, str]:
- if var.type:
- return var.type, "NULL"
- elif var.is_array():
+ if var.is_array():
return "_PyStackRef *", "NULL"
else:
return "_PyStackRef", "PyStackRef_NULL"
@@ -108,8 +106,9 @@ class Emitter:
out: CWriter
labels: dict[str, Label]
_replacers: dict[str, ReplacementFunctionType]
+ cannot_escape: bool
- def __init__(self, out: CWriter, labels: dict[str, Label]):
+ def __init__(self, out: CWriter, labels: dict[str, Label], cannot_escape: bool = False):
self._replacers = {
"EXIT_IF": self.exit_if,
"DEOPT_IF": self.deopt_if,
@@ -129,6 +128,7 @@ class Emitter:
}
self.out = out
self.labels = labels
+ self.cannot_escape = cannot_escape
def dispatch(
self,
@@ -140,6 +140,7 @@ class Emitter:
) -> bool:
if storage.spilled:
raise analysis_error("stack_pointer needs reloading before dispatch", tkn)
+ storage.stack.flush(self.out)
self.emit(tkn)
return False
@@ -239,7 +240,8 @@ class Emitter:
next(tkn_iter)
self._print_storage("DECREF_INPUTS", storage)
try:
- storage.close_inputs(self.out)
+ if not self.cannot_escape:
+ storage.close_inputs(self.out)
except StackError as ex:
raise analysis_error(ex.args[0], tkn)
except Exception as ex:
@@ -477,7 +479,7 @@ class Emitter:
reachable = True
tkn = stmt.contents[-1]
try:
- if stmt in uop.properties.escaping_calls:
+ if stmt in uop.properties.escaping_calls and not self.cannot_escape:
escape = uop.properties.escaping_calls[stmt]
if escape.kills is not None:
self.stackref_kill(escape.kills, storage, True)
@@ -514,7 +516,7 @@ class Emitter:
self.out.emit(tkn)
else:
self.out.emit(tkn)
- if stmt in uop.properties.escaping_calls:
+ if stmt in uop.properties.escaping_calls and not self.cannot_escape:
self.emit_reload(storage)
return reachable, None, storage
except StackError as ex:
diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md
index 1ee4306f3ea..72020133738 100644
--- a/Tools/cases_generator/interpreter_definition.md
+++ b/Tools/cases_generator/interpreter_definition.md
@@ -81,7 +81,7 @@ and a piece of C code describing its semantics:
(definition | family | pseudo)+
definition:
- "inst" "(" NAME ["," stack_effect] ")" "{" C-code "}"
+ "inst" "(" NAME "," stack_effect ")" "{" C-code "}"
|
"op" "(" NAME "," stack_effect ")" "{" C-code "}"
|
diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py
index 620e4b6f1f4..0bcdc5395dc 100644
--- a/Tools/cases_generator/opcode_metadata_generator.py
+++ b/Tools/cases_generator/opcode_metadata_generator.py
@@ -157,6 +157,13 @@ def generate_deopt_table(analysis: Analysis, out: CWriter) -> None:
if inst.family is not None:
deopt = inst.family.name
deopts.append((inst.name, deopt))
+ defined = set(analysis.opmap.values())
+ for i in range(256):
+ if i not in defined:
+ deopts.append((f'{i}', f'{i}'))
+
+ assert len(deopts) == 256
+ assert len(set(x[0] for x in deopts)) == 256
for name, deopt in sorted(deopts):
out.emit(f"[{name}] = {deopt},\n")
out.emit("};\n\n")
@@ -235,14 +242,10 @@ def generate_expansion_table(analysis: Analysis, out: CWriter) -> None:
assert name2 in analysis.instructions, f"{name2} doesn't match any instr"
instr1 = analysis.instructions[name1]
instr2 = analysis.instructions[name2]
- assert (
- len(instr1.parts) == 1
- ), f"{name1} is not a good superinstruction part"
- assert (
- len(instr2.parts) == 1
- ), f"{name2} is not a good superinstruction part"
- expansions.append((instr1.parts[0].name, "OPARG_TOP", 0))
- expansions.append((instr2.parts[0].name, "OPARG_BOTTOM", 0))
+ for part in instr1.parts:
+ expansions.append((part.name, "OPARG_TOP", 0))
+ for part in instr2.parts:
+ expansions.append((part.name, "OPARG_BOTTOM", 0))
elif not is_viable_expansion(inst):
continue
else:
diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py
index 7a32275347e..81ae534bdda 100644
--- a/Tools/cases_generator/optimizer_generator.py
+++ b/Tools/cases_generator/optimizer_generator.py
@@ -12,6 +12,8 @@ from analyzer import (
analyze_files,
StackItem,
analysis_error,
+ CodeSection,
+ Label,
)
from generators_common import (
DEFAULT_INPUT,
@@ -19,6 +21,7 @@ from generators_common import (
write_header,
Emitter,
TokenIterator,
+ always_true,
)
from cwriter import CWriter
from typing import TextIO
@@ -30,17 +33,54 @@ DEFAULT_ABSTRACT_INPUT = (ROOT / "Python/optimizer_bytecodes.c").absolute().as_p
def validate_uop(override: Uop, uop: Uop) -> None:
- # To do
- pass
+ """
+ Check that the overridden uop (defined in 'optimizer_bytecodes.c')
+ has the same stack effects as the original uop (defined in 'bytecodes.c').
+
+ Ensure that:
+ - The number of inputs and outputs is the same.
+ - The names of the inputs and outputs are the same
+ (except for 'unused' which is ignored).
+ - The sizes of the inputs and outputs are the same.
+ """
+ for stack_effect in ('inputs', 'outputs'):
+ orig_effects = getattr(uop.stack, stack_effect)
+ new_effects = getattr(override.stack, stack_effect)
+
+ if len(orig_effects) != len(new_effects):
+ msg = (
+ f"{uop.name}: Must have the same number of {stack_effect} "
+ "in bytecodes.c and optimizer_bytecodes.c "
+ f"({len(orig_effects)} != {len(new_effects)})"
+ )
+ raise analysis_error(msg, override.body.open)
+
+ for orig, new in zip(orig_effects, new_effects, strict=True):
+ if orig.name != new.name and orig.name != "unused" and new.name != "unused":
+ msg = (
+ f"{uop.name}: {stack_effect.capitalize()} must have "
+ "equal names in bytecodes.c and optimizer_bytecodes.c "
+ f"({orig.name} != {new.name})"
+ )
+ raise analysis_error(msg, override.body.open)
+
+ if orig.size != new.size:
+ msg = (
+ f"{uop.name}: {stack_effect.capitalize()} must have "
+ "equal sizes in bytecodes.c and optimizer_bytecodes.c "
+ f"({orig.size!r} != {new.size!r})"
+ )
+ raise analysis_error(msg, override.body.open)
def type_name(var: StackItem) -> str:
if var.is_array():
- return f"JitOptSymbol **"
- if var.type:
- return var.type
- return f"JitOptSymbol *"
+ return "JitOptRef *"
+ return "JitOptRef "
+def stackref_type_name(var: StackItem) -> str:
+ assert not var.is_array(), "Unsafe to convert a symbol to an array-like StackRef."
+ return "_PyStackRef "
def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None:
variables = {"unused"}
@@ -101,6 +141,12 @@ def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None:
class OptimizerEmitter(Emitter):
+ def __init__(self, out: CWriter, labels: dict[str, Label], original_uop: Uop, stack: Stack):
+ super().__init__(out, labels)
+ self._replacers["REPLACE_OPCODE_IF_EVALUATES_PURE"] = self.replace_opcode_if_evaluates_pure
+ self.original_uop = original_uop
+ self.stack = stack
+
def emit_save(self, storage: Storage) -> None:
storage.flush(self.out)
@@ -111,6 +157,186 @@ class OptimizerEmitter(Emitter):
self.out.emit(goto)
self.out.emit(label)
+ def replace_opcode_if_evaluates_pure(
+ self,
+ tkn: Token,
+ tkn_iter: TokenIterator,
+ uop: CodeSection,
+ storage: Storage,
+ inst: Instruction | None,
+ ) -> bool:
+ assert isinstance(uop, Uop)
+ input_identifiers = []
+ for token in tkn_iter:
+ if token.kind == "IDENTIFIER":
+ input_identifiers.append(token)
+ if token.kind == "SEMI":
+ break
+
+ if len(input_identifiers) == 0:
+ raise analysis_error(
+ "To evaluate an operation as pure, it must have at least 1 input",
+ tkn
+ )
+ # Check that the input identifiers belong to the uop's
+ # input stack effect
+ uop_stack_effect_input_identifers = {inp.name for inp in uop.stack.inputs}
+ for input_tkn in input_identifiers:
+ if input_tkn.text not in uop_stack_effect_input_identifers:
+ raise analysis_error(f"{input_tkn.text} referenced in "
+ f"REPLACE_OPCODE_IF_EVALUATES_PURE but does not "
+ f"exist in the base uop's input stack effects",
+ input_tkn)
+ input_identifiers_as_str = {tkn.text for tkn in input_identifiers}
+ used_stack_inputs = [inp for inp in uop.stack.inputs if inp.name in input_identifiers_as_str]
+ assert len(used_stack_inputs) > 0
+ emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, self.stack.copy())
+ emitter.emit("if (\n")
+ for inp in used_stack_inputs[:-1]:
+ emitter.emit(f"sym_is_safe_const(ctx, {inp.name}) &&\n")
+ emitter.emit(f"sym_is_safe_const(ctx, {used_stack_inputs[-1].name})\n")
+ emitter.emit(') {\n')
+ # Declare variables, before they are shadowed.
+ for inp in used_stack_inputs:
+ if inp.used:
+ emitter.emit(f"{type_name(inp)}{inp.name}_sym = {inp.name};\n")
+ # Shadow the symbolic variables with stackrefs.
+ for inp in used_stack_inputs:
+ if inp.is_array():
+ raise analysis_error("Pure evaluation cannot take array-like inputs.", tkn)
+ if inp.used:
+ emitter.emit(f"{stackref_type_name(inp)}{inp.name} = sym_get_const_as_stackref(ctx, {inp.name}_sym);\n")
+ # Rename all output variables to stackref variant.
+ for outp in self.original_uop.stack.outputs:
+ if outp.is_array():
+ raise analysis_error(
+ "Array output StackRefs not supported for evaluating pure ops.",
+ self.original_uop.body.open
+ )
+ emitter.emit(f"_PyStackRef {outp.name}_stackref;\n")
+
+
+ storage = Storage.for_uop(self.stack, self.original_uop, CWriter.null(), check_liveness=False)
+ # No reference management of outputs needed.
+ for var in storage.outputs:
+ var.in_local = True
+ emitter.emit("/* Start of uop copied from bytecodes for constant evaluation */\n")
+ emitter.emit_tokens(self.original_uop, storage, inst=None, emit_braces=False)
+ self.out.start_line()
+ emitter.emit("/* End of uop copied from bytecodes for constant evaluation */\n")
+ # Finally, assign back the output stackrefs to symbolics.
+ for outp in self.original_uop.stack.outputs:
+ # All new stackrefs are created from new references.
+ # That's how the stackref contract works.
+ if not outp.peek:
+ emitter.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal({outp.name}_stackref));\n")
+ else:
+ emitter.emit(f"{outp.name} = sym_new_const(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n")
+ storage.flush(self.out)
+ emitter.emit("break;\n")
+ emitter.emit("}\n")
+ return True
+
+class OptimizerConstantEmitter(OptimizerEmitter):
+ def __init__(self, out: CWriter, labels: dict[str, Label], original_uop: Uop, stack: Stack):
+ super().__init__(out, labels, original_uop, stack)
+ # Replace all outputs to point to their stackref versions.
+ overrides = {
+ outp.name: self.emit_stackref_override for outp in self.original_uop.stack.outputs
+ }
+ self._replacers = {**self._replacers, **overrides}
+ self.cannot_escape = True
+
+ def emit_to_with_replacement(
+ self,
+ out: CWriter,
+ tkn_iter: TokenIterator,
+ end: str,
+ uop: CodeSection,
+ storage: Storage,
+ inst: Instruction | None
+ ) -> Token:
+ parens = 0
+ for tkn in tkn_iter:
+ if tkn.kind == end and parens == 0:
+ return tkn
+ if tkn.kind == "LPAREN":
+ parens += 1
+ if tkn.kind == "RPAREN":
+ parens -= 1
+ if tkn.text in self._replacers:
+ self._replacers[tkn.text](tkn, tkn_iter, uop, storage, inst)
+ else:
+ out.emit(tkn)
+ raise analysis_error(f"Expecting {end}. Reached end of file", tkn)
+
+ def emit_stackref_override(
+ self,
+ tkn: Token,
+ tkn_iter: TokenIterator,
+ uop: CodeSection,
+ storage: Storage,
+ inst: Instruction | None,
+ ) -> bool:
+ self.out.emit(tkn)
+ self.out.emit("_stackref ")
+ return True
+
+ def deopt_if(
+ self,
+ tkn: Token,
+ tkn_iter: TokenIterator,
+ uop: CodeSection,
+ storage: Storage,
+ inst: Instruction | None,
+ ) -> bool:
+ self.out.start_line()
+ self.out.emit("if (")
+ lparen = next(tkn_iter)
+ assert lparen.kind == "LPAREN"
+ first_tkn = tkn_iter.peek()
+ self.emit_to_with_replacement(self.out, tkn_iter, "RPAREN", uop, storage, inst)
+ self.emit(") {\n")
+ next(tkn_iter) # Semi colon
+ # We guarantee this will deopt in real-world code
+ # via constants analysis. So just bail.
+ self.emit("ctx->done = true;\n")
+ self.emit("break;\n")
+ self.emit("}\n")
+ return not always_true(first_tkn)
+
+ exit_if = deopt_if
+
+ def error_if(
+ self,
+ tkn: Token,
+ tkn_iter: TokenIterator,
+ uop: CodeSection,
+ storage: Storage,
+ inst: Instruction | None,
+ ) -> bool:
+ lparen = next(tkn_iter)
+ assert lparen.kind == "LPAREN"
+ first_tkn = tkn_iter.peek()
+ unconditional = always_true(first_tkn)
+ if unconditional:
+ next(tkn_iter)
+ next(tkn_iter) # RPAREN
+ self.out.start_line()
+ else:
+ self.out.emit_at("if ", tkn)
+ self.emit(lparen)
+ self.emit_to_with_replacement(self.out, tkn_iter, "RPAREN", uop, storage, inst)
+ self.out.emit(") {\n")
+ next(tkn_iter) # Semi colon
+ storage.clear_inputs("at ERROR_IF")
+
+ self.out.emit("goto error;\n")
+ if not unconditional:
+ self.out.emit("}\n")
+ return not unconditional
+
+
def write_uop(
override: Uop | None,
uop: Uop,
@@ -141,13 +367,14 @@ def write_uop(
cast = f"uint{cache.size*16}_t"
out.emit(f"{type}{cache.name} = ({cast})this_instr->operand0;\n")
if override:
- emitter = OptimizerEmitter(out, {})
+ emitter = OptimizerEmitter(out, {}, uop, stack.copy())
# No reference management of inputs needed.
for var in storage.inputs: # type: ignore[possibly-undefined]
var.in_local = False
_, storage = emitter.emit_tokens(override, storage, None, False)
out.start_line()
storage.flush(out)
+ out.start_line()
else:
emit_default(out, uop, stack)
out.start_line()
@@ -194,7 +421,7 @@ def generate_abstract_interpreter(
declare_variables(override, out, skip_inputs=False)
else:
declare_variables(uop, out, skip_inputs=True)
- stack = Stack(extract_bits=False, cast_type="JitOptSymbol *")
+ stack = Stack()
write_uop(override, uop, out, stack, debug, skip_inputs=(override is None))
out.start_line()
out.emit("break;\n")
diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py
index 9c9b0053a59..c7fe0d162ac 100644
--- a/Tools/cases_generator/parsing.py
+++ b/Tools/cases_generator/parsing.py
@@ -247,12 +247,11 @@ class SimpleStmt(Stmt):
@dataclass
class StackEffect(Node):
name: str = field(compare=False) # __eq__ only uses type, cond, size
- type: str = "" # Optional `:type`
size: str = "" # Optional `[size]`
# Note: size cannot be combined with type or cond
def __repr__(self) -> str:
- items = [self.name, self.type, self.size]
+ items = [self.name, self.size]
while items and items[-1] == "":
del items[-1]
return f"StackEffect({', '.join(repr(item) for item in items)})"
@@ -380,9 +379,13 @@ class Parser(PLexer):
while anno := self.expect(lx.ANNOTATION):
if anno.text == "replicate":
self.require(lx.LPAREN)
- times = self.require(lx.NUMBER)
+ stop = self.require(lx.NUMBER)
+ start_text = "0"
+ if self.expect(lx.COLON):
+ start_text = stop.text
+ stop = self.require(lx.NUMBER)
self.require(lx.RPAREN)
- annotations.append(f"replicate({times.text})")
+ annotations.append(f"replicate({start_text}:{stop.text})")
else:
annotations.append(anno.text)
tkn = self.expect(lx.INST)
@@ -463,20 +466,13 @@ class Parser(PLexer):
# IDENTIFIER [':' IDENTIFIER [TIMES]] ['if' '(' expression ')']
# | IDENTIFIER '[' expression ']'
if tkn := self.expect(lx.IDENTIFIER):
- type_text = ""
- if self.expect(lx.COLON):
- type_text = self.require(lx.IDENTIFIER).text.strip()
- if self.expect(lx.TIMES):
- type_text += " *"
size_text = ""
if self.expect(lx.LBRACKET):
- if type_text:
- raise self.make_syntax_error("Unexpected [")
if not (size := self.expression()):
raise self.make_syntax_error("Expected expression")
self.require(lx.RBRACKET)
size_text = size.text.strip()
- return StackEffect(tkn.text, type_text, size_text)
+ return StackEffect(tkn.text, size_text)
return None
@contextual
diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py
index 6b681775f48..3a0e7e5d0d5 100644
--- a/Tools/cases_generator/stack.py
+++ b/Tools/cases_generator/stack.py
@@ -168,7 +168,7 @@ class Local:
@staticmethod
def register(name: str) -> "Local":
- item = StackItem(name, None, "", False, True)
+ item = StackItem(name, "", False, True)
return Local(item, None, True)
def kill(self) -> None:
@@ -216,13 +216,11 @@ def array_or_scalar(var: StackItem | Local) -> str:
return "array" if var.is_array() else "scalar"
class Stack:
- def __init__(self, extract_bits: bool=True, cast_type: str = "uintptr_t") -> None:
+ def __init__(self) -> None:
self.base_offset = PointerOffset.zero()
self.physical_sp = PointerOffset.zero()
self.logical_sp = PointerOffset.zero()
self.variables: list[Local] = []
- self.extract_bits = extract_bits
- self.cast_type = cast_type
def drop(self, var: StackItem, check_liveness: bool) -> None:
self.logical_sp = self.logical_sp.pop(var)
@@ -268,10 +266,8 @@ class Stack:
self.base_offset = self.logical_sp
if var.name in UNUSED or not var.used:
return Local.unused(var, self.base_offset)
- cast = f"({var.type})" if (not indirect and var.type) else ""
- bits = ".bits" if cast and self.extract_bits else ""
c_offset = (self.base_offset - self.physical_sp).to_c()
- assign = f"{var.name} = {cast}{indirect}stack_pointer[{c_offset}]{bits};\n"
+ assign = f"{var.name} = {indirect}stack_pointer[{c_offset}];\n"
out.emit(assign)
self._print(out)
return Local.from_memory(var, self.base_offset)
@@ -292,12 +288,8 @@ class Stack:
out: CWriter,
var: StackItem,
stack_offset: PointerOffset,
- cast_type: str,
- extract_bits: bool,
) -> None:
- cast = f"({cast_type})" if var.type else ""
- bits = ".bits" if cast and extract_bits else ""
- out.emit(f"stack_pointer[{stack_offset.to_c()}]{bits} = {cast}{var.name};\n")
+ out.emit(f"stack_pointer[{stack_offset.to_c()}] = {var.name};\n")
def _save_physical_sp(self, out: CWriter) -> None:
if self.physical_sp != self.logical_sp:
@@ -320,7 +312,7 @@ class Stack:
self._print(out)
var.memory_offset = var_offset
stack_offset = var_offset - self.physical_sp
- Stack._do_emit(out, var.item, stack_offset, self.cast_type, self.extract_bits)
+ Stack._do_emit(out, var.item, stack_offset)
self._print(out)
var_offset = var_offset.push(var.item)
@@ -350,7 +342,7 @@ class Stack:
out.emit(self.as_comment() + "\n")
def copy(self) -> "Stack":
- other = Stack(self.extract_bits, self.cast_type)
+ other = Stack()
other.base_offset = self.base_offset
other.physical_sp = self.physical_sp
other.logical_sp = self.logical_sp
@@ -496,7 +488,7 @@ class Storage:
f"Expected '{undefined}' to be defined before '{out.name}'"
else:
undefined = out.name
- while len(self.outputs) > self.peeks and not self.needs_defining(self.outputs[0]):
+ while len(self.outputs) > self.peeks and not self.needs_defining(self.outputs[self.peeks]):
out = self.outputs.pop(self.peeks)
self.stack.push(out)
diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py
index 276f306dfff..fc3bc47286f 100644
--- a/Tools/cases_generator/tier2_generator.py
+++ b/Tools/cases_generator/tier2_generator.py
@@ -91,7 +91,7 @@ class Tier2Emitter(Emitter):
self.emit("}\n")
return not always_true(first_tkn)
- def exit_if( # type: ignore[override]
+ def exit_if(
self,
tkn: Token,
tkn_iter: TokenIterator,
diff --git a/Tools/cases_generator/uop_metadata_generator.py b/Tools/cases_generator/uop_metadata_generator.py
index 6f995e5c46b..1cc23837a72 100644
--- a/Tools/cases_generator/uop_metadata_generator.py
+++ b/Tools/cases_generator/uop_metadata_generator.py
@@ -24,7 +24,8 @@ DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_uop_metadata.h"
def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None:
out.emit("extern const uint16_t _PyUop_Flags[MAX_UOP_ID+1];\n")
- out.emit("extern const uint8_t _PyUop_Replication[MAX_UOP_ID+1];\n")
+ out.emit("typedef struct _rep_range { uint8_t start; uint8_t stop; } ReplicationRange;\n")
+ out.emit("extern const ReplicationRange _PyUop_Replication[MAX_UOP_ID+1];\n")
out.emit("extern const char * const _PyOpcode_uop_name[MAX_UOP_ID+1];\n\n")
out.emit("extern int _PyUop_num_popped(int opcode, int oparg);\n\n")
out.emit("#ifdef NEED_OPCODE_METADATA\n")
@@ -34,10 +35,11 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None:
out.emit(f"[{uop.name}] = {cflags(uop.properties)},\n")
out.emit("};\n\n")
- out.emit("const uint8_t _PyUop_Replication[MAX_UOP_ID+1] = {\n")
+ out.emit("const ReplicationRange _PyUop_Replication[MAX_UOP_ID+1] = {\n")
for uop in analysis.uops.values():
if uop.replicated:
- out.emit(f"[{uop.name}] = {uop.replicated},\n")
+ assert(uop.replicated.step == 1)
+ out.emit(f"[{uop.name}] = {{ {uop.replicated.start}, {uop.replicated.stop} }},\n")
out.emit("};\n\n")
out.emit("const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {\n")
diff --git a/Tools/clinic/libclinic/converters.py b/Tools/clinic/libclinic/converters.py
index 633fb5f56a6..39d0ac557a6 100644
--- a/Tools/clinic/libclinic/converters.py
+++ b/Tools/clinic/libclinic/converters.py
@@ -17,6 +17,54 @@ from libclinic.converter import (
TypeSet = set[bltns.type[object]]
+class BaseUnsignedIntConverter(CConverter):
+
+ def use_converter(self) -> None:
+ if self.converter:
+ self.add_include('pycore_long.h',
+ f'{self.converter}()')
+
+ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
+ if not limited_capi:
+ return super().parse_arg(argname, displayname, limited_capi=limited_capi)
+ return self.format_code("""
+ {{{{
+ Py_ssize_t _bytes = PyLong_AsNativeBytes({argname}, &{paramname}, sizeof({type}),
+ Py_ASNATIVEBYTES_NATIVE_ENDIAN |
+ Py_ASNATIVEBYTES_ALLOW_INDEX |
+ Py_ASNATIVEBYTES_REJECT_NEGATIVE |
+ Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
+ if (_bytes < 0) {{{{
+ goto exit;
+ }}}}
+ if ((size_t)_bytes > sizeof({type})) {{{{
+ PyErr_SetString(PyExc_OverflowError,
+ "Python int too large for C {type}");
+ goto exit;
+ }}}}
+ }}}}
+ """,
+ argname=argname,
+ type=self.type)
+
+
+class uint8_converter(BaseUnsignedIntConverter):
+ type = "uint8_t"
+ converter = '_PyLong_UInt8_Converter'
+
+class uint16_converter(BaseUnsignedIntConverter):
+ type = "uint16_t"
+ converter = '_PyLong_UInt16_Converter'
+
+class uint32_converter(BaseUnsignedIntConverter):
+ type = "uint32_t"
+ converter = '_PyLong_UInt32_Converter'
+
+class uint64_converter(BaseUnsignedIntConverter):
+ type = "uint64_t"
+ converter = '_PyLong_UInt64_Converter'
+
+
class bool_converter(CConverter):
type = 'int'
default_type = bool
@@ -211,29 +259,7 @@ class short_converter(CConverter):
return super().parse_arg(argname, displayname, limited_capi=limited_capi)
-def format_inline_unsigned_int_converter(self: CConverter, argname: str) -> str:
- return self.format_code("""
- {{{{
- Py_ssize_t _bytes = PyLong_AsNativeBytes({argname}, &{paramname}, sizeof({type}),
- Py_ASNATIVEBYTES_NATIVE_ENDIAN |
- Py_ASNATIVEBYTES_ALLOW_INDEX |
- Py_ASNATIVEBYTES_REJECT_NEGATIVE |
- Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
- if (_bytes < 0) {{{{
- goto exit;
- }}}}
- if ((size_t)_bytes > sizeof({type})) {{{{
- PyErr_SetString(PyExc_OverflowError,
- "Python int too large for C {type}");
- goto exit;
- }}}}
- }}}}
- """,
- argname=argname,
- type=self.type)
-
-
-class unsigned_short_converter(CConverter):
+class unsigned_short_converter(BaseUnsignedIntConverter):
type = 'unsigned short'
default_type = int
c_ignored_default = "0"
@@ -244,11 +270,6 @@ class unsigned_short_converter(CConverter):
else:
self.converter = '_PyLong_UnsignedShort_Converter'
- def use_converter(self) -> None:
- if self.converter == '_PyLong_UnsignedShort_Converter':
- self.add_include('pycore_long.h',
- '_PyLong_UnsignedShort_Converter()')
-
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
if self.format_unit == 'H':
return self.format_code("""
@@ -258,9 +279,7 @@ class unsigned_short_converter(CConverter):
}}}}
""",
argname=argname)
- if not limited_capi:
- return super().parse_arg(argname, displayname, limited_capi=limited_capi)
- return format_inline_unsigned_int_converter(self, argname)
+ return super().parse_arg(argname, displayname, limited_capi=limited_capi)
@add_legacy_c_converter('C', accept={str})
@@ -311,7 +330,7 @@ class int_converter(CConverter):
return super().parse_arg(argname, displayname, limited_capi=limited_capi)
-class unsigned_int_converter(CConverter):
+class unsigned_int_converter(BaseUnsignedIntConverter):
type = 'unsigned int'
default_type = int
c_ignored_default = "0"
@@ -322,11 +341,6 @@ class unsigned_int_converter(CConverter):
else:
self.converter = '_PyLong_UnsignedInt_Converter'
- def use_converter(self) -> None:
- if self.converter == '_PyLong_UnsignedInt_Converter':
- self.add_include('pycore_long.h',
- '_PyLong_UnsignedInt_Converter()')
-
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
if self.format_unit == 'I':
return self.format_code("""
@@ -336,9 +350,7 @@ class unsigned_int_converter(CConverter):
}}}}
""",
argname=argname)
- if not limited_capi:
- return super().parse_arg(argname, displayname, limited_capi=limited_capi)
- return format_inline_unsigned_int_converter(self, argname)
+ return super().parse_arg(argname, displayname, limited_capi=limited_capi)
class long_converter(CConverter):
@@ -359,7 +371,7 @@ class long_converter(CConverter):
return super().parse_arg(argname, displayname, limited_capi=limited_capi)
-class unsigned_long_converter(CConverter):
+class unsigned_long_converter(BaseUnsignedIntConverter):
type = 'unsigned long'
default_type = int
c_ignored_default = "0"
@@ -370,11 +382,6 @@ class unsigned_long_converter(CConverter):
else:
self.converter = '_PyLong_UnsignedLong_Converter'
- def use_converter(self) -> None:
- if self.converter == '_PyLong_UnsignedLong_Converter':
- self.add_include('pycore_long.h',
- '_PyLong_UnsignedLong_Converter()')
-
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
if self.format_unit == 'k':
return self.format_code("""
@@ -387,9 +394,7 @@ class unsigned_long_converter(CConverter):
argname=argname,
bad_argument=self.bad_argument(displayname, 'int', limited_capi=limited_capi),
)
- if not limited_capi:
- return super().parse_arg(argname, displayname, limited_capi=limited_capi)
- return format_inline_unsigned_int_converter(self, argname)
+ return super().parse_arg(argname, displayname, limited_capi=limited_capi)
class long_long_converter(CConverter):
@@ -410,7 +415,7 @@ class long_long_converter(CConverter):
return super().parse_arg(argname, displayname, limited_capi=limited_capi)
-class unsigned_long_long_converter(CConverter):
+class unsigned_long_long_converter(BaseUnsignedIntConverter):
type = 'unsigned long long'
default_type = int
c_ignored_default = "0"
@@ -421,11 +426,6 @@ class unsigned_long_long_converter(CConverter):
else:
self.converter = '_PyLong_UnsignedLongLong_Converter'
- def use_converter(self) -> None:
- if self.converter == '_PyLong_UnsignedLongLong_Converter':
- self.add_include('pycore_long.h',
- '_PyLong_UnsignedLongLong_Converter()')
-
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
if self.format_unit == 'K':
return self.format_code("""
@@ -438,9 +438,7 @@ class unsigned_long_long_converter(CConverter):
argname=argname,
bad_argument=self.bad_argument(displayname, 'int', limited_capi=limited_capi),
)
- if not limited_capi:
- return super().parse_arg(argname, displayname, limited_capi=limited_capi)
- return format_inline_unsigned_int_converter(self, argname)
+ return super().parse_arg(argname, displayname, limited_capi=limited_capi)
class Py_ssize_t_converter(CConverter):
@@ -557,15 +555,11 @@ class slice_index_converter(CConverter):
argname=argname)
-class size_t_converter(CConverter):
+class size_t_converter(BaseUnsignedIntConverter):
type = 'size_t'
converter = '_PyLong_Size_t_Converter'
c_ignored_default = "0"
- def use_converter(self) -> None:
- self.add_include('pycore_long.h',
- '_PyLong_Size_t_Converter()')
-
def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
if self.format_unit == 'n':
return self.format_code("""
@@ -575,9 +569,7 @@ class size_t_converter(CConverter):
}}}}
""",
argname=argname)
- if not limited_capi:
- return super().parse_arg(argname, displayname, limited_capi=limited_capi)
- return format_inline_unsigned_int_converter(self, argname)
+ return super().parse_arg(argname, displayname, limited_capi=limited_capi)
class fildes_converter(CConverter):
diff --git a/Tools/ftscalingbench/ftscalingbench.py b/Tools/ftscalingbench/ftscalingbench.py
index 926bc66b944..1a59e25189d 100644
--- a/Tools/ftscalingbench/ftscalingbench.py
+++ b/Tools/ftscalingbench/ftscalingbench.py
@@ -27,6 +27,7 @@ import queue
import sys
import threading
import time
+from operator import methodcaller
# The iterations in individual benchmarks are scaled by this factor.
WORK_SCALE = 100
@@ -188,6 +189,18 @@ def thread_local_read():
_ = tmp.x
_ = tmp.x
+class MyClass:
+ __slots__ = ()
+
+ def func(self):
+ pass
+
+@register_benchmark
+def method_caller():
+ mc = methodcaller("func")
+ obj = MyClass()
+ for i in range(1000 * WORK_SCALE):
+ mc(obj)
def bench_one_thread(func):
t0 = time.perf_counter_ns()
diff --git a/Tools/i18n/makelocalealias.py b/Tools/i18n/makelocalealias.py
index b407a8a643b..02af1caff7d 100755
--- a/Tools/i18n/makelocalealias.py
+++ b/Tools/i18n/makelocalealias.py
@@ -140,6 +140,9 @@ if __name__ == '__main__':
data = locale.locale_alias.copy()
data.update(parse_glibc_supported(args.glibc_supported))
data.update(parse(args.locale_alias))
+ # Hardcode 'c.utf8' -> 'C.UTF-8' because 'en_US.UTF-8' does not exist
+ # on all platforms.
+ data['c.utf8'] = 'C.UTF-8'
while True:
# Repeat optimization while the size is decreased.
n = len(data)
diff --git a/Tools/inspection/benchmark_external_inspection.py b/Tools/inspection/benchmark_external_inspection.py
new file mode 100644
index 00000000000..0ac7ac4d385
--- /dev/null
+++ b/Tools/inspection/benchmark_external_inspection.py
@@ -0,0 +1,473 @@
+import _remote_debugging
+import time
+import subprocess
+import sys
+import contextlib
+import tempfile
+import os
+import argparse
+from _colorize import get_colors, can_colorize
+
+CODE = '''\
+import time
+import os
+import sys
+import math
+
+def slow_fibonacci(n):
+ """Intentionally slow recursive fibonacci - should show up prominently in profiler"""
+ if n <= 1:
+ return n
+ return slow_fibonacci(n-1) + slow_fibonacci(n-2)
+
+def medium_computation():
+ """Medium complexity function"""
+ result = 0
+ for i in range(1000):
+ result += math.sqrt(i) * math.sin(i)
+ return result
+
+def fast_loop():
+ """Fast simple loop"""
+ total = 0
+ for i in range(100):
+ total += i
+ return total
+
+def string_operations():
+ """String manipulation that should be visible in profiler"""
+ text = "hello world " * 100
+ words = text.split()
+ return " ".join(reversed(words))
+
+def nested_calls():
+ """Nested function calls to test call stack depth"""
+ def level1():
+ def level2():
+ def level3():
+ return medium_computation()
+ return level3()
+ return level2()
+ return level1()
+
+def main_loop():
+ """Main computation loop with different execution paths"""
+ iteration = 0
+
+ while True:
+ iteration += 1
+
+ # Different execution paths with different frequencies
+ if iteration % 50 == 0:
+ # Expensive operation - should show high per-call time
+ result = slow_fibonacci(20)
+
+ elif iteration % 10 == 0:
+ # Medium operation
+ result = nested_calls()
+
+ elif iteration % 5 == 0:
+ # String operations
+ result = string_operations()
+
+ else:
+ # Fast operation - most common
+ result = fast_loop()
+
+ # Small delay to make sampling more interesting
+ time.sleep(0.001)
+
+if __name__ == "__main__":
+ main_loop()
+'''
+
+DEEP_STATIC_CODE = """\
+import time
+def factorial(n):
+ if n <= 1:
+ time.sleep(10000)
+ return 1
+ return n * factorial(n-1)
+
+factorial(900)
+"""
+
+CODE_WITH_TONS_OF_THREADS = '''\
+import time
+import threading
+import random
+import math
+
+def cpu_intensive_work():
+ """Do some CPU intensive calculations"""
+ result = 0
+ for _ in range(10000):
+ result += math.sin(random.random()) * math.cos(random.random())
+ return result
+
+def io_intensive_work():
+ """Simulate IO intensive work with sleeps"""
+ time.sleep(0.1)
+
+def mixed_workload():
+ """Mix of CPU and IO work"""
+ while True:
+ if random.random() < 0.3:
+ cpu_intensive_work()
+ else:
+ io_intensive_work()
+
+def create_threads(n):
+ """Create n threads doing mixed workloads"""
+ threads = []
+ for _ in range(n):
+ t = threading.Thread(target=mixed_workload, daemon=True)
+ t.start()
+ threads.append(t)
+ return threads
+
+# Start with 5 threads
+active_threads = create_threads(5)
+thread_count = 5
+
+# Main thread manages threads and does work
+while True:
+ # Randomly add or remove threads
+ if random.random() < 0.1: # 10% chance each iteration
+ if random.random() < 0.5 and thread_count < 100:
+ # Add 1-5 new threads
+ new_count = random.randint(1, 5)
+ new_threads = create_threads(new_count)
+ active_threads.extend(new_threads)
+ thread_count += new_count
+ elif thread_count > 10:
+ # Remove 1-3 threads
+ remove_count = random.randint(1, 5)
+ # The threads will terminate naturally since they're daemons
+ active_threads = active_threads[remove_count:]
+ thread_count -= remove_count
+
+ cpu_intensive_work()
+ time.sleep(0.05)
+'''
+
+CODE_EXAMPLES = {
+ "basic": {
+ "code": CODE,
+ "description": "Mixed workload with fibonacci, computations, and string operations",
+ },
+ "deep_static": {
+ "code": DEEP_STATIC_CODE,
+ "description": "Deep recursive call stack with 900+ frames (factorial)",
+ },
+ "threads": {
+ "code": CODE_WITH_TONS_OF_THREADS,
+ "description": "Tons of threads doing mixed CPU/IO work",
+ },
+}
+
+
+def benchmark(unwinder, duration_seconds=10):
+ """Benchmark mode - measure raw sampling speed for specified duration"""
+ sample_count = 0
+ fail_count = 0
+ total_work_time = 0.0
+ start_time = time.perf_counter()
+ end_time = start_time + duration_seconds
+ total_attempts = 0
+
+ colors = get_colors(can_colorize())
+
+ print(
+ f"{colors.BOLD_BLUE}Benchmarking sampling speed for {duration_seconds} seconds...{colors.RESET}"
+ )
+
+ try:
+ while time.perf_counter() < end_time:
+ total_attempts += 1
+ work_start = time.perf_counter()
+ try:
+ stack_trace = unwinder.get_stack_trace()
+ if stack_trace:
+ sample_count += 1
+ except (OSError, RuntimeError, UnicodeDecodeError) as e:
+ fail_count += 1
+
+ work_end = time.perf_counter()
+ total_work_time += work_end - work_start
+
+ if total_attempts % 10000 == 0:
+ avg_work_time_us = (total_work_time / total_attempts) * 1e6
+ work_rate = (
+ total_attempts / total_work_time if total_work_time > 0 else 0
+ )
+ success_rate = (sample_count / total_attempts) * 100
+
+ # Color code the success rate
+ if success_rate >= 95:
+ success_color = colors.GREEN
+ elif success_rate >= 80:
+ success_color = colors.YELLOW
+ else:
+ success_color = colors.RED
+
+ print(
+ f"{colors.CYAN}Attempts:{colors.RESET} {total_attempts} | "
+ f"{colors.CYAN}Success:{colors.RESET} {success_color}{success_rate:.1f}%{colors.RESET} | "
+ f"{colors.CYAN}Rate:{colors.RESET} {colors.MAGENTA}{work_rate:.1f}Hz{colors.RESET} | "
+ f"{colors.CYAN}Avg:{colors.RESET} {colors.YELLOW}{avg_work_time_us:.2f}µs{colors.RESET}"
+ )
+ except KeyboardInterrupt:
+ print(f"\n{colors.YELLOW}Benchmark interrupted by user{colors.RESET}")
+
+ actual_end_time = time.perf_counter()
+ wall_time = actual_end_time - start_time
+
+ # Return final statistics
+ return {
+ "wall_time": wall_time,
+ "total_attempts": total_attempts,
+ "sample_count": sample_count,
+ "fail_count": fail_count,
+ "success_rate": (
+ (sample_count / total_attempts) * 100 if total_attempts > 0 else 0
+ ),
+ "total_work_time": total_work_time,
+ "avg_work_time_us": (
+ (total_work_time / total_attempts) * 1e6 if total_attempts > 0 else 0
+ ),
+ "work_rate_hz": total_attempts / total_work_time if total_work_time > 0 else 0,
+ "samples_per_sec": sample_count / wall_time if wall_time > 0 else 0,
+ }
+
+
+def print_benchmark_results(results):
+ """Print comprehensive benchmark results"""
+ colors = get_colors(can_colorize())
+
+ print(f"\n{colors.BOLD_GREEN}{'='*60}{colors.RESET}")
+ print(f"{colors.BOLD_GREEN}get_stack_trace() Benchmark Results{colors.RESET}")
+ print(f"{colors.BOLD_GREEN}{'='*60}{colors.RESET}")
+
+ # Basic statistics
+ print(f"\n{colors.BOLD_CYAN}Basic Statistics:{colors.RESET}")
+ print(
+ f" {colors.CYAN}Wall time:{colors.RESET} {colors.YELLOW}{results['wall_time']:.3f}{colors.RESET} seconds"
+ )
+ print(
+ f" {colors.CYAN}Total attempts:{colors.RESET} {colors.MAGENTA}{results['total_attempts']:,}{colors.RESET}"
+ )
+ print(
+ f" {colors.CYAN}Successful samples:{colors.RESET} {colors.GREEN}{results['sample_count']:,}{colors.RESET}"
+ )
+ print(
+ f" {colors.CYAN}Failed samples:{colors.RESET} {colors.RED}{results['fail_count']:,}{colors.RESET}"
+ )
+
+ # Color code the success rate
+ success_rate = results["success_rate"]
+ if success_rate >= 95:
+ success_color = colors.BOLD_GREEN
+ elif success_rate >= 80:
+ success_color = colors.BOLD_YELLOW
+ else:
+ success_color = colors.BOLD_RED
+
+ print(
+ f" {colors.CYAN}Success rate:{colors.RESET} {success_color}{success_rate:.2f}%{colors.RESET}"
+ )
+
+ # Performance metrics
+ print(f"\n{colors.BOLD_CYAN}Performance Metrics:{colors.RESET}")
+ print(
+ f" {colors.CYAN}Average call time:{colors.RESET} {colors.YELLOW}{results['avg_work_time_us']:.2f}{colors.RESET} µs"
+ )
+ print(
+ f" {colors.CYAN}Work rate:{colors.RESET} {colors.MAGENTA}{results['work_rate_hz']:.1f}{colors.RESET} calls/sec"
+ )
+ print(
+ f" {colors.CYAN}Sample rate:{colors.RESET} {colors.MAGENTA}{results['samples_per_sec']:.1f}{colors.RESET} samples/sec"
+ )
+ print(
+ f" {colors.CYAN}Total work time:{colors.RESET} {colors.YELLOW}{results['total_work_time']:.3f}{colors.RESET} seconds"
+ )
+
+ # Color code work efficiency
+ efficiency = (results["total_work_time"] / results["wall_time"]) * 100
+ if efficiency >= 80:
+ efficiency_color = colors.GREEN
+ elif efficiency >= 50:
+ efficiency_color = colors.YELLOW
+ else:
+ efficiency_color = colors.RED
+
+ print(
+ f" {colors.CYAN}Work efficiency:{colors.RESET} {efficiency_color}{efficiency:.1f}%{colors.RESET}"
+ )
+
+
+def parse_arguments():
+ """Parse command line arguments"""
+ # Build the code examples description
+ examples_desc = "\n".join(
+ [f" {name}: {info['description']}" for name, info in CODE_EXAMPLES.items()]
+ )
+
+ parser = argparse.ArgumentParser(
+ description="Benchmark get_stack_trace() performance",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog=f"""
+Examples:
+ %(prog)s # Run basic benchmark for 10 seconds (default)
+ %(prog)s --duration 30 # Run basic benchmark for 30 seconds
+ %(prog)s -d 60 # Run basic benchmark for 60 seconds
+ %(prog)s --code deep_static # Run deep static call stack benchmark
+ %(prog)s --code deep_static -d 30 # Run deep static benchmark for 30 seconds
+
+Available code examples:
+{examples_desc}
+ """,
+ color=True,
+ )
+
+ parser.add_argument(
+ "--duration",
+ "-d",
+ type=int,
+ default=10,
+ help="Benchmark duration in seconds (default: 10)",
+ )
+
+ parser.add_argument(
+ "--code",
+ "-c",
+ choices=list(CODE_EXAMPLES.keys()),
+ default="basic",
+ help="Code example to benchmark (default: basic)",
+ )
+
+ parser.add_argument(
+ "--threads",
+ choices=["all", "main", "only_active"],
+ default="all",
+ help="Which threads to include in the benchmark (default: all)",
+ )
+
+ return parser.parse_args()
+
+
+def create_target_process(temp_file, code_example="basic"):
+ """Create and start the target process for benchmarking"""
+ example_info = CODE_EXAMPLES.get(code_example, {"code": CODE})
+ selected_code = example_info["code"]
+ temp_file.write(selected_code)
+ temp_file.flush()
+
+ process = subprocess.Popen(
+ [sys.executable, temp_file.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+
+ # Give it time to start
+ time.sleep(1.0)
+
+ # Check if it's still running
+ if process.poll() is not None:
+ stdout, stderr = process.communicate()
+ raise RuntimeError(
+ f"Target process exited unexpectedly:\nSTDOUT: {stdout.decode()}\nSTDERR: {stderr.decode()}"
+ )
+
+ return process, temp_file.name
+
+
+def cleanup_process(process, temp_file_path):
+ """Clean up the target process and temporary file"""
+ with contextlib.suppress(Exception):
+ if process.poll() is None:
+ process.terminate()
+ try:
+ process.wait(timeout=5.0)
+ except subprocess.TimeoutExpired:
+ process.kill()
+ process.wait()
+
+
+def main():
+ """Main benchmark function"""
+ colors = get_colors(can_colorize())
+ args = parse_arguments()
+
+ print(f"{colors.BOLD_MAGENTA}External Inspection Benchmark Tool{colors.RESET}")
+ print(f"{colors.BOLD_MAGENTA}{'=' * 34}{colors.RESET}")
+
+ example_info = CODE_EXAMPLES.get(args.code, {"description": "Unknown"})
+ print(
+ f"\n{colors.CYAN}Code Example:{colors.RESET} {colors.GREEN}{args.code}{colors.RESET}"
+ )
+ print(f"{colors.CYAN}Description:{colors.RESET} {example_info['description']}")
+ print(
+ f"{colors.CYAN}Benchmark Duration:{colors.RESET} {colors.YELLOW}{args.duration}{colors.RESET} seconds"
+ )
+
+ process = None
+ temp_file_path = None
+
+ try:
+ # Create target process
+ print(f"\n{colors.BLUE}Creating and starting target process...{colors.RESET}")
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as temp_file:
+ process, temp_file_path = create_target_process(temp_file, args.code)
+ print(
+ f"{colors.GREEN}Target process started with PID: {colors.BOLD_WHITE}{process.pid}{colors.RESET}"
+ )
+
+ # Run benchmark with specified duration
+ with process:
+ # Create unwinder and run benchmark
+ print(f"{colors.BLUE}Initializing unwinder...{colors.RESET}")
+ try:
+ kwargs = {}
+ if args.threads == "all":
+ kwargs["all_threads"] = True
+ elif args.threads == "main":
+ kwargs["all_threads"] = False
+ elif args.threads == "only_active":
+ kwargs["only_active_thread"] = True
+ unwinder = _remote_debugging.RemoteUnwinder(
+ process.pid, **kwargs
+ )
+ results = benchmark(unwinder, duration_seconds=args.duration)
+ finally:
+ cleanup_process(process, temp_file_path)
+
+ # Print results
+ print_benchmark_results(results)
+
+ except PermissionError as e:
+ print(
+ f"{colors.BOLD_RED}Error: Insufficient permissions to read stack trace: {e}{colors.RESET}"
+ )
+ print(
+ f"{colors.YELLOW}Try running with appropriate privileges (e.g., sudo){colors.RESET}"
+ )
+ return 1
+ except Exception as e:
+ print(f"{colors.BOLD_RED}Error during benchmarking: {e}{colors.RESET}")
+ if process:
+ with contextlib.suppress(Exception):
+ stdout, stderr = process.communicate(timeout=1)
+ if stdout:
+ print(
+ f"{colors.CYAN}Process STDOUT:{colors.RESET} {stdout.decode()}"
+ )
+ if stderr:
+ print(
+ f"{colors.RED}Process STDERR:{colors.RESET} {stderr.decode()}"
+ )
+ raise
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/Tools/jit/README.md b/Tools/jit/README.md
index ff4b3964e65..8e817574b4d 100644
--- a/Tools/jit/README.md
+++ b/Tools/jit/README.md
@@ -54,13 +54,13 @@ choco install llvm --version=19.1.0
## Building
-For `PCbuild`-based builds, pass the new `--experimental-jit` option to `build.bat`.
+For `PCbuild`-based builds, pass the `--experimental-jit` option to `build.bat`.
-For all other builds, pass the new `--enable-experimental-jit` option to `configure`.
+For all other builds, pass the `--enable-experimental-jit` option to `configure`.
Otherwise, just configure and build as you normally would. Cross-compiling "just works", since the JIT is built for the host platform.
-The JIT can also be enabled or disabled using the `PYTHON_JIT` environment variable, even on builds where it is enabled or disabled by default. More details about configuring CPython with the JIT and optional values for `--enable-experimental-jit` can be found [here](https://docs.python.org/dev/whatsnew/3.13.html#experimental-jit-compiler).
+The JIT can also be enabled or disabled using the `PYTHON_JIT` environment variable, even on builds where it is enabled or disabled by default. More details about configuring CPython with the JIT and optional values for `--enable-experimental-jit` can be found [here](https://docs.python.org/dev/using/configure.html#cmdoption-enable-experimental-jit).
[^pep-744]: [PEP 744](https://peps.python.org/pep-0744/)
diff --git a/Tools/jit/_optimizers.py b/Tools/jit/_optimizers.py
new file mode 100644
index 00000000000..1077e4106fd
--- /dev/null
+++ b/Tools/jit/_optimizers.py
@@ -0,0 +1,319 @@
+"""Low-level optimization of textual assembly."""
+
+import dataclasses
+import pathlib
+import re
+import typing
+
+# Same as saying "not string.startswith('')":
+_RE_NEVER_MATCH = re.compile(r"(?!)")
+# Dictionary mapping branch instructions to their inverted branch instructions.
+# If a branch cannot be inverted, the value is None:
+_X86_BRANCHES = {
+ # https://www.felixcloutier.com/x86/jcc
+ "ja": "jna",
+ "jae": "jnae",
+ "jb": "jnb",
+ "jbe": "jnbe",
+ "jc": "jnc",
+ "jcxz": None,
+ "je": "jne",
+ "jecxz": None,
+ "jg": "jng",
+ "jge": "jnge",
+ "jl": "jnl",
+ "jle": "jnle",
+ "jo": "jno",
+ "jp": "jnp",
+ "jpe": "jpo",
+ "jrcxz": None,
+ "js": "jns",
+ "jz": "jnz",
+ # https://www.felixcloutier.com/x86/loop:loopcc
+ "loop": None,
+ "loope": None,
+ "loopne": None,
+ "loopnz": None,
+ "loopz": None,
+}
+# Update with all of the inverted branches, too:
+_X86_BRANCHES |= {v: k for k, v in _X86_BRANCHES.items() if v}
+
+
+@dataclasses.dataclass
+class _Block:
+ label: str | None = None
+ # Non-instruction lines like labels, directives, and comments:
+ noninstructions: list[str] = dataclasses.field(default_factory=list)
+ # Instruction lines:
+ instructions: list[str] = dataclasses.field(default_factory=list)
+ # If this block ends in a jump, where to?
+ target: typing.Self | None = None
+ # The next block in the linked list:
+ link: typing.Self | None = None
+ # Whether control flow can fall through to the linked block above:
+ fallthrough: bool = True
+ # Whether this block can eventually reach the next uop (_JIT_CONTINUE):
+ hot: bool = False
+
+ def resolve(self) -> typing.Self:
+ """Find the first non-empty block reachable from this one."""
+ block = self
+ while block.link and not block.instructions:
+ block = block.link
+ return block
+
+
+@dataclasses.dataclass
+class Optimizer:
+ """Several passes of analysis and optimization for textual assembly."""
+
+ path: pathlib.Path
+ _: dataclasses.KW_ONLY
+ # prefix used to mangle symbols on some platforms:
+ prefix: str = ""
+ # The first block in the linked list:
+ _root: _Block = dataclasses.field(init=False, default_factory=_Block)
+ _labels: dict[str, _Block] = dataclasses.field(init=False, default_factory=dict)
+ # No groups:
+ _re_noninstructions: typing.ClassVar[re.Pattern[str]] = re.compile(
+ r"\s*(?:\.|#|//|$)"
+ )
+ # One group (label):
+ _re_label: typing.ClassVar[re.Pattern[str]] = re.compile(
+ r'\s*(?P<label>[\w."$?@]+):'
+ )
+ # Override everything that follows in subclasses:
+ _alignment: typing.ClassVar[int] = 1
+ _branches: typing.ClassVar[dict[str, str | None]] = {}
+ # Two groups (instruction and target):
+ _re_branch: typing.ClassVar[re.Pattern[str]] = _RE_NEVER_MATCH
+ # One group (target):
+ _re_jump: typing.ClassVar[re.Pattern[str]] = _RE_NEVER_MATCH
+ # No groups:
+ _re_return: typing.ClassVar[re.Pattern[str]] = _RE_NEVER_MATCH
+
+ def __post_init__(self) -> None:
+ # Split the code into a linked list of basic blocks. A basic block is an
+ # optional label, followed by zero or more non-instruction lines,
+ # followed by zero or more instruction lines (only the last of which may
+ # be a branch, jump, or return):
+ text = self._preprocess(self.path.read_text())
+ block = self._root
+ for line in text.splitlines():
+ # See if we need to start a new block:
+ if match := self._re_label.match(line):
+ # Label. New block:
+ block.link = block = self._lookup_label(match["label"])
+ block.noninstructions.append(line)
+ continue
+ if self._re_noninstructions.match(line):
+ if block.instructions:
+ # Non-instruction lines. New block:
+ block.link = block = _Block()
+ block.noninstructions.append(line)
+ continue
+ if block.target or not block.fallthrough:
+ # Current block ends with a branch, jump, or return. New block:
+ block.link = block = _Block()
+ block.instructions.append(line)
+ if match := self._re_branch.match(line):
+ # A block ending in a branch has a target and fallthrough:
+ block.target = self._lookup_label(match["target"])
+ assert block.fallthrough
+ elif match := self._re_jump.match(line):
+ # A block ending in a jump has a target and no fallthrough:
+ block.target = self._lookup_label(match["target"])
+ block.fallthrough = False
+ elif self._re_return.match(line):
+ # A block ending in a return has no target and fallthrough:
+ assert not block.target
+ block.fallthrough = False
+
+ def _preprocess(self, text: str) -> str:
+ # Override this method to do preprocessing of the textual assembly:
+ return text
+
+ @classmethod
+ def _invert_branch(cls, line: str, target: str) -> str | None:
+ match = cls._re_branch.match(line)
+ assert match
+ inverted = cls._branches.get(match["instruction"])
+ if not inverted:
+ return None
+ (a, b), (c, d) = match.span("instruction"), match.span("target")
+ # Before:
+ # je FOO
+ # After:
+ # jne BAR
+ return "".join([line[:a], inverted, line[b:c], target, line[d:]])
+
+ @classmethod
+ def _update_jump(cls, line: str, target: str) -> str:
+ match = cls._re_jump.match(line)
+ assert match
+ a, b = match.span("target")
+ # Before:
+ # jmp FOO
+ # After:
+ # jmp BAR
+ return "".join([line[:a], target, line[b:]])
+
+ def _lookup_label(self, label: str) -> _Block:
+ if label not in self._labels:
+ self._labels[label] = _Block(label)
+ return self._labels[label]
+
+ def _blocks(self) -> typing.Generator[_Block, None, None]:
+ block: _Block | None = self._root
+ while block:
+ yield block
+ block = block.link
+
+ def _body(self) -> str:
+ lines = []
+ hot = True
+ for block in self._blocks():
+ if hot != block.hot:
+ hot = block.hot
+ # Make it easy to tell at a glance where cold code is:
+ lines.append(f"# JIT: {'HOT' if hot else 'COLD'} ".ljust(80, "#"))
+ lines.extend(block.noninstructions)
+ lines.extend(block.instructions)
+ return "\n".join(lines)
+
+ def _predecessors(self, block: _Block) -> typing.Generator[_Block, None, None]:
+ # This is inefficient, but it's never wrong:
+ for pre in self._blocks():
+ if pre.target is block or pre.fallthrough and pre.link is block:
+ yield pre
+
+ def _insert_continue_label(self) -> None:
+ # Find the block with the last instruction:
+ for end in reversed(list(self._blocks())):
+ if end.instructions:
+ break
+ # Before:
+ # jmp FOO
+ # After:
+ # jmp FOO
+ # .balign 8
+ # _JIT_CONTINUE:
+ # This lets the assembler encode _JIT_CONTINUE jumps at build time!
+ align = _Block()
+ align.noninstructions.append(f"\t.balign\t{self._alignment}")
+ continuation = self._lookup_label(f"{self.prefix}_JIT_CONTINUE")
+ assert continuation.label
+ continuation.noninstructions.append(f"{continuation.label}:")
+ end.link, align.link, continuation.link = align, continuation, end.link
+
+ def _mark_hot_blocks(self) -> None:
+ # Start with the last block, and perform a DFS to find all blocks that
+ # can eventually reach it:
+ todo = list(self._blocks())[-1:]
+ while todo:
+ block = todo.pop()
+ block.hot = True
+ todo.extend(pre for pre in self._predecessors(block) if not pre.hot)
+
+ def _invert_hot_branches(self) -> None:
+ for branch in self._blocks():
+ link = branch.link
+ if link is None:
+ continue
+ jump = link.resolve()
+ # Before:
+ # je HOT
+ # jmp COLD
+ # After:
+ # jne COLD
+ # jmp HOT
+ if (
+ # block ends with a branch to hot code...
+ branch.target
+ and branch.fallthrough
+ and branch.target.hot
+ # ...followed by a jump to cold code with no other predecessors:
+ and jump.target
+ and not jump.fallthrough
+ and not jump.target.hot
+ and len(jump.instructions) == 1
+ and list(self._predecessors(jump)) == [branch]
+ ):
+ assert jump.target.label
+ assert branch.target.label
+ inverted = self._invert_branch(
+ branch.instructions[-1], jump.target.label
+ )
+ # Check to see if the branch can even be inverted:
+ if inverted is None:
+ continue
+ branch.instructions[-1] = inverted
+ jump.instructions[-1] = self._update_jump(
+ jump.instructions[-1], branch.target.label
+ )
+ branch.target, jump.target = jump.target, branch.target
+ jump.hot = True
+
+ def _remove_redundant_jumps(self) -> None:
+ # Zero-length jumps can be introduced by _insert_continue_label and
+ # _invert_hot_branches:
+ for block in self._blocks():
+ # Before:
+ # jmp FOO
+ # FOO:
+ # After:
+ # FOO:
+ if (
+ block.target
+ and block.link
+ and block.target.resolve() is block.link.resolve()
+ ):
+ block.target = None
+ block.fallthrough = True
+ block.instructions.pop()
+
+ def run(self) -> None:
+ """Run this optimizer."""
+ self._insert_continue_label()
+ self._mark_hot_blocks()
+ self._invert_hot_branches()
+ self._remove_redundant_jumps()
+ self.path.write_text(self._body())
+
+
+class OptimizerAArch64(Optimizer): # pylint: disable = too-few-public-methods
+ """aarch64-apple-darwin/aarch64-pc-windows-msvc/aarch64-unknown-linux-gnu"""
+
+ # TODO: @diegorusso
+ _alignment = 8
+ # https://developer.arm.com/documentation/ddi0602/2025-03/Base-Instructions/B--Branch-
+ _re_jump = re.compile(r"\s*b\s+(?P<target>[\w.]+)")
+
+
+class OptimizerX86(Optimizer): # pylint: disable = too-few-public-methods
+ """i686-pc-windows-msvc/x86_64-apple-darwin/x86_64-unknown-linux-gnu"""
+
+ _branches = _X86_BRANCHES
+ _re_branch = re.compile(
+ rf"\s*(?P<instruction>{'|'.join(_X86_BRANCHES)})\s+(?P<target>[\w.]+)"
+ )
+ # https://www.felixcloutier.com/x86/jmp
+ _re_jump = re.compile(r"\s*jmp\s+(?P<target>[\w.]+)")
+ # https://www.felixcloutier.com/x86/ret
+ _re_return = re.compile(r"\s*ret\b")
+
+
+class OptimizerX8664Windows(OptimizerX86): # pylint: disable = too-few-public-methods
+ """x86_64-pc-windows-msvc"""
+
+ def _preprocess(self, text: str) -> str:
+ text = super()._preprocess(text)
+ # Before:
+ # rex64 jmpq *__imp__JIT_CONTINUE(%rip)
+ # After:
+ # jmp _JIT_CONTINUE
+ far_indirect_jump = (
+ rf"rex64\s+jmpq\s+\*__imp_(?P<target>{self.prefix}_JIT_\w+)\(%rip\)"
+ )
+ return re.sub(far_indirect_jump, r"jmp\t\g<target>", text)
diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py
index 03b0ba647b0..1d82f5366f6 100644
--- a/Tools/jit/_stencils.py
+++ b/Tools/jit/_stencils.py
@@ -17,8 +17,6 @@ class HoleValue(enum.Enum):
# The base address of the machine code for the current uop (exposed as _JIT_ENTRY):
CODE = enum.auto()
- # The base address of the machine code for the next uop (exposed as _JIT_CONTINUE):
- CONTINUE = enum.auto()
# The base address of the read-only data for this uop:
DATA = enum.auto()
# The address of the current executor (exposed as _JIT_EXECUTOR):
@@ -97,7 +95,6 @@ _PATCH_FUNCS = {
# Translate HoleValues to C expressions:
_HOLE_EXPRS = {
HoleValue.CODE: "(uintptr_t)code",
- HoleValue.CONTINUE: "(uintptr_t)code + sizeof(code_body)",
HoleValue.DATA: "(uintptr_t)data",
HoleValue.EXECUTOR: "(uintptr_t)executor",
# These should all have been turned into DATA values by process_relocations:
@@ -209,64 +206,6 @@ class Stencil:
self.disassembly.append(f"{offset:x}: {' '.join(['00'] * padding)}")
self.body.extend([0] * padding)
- def add_nops(self, nop: bytes, alignment: int) -> None:
- """Add NOPs until there is alignment. Fail if it is not possible."""
- offset = len(self.body)
- nop_size = len(nop)
-
- # Calculate the gap to the next multiple of alignment.
- gap = -offset % alignment
- if gap:
- if gap % nop_size == 0:
- count = gap // nop_size
- self.body.extend(nop * count)
- else:
- raise ValueError(
- f"Cannot add nops of size '{nop_size}' to a body with "
- f"offset '{offset}' to align with '{alignment}'"
- )
-
- def remove_jump(self) -> None:
- """Remove a zero-length continuation jump, if it exists."""
- hole = max(self.holes, key=lambda hole: hole.offset)
- match hole:
- case Hole(
- offset=offset,
- kind="IMAGE_REL_AMD64_REL32",
- value=HoleValue.GOT,
- symbol="_JIT_CONTINUE",
- addend=-4,
- ) as hole:
- # jmp qword ptr [rip]
- jump = b"\x48\xff\x25\x00\x00\x00\x00"
- offset -= 3
- case Hole(
- offset=offset,
- kind="IMAGE_REL_I386_REL32" | "R_X86_64_PLT32" | "X86_64_RELOC_BRANCH",
- value=HoleValue.CONTINUE,
- symbol=None,
- addend=addend,
- ) as hole if (
- _signed(addend) == -4
- ):
- # jmp 5
- jump = b"\xe9\x00\x00\x00\x00"
- offset -= 1
- case Hole(
- offset=offset,
- kind="R_AARCH64_JUMP26",
- value=HoleValue.CONTINUE,
- symbol=None,
- addend=0,
- ) as hole:
- # b #4
- jump = b"\x00\x00\x00\x14"
- case _:
- return
- if self.body[offset:] == jump:
- self.body = self.body[:offset]
- self.holes.remove(hole)
-
@dataclasses.dataclass
class StencilGroup:
@@ -284,9 +223,7 @@ class StencilGroup:
_got: dict[str, int] = dataclasses.field(default_factory=dict, init=False)
_trampolines: set[int] = dataclasses.field(default_factory=set, init=False)
- def process_relocations(
- self, known_symbols: dict[str, int], *, alignment: int = 1, nop: bytes = b""
- ) -> None:
+ def process_relocations(self, known_symbols: dict[str, int]) -> None:
"""Fix up all GOT and internal relocations for this stencil group."""
for hole in self.code.holes.copy():
if (
@@ -306,8 +243,6 @@ class StencilGroup:
self._trampolines.add(ordinal)
hole.addend = ordinal
hole.symbol = None
- self.code.remove_jump()
- self.code.add_nops(nop=nop, alignment=alignment)
self.data.pad(8)
for stencil in [self.code, self.data]:
for hole in stencil.holes:
diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py
index 6ceb4404e74..ed10329d25d 100644
--- a/Tools/jit/_targets.py
+++ b/Tools/jit/_targets.py
@@ -10,8 +10,10 @@ import re
import sys
import tempfile
import typing
+import shlex
import _llvm
+import _optimizers
import _schema
import _stencils
import _writer
@@ -40,13 +42,15 @@ class _Target(typing.Generic[_S, _R]):
triple: str
condition: str
_: dataclasses.KW_ONLY
- alignment: int = 1
args: typing.Sequence[str] = ()
+ optimizer: type[_optimizers.Optimizer] = _optimizers.Optimizer
prefix: str = ""
stable: bool = False
debug: bool = False
verbose: bool = False
+ cflags: str = ""
known_symbols: dict[str, int] = dataclasses.field(default_factory=dict)
+ pyconfig_dir: pathlib.Path = pathlib.Path.cwd().resolve()
def _get_nop(self) -> bytes:
if re.fullmatch(r"aarch64-.*", self.triple):
@@ -57,13 +61,14 @@ class _Target(typing.Generic[_S, _R]):
raise ValueError(f"NOP not defined for {self.triple}")
return nop
- def _compute_digest(self, out: pathlib.Path) -> str:
+ def _compute_digest(self) -> str:
hasher = hashlib.sha256()
hasher.update(self.triple.encode())
hasher.update(self.debug.to_bytes())
+ hasher.update(self.cflags.encode())
# These dependencies are also reflected in _JITSources in regen.targets:
hasher.update(PYTHON_EXECUTOR_CASES_C_H.read_bytes())
- hasher.update((out / "pyconfig.h").read_bytes())
+ hasher.update((self.pyconfig_dir / "pyconfig.h").read_bytes())
for dirpath, _, filenames in sorted(os.walk(TOOLS_JIT)):
for filename in filenames:
hasher.update(pathlib.Path(dirpath, filename).read_bytes())
@@ -117,22 +122,23 @@ class _Target(typing.Generic[_S, _R]):
async def _compile(
self, opname: str, c: pathlib.Path, tempdir: pathlib.Path
) -> _stencils.StencilGroup:
+ s = tempdir / f"{opname}.s"
o = tempdir / f"{opname}.o"
- args = [
+ args_s = [
f"--target={self.triple}",
"-DPy_BUILD_CORE_MODULE",
"-D_DEBUG" if self.debug else "-DNDEBUG",
f"-D_JIT_OPCODE={opname}",
"-D_PyJIT_ACTIVE",
"-D_Py_JIT",
- "-I.",
+ f"-I{self.pyconfig_dir}",
f"-I{CPYTHON / 'Include'}",
f"-I{CPYTHON / 'Include' / 'internal'}",
f"-I{CPYTHON / 'Include' / 'internal' / 'mimalloc'}",
f"-I{CPYTHON / 'Python'}",
f"-I{CPYTHON / 'Tools' / 'jit'}",
"-O3",
- "-c",
+ "-S",
# Shorten full absolute file paths in the generated code (like the
# __FILE__ macro and assert failure messages) for reproducibility:
f"-ffile-prefix-map={CPYTHON}=.",
@@ -151,11 +157,16 @@ class _Target(typing.Generic[_S, _R]):
"-fno-stack-protector",
"-std=c11",
"-o",
- f"{o}",
+ f"{s}",
f"{c}",
*self.args,
+ # Allow user-provided CFLAGS to override any defaults
+ *shlex.split(self.cflags),
]
- await _llvm.run("clang", args, echo=self.verbose)
+ await _llvm.run("clang", args_s, echo=self.verbose)
+ self.optimizer(s, prefix=self.prefix).run()
+ args_o = [f"--target={self.triple}", "-c", "-o", f"{o}", f"{s}"]
+ await _llvm.run("clang", args_o, echo=self.verbose)
return await self._parse(o)
async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]:
@@ -184,29 +195,24 @@ class _Target(typing.Generic[_S, _R]):
tasks.append(group.create_task(coro, name=opname))
stencil_groups = {task.get_name(): task.result() for task in tasks}
for stencil_group in stencil_groups.values():
- stencil_group.process_relocations(
- known_symbols=self.known_symbols,
- alignment=self.alignment,
- nop=self._get_nop(),
- )
+ stencil_group.process_relocations(self.known_symbols)
return stencil_groups
def build(
self,
- out: pathlib.Path,
*,
comment: str = "",
force: bool = False,
- stencils_h: str = "jit_stencils.h",
+ jit_stencils: pathlib.Path,
) -> None:
"""Build jit_stencils.h in the given directory."""
+ jit_stencils.parent.mkdir(parents=True, exist_ok=True)
if not self.stable:
warning = f"JIT support for {self.triple} is still experimental!"
request = "Please report any issues you encounter.".center(len(warning))
outline = "=" * len(warning)
print("\n".join(["", outline, warning, request, outline, ""]))
- digest = f"// {self._compute_digest(out)}\n"
- jit_stencils = out / stencils_h
+ digest = f"// {self._compute_digest()}\n"
if (
not force
and jit_stencils.exists()
@@ -214,7 +220,7 @@ class _Target(typing.Generic[_S, _R]):
):
return
stencil_groups = ASYNCIO_RUNNER.run(self._build_stencils())
- jit_stencils_new = out / "jit_stencils.h.new"
+ jit_stencils_new = jit_stencils.parent / "jit_stencils.h.new"
try:
with jit_stencils_new.open("w") as file:
file.write(digest)
@@ -519,42 +525,43 @@ class _MachO(
def get_target(host: str) -> _COFF | _ELF | _MachO:
"""Build a _Target for the given host "triple" and options."""
+ optimizer: type[_optimizers.Optimizer]
target: _COFF | _ELF | _MachO
if re.fullmatch(r"aarch64-apple-darwin.*", host):
condition = "defined(__aarch64__) && defined(__APPLE__)"
- target = _MachO(host, condition, alignment=8, prefix="_")
+ optimizer = _optimizers.OptimizerAArch64
+ target = _MachO(host, condition, optimizer=optimizer, prefix="_")
elif re.fullmatch(r"aarch64-pc-windows-msvc", host):
args = ["-fms-runtime-lib=dll", "-fplt"]
condition = "defined(_M_ARM64)"
- target = _COFF(host, condition, alignment=8, args=args)
+ optimizer = _optimizers.OptimizerAArch64
+ target = _COFF(host, condition, args=args, optimizer=optimizer)
elif re.fullmatch(r"aarch64-.*-linux-gnu", host):
- args = [
- "-fpic",
- # On aarch64 Linux, intrinsics were being emitted and this flag
- # was required to disable them.
- "-mno-outline-atomics",
- ]
+ # -mno-outline-atomics: Keep intrinsics from being emitted.
+ args = ["-fpic", "-mno-outline-atomics"]
condition = "defined(__aarch64__) && defined(__linux__)"
- target = _ELF(host, condition, alignment=8, args=args)
+ optimizer = _optimizers.OptimizerAArch64
+ target = _ELF(host, condition, args=args, optimizer=optimizer)
elif re.fullmatch(r"i686-pc-windows-msvc", host):
- args = [
- "-DPy_NO_ENABLE_SHARED",
- # __attribute__((preserve_none)) is not supported
- "-Wno-ignored-attributes",
- ]
+ # -Wno-ignored-attributes: __attribute__((preserve_none)) is not supported here.
+ args = ["-DPy_NO_ENABLE_SHARED", "-Wno-ignored-attributes"]
+ optimizer = _optimizers.OptimizerX86
condition = "defined(_M_IX86)"
- target = _COFF(host, condition, args=args, prefix="_")
+ target = _COFF(host, condition, args=args, optimizer=optimizer, prefix="_")
elif re.fullmatch(r"x86_64-apple-darwin.*", host):
condition = "defined(__x86_64__) && defined(__APPLE__)"
- target = _MachO(host, condition, prefix="_")
+ optimizer = _optimizers.OptimizerX86
+ target = _MachO(host, condition, optimizer=optimizer, prefix="_")
elif re.fullmatch(r"x86_64-pc-windows-msvc", host):
args = ["-fms-runtime-lib=dll"]
condition = "defined(_M_X64)"
- target = _COFF(host, condition, args=args)
+ optimizer = _optimizers.OptimizerX8664Windows
+ target = _COFF(host, condition, args=args, optimizer=optimizer)
elif re.fullmatch(r"x86_64-.*-linux-gnu", host):
args = ["-fno-pic", "-mcmodel=medium", "-mlarge-data-threshold=0"]
condition = "defined(__x86_64__) && defined(__linux__)"
- target = _ELF(host, condition, args=args)
+ optimizer = _optimizers.OptimizerX86
+ target = _ELF(host, condition, args=args, optimizer=optimizer)
else:
raise ValueError(host)
return target
diff --git a/Tools/jit/build.py b/Tools/jit/build.py
index 49b08f477db..a0733005929 100644
--- a/Tools/jit/build.py
+++ b/Tools/jit/build.py
@@ -8,7 +8,6 @@ import sys
import _targets
if __name__ == "__main__":
- out = pathlib.Path.cwd().resolve()
comment = f"$ {shlex.join([pathlib.Path(sys.executable).name] + sys.argv)}"
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
@@ -24,20 +23,38 @@ if __name__ == "__main__":
"-f", "--force", action="store_true", help="force the entire JIT to be rebuilt"
)
parser.add_argument(
+ "-o",
+ "--output-dir",
+ help="where to output generated files",
+ required=True,
+ type=lambda p: pathlib.Path(p).resolve(),
+ )
+ parser.add_argument(
+ "-p",
+ "--pyconfig-dir",
+ help="where to find pyconfig.h",
+ required=True,
+ type=lambda p: pathlib.Path(p).resolve(),
+ )
+ parser.add_argument(
"-v", "--verbose", action="store_true", help="echo commands as they are run"
)
+ parser.add_argument(
+ "--cflags", help="additional flags to pass to the compiler", default=""
+ )
args = parser.parse_args()
for target in args.target:
target.debug = args.debug
target.force = args.force
target.verbose = args.verbose
+ target.cflags = args.cflags
+ target.pyconfig_dir = args.pyconfig_dir
target.build(
- out,
comment=comment,
- stencils_h=f"jit_stencils-{target.triple}.h",
force=args.force,
+ jit_stencils=args.output_dir / f"jit_stencils-{target.triple}.h",
)
- jit_stencils_h = out / "jit_stencils.h"
+ jit_stencils_h = args.output_dir / "jit_stencils.h"
lines = [f"// {comment}\n"]
guard = "#if"
for target in args.target:
diff --git a/Tools/jit/template.c b/Tools/jit/template.c
index 68cf75942d8..5ee26f93f1e 100644
--- a/Tools/jit/template.c
+++ b/Tools/jit/template.c
@@ -50,13 +50,16 @@
#define GOTO_TIER_TWO(EXECUTOR) \
do { \
OPT_STAT_INC(traces_executed); \
- jit_func_preserve_none jitted = (EXECUTOR)->jit_side_entry; \
+ _PyExecutorObject *_executor = (EXECUTOR); \
+ tstate->current_executor = (PyObject *)_executor; \
+ jit_func_preserve_none jitted = _executor->jit_side_entry; \
__attribute__((musttail)) return jitted(frame, stack_pointer, tstate); \
} while (0)
#undef GOTO_TIER_ONE
#define GOTO_TIER_ONE(TARGET) \
do { \
+ tstate->current_executor = NULL; \
_PyFrame_SetStackPointer(frame, stack_pointer); \
return TARGET; \
} while (0)
diff --git a/Tools/msi/dev/dev_files.wxs b/Tools/msi/dev/dev_files.wxs
index 4357dc86d9d..21f9c848cc6 100644
--- a/Tools/msi/dev/dev_files.wxs
+++ b/Tools/msi/dev/dev_files.wxs
@@ -3,7 +3,7 @@
<Fragment>
<ComponentGroup Id="dev_pyconfig">
<Component Id="include_pyconfig.h" Directory="include" Guid="*">
- <File Id="include_pyconfig.h" Name="pyconfig.h" Source="pyconfig.h" KeyPath="yes" />
+ <File Id="include_pyconfig.h" Name="pyconfig.h" Source="!(bindpath.src)PC\pyconfig.h" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
diff --git a/Tools/msi/freethreaded/freethreaded_files.wxs b/Tools/msi/freethreaded/freethreaded_files.wxs
index b3ce28e7aed..0707e77b5e9 100644
--- a/Tools/msi/freethreaded/freethreaded_files.wxs
+++ b/Tools/msi/freethreaded/freethreaded_files.wxs
@@ -103,7 +103,7 @@
</ComponentGroup>
</Fragment>
- <?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_uuid;_wmi;_zoneinfo;_testcapi;_ctypes_test;_testbuffer;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testinternalcapi;_testclinic;_testclinic_limited;_tkinter ?>
+ <?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_remote_debugging;_uuid;_wmi;_zoneinfo;_zstd;_testcapi;_ctypes_test;_testbuffer;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testinternalcapi;_testclinic;_testclinic_limited;_tkinter ?>
<Fragment>
<DirectoryRef Id="Lib_venv_scripts_nt__freethreaded" />
diff --git a/Tools/msi/lib/lib.wixproj b/Tools/msi/lib/lib.wixproj
index 02078e503d7..3ea46dd40ea 100644
--- a/Tools/msi/lib/lib.wixproj
+++ b/Tools/msi/lib/lib.wixproj
@@ -15,12 +15,11 @@
<EmbeddedResource Include="*.wxl" />
</ItemGroup>
<ItemGroup>
- <ExcludeFolders Include="Lib\test;Lib\tests;Lib\tkinter;Lib\idlelib;Lib\turtledemo" />
+ <ExcludeFolders Include="Lib\site-packages;Lib\test;Lib\tests;Lib\tkinter;Lib\idlelib;Lib\turtledemo" />
<InstallFiles Include="$(PySourcePath)Lib\**\*"
Exclude="$(PySourcePath)Lib\**\*.pyc;
$(PySourcePath)Lib\**\*.pyo;
$(PySourcePath)Lib\turtle.py;
- $(PySourcePath)Lib\site-packages\README;
@(ExcludeFolders->'$(PySourcePath)%(Identity)\*');
@(ExcludeFolders->'$(PySourcePath)%(Identity)\**\*')">
<SourceBase>$(PySourcePath)Lib</SourceBase>
diff --git a/Tools/msi/lib/lib_files.wxs b/Tools/msi/lib/lib_files.wxs
index b8e16b5fe23..4d44299f783 100644
--- a/Tools/msi/lib/lib_files.wxs
+++ b/Tools/msi/lib/lib_files.wxs
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
- <?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_uuid;_wmi;_zoneinfo ?>
+ <?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_remote_debugging;_uuid;_wmi;_zoneinfo;_zstd ?>
<Fragment>
<DirectoryRef Id="Lib_venv_scripts_nt" />
diff --git a/Tools/patchcheck/patchcheck.py b/Tools/patchcheck/patchcheck.py
index 0dcf6ef844a..afd010a5254 100755
--- a/Tools/patchcheck/patchcheck.py
+++ b/Tools/patchcheck/patchcheck.py
@@ -53,19 +53,43 @@ def get_git_branch():
def get_git_upstream_remote():
- """Get the remote name to use for upstream branches
+ """
+ Get the remote name to use for upstream branches
- Uses "upstream" if it exists, "origin" otherwise
+ Check for presence of "https://github.com/python/cpython" remote URL.
+ If only one is found, return that remote name. If multiple are found,
+ check for and return "upstream", "origin", or "python", in that
+ order. Raise an error if no valid matches are found.
"""
- cmd = "git remote get-url upstream".split()
- try:
- subprocess.check_output(cmd,
- stderr=subprocess.DEVNULL,
- cwd=SRCDIR,
- encoding='UTF-8')
- except subprocess.CalledProcessError:
- return "origin"
- return "upstream"
+ cmd = "git remote -v".split()
+ output = subprocess.check_output(
+ cmd,
+ stderr=subprocess.DEVNULL,
+ cwd=SRCDIR,
+ encoding="UTF-8"
+ )
+ # Filter to desired remotes, accounting for potential uppercasing
+ filtered_remotes = {
+ remote.split("\t")[0].lower() for remote in output.split('\n')
+ if "python/cpython" in remote.lower() and remote.endswith("(fetch)")
+ }
+ if len(filtered_remotes) == 1:
+ [remote] = filtered_remotes
+ return remote
+ for remote_name in ["upstream", "origin", "python"]:
+ if remote_name in filtered_remotes:
+ return remote_name
+ remotes_found = "\n".join(
+ {remote for remote in output.split('\n') if remote.endswith("(fetch)")}
+ )
+ raise ValueError(
+ f"Patchcheck was unable to find an unambiguous upstream remote, "
+ f"with URL matching 'https://github.com/python/cpython'. "
+ f"For help creating an upstream remote, see Dev Guide: "
+ f"https://devguide.python.org/getting-started/"
+ f"git-boot-camp/#cloning-a-forked-cpython-repository "
+ f"\nRemotes found: \n{remotes_found}"
+ )
def get_git_remote_default_branch(remote_name):
diff --git a/Tools/peg_generator/pegen/build.py b/Tools/peg_generator/pegen/build.py
index 41338c29bdd..be289c352de 100644
--- a/Tools/peg_generator/pegen/build.py
+++ b/Tools/peg_generator/pegen/build.py
@@ -108,6 +108,8 @@ def compile_c_extension(
extra_compile_args.append("-DPy_BUILD_CORE_MODULE")
# Define _Py_TEST_PEGEN to not call PyAST_Validate() in Parser/pegen.c
extra_compile_args.append("-D_Py_TEST_PEGEN")
+ if sys.platform == "win32" and sysconfig.get_config_var("Py_GIL_DISABLED"):
+ extra_compile_args.append("-DPy_GIL_DISABLED")
extra_link_args = get_extra_flags("LDFLAGS", "PY_LDFLAGS_NODIST")
if keep_asserts:
extra_compile_args.append("-UNDEBUG")
diff --git a/Tools/peg_generator/pegen/c_generator.py b/Tools/peg_generator/pegen/c_generator.py
index 2be85a163b4..04f66eec1a0 100644
--- a/Tools/peg_generator/pegen/c_generator.py
+++ b/Tools/peg_generator/pegen/c_generator.py
@@ -44,7 +44,7 @@ EXTENSION_PREFIX = """\
# define MAXSTACK 4000
# endif
#else
-# define MAXSTACK 4000
+# define MAXSTACK 6000
#endif
"""
@@ -214,33 +214,47 @@ class CCallMakerVisitor(GrammarVisitor):
call.assigned_variable_type = node.type
return call
+ def assert_no_undefined_behavior(
+ self, call: FunctionCall, wrapper: str, expected_rtype: str | None,
+ ) -> None:
+ if call.return_type != expected_rtype:
+ raise RuntimeError(
+ f"{call.function} return type is incompatible with {wrapper}: "
+ f"expect: {expected_rtype}, actual: {call.return_type}"
+ )
+
def lookahead_call_helper(self, node: Lookahead, positive: int) -> FunctionCall:
call = self.generate_call(node.node)
- if call.nodetype == NodeTypes.NAME_TOKEN:
- return FunctionCall(
- function=f"_PyPegen_lookahead_with_name",
- arguments=[positive, call.function, *call.arguments],
- return_type="int",
- )
+ comment = None
+ if call.nodetype is NodeTypes.NAME_TOKEN:
+ function = "_PyPegen_lookahead_for_expr"
+ self.assert_no_undefined_behavior(call, function, "expr_ty")
+ elif call.nodetype is NodeTypes.STRING_TOKEN:
+ # _PyPegen_string_token() returns 'void *' instead of 'Token *';
+ # in addition, the overall function call would return 'expr_ty'.
+ assert call.function == "_PyPegen_string_token"
+ function = "_PyPegen_lookahead"
+ self.assert_no_undefined_behavior(call, function, "expr_ty")
elif call.nodetype == NodeTypes.SOFT_KEYWORD:
- return FunctionCall(
- function=f"_PyPegen_lookahead_with_string",
- arguments=[positive, call.function, *call.arguments],
- return_type="int",
- )
+ function = "_PyPegen_lookahead_with_string"
+ self.assert_no_undefined_behavior(call, function, "expr_ty")
elif call.nodetype in {NodeTypes.GENERIC_TOKEN, NodeTypes.KEYWORD}:
- return FunctionCall(
- function=f"_PyPegen_lookahead_with_int",
- arguments=[positive, call.function, *call.arguments],
- return_type="int",
- comment=f"token={node.node}",
- )
+ function = "_PyPegen_lookahead_with_int"
+ self.assert_no_undefined_behavior(call, function, "Token *")
+ comment = f"token={node.node}"
+ elif call.return_type == "expr_ty":
+ function = "_PyPegen_lookahead_for_expr"
+ elif call.return_type == "stmt_ty":
+ function = "_PyPegen_lookahead_for_stmt"
else:
- return FunctionCall(
- function=f"_PyPegen_lookahead",
- arguments=[positive, f"(void *(*)(Parser *)) {call.function}", *call.arguments],
- return_type="int",
- )
+ function = "_PyPegen_lookahead"
+ self.assert_no_undefined_behavior(call, function, None)
+ return FunctionCall(
+ function=function,
+ arguments=[positive, call.function, *call.arguments],
+ return_type="int",
+ comment=comment,
+ )
def visit_PositiveLookahead(self, node: PositiveLookahead) -> FunctionCall:
return self.lookahead_call_helper(node, 1)
diff --git a/Tools/peg_generator/pegen/parser_generator.py b/Tools/peg_generator/pegen/parser_generator.py
index 6ce0649aefe..52ae743c26b 100644
--- a/Tools/peg_generator/pegen/parser_generator.py
+++ b/Tools/peg_generator/pegen/parser_generator.py
@@ -81,6 +81,11 @@ class RuleCheckingVisitor(GrammarVisitor):
self.tokens.add("FSTRING_START")
self.tokens.add("FSTRING_END")
self.tokens.add("FSTRING_MIDDLE")
+ # If python < 3.14 add the virtual tstring tokens
+ if sys.version_info < (3, 14, 0, 'beta', 1):
+ self.tokens.add("TSTRING_START")
+ self.tokens.add("TSTRING_END")
+ self.tokens.add("TSTRING_MIDDLE")
def visit_NameLeaf(self, node: NameLeaf) -> None:
if node.value not in self.rules and node.value not in self.tokens:
diff --git a/Tools/requirements-dev.txt b/Tools/requirements-dev.txt
index e5badaccadd..0beaab2d3e7 100644
--- a/Tools/requirements-dev.txt
+++ b/Tools/requirements-dev.txt
@@ -1,7 +1,7 @@
# Requirements file for external linters and checks we run on
# Tools/clinic, Tools/cases_generator/, and Tools/peg_generator/ in CI
-mypy==1.13
+mypy==1.16.1
# needed for peg_generator:
-types-psutil==6.0.0.20240901
-types-setuptools==74.0.0.20240831
+types-psutil==7.0.0.20250601
+types-setuptools==80.9.0.20250529
diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py
index 68cfad3f92c..905af9dcfd8 100644
--- a/Tools/scripts/summarize_stats.py
+++ b/Tools/scripts/summarize_stats.py
@@ -492,7 +492,7 @@ class Stats:
): (trace_too_long, attempts),
Doc(
"Trace too short",
- "A potential trace is abandoned because it it too short.",
+ "A potential trace is abandoned because it is too short.",
): (trace_too_short, attempts),
Doc(
"Inner loop found", "A trace is truncated because it has an inner loop"
diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt
index 21224e490b8..93421b623b9 100644
--- a/Tools/tsan/suppressions_free_threading.txt
+++ b/Tools/tsan/suppressions_free_threading.txt
@@ -12,15 +12,12 @@
# These warnings trigger directly in a CPython function.
-race_top:assign_version_tag
-race_top:_Py_slot_tp_getattr_hook
race_top:dump_traceback
race_top:fatal_error
race_top:_PyFrame_GetCode
race_top:_PyFrame_Initialize
race_top:_PyObject_TryGetInstanceAttribute
race_top:PyUnstable_InterpreterFrame_GetLine
-race_top:type_modified_unlocked
race_top:write_thread_id
# gh-129068: race on shared range iterators (test_free_threading.test_zip.ZipThreading.test_threading)
@@ -29,9 +26,6 @@ race_top:rangeiter_next
# gh-129748: test.test_free_threading.test_slots.TestSlots.test_object
race_top:mi_block_set_nextx
-# gh-127266: type slot updates are not thread-safe (test_opcache.test_load_attr_method_lazy_dict)
-race_top:update_one_slot
-
# https://gist.github.com/mpage/6962e8870606cfc960e159b407a0cb40
thread:pthread_create
@@ -46,4 +40,11 @@ race:list_inplace_repeat_lock_held
# PyObject_Realloc internally does memcpy which isn't atomic so can race
# with non-locking reads. See #132070
-race:PyObject_Realloc \ No newline at end of file
+race:PyObject_Realloc
+
+# gh-133467. Some of these could be hard to trigger.
+race_top:_Py_slot_tp_getattr_hook
+race_top:slot_tp_descr_get
+race_top:type_set_name
+race_top:set_tp_bases
+race_top:type_set_bases_unlocked
diff --git a/Tools/unicode/makeunicodedata.py b/Tools/unicode/makeunicodedata.py
index 889ae8fc869..d4cca68c3e3 100644
--- a/Tools/unicode/makeunicodedata.py
+++ b/Tools/unicode/makeunicodedata.py
@@ -43,7 +43,7 @@ VERSION = "3.3"
# When changing UCD version please update
# * Doc/library/stdtypes.rst, and
# * Doc/library/unicodedata.rst
-# * Doc/reference/lexical_analysis.rst (two occurrences)
+# * Doc/reference/lexical_analysis.rst (three occurrences)
UNIDATA_VERSION = "16.0.0"
UNICODE_DATA = "UnicodeData%s.txt"
COMPOSITION_EXCLUSIONS = "CompositionExclusions%s.txt"
diff --git a/Tools/wasm/.editorconfig b/Tools/wasm/emscripten/.editorconfig
index 4de5fe5954d..4de5fe5954d 100644
--- a/Tools/wasm/.editorconfig
+++ b/Tools/wasm/emscripten/.editorconfig
diff --git a/Tools/wasm/emscripten/__main__.py b/Tools/wasm/emscripten/__main__.py
index 849bd5de44e..c0d58aeaadd 100644
--- a/Tools/wasm/emscripten/__main__.py
+++ b/Tools/wasm/emscripten/__main__.py
@@ -167,11 +167,12 @@ def make_build_python(context, working_dir):
@subdir(HOST_BUILD_DIR, clean_ok=True)
def make_emscripten_libffi(context, working_dir):
shutil.rmtree(working_dir / "libffi-3.4.6", ignore_errors=True)
- with tempfile.NamedTemporaryFile(suffix=".tar.gz") as tmp_file:
+ with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete_on_close=False) as tmp_file:
with urlopen(
"https://github.com/libffi/libffi/releases/download/v3.4.6/libffi-3.4.6.tar.gz"
) as response:
shutil.copyfileobj(response, tmp_file)
+ tmp_file.close()
shutil.unpack_archive(tmp_file.name, working_dir)
call(
[EMSCRIPTEN_DIR / "make_libffi.sh"],
diff --git a/Tools/wasm/mypy.ini b/Tools/wasm/mypy.ini
deleted file mode 100644
index 4de0a30c260..00000000000
--- a/Tools/wasm/mypy.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-[mypy]
-files = Tools/wasm/wasm_*.py
-pretty = True
-show_traceback = True
-
-# Make sure the wasm can be run using Python 3.8:
-python_version = 3.8
-
-# Be strict...
-strict = True
-enable_error_code = truthy-bool,ignore-without-code
diff --git a/Tools/wasm/wasi-env b/Tools/wasm/wasi-env
index 4c5078a1f67..08d4f499baa 100755
--- a/Tools/wasm/wasi-env
+++ b/Tools/wasm/wasi-env
@@ -1,7 +1,8 @@
#!/bin/sh
set -e
-# NOTE: to be removed once no longer used in https://github.com/python/buildmaster-config/blob/main/master/custom/factories.py .
+# NOTE: to be removed once no longer used in https://github.com/python/buildmaster-config/blob/main/master/custom/factories.py ;
+# expected in Python 3.18 as 3.13 is when `wasi.py` was introduced.
# function
usage() {
diff --git a/Tools/wasm/wasi.py b/Tools/wasm/wasi.py
index a742043e4be..b49b27cbbbe 100644
--- a/Tools/wasm/wasi.py
+++ b/Tools/wasm/wasi.py
@@ -1,367 +1,10 @@
-#!/usr/bin/env python3
-
-import argparse
-import contextlib
-import functools
-import os
-try:
- from os import process_cpu_count as cpu_count
-except ImportError:
- from os import cpu_count
-import pathlib
-import shutil
-import subprocess
-import sys
-import sysconfig
-import tempfile
-
-
-CHECKOUT = pathlib.Path(__file__).parent.parent.parent
-
-CROSS_BUILD_DIR = CHECKOUT / "cross-build"
-BUILD_DIR = CROSS_BUILD_DIR / "build"
-
-LOCAL_SETUP = CHECKOUT / "Modules" / "Setup.local"
-LOCAL_SETUP_MARKER = "# Generated by Tools/wasm/wasi.py\n".encode("utf-8")
-
-WASMTIME_VAR_NAME = "WASMTIME"
-WASMTIME_HOST_RUNNER_VAR = f"{{{WASMTIME_VAR_NAME}}}"
-
-
-def updated_env(updates={}):
- """Create a new dict representing the environment to use.
-
- The changes made to the execution environment are printed out.
- """
- env_defaults = {}
- # https://reproducible-builds.org/docs/source-date-epoch/
- git_epoch_cmd = ["git", "log", "-1", "--pretty=%ct"]
- try:
- epoch = subprocess.check_output(git_epoch_cmd, encoding="utf-8").strip()
- env_defaults["SOURCE_DATE_EPOCH"] = epoch
- except subprocess.CalledProcessError:
- pass # Might be building from a tarball.
- # This layering lets SOURCE_DATE_EPOCH from os.environ takes precedence.
- environment = env_defaults | os.environ | updates
-
- env_diff = {}
- for key, value in environment.items():
- if os.environ.get(key) != value:
- env_diff[key] = value
-
- print("🌎 Environment changes:")
- for key in sorted(env_diff.keys()):
- print(f" {key}={env_diff[key]}")
-
- return environment
-
-
-def subdir(working_dir, *, clean_ok=False):
- """Decorator to change to a working directory."""
- def decorator(func):
- @functools.wraps(func)
- def wrapper(context):
- nonlocal working_dir
-
- if callable(working_dir):
- working_dir = working_dir(context)
- try:
- tput_output = subprocess.check_output(["tput", "cols"],
- encoding="utf-8")
- except subprocess.CalledProcessError:
- terminal_width = 80
- else:
- terminal_width = int(tput_output.strip())
- print("⎯" * terminal_width)
- print("📁", working_dir)
- if (clean_ok and getattr(context, "clean", False) and
- working_dir.exists()):
- print(f"🚮 Deleting directory (--clean)...")
- shutil.rmtree(working_dir)
-
- working_dir.mkdir(parents=True, exist_ok=True)
-
- with contextlib.chdir(working_dir):
- return func(context, working_dir)
-
- return wrapper
-
- return decorator
-
-
-def call(command, *, quiet, **kwargs):
- """Execute a command.
-
- If 'quiet' is true, then redirect stdout and stderr to a temporary file.
- """
- print("❯", " ".join(map(str, command)))
- if not quiet:
- stdout = None
- stderr = None
- else:
- stdout = tempfile.NamedTemporaryFile("w", encoding="utf-8",
- delete=False,
- prefix="cpython-wasi-",
- suffix=".log")
- stderr = subprocess.STDOUT
- print(f"📝 Logging output to {stdout.name} (--quiet)...")
-
- subprocess.check_call(command, **kwargs, stdout=stdout, stderr=stderr)
-
-
-def build_platform():
- """The name of the build/host platform."""
- # Can also be found via `config.guess`.`
- return sysconfig.get_config_var("BUILD_GNU_TYPE")
-
-
-def build_python_path():
- """The path to the build Python binary."""
- binary = BUILD_DIR / "python"
- if not binary.is_file():
- binary = binary.with_suffix(".exe")
- if not binary.is_file():
- raise FileNotFoundError("Unable to find `python(.exe)` in "
- f"{BUILD_DIR}")
-
- return binary
-
-
-@subdir(BUILD_DIR, clean_ok=True)
-def configure_build_python(context, working_dir):
- """Configure the build/host Python."""
- if LOCAL_SETUP.exists():
- print(f"👍 {LOCAL_SETUP} exists ...")
- else:
- print(f"📝 Touching {LOCAL_SETUP} ...")
- LOCAL_SETUP.write_bytes(LOCAL_SETUP_MARKER)
-
- configure = [os.path.relpath(CHECKOUT / 'configure', working_dir)]
- if context.args:
- configure.extend(context.args)
-
- call(configure, quiet=context.quiet)
-
-
-@subdir(BUILD_DIR)
-def make_build_python(context, working_dir):
- """Make/build the build Python."""
- call(["make", "--jobs", str(cpu_count()), "all"],
- quiet=context.quiet)
-
- binary = build_python_path()
- cmd = [binary, "-c",
- "import sys; "
- "print(f'{sys.version_info.major}.{sys.version_info.minor}')"]
- version = subprocess.check_output(cmd, encoding="utf-8").strip()
-
- print(f"🎉 {binary} {version}")
-
-
-def find_wasi_sdk():
- """Find the path to wasi-sdk."""
- if wasi_sdk_path := os.environ.get("WASI_SDK_PATH"):
- return pathlib.Path(wasi_sdk_path)
- elif (default_path := pathlib.Path("/opt/wasi-sdk")).exists():
- return default_path
-
-
-def wasi_sdk_env(context):
- """Calculate environment variables for building with wasi-sdk."""
- wasi_sdk_path = context.wasi_sdk_path
- sysroot = wasi_sdk_path / "share" / "wasi-sysroot"
- env = {"CC": "clang", "CPP": "clang-cpp", "CXX": "clang++",
- "AR": "llvm-ar", "RANLIB": "ranlib"}
-
- for env_var, binary_name in list(env.items()):
- env[env_var] = os.fsdecode(wasi_sdk_path / "bin" / binary_name)
-
- if wasi_sdk_path != pathlib.Path("/opt/wasi-sdk"):
- for compiler in ["CC", "CPP", "CXX"]:
- env[compiler] += f" --sysroot={sysroot}"
-
- env["PKG_CONFIG_PATH"] = ""
- env["PKG_CONFIG_LIBDIR"] = os.pathsep.join(
- map(os.fsdecode,
- [sysroot / "lib" / "pkgconfig",
- sysroot / "share" / "pkgconfig"]))
- env["PKG_CONFIG_SYSROOT_DIR"] = os.fsdecode(sysroot)
-
- env["WASI_SDK_PATH"] = os.fsdecode(wasi_sdk_path)
- env["WASI_SYSROOT"] = os.fsdecode(sysroot)
-
- env["PATH"] = os.pathsep.join([os.fsdecode(wasi_sdk_path / "bin"),
- os.environ["PATH"]])
-
- return env
-
-
-@subdir(lambda context: CROSS_BUILD_DIR / context.host_triple, clean_ok=True)
-def configure_wasi_python(context, working_dir):
- """Configure the WASI/host build."""
- if not context.wasi_sdk_path or not context.wasi_sdk_path.exists():
- raise ValueError("WASI-SDK not found; "
- "download from "
- "https://github.com/WebAssembly/wasi-sdk and/or "
- "specify via $WASI_SDK_PATH or --wasi-sdk")
-
- config_site = os.fsdecode(CHECKOUT / "Tools" / "wasm" / "config.site-wasm32-wasi")
-
- wasi_build_dir = working_dir.relative_to(CHECKOUT)
-
- python_build_dir = BUILD_DIR / "build"
- lib_dirs = list(python_build_dir.glob("lib.*"))
- assert len(lib_dirs) == 1, f"Expected a single lib.* directory in {python_build_dir}"
- lib_dir = os.fsdecode(lib_dirs[0])
- pydebug = lib_dir.endswith("-pydebug")
- python_version = lib_dir.removesuffix("-pydebug").rpartition("-")[-1]
- sysconfig_data = f"{wasi_build_dir}/build/lib.wasi-wasm32-{python_version}"
- if pydebug:
- sysconfig_data += "-pydebug"
-
- # Use PYTHONPATH to include sysconfig data which must be anchored to the
- # WASI guest's `/` directory.
- args = {"GUEST_DIR": "/",
- "HOST_DIR": CHECKOUT,
- "ENV_VAR_NAME": "PYTHONPATH",
- "ENV_VAR_VALUE": f"/{sysconfig_data}",
- "PYTHON_WASM": working_dir / "python.wasm"}
- # Check dynamically for wasmtime in case it was specified manually via
- # `--host-runner`.
- if WASMTIME_HOST_RUNNER_VAR in context.host_runner:
- if wasmtime := shutil.which("wasmtime"):
- args[WASMTIME_VAR_NAME] = wasmtime
- else:
- raise FileNotFoundError("wasmtime not found; download from "
- "https://github.com/bytecodealliance/wasmtime")
- host_runner = context.host_runner.format_map(args)
- env_additions = {"CONFIG_SITE": config_site, "HOSTRUNNER": host_runner}
- build_python = os.fsdecode(build_python_path())
- # The path to `configure` MUST be relative, else `python.wasm` is unable
- # to find the stdlib due to Python not recognizing that it's being
- # executed from within a checkout.
- configure = [os.path.relpath(CHECKOUT / 'configure', working_dir),
- f"--host={context.host_triple}",
- f"--build={build_platform()}",
- f"--with-build-python={build_python}"]
- if pydebug:
- configure.append("--with-pydebug")
- if context.args:
- configure.extend(context.args)
- call(configure,
- env=updated_env(env_additions | wasi_sdk_env(context)),
- quiet=context.quiet)
-
- python_wasm = working_dir / "python.wasm"
- exec_script = working_dir / "python.sh"
- with exec_script.open("w", encoding="utf-8") as file:
- file.write(f'#!/bin/sh\nexec {host_runner} {python_wasm} "$@"\n')
- exec_script.chmod(0o755)
- print(f"🏃‍♀️ Created {exec_script} ... ")
- sys.stdout.flush()
-
-
-@subdir(lambda context: CROSS_BUILD_DIR / context.host_triple)
-def make_wasi_python(context, working_dir):
- """Run `make` for the WASI/host build."""
- call(["make", "--jobs", str(cpu_count()), "all"],
- env=updated_env(),
- quiet=context.quiet)
-
- exec_script = working_dir / "python.sh"
- subprocess.check_call([exec_script, "--version"])
- print(
- f"🎉 Use '{exec_script.relative_to(context.init_dir)}' "
- "to run CPython in wasm runtime"
- )
-
-
-def build_all(context):
- """Build everything."""
- steps = [configure_build_python, make_build_python, configure_wasi_python,
- make_wasi_python]
- for step in steps:
- step(context)
-
-def clean_contents(context):
- """Delete all files created by this script."""
- if CROSS_BUILD_DIR.exists():
- print(f"🧹 Deleting {CROSS_BUILD_DIR} ...")
- shutil.rmtree(CROSS_BUILD_DIR)
-
- if LOCAL_SETUP.exists():
- with LOCAL_SETUP.open("rb") as file:
- if file.read(len(LOCAL_SETUP_MARKER)) == LOCAL_SETUP_MARKER:
- print(f"🧹 Deleting generated {LOCAL_SETUP} ...")
-
-
-def main():
- default_host_runner = (f"{WASMTIME_HOST_RUNNER_VAR} run "
- # Make sure the stack size will work for a pydebug
- # build.
- # Use 16 MiB stack.
- "--wasm max-wasm-stack=16777216 "
- # Enable thread support; causes use of preview1.
- #"--wasm threads=y --wasi threads=y "
- # Map the checkout to / to load the stdlib from /Lib.
- "--dir {HOST_DIR}::{GUEST_DIR} "
- # Set PYTHONPATH to the sysconfig data.
- "--env {ENV_VAR_NAME}={ENV_VAR_VALUE}")
-
- parser = argparse.ArgumentParser()
- subcommands = parser.add_subparsers(dest="subcommand")
- build = subcommands.add_parser("build", help="Build everything")
- configure_build = subcommands.add_parser("configure-build-python",
- help="Run `configure` for the "
- "build Python")
- make_build = subcommands.add_parser("make-build-python",
- help="Run `make` for the build Python")
- configure_host = subcommands.add_parser("configure-host",
- help="Run `configure` for the "
- "host/WASI (pydebug builds "
- "are inferred from the build "
- "Python)")
- make_host = subcommands.add_parser("make-host",
- help="Run `make` for the host/WASI")
- clean = subcommands.add_parser("clean", help="Delete files and directories "
- "created by this script")
- for subcommand in build, configure_build, make_build, configure_host, make_host:
- subcommand.add_argument("--quiet", action="store_true", default=False,
- dest="quiet",
- help="Redirect output from subprocesses to a log file")
- for subcommand in configure_build, configure_host:
- subcommand.add_argument("--clean", action="store_true", default=False,
- dest="clean",
- help="Delete any relevant directories before building")
- for subcommand in build, configure_build, configure_host:
- subcommand.add_argument("args", nargs="*",
- help="Extra arguments to pass to `configure`")
- for subcommand in build, configure_host:
- subcommand.add_argument("--wasi-sdk", type=pathlib.Path,
- dest="wasi_sdk_path",
- default=find_wasi_sdk(),
- help="Path to wasi-sdk; defaults to "
- "$WASI_SDK_PATH or /opt/wasi-sdk")
- subcommand.add_argument("--host-runner", action="store",
- default=default_host_runner, dest="host_runner",
- help="Command template for running the WASI host "
- "(default designed for wasmtime 14 or newer: "
- f"`{default_host_runner}`)")
- for subcommand in build, configure_host, make_host:
- subcommand.add_argument("--host-triple", action="store", default="wasm32-wasip1",
- help="The target triple for the WASI host build")
-
- context = parser.parse_args()
- context.init_dir = pathlib.Path().absolute()
-
- dispatch = {"configure-build-python": configure_build_python,
- "make-build-python": make_build_python,
- "configure-host": configure_wasi_python,
- "make-host": make_wasi_python,
- "build": build_all,
- "clean": clean_contents}
- dispatch[context.subcommand](context)
+if __name__ == "__main__":
+ import pathlib
+ import runpy
+ import sys
+ print("⚠️ WARNING: This script is deprecated and slated for removal in Python 3.20; "
+ "execute the `wasi/` directory instead (i.e. `python Tools/wasm/wasi`)\n",
+ file=sys.stderr)
-if __name__ == "__main__":
- main()
+ runpy.run_path(pathlib.Path(__file__).parent / "wasi", run_name="__main__")
diff --git a/Tools/wasm/wasi/__main__.py b/Tools/wasm/wasi/__main__.py
new file mode 100644
index 00000000000..54ccc95157d
--- /dev/null
+++ b/Tools/wasm/wasi/__main__.py
@@ -0,0 +1,374 @@
+#!/usr/bin/env python3
+
+import argparse
+import contextlib
+import functools
+import os
+try:
+ from os import process_cpu_count as cpu_count
+except ImportError:
+ from os import cpu_count
+import pathlib
+import shutil
+import subprocess
+import sys
+import sysconfig
+import tempfile
+
+
+CHECKOUT = pathlib.Path(__file__).parent.parent.parent.parent
+assert (CHECKOUT / "configure").is_file(), "Please update the location of the file"
+
+CROSS_BUILD_DIR = CHECKOUT / "cross-build"
+BUILD_DIR = CROSS_BUILD_DIR / "build"
+
+LOCAL_SETUP = CHECKOUT / "Modules" / "Setup.local"
+LOCAL_SETUP_MARKER = "# Generated by Tools/wasm/wasi.py\n".encode("utf-8")
+
+WASMTIME_VAR_NAME = "WASMTIME"
+WASMTIME_HOST_RUNNER_VAR = f"{{{WASMTIME_VAR_NAME}}}"
+
+
+def updated_env(updates={}):
+ """Create a new dict representing the environment to use.
+
+ The changes made to the execution environment are printed out.
+ """
+ env_defaults = {}
+ # https://reproducible-builds.org/docs/source-date-epoch/
+ git_epoch_cmd = ["git", "log", "-1", "--pretty=%ct"]
+ try:
+ epoch = subprocess.check_output(git_epoch_cmd, encoding="utf-8").strip()
+ env_defaults["SOURCE_DATE_EPOCH"] = epoch
+ except subprocess.CalledProcessError:
+ pass # Might be building from a tarball.
+ # This layering lets SOURCE_DATE_EPOCH from os.environ takes precedence.
+ environment = env_defaults | os.environ | updates
+
+ env_diff = {}
+ for key, value in environment.items():
+ if os.environ.get(key) != value:
+ env_diff[key] = value
+
+ print("🌎 Environment changes:")
+ for key in sorted(env_diff.keys()):
+ print(f" {key}={env_diff[key]}")
+
+ return environment
+
+
+def subdir(working_dir, *, clean_ok=False):
+ """Decorator to change to a working directory."""
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(context):
+ nonlocal working_dir
+
+ if callable(working_dir):
+ working_dir = working_dir(context)
+ try:
+ tput_output = subprocess.check_output(["tput", "cols"],
+ encoding="utf-8")
+ except subprocess.CalledProcessError:
+ terminal_width = 80
+ else:
+ terminal_width = int(tput_output.strip())
+ print("⎯" * terminal_width)
+ print("📁", working_dir)
+ if (clean_ok and getattr(context, "clean", False) and
+ working_dir.exists()):
+ print(f"🚮 Deleting directory (--clean)...")
+ shutil.rmtree(working_dir)
+
+ working_dir.mkdir(parents=True, exist_ok=True)
+
+ with contextlib.chdir(working_dir):
+ return func(context, working_dir)
+
+ return wrapper
+
+ return decorator
+
+
+def call(command, *, quiet, **kwargs):
+ """Execute a command.
+
+ If 'quiet' is true, then redirect stdout and stderr to a temporary file.
+ """
+ print("❯", " ".join(map(str, command)))
+ if not quiet:
+ stdout = None
+ stderr = None
+ else:
+ stdout = tempfile.NamedTemporaryFile("w", encoding="utf-8",
+ delete=False,
+ prefix="cpython-wasi-",
+ suffix=".log")
+ stderr = subprocess.STDOUT
+ print(f"📝 Logging output to {stdout.name} (--quiet)...")
+
+ subprocess.check_call(command, **kwargs, stdout=stdout, stderr=stderr)
+
+
+def build_platform():
+ """The name of the build/host platform."""
+ # Can also be found via `config.guess`.
+ return sysconfig.get_config_var("BUILD_GNU_TYPE")
+
+
+def build_python_path():
+ """The path to the build Python binary."""
+ binary = BUILD_DIR / "python"
+ if not binary.is_file():
+ binary = binary.with_suffix(".exe")
+ if not binary.is_file():
+ raise FileNotFoundError("Unable to find `python(.exe)` in "
+ f"{BUILD_DIR}")
+
+ return binary
+
+
+def build_python_is_pydebug():
+ """Find out if the build Python is a pydebug build."""
+ test = "import sys, test.support; sys.exit(test.support.Py_DEBUG)"
+ result = subprocess.run([build_python_path(), "-c", test],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ return bool(result.returncode)
+
+
+@subdir(BUILD_DIR, clean_ok=True)
+def configure_build_python(context, working_dir):
+ """Configure the build/host Python."""
+ if LOCAL_SETUP.exists():
+ print(f"👍 {LOCAL_SETUP} exists ...")
+ else:
+ print(f"📝 Touching {LOCAL_SETUP} ...")
+ LOCAL_SETUP.write_bytes(LOCAL_SETUP_MARKER)
+
+ configure = [os.path.relpath(CHECKOUT / 'configure', working_dir)]
+ if context.args:
+ configure.extend(context.args)
+
+ call(configure, quiet=context.quiet)
+
+
+@subdir(BUILD_DIR)
+def make_build_python(context, working_dir):
+ """Make/build the build Python."""
+ call(["make", "--jobs", str(cpu_count()), "all"],
+ quiet=context.quiet)
+
+ binary = build_python_path()
+ cmd = [binary, "-c",
+ "import sys; "
+ "print(f'{sys.version_info.major}.{sys.version_info.minor}')"]
+ version = subprocess.check_output(cmd, encoding="utf-8").strip()
+
+ print(f"🎉 {binary} {version}")
+
+
+def find_wasi_sdk():
+ """Find the path to wasi-sdk."""
+ if wasi_sdk_path := os.environ.get("WASI_SDK_PATH"):
+ return pathlib.Path(wasi_sdk_path)
+ elif (default_path := pathlib.Path("/opt/wasi-sdk")).exists():
+ return default_path
+
+
+def wasi_sdk_env(context):
+ """Calculate environment variables for building with wasi-sdk."""
+ wasi_sdk_path = context.wasi_sdk_path
+ sysroot = wasi_sdk_path / "share" / "wasi-sysroot"
+ env = {"CC": "clang", "CPP": "clang-cpp", "CXX": "clang++",
+ "AR": "llvm-ar", "RANLIB": "ranlib"}
+
+ for env_var, binary_name in list(env.items()):
+ env[env_var] = os.fsdecode(wasi_sdk_path / "bin" / binary_name)
+
+ if wasi_sdk_path != pathlib.Path("/opt/wasi-sdk"):
+ for compiler in ["CC", "CPP", "CXX"]:
+ env[compiler] += f" --sysroot={sysroot}"
+
+ env["PKG_CONFIG_PATH"] = ""
+ env["PKG_CONFIG_LIBDIR"] = os.pathsep.join(
+ map(os.fsdecode,
+ [sysroot / "lib" / "pkgconfig",
+ sysroot / "share" / "pkgconfig"]))
+ env["PKG_CONFIG_SYSROOT_DIR"] = os.fsdecode(sysroot)
+
+ env["WASI_SDK_PATH"] = os.fsdecode(wasi_sdk_path)
+ env["WASI_SYSROOT"] = os.fsdecode(sysroot)
+
+ env["PATH"] = os.pathsep.join([os.fsdecode(wasi_sdk_path / "bin"),
+ os.environ["PATH"]])
+
+ return env
+
+
+@subdir(lambda context: CROSS_BUILD_DIR / context.host_triple, clean_ok=True)
+def configure_wasi_python(context, working_dir):
+ """Configure the WASI/host build."""
+ if not context.wasi_sdk_path or not context.wasi_sdk_path.exists():
+ raise ValueError("WASI-SDK not found; "
+ "download from "
+ "https://github.com/WebAssembly/wasi-sdk and/or "
+ "specify via $WASI_SDK_PATH or --wasi-sdk")
+
+ config_site = os.fsdecode(CHECKOUT / "Tools" / "wasm" / "wasi" / "config.site-wasm32-wasi")
+
+ wasi_build_dir = working_dir.relative_to(CHECKOUT)
+
+ python_build_dir = BUILD_DIR / "build"
+ lib_dirs = list(python_build_dir.glob("lib.*"))
+ assert len(lib_dirs) == 1, f"Expected a single lib.* directory in {python_build_dir}"
+ lib_dir = os.fsdecode(lib_dirs[0])
+ python_version = lib_dir.rpartition("-")[-1]
+ sysconfig_data_dir = f"{wasi_build_dir}/build/lib.wasi-wasm32-{python_version}"
+
+ # Use PYTHONPATH to include sysconfig data which must be anchored to the
+ # WASI guest's `/` directory.
+ args = {"GUEST_DIR": "/",
+ "HOST_DIR": CHECKOUT,
+ "ENV_VAR_NAME": "PYTHONPATH",
+ "ENV_VAR_VALUE": f"/{sysconfig_data_dir}",
+ "PYTHON_WASM": working_dir / "python.wasm"}
+ # Check dynamically for wasmtime in case it was specified manually via
+ # `--host-runner`.
+ if WASMTIME_HOST_RUNNER_VAR in context.host_runner:
+ if wasmtime := shutil.which("wasmtime"):
+ args[WASMTIME_VAR_NAME] = wasmtime
+ else:
+ raise FileNotFoundError("wasmtime not found; download from "
+ "https://github.com/bytecodealliance/wasmtime")
+ host_runner = context.host_runner.format_map(args)
+ env_additions = {"CONFIG_SITE": config_site, "HOSTRUNNER": host_runner}
+ build_python = os.fsdecode(build_python_path())
+ # The path to `configure` MUST be relative, else `python.wasm` is unable
+ # to find the stdlib due to Python not recognizing that it's being
+ # executed from within a checkout.
+ configure = [os.path.relpath(CHECKOUT / 'configure', working_dir),
+ f"--host={context.host_triple}",
+ f"--build={build_platform()}",
+ f"--with-build-python={build_python}"]
+ if build_python_is_pydebug():
+ configure.append("--with-pydebug")
+ if context.args:
+ configure.extend(context.args)
+ call(configure,
+ env=updated_env(env_additions | wasi_sdk_env(context)),
+ quiet=context.quiet)
+
+ python_wasm = working_dir / "python.wasm"
+ exec_script = working_dir / "python.sh"
+ with exec_script.open("w", encoding="utf-8") as file:
+ file.write(f'#!/bin/sh\nexec {host_runner} {python_wasm} "$@"\n')
+ exec_script.chmod(0o755)
+ print(f"🏃‍♀️ Created {exec_script} (--host-runner)... ")
+ sys.stdout.flush()
+
+
+@subdir(lambda context: CROSS_BUILD_DIR / context.host_triple)
+def make_wasi_python(context, working_dir):
+ """Run `make` for the WASI/host build."""
+ call(["make", "--jobs", str(cpu_count()), "all"],
+ env=updated_env(),
+ quiet=context.quiet)
+
+ exec_script = working_dir / "python.sh"
+ call([exec_script, "--version"], quiet=False)
+ print(
+ f"🎉 Use `{exec_script.relative_to(context.init_dir)}` "
+ "to run CPython w/ the WASI host specified by --host-runner"
+ )
+
+
+def build_all(context):
+ """Build everything."""
+ steps = [configure_build_python, make_build_python, configure_wasi_python,
+ make_wasi_python]
+ for step in steps:
+ step(context)
+
+def clean_contents(context):
+ """Delete all files created by this script."""
+ if CROSS_BUILD_DIR.exists():
+ print(f"🧹 Deleting {CROSS_BUILD_DIR} ...")
+ shutil.rmtree(CROSS_BUILD_DIR)
+
+ if LOCAL_SETUP.exists():
+ with LOCAL_SETUP.open("rb") as file:
+ if file.read(len(LOCAL_SETUP_MARKER)) == LOCAL_SETUP_MARKER:
+ print(f"🧹 Deleting generated {LOCAL_SETUP} ...")
+
+
+def main():
+ default_host_runner = (f"{WASMTIME_HOST_RUNNER_VAR} run "
+ # Make sure the stack size will work for a pydebug
+ # build.
+ # Use 16 MiB stack.
+ "--wasm max-wasm-stack=16777216 "
+ # Enable thread support; causes use of preview1.
+ #"--wasm threads=y --wasi threads=y "
+ # Map the checkout to / to load the stdlib from /Lib.
+ "--dir {HOST_DIR}::{GUEST_DIR} "
+ # Set PYTHONPATH to the sysconfig data.
+ "--env {ENV_VAR_NAME}={ENV_VAR_VALUE}")
+
+ parser = argparse.ArgumentParser()
+ subcommands = parser.add_subparsers(dest="subcommand")
+ build = subcommands.add_parser("build", help="Build everything")
+ configure_build = subcommands.add_parser("configure-build-python",
+ help="Run `configure` for the "
+ "build Python")
+ make_build = subcommands.add_parser("make-build-python",
+ help="Run `make` for the build Python")
+ configure_host = subcommands.add_parser("configure-host",
+ help="Run `configure` for the "
+ "host/WASI (pydebug builds "
+ "are inferred from the build "
+ "Python)")
+ make_host = subcommands.add_parser("make-host",
+ help="Run `make` for the host/WASI")
+ clean = subcommands.add_parser("clean", help="Delete files and directories "
+ "created by this script")
+ for subcommand in build, configure_build, make_build, configure_host, make_host:
+ subcommand.add_argument("--quiet", action="store_true", default=False,
+ dest="quiet",
+ help="Redirect output from subprocesses to a log file")
+ for subcommand in configure_build, configure_host:
+ subcommand.add_argument("--clean", action="store_true", default=False,
+ dest="clean",
+ help="Delete any relevant directories before building")
+ for subcommand in build, configure_build, configure_host:
+ subcommand.add_argument("args", nargs="*",
+ help="Extra arguments to pass to `configure`")
+ for subcommand in build, configure_host:
+ subcommand.add_argument("--wasi-sdk", type=pathlib.Path,
+ dest="wasi_sdk_path",
+ default=find_wasi_sdk(),
+ help="Path to wasi-sdk; defaults to "
+ "$WASI_SDK_PATH or /opt/wasi-sdk")
+ subcommand.add_argument("--host-runner", action="store",
+ default=default_host_runner, dest="host_runner",
+ help="Command template for running the WASI host "
+ "(default designed for wasmtime 14 or newer: "
+ f"`{default_host_runner}`)")
+ for subcommand in build, configure_host, make_host:
+ subcommand.add_argument("--host-triple", action="store", default="wasm32-wasip1",
+ help="The target triple for the WASI host build")
+
+ context = parser.parse_args()
+ context.init_dir = pathlib.Path().absolute()
+
+ dispatch = {"configure-build-python": configure_build_python,
+ "make-build-python": make_build_python,
+ "configure-host": configure_wasi_python,
+ "make-host": make_wasi_python,
+ "build": build_all,
+ "clean": clean_contents}
+ dispatch[context.subcommand](context)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/Tools/wasm/config.site-wasm32-wasi b/Tools/wasm/wasi/config.site-wasm32-wasi
index c5d8b3e205d..c5d8b3e205d 100644
--- a/Tools/wasm/config.site-wasm32-wasi
+++ b/Tools/wasm/wasi/config.site-wasm32-wasi
diff --git a/Tools/wasm/wasm_build.py b/Tools/wasm/wasm_build.py
deleted file mode 100755
index bcb80212362..00000000000
--- a/Tools/wasm/wasm_build.py
+++ /dev/null
@@ -1,932 +0,0 @@
-#!/usr/bin/env python3
-"""Build script for Python on WebAssembly platforms.
-
- $ ./Tools/wasm/wasm_builder.py emscripten-browser build repl
- $ ./Tools/wasm/wasm_builder.py emscripten-node-dl build test
- $ ./Tools/wasm/wasm_builder.py wasi build test
-
-Primary build targets are "emscripten-node-dl" (NodeJS, dynamic linking),
-"emscripten-browser", and "wasi".
-
-Emscripten builds require a recent Emscripten SDK. The tools looks for an
-activated EMSDK environment (". /path/to/emsdk_env.sh"). System packages
-(Debian, Homebrew) are not supported.
-
-WASI builds require WASI SDK and wasmtime. The tool looks for 'WASI_SDK_PATH'
-and falls back to /opt/wasi-sdk.
-
-The 'build' Python interpreter must be rebuilt every time Python's byte code
-changes.
-
- ./Tools/wasm/wasm_builder.py --clean build build
-
-"""
-import argparse
-import enum
-import dataclasses
-import logging
-import os
-import pathlib
-import re
-import shlex
-import shutil
-import socket
-import subprocess
-import sys
-import sysconfig
-import tempfile
-import time
-import warnings
-import webbrowser
-
-# for Python 3.8
-from typing import (
- cast,
- Any,
- Callable,
- Dict,
- Iterable,
- List,
- Optional,
- Tuple,
- Union,
-)
-
-logger = logging.getLogger("wasm_build")
-
-SRCDIR = pathlib.Path(__file__).parent.parent.parent.absolute()
-WASMTOOLS = SRCDIR / "Tools" / "wasm"
-BUILDDIR = SRCDIR / "builddir"
-CONFIGURE = SRCDIR / "configure"
-SETUP_LOCAL = SRCDIR / "Modules" / "Setup.local"
-
-HAS_CCACHE = shutil.which("ccache") is not None
-
-# path to WASI-SDK root
-WASI_SDK_PATH = pathlib.Path(os.environ.get("WASI_SDK_PATH", "/opt/wasi-sdk"))
-
-# path to Emscripten SDK config file.
-# auto-detect's EMSDK in /opt/emsdk without ". emsdk_env.sh".
-EM_CONFIG = pathlib.Path(os.environ.setdefault("EM_CONFIG", "/opt/emsdk/.emscripten"))
-EMSDK_MIN_VERSION = (3, 1, 19)
-EMSDK_BROKEN_VERSION = {
- (3, 1, 14): "https://github.com/emscripten-core/emscripten/issues/17338",
- (3, 1, 16): "https://github.com/emscripten-core/emscripten/issues/17393",
- (3, 1, 20): "https://github.com/emscripten-core/emscripten/issues/17720",
-}
-_MISSING = pathlib.Path("MISSING")
-
-WASM_WEBSERVER = WASMTOOLS / "wasm_webserver.py"
-
-CLEAN_SRCDIR = f"""
-Builds require a clean source directory. Please use a clean checkout or
-run "make clean -C '{SRCDIR}'".
-"""
-
-INSTALL_NATIVE = """
-Builds require a C compiler (gcc, clang), make, pkg-config, and development
-headers for dependencies like zlib.
-
-Debian/Ubuntu: sudo apt install build-essential git curl pkg-config zlib1g-dev
-Fedora/CentOS: sudo dnf install gcc make git-core curl pkgconfig zlib-devel
-"""
-
-INSTALL_EMSDK = """
-wasm32-emscripten builds need Emscripten SDK. Please follow instructions at
-https://emscripten.org/docs/getting_started/downloads.html how to install
-Emscripten and how to activate the SDK with "emsdk_env.sh".
-
- git clone https://github.com/emscripten-core/emsdk.git /path/to/emsdk
- cd /path/to/emsdk
- ./emsdk install latest
- ./emsdk activate latest
- source /path/to/emsdk_env.sh
-"""
-
-INSTALL_WASI_SDK = """
-wasm32-wasi builds need WASI SDK. Please fetch the latest SDK from
-https://github.com/WebAssembly/wasi-sdk/releases and install it to
-"/opt/wasi-sdk". Alternatively you can install the SDK in a different location
-and point the environment variable WASI_SDK_PATH to the root directory
-of the SDK. The SDK is available for Linux x86_64, macOS x86_64, and MinGW.
-"""
-
-INSTALL_WASMTIME = """
-wasm32-wasi tests require wasmtime on PATH. Please follow instructions at
-https://wasmtime.dev/ to install wasmtime.
-"""
-
-
-def parse_emconfig(
- emconfig: pathlib.Path = EM_CONFIG,
-) -> Tuple[pathlib.Path, pathlib.Path]:
- """Parse EM_CONFIG file and lookup EMSCRIPTEN_ROOT and NODE_JS.
-
- The ".emscripten" config file is a Python snippet that uses "EM_CONFIG"
- environment variable. EMSCRIPTEN_ROOT is the "upstream/emscripten"
- subdirectory with tools like "emconfigure".
- """
- if not emconfig.exists():
- return _MISSING, _MISSING
- with open(emconfig, encoding="utf-8") as f:
- code = f.read()
- # EM_CONFIG file is a Python snippet
- local: Dict[str, Any] = {}
- exec(code, globals(), local)
- emscripten_root = pathlib.Path(local["EMSCRIPTEN_ROOT"])
- node_js = pathlib.Path(local["NODE_JS"])
- return emscripten_root, node_js
-
-
-EMSCRIPTEN_ROOT, NODE_JS = parse_emconfig()
-
-
-def read_python_version(configure: pathlib.Path = CONFIGURE) -> str:
- """Read PACKAGE_VERSION from configure script
-
- configure and configure.ac are the canonical source for major and
- minor version number.
- """
- version_re = re.compile(r"^PACKAGE_VERSION='(\d\.\d+)'")
- with configure.open(encoding="utf-8") as f:
- for line in f:
- mo = version_re.match(line)
- if mo:
- return mo.group(1)
- raise ValueError(f"PACKAGE_VERSION not found in {configure}")
-
-
-PYTHON_VERSION = read_python_version()
-
-
-class ConditionError(ValueError):
- def __init__(self, info: str, text: str) -> None:
- self.info = info
- self.text = text
-
- def __str__(self) -> str:
- return f"{type(self).__name__}: '{self.info}'\n{self.text}"
-
-
-class MissingDependency(ConditionError):
- pass
-
-
-class DirtySourceDirectory(ConditionError):
- pass
-
-
-@dataclasses.dataclass
-class Platform:
- """Platform-specific settings
-
- - CONFIG_SITE override
- - configure wrapper (e.g. emconfigure)
- - make wrapper (e.g. emmake)
- - additional environment variables
- - check function to verify SDK
- """
-
- name: str
- pythonexe: str
- config_site: Optional[pathlib.PurePath]
- configure_wrapper: Optional[pathlib.Path]
- make_wrapper: Optional[pathlib.PurePath]
- environ: Dict[str, Any]
- check: Callable[[], None]
- # Used for build_emports().
- ports: Optional[pathlib.PurePath]
- cc: Optional[pathlib.PurePath]
-
- def getenv(self, profile: "BuildProfile") -> Dict[str, Any]:
- return self.environ.copy()
-
-
-def _check_clean_src() -> None:
- candidates = [
- SRCDIR / "Programs" / "python.o",
- SRCDIR / "Python" / "frozen_modules" / "importlib._bootstrap.h",
- ]
- for candidate in candidates:
- if candidate.exists():
- raise DirtySourceDirectory(os.fspath(candidate), CLEAN_SRCDIR)
-
-
-def _check_native() -> None:
- if not any(shutil.which(cc) for cc in ["cc", "gcc", "clang"]):
- raise MissingDependency("cc", INSTALL_NATIVE)
- if not shutil.which("make"):
- raise MissingDependency("make", INSTALL_NATIVE)
- if sys.platform == "linux":
- # skip pkg-config check on macOS
- if not shutil.which("pkg-config"):
- raise MissingDependency("pkg-config", INSTALL_NATIVE)
- # zlib is needed to create zip files
- for devel in ["zlib"]:
- try:
- subprocess.check_call(["pkg-config", "--exists", devel])
- except subprocess.CalledProcessError:
- raise MissingDependency(devel, INSTALL_NATIVE) from None
- _check_clean_src()
-
-
-NATIVE = Platform(
- "native",
- # macOS has python.exe
- pythonexe=sysconfig.get_config_var("BUILDPYTHON") or "python",
- config_site=None,
- configure_wrapper=None,
- ports=None,
- cc=None,
- make_wrapper=None,
- environ={},
- check=_check_native,
-)
-
-
-def _check_emscripten() -> None:
- if EMSCRIPTEN_ROOT is _MISSING:
- raise MissingDependency("Emscripten SDK EM_CONFIG", INSTALL_EMSDK)
- # sanity check
- emconfigure = EMSCRIPTEN.configure_wrapper
- if emconfigure is not None and not emconfigure.exists():
- raise MissingDependency(os.fspath(emconfigure), INSTALL_EMSDK)
- # version check
- version_txt = EMSCRIPTEN_ROOT / "emscripten-version.txt"
- if not version_txt.exists():
- raise MissingDependency(os.fspath(version_txt), INSTALL_EMSDK)
- with open(version_txt) as f:
- version = f.read().strip().strip('"')
- if version.endswith("-git"):
- # git / upstream / tot-upstream installation
- version = version[:-4]
- version_tuple = cast(
- Tuple[int, int, int],
- tuple(int(v) for v in version.split("."))
- )
- if version_tuple < EMSDK_MIN_VERSION:
- raise ConditionError(
- os.fspath(version_txt),
- f"Emscripten SDK {version} in '{EMSCRIPTEN_ROOT}' is older than "
- "minimum required version "
- f"{'.'.join(str(v) for v in EMSDK_MIN_VERSION)}.",
- )
- broken = EMSDK_BROKEN_VERSION.get(version_tuple)
- if broken is not None:
- raise ConditionError(
- os.fspath(version_txt),
- (
- f"Emscripten SDK {version} in '{EMSCRIPTEN_ROOT}' has known "
- f"bugs, see {broken}."
- ),
- )
- if os.environ.get("PKG_CONFIG_PATH"):
- warnings.warn(
- "PKG_CONFIG_PATH is set and not empty. emconfigure overrides "
- "this environment variable. Use EM_PKG_CONFIG_PATH instead."
- )
- _check_clean_src()
-
-
-EMSCRIPTEN = Platform(
- "emscripten",
- pythonexe="python.js",
- config_site=WASMTOOLS / "config.site-wasm32-emscripten",
- configure_wrapper=EMSCRIPTEN_ROOT / "emconfigure",
- ports=EMSCRIPTEN_ROOT / "embuilder",
- cc=EMSCRIPTEN_ROOT / "emcc",
- make_wrapper=EMSCRIPTEN_ROOT / "emmake",
- environ={
- # workaround for https://github.com/emscripten-core/emscripten/issues/17635
- "TZ": "UTC",
- "EM_COMPILER_WRAPPER": "ccache" if HAS_CCACHE else None,
- "PATH": [EMSCRIPTEN_ROOT, os.environ["PATH"]],
- },
- check=_check_emscripten,
-)
-
-
-def _check_wasi() -> None:
- wasm_ld = WASI_SDK_PATH / "bin" / "wasm-ld"
- if not wasm_ld.exists():
- raise MissingDependency(os.fspath(wasm_ld), INSTALL_WASI_SDK)
- wasmtime = shutil.which("wasmtime")
- if wasmtime is None:
- raise MissingDependency("wasmtime", INSTALL_WASMTIME)
- _check_clean_src()
-
-
-WASI = Platform(
- "wasi",
- pythonexe="python.wasm",
- config_site=WASMTOOLS / "config.site-wasm32-wasi",
- configure_wrapper=WASMTOOLS / "wasi-env",
- ports=None,
- cc=WASI_SDK_PATH / "bin" / "clang",
- make_wrapper=None,
- environ={
- "WASI_SDK_PATH": WASI_SDK_PATH,
- # workaround for https://github.com/python/cpython/issues/95952
- "HOSTRUNNER": (
- "wasmtime run "
- "--wasm max-wasm-stack=16777216 "
- "--wasi preview2 "
- "--dir {srcdir}::/ "
- "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib"
- ),
- "PATH": [WASI_SDK_PATH / "bin", os.environ["PATH"]],
- },
- check=_check_wasi,
-)
-
-
-class Host(enum.Enum):
- """Target host triplet"""
-
- wasm32_emscripten = "wasm32-unknown-emscripten"
- wasm64_emscripten = "wasm64-unknown-emscripten"
- wasm32_wasi = "wasm32-unknown-wasi"
- wasm64_wasi = "wasm64-unknown-wasi"
- # current platform
- build = sysconfig.get_config_var("BUILD_GNU_TYPE")
-
- @property
- def platform(self) -> Platform:
- if self.is_emscripten:
- return EMSCRIPTEN
- elif self.is_wasi:
- return WASI
- else:
- return NATIVE
-
- @property
- def is_emscripten(self) -> bool:
- cls = type(self)
- return self in {cls.wasm32_emscripten, cls.wasm64_emscripten}
-
- @property
- def is_wasi(self) -> bool:
- cls = type(self)
- return self in {cls.wasm32_wasi, cls.wasm64_wasi}
-
- def get_extra_paths(self) -> Iterable[pathlib.PurePath]:
- """Host-specific os.environ["PATH"] entries.
-
- Emscripten's Node version 14.x works well for wasm32-emscripten.
- wasm64-emscripten requires more recent v8 version, e.g. node 16.x.
- Attempt to use system's node command.
- """
- cls = type(self)
- if self == cls.wasm32_emscripten:
- return [NODE_JS.parent]
- elif self == cls.wasm64_emscripten:
- # TODO: look for recent node
- return []
- else:
- return []
-
- @property
- def emport_args(self) -> List[str]:
- """Host-specific port args (Emscripten)."""
- cls = type(self)
- if self is cls.wasm64_emscripten:
- return ["-sMEMORY64=1"]
- elif self is cls.wasm32_emscripten:
- return ["-sMEMORY64=0"]
- else:
- return []
-
- @property
- def embuilder_args(self) -> List[str]:
- """Host-specific embuilder args (Emscripten)."""
- cls = type(self)
- if self is cls.wasm64_emscripten:
- return ["--wasm64"]
- else:
- return []
-
-
-class EmscriptenTarget(enum.Enum):
- """Emscripten-specific targets (--with-emscripten-target)"""
-
- browser = "browser"
- browser_debug = "browser-debug"
- node = "node"
- node_debug = "node-debug"
-
- @property
- def is_browser(self) -> bool:
- cls = type(self)
- return self in {cls.browser, cls.browser_debug}
-
- @property
- def emport_args(self) -> List[str]:
- """Target-specific port args."""
- cls = type(self)
- if self in {cls.browser_debug, cls.node_debug}:
- # some libs come in debug and non-debug builds
- return ["-O0"]
- else:
- return ["-O2"]
-
-
-class SupportLevel(enum.Enum):
- supported = "tier 3, supported"
- working = "working, unsupported"
- experimental = "experimental, may be broken"
- broken = "broken / unavailable"
-
- def __bool__(self) -> bool:
- cls = type(self)
- return self in {cls.supported, cls.working}
-
-
-@dataclasses.dataclass
-class BuildProfile:
- name: str
- support_level: SupportLevel
- host: Host
- target: Union[EmscriptenTarget, None] = None
- dynamic_linking: Union[bool, None] = None
- pthreads: Union[bool, None] = None
- default_testopts: str = "-j2"
-
- @property
- def is_browser(self) -> bool:
- """Is this a browser build?"""
- return self.target is not None and self.target.is_browser
-
- @property
- def builddir(self) -> pathlib.Path:
- """Path to build directory"""
- return BUILDDIR / self.name
-
- @property
- def python_cmd(self) -> pathlib.Path:
- """Path to python executable"""
- return self.builddir / self.host.platform.pythonexe
-
- @property
- def makefile(self) -> pathlib.Path:
- """Path to Makefile"""
- return self.builddir / "Makefile"
-
- @property
- def configure_cmd(self) -> List[str]:
- """Generate configure command"""
- # use relative path, so WASI tests can find lib prefix.
- # pathlib.Path.relative_to() does not work here.
- configure = os.path.relpath(CONFIGURE, self.builddir)
- cmd = [configure, "-C"]
- platform = self.host.platform
- if platform.configure_wrapper:
- cmd.insert(0, os.fspath(platform.configure_wrapper))
-
- cmd.append(f"--host={self.host.value}")
- cmd.append(f"--build={Host.build.value}")
-
- if self.target is not None:
- assert self.host.is_emscripten
- cmd.append(f"--with-emscripten-target={self.target.value}")
-
- if self.dynamic_linking is not None:
- assert self.host.is_emscripten
- opt = "enable" if self.dynamic_linking else "disable"
- cmd.append(f"--{opt}-wasm-dynamic-linking")
-
- if self.pthreads is not None:
- opt = "enable" if self.pthreads else "disable"
- cmd.append(f"--{opt}-wasm-pthreads")
-
- if self.host != Host.build:
- cmd.append(f"--with-build-python={BUILD.python_cmd}")
-
- if platform.config_site is not None:
- cmd.append(f"CONFIG_SITE={platform.config_site}")
-
- return cmd
-
- @property
- def make_cmd(self) -> List[str]:
- """Generate make command"""
- cmd = ["make"]
- platform = self.host.platform
- if platform.make_wrapper:
- cmd.insert(0, os.fspath(platform.make_wrapper))
- return cmd
-
- def getenv(self) -> Dict[str, Any]:
- """Generate environ dict for platform"""
- env = os.environ.copy()
- if hasattr(os, 'process_cpu_count'):
- cpu_count = os.process_cpu_count()
- else:
- cpu_count = os.cpu_count()
- env.setdefault("MAKEFLAGS", f"-j{cpu_count}")
- platenv = self.host.platform.getenv(self)
- for key, value in platenv.items():
- if value is None:
- env.pop(key, None)
- elif key == "PATH":
- # list of path items, prefix with extra paths
- new_path: List[pathlib.PurePath] = []
- new_path.extend(self.host.get_extra_paths())
- new_path.extend(value)
- env[key] = os.pathsep.join(os.fspath(p) for p in new_path)
- elif isinstance(value, str):
- env[key] = value.format(
- relbuilddir=self.builddir.relative_to(SRCDIR),
- srcdir=SRCDIR,
- version=PYTHON_VERSION,
- )
- else:
- env[key] = value
- return env
-
- def _run_cmd(
- self,
- cmd: Iterable[str],
- args: Iterable[str] = (),
- cwd: Optional[pathlib.Path] = None,
- ) -> int:
- cmd = list(cmd)
- cmd.extend(args)
- if cwd is None:
- cwd = self.builddir
- logger.info('Running "%s" in "%s"', shlex.join(cmd), cwd)
- return subprocess.check_call(
- cmd,
- cwd=os.fspath(cwd),
- env=self.getenv(),
- )
-
- def _check_execute(self) -> None:
- if self.is_browser:
- raise ValueError(f"Cannot execute on {self.target}")
-
- def run_build(self, *args: str) -> None:
- """Run configure (if necessary) and make"""
- if not self.makefile.exists():
- logger.info("Makefile not found, running configure")
- self.run_configure(*args)
- self.run_make("all", *args)
-
- def run_configure(self, *args: str) -> int:
- """Run configure script to generate Makefile"""
- os.makedirs(self.builddir, exist_ok=True)
- return self._run_cmd(self.configure_cmd, args)
-
- def run_make(self, *args: str) -> int:
- """Run make (defaults to build all)"""
- return self._run_cmd(self.make_cmd, args)
-
- def run_pythoninfo(self, *args: str) -> int:
- """Run 'make pythoninfo'"""
- self._check_execute()
- return self.run_make("pythoninfo", *args)
-
- def run_test(self, target: str, testopts: Optional[str] = None) -> int:
- """Run buildbottests"""
- self._check_execute()
- if testopts is None:
- testopts = self.default_testopts
- return self.run_make(target, f"TESTOPTS={testopts}")
-
- def run_py(self, *args: str) -> int:
- """Run Python with hostrunner"""
- self._check_execute()
- return self.run_make(
- "--eval", f"run: all; $(HOSTRUNNER) ./$(PYTHON) {shlex.join(args)}", "run"
- )
-
- def run_browser(self, bind: str = "127.0.0.1", port: int = 8000) -> None:
- """Run WASM webserver and open build in browser"""
- relbuilddir = self.builddir.relative_to(SRCDIR)
- url = f"http://{bind}:{port}/{relbuilddir}/python.html"
- args = [
- sys.executable,
- os.fspath(WASM_WEBSERVER),
- "--bind",
- bind,
- "--port",
- str(port),
- ]
- srv = subprocess.Popen(args, cwd=SRCDIR)
- # wait for server
- end = time.monotonic() + 3.0
- while time.monotonic() < end and srv.returncode is None:
- try:
- with socket.create_connection((bind, port), timeout=0.1) as _:
- pass
- except OSError:
- time.sleep(0.01)
- else:
- break
-
- webbrowser.open(url)
-
- try:
- srv.wait()
- except KeyboardInterrupt:
- pass
-
- def clean(self, all: bool = False) -> None:
- """Clean build directory"""
- if all:
- if self.builddir.exists():
- shutil.rmtree(self.builddir)
- elif self.makefile.exists():
- self.run_make("clean")
-
- def build_emports(self, force: bool = False) -> None:
- """Pre-build emscripten ports."""
- platform = self.host.platform
- if platform.ports is None or platform.cc is None:
- raise ValueError("Need ports and CC command")
-
- embuilder_cmd = [os.fspath(platform.ports)]
- embuilder_cmd.extend(self.host.embuilder_args)
- if force:
- embuilder_cmd.append("--force")
-
- ports_cmd = [os.fspath(platform.cc)]
- ports_cmd.extend(self.host.emport_args)
- if self.target:
- ports_cmd.extend(self.target.emport_args)
-
- if self.dynamic_linking:
- # Trigger PIC build.
- ports_cmd.append("-sMAIN_MODULE")
- embuilder_cmd.append("--pic")
-
- if self.pthreads:
- # Trigger multi-threaded build.
- ports_cmd.append("-sUSE_PTHREADS")
-
- # Pre-build libbz2, libsqlite3, libz, and some system libs.
- ports_cmd.extend(["-sUSE_ZLIB", "-sUSE_BZIP2", "-sUSE_SQLITE3"])
- # Multi-threaded sqlite3 has different suffix
- embuilder_cmd.extend(
- ["build", "bzip2", "sqlite3-mt" if self.pthreads else "sqlite3", "zlib"]
- )
-
- self._run_cmd(embuilder_cmd, cwd=SRCDIR)
-
- with tempfile.TemporaryDirectory(suffix="-py-emport") as tmpdir:
- tmppath = pathlib.Path(tmpdir)
- main_c = tmppath / "main.c"
- main_js = tmppath / "main.js"
- with main_c.open("w") as f:
- f.write("int main(void) { return 0; }\n")
- args = [
- os.fspath(main_c),
- "-o",
- os.fspath(main_js),
- ]
- self._run_cmd(ports_cmd, args, cwd=tmppath)
-
-
-# native build (build Python)
-BUILD = BuildProfile(
- "build",
- support_level=SupportLevel.working,
- host=Host.build,
-)
-
-_profiles = [
- BUILD,
- # wasm32-emscripten
- BuildProfile(
- "emscripten-browser",
- support_level=SupportLevel.supported,
- host=Host.wasm32_emscripten,
- target=EmscriptenTarget.browser,
- dynamic_linking=True,
- ),
- BuildProfile(
- "emscripten-browser-debug",
- support_level=SupportLevel.working,
- host=Host.wasm32_emscripten,
- target=EmscriptenTarget.browser_debug,
- dynamic_linking=True,
- ),
- BuildProfile(
- "emscripten-node-dl",
- support_level=SupportLevel.supported,
- host=Host.wasm32_emscripten,
- target=EmscriptenTarget.node,
- dynamic_linking=True,
- ),
- BuildProfile(
- "emscripten-node-dl-debug",
- support_level=SupportLevel.working,
- host=Host.wasm32_emscripten,
- target=EmscriptenTarget.node_debug,
- dynamic_linking=True,
- ),
- BuildProfile(
- "emscripten-node-pthreads",
- support_level=SupportLevel.supported,
- host=Host.wasm32_emscripten,
- target=EmscriptenTarget.node,
- pthreads=True,
- ),
- BuildProfile(
- "emscripten-node-pthreads-debug",
- support_level=SupportLevel.working,
- host=Host.wasm32_emscripten,
- target=EmscriptenTarget.node_debug,
- pthreads=True,
- ),
- # Emscripten build with both pthreads and dynamic linking is crashing.
- BuildProfile(
- "emscripten-node-dl-pthreads-debug",
- support_level=SupportLevel.broken,
- host=Host.wasm32_emscripten,
- target=EmscriptenTarget.node_debug,
- dynamic_linking=True,
- pthreads=True,
- ),
- # wasm64-emscripten (requires Emscripten >= 3.1.21)
- BuildProfile(
- "wasm64-emscripten-node-debug",
- support_level=SupportLevel.experimental,
- host=Host.wasm64_emscripten,
- target=EmscriptenTarget.node_debug,
- # MEMORY64 is not compatible with dynamic linking
- dynamic_linking=False,
- pthreads=False,
- ),
- # wasm32-wasi
- BuildProfile(
- "wasi",
- support_level=SupportLevel.supported,
- host=Host.wasm32_wasi,
- ),
- # wasm32-wasi-threads
- BuildProfile(
- "wasi-threads",
- support_level=SupportLevel.experimental,
- host=Host.wasm32_wasi,
- pthreads=True,
- ),
- # no SDK available yet
- # BuildProfile(
- # "wasm64-wasi",
- # support_level=SupportLevel.broken,
- # host=Host.wasm64_wasi,
- # ),
-]
-
-PROFILES = {p.name: p for p in _profiles}
-
-parser = argparse.ArgumentParser(
- "wasm_build.py",
- description=__doc__,
- formatter_class=argparse.RawTextHelpFormatter,
-)
-
-parser.add_argument(
- "--clean",
- "-c",
- help="Clean build directories first",
- action="store_true",
-)
-
-parser.add_argument(
- "--verbose",
- "-v",
- help="Verbose logging",
- action="store_true",
-)
-
-parser.add_argument(
- "--silent",
- help="Run configure and make in silent mode",
- action="store_true",
-)
-
-parser.add_argument(
- "--testopts",
- help=(
- "Additional test options for 'test' and 'hostrunnertest', e.g. "
- "--testopts='-v test_os'."
- ),
- default=None,
-)
-
-# Don't list broken and experimental variants in help
-platforms_choices = list(p.name for p in _profiles) + ["cleanall"]
-platforms_help = list(p.name for p in _profiles if p.support_level) + ["cleanall"]
-parser.add_argument(
- "platform",
- metavar="PLATFORM",
- help=f"Build platform: {', '.join(platforms_help)}",
- choices=platforms_choices,
-)
-
-ops = dict(
- build="auto build (build 'build' Python, emports, configure, compile)",
- configure="run ./configure",
- compile="run 'make all'",
- pythoninfo="run 'make pythoninfo'",
- test="run 'make buildbottest TESTOPTS=...' (supports parallel tests)",
- hostrunnertest="run 'make hostrunnertest TESTOPTS=...'",
- repl="start interactive REPL / webserver + browser session",
- clean="run 'make clean'",
- cleanall="remove all build directories",
- emports="build Emscripten port with embuilder (only Emscripten)",
-)
-ops_help = "\n".join(f"{op:16s} {help}" for op, help in ops.items())
-parser.add_argument(
- "ops",
- metavar="OP",
- help=f"operation (default: build)\n\n{ops_help}",
- choices=tuple(ops),
- default="build",
- nargs="*",
-)
-
-
-def main() -> None:
- args = parser.parse_args()
- logging.basicConfig(
- level=logging.INFO if args.verbose else logging.ERROR,
- format="%(message)s",
- )
-
- if args.platform == "cleanall":
- for builder in PROFILES.values():
- builder.clean(all=True)
- parser.exit(0)
-
- # additional configure and make args
- cm_args = ("--silent",) if args.silent else ()
-
- # nargs=* with default quirk
- if args.ops == "build":
- args.ops = ["build"]
-
- builder = PROFILES[args.platform]
- try:
- builder.host.platform.check()
- except ConditionError as e:
- parser.error(str(e))
-
- if args.clean:
- builder.clean(all=False)
-
- # hack for WASI
- if builder.host.is_wasi and not SETUP_LOCAL.exists():
- SETUP_LOCAL.touch()
-
- # auto-build
- if "build" in args.ops:
- # check and create build Python
- if builder is not BUILD:
- logger.info("Auto-building 'build' Python.")
- try:
- BUILD.host.platform.check()
- except ConditionError as e:
- parser.error(str(e))
- if args.clean:
- BUILD.clean(all=False)
- BUILD.run_build(*cm_args)
- # build Emscripten ports with embuilder
- if builder.host.is_emscripten and "emports" not in args.ops:
- builder.build_emports()
-
- for op in args.ops:
- logger.info("\n*** %s %s", args.platform, op)
- if op == "build":
- builder.run_build(*cm_args)
- elif op == "configure":
- builder.run_configure(*cm_args)
- elif op == "compile":
- builder.run_make("all", *cm_args)
- elif op == "pythoninfo":
- builder.run_pythoninfo(*cm_args)
- elif op == "repl":
- if builder.is_browser:
- builder.run_browser()
- else:
- builder.run_py()
- elif op == "test":
- builder.run_test("buildbottest", testopts=args.testopts)
- elif op == "hostrunnertest":
- builder.run_test("hostrunnertest", testopts=args.testopts)
- elif op == "clean":
- builder.clean(all=False)
- elif op == "cleanall":
- builder.clean(all=True)
- elif op == "emports":
- builder.build_emports(force=args.clean)
- else:
- raise ValueError(op)
-
- print(builder.builddir)
- parser.exit(0)
-
-
-if __name__ == "__main__":
- main()
diff --git a/configure b/configure
index 7dbb35f9f45..9df366697b8 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.72 for python 3.14.
+# Generated by GNU Autoconf 2.72 for python 3.15.
#
# Report bugs to <https://github.com/python/cpython/issues/>.
#
@@ -604,8 +604,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='python'
PACKAGE_TARNAME='python'
-PACKAGE_VERSION='3.14'
-PACKAGE_STRING='python 3.14'
+PACKAGE_VERSION='3.15'
+PACKAGE_STRING='python 3.15'
PACKAGE_BUGREPORT='https://github.com/python/cpython/issues/'
PACKAGE_URL=''
@@ -654,8 +654,6 @@ MODULE__XXTESTFUZZ_FALSE
MODULE__XXTESTFUZZ_TRUE
MODULE_XXSUBTYPE_FALSE
MODULE_XXSUBTYPE_TRUE
-MODULE__TESTEXTERNALINSPECTION_FALSE
-MODULE__TESTEXTERNALINSPECTION_TRUE
MODULE__TESTSINGLEPHASE_FALSE
MODULE__TESTSINGLEPHASE_TRUE
MODULE__TESTMULTIPHASE_FALSE
@@ -678,6 +676,8 @@ MODULE__HASHLIB_FALSE
MODULE__HASHLIB_TRUE
MODULE__SSL_FALSE
MODULE__SSL_TRUE
+MODULE__ZSTD_FALSE
+MODULE__ZSTD_TRUE
MODULE__LZMA_FALSE
MODULE__LZMA_TRUE
MODULE__BZ2_FALSE
@@ -791,6 +791,8 @@ MODULE__STRUCT_FALSE
MODULE__STRUCT_TRUE
MODULE_SELECT_FALSE
MODULE_SELECT_TRUE
+MODULE__REMOTE_DEBUGGING_FALSE
+MODULE__REMOTE_DEBUGGING_TRUE
MODULE__RANDOM_FALSE
MODULE__RANDOM_TRUE
MODULE__QUEUE_FALSE
@@ -852,6 +854,8 @@ HAVE_GETHOSTBYNAME_R_3_ARG
HAVE_GETHOSTBYNAME_R_5_ARG
HAVE_GETHOSTBYNAME_R_6_ARG
LIBOBJS
+LIBZSTD_LIBS
+LIBZSTD_CFLAGS
LIBLZMA_LIBS
LIBLZMA_CFLAGS
BZIP2_LIBS
@@ -1172,6 +1176,8 @@ BZIP2_CFLAGS
BZIP2_LIBS
LIBLZMA_CFLAGS
LIBLZMA_LIBS
+LIBZSTD_CFLAGS
+LIBZSTD_LIBS
LIBREADLINE_CFLAGS
LIBREADLINE_LIBS
LIBEDIT_CFLAGS
@@ -1728,7 +1734,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-'configure' configures python 3.14 to adapt to many kinds of systems.
+'configure' configures python 3.15 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1794,7 +1800,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of python 3.14:";;
+ short | recursive ) echo "Configuration of python 3.15:";;
esac
cat <<\_ACEOF
@@ -1820,8 +1826,8 @@ Optional Features:
no)
--enable-profiling enable C-level code profiling with gprof (default is
no)
- --disable-gil enable experimental support for running without the
- GIL (default is no)
+ --disable-gil enable support for running without the GIL (default
+ is no)
--enable-pystats enable internal statistics gathering (default is no)
--enable-optimizations enable expensive, stable optimizations (PGO, etc.)
(default is no)
@@ -1847,9 +1853,9 @@ Optional Features:
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
- --with-build-python=python3.14
+ --with-build-python=python3.15
path to build python binary for cross compiling
- (default: _bootstrap_python or python3.14)
+ (default: _bootstrap_python or python3.15)
--with-pkg-config=[yes|no|check]
use pkg-config to detect build options (default is
check)
@@ -2011,6 +2017,10 @@ Some influential environment variables:
C compiler flags for LIBLZMA, overriding pkg-config
LIBLZMA_LIBS
linker flags for LIBLZMA, overriding pkg-config
+ LIBZSTD_CFLAGS
+ C compiler flags for LIBZSTD, overriding pkg-config
+ LIBZSTD_LIBS
+ linker flags for LIBZSTD, overriding pkg-config
LIBREADLINE_CFLAGS
C compiler flags for LIBREADLINE, overriding pkg-config
LIBREADLINE_LIBS
@@ -2093,7 +2103,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-python configure 3.14
+python configure 3.15
generated by GNU Autoconf 2.72
Copyright (C) 2023 Free Software Foundation, Inc.
@@ -2771,7 +2781,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by python $as_me 3.14, which was
+It was created by python $as_me 3.15, which was
generated by GNU Autoconf 2.72. Invocation command line was
$ $0$ac_configure_args_raw
@@ -3882,7 +3892,7 @@ rm confdefs.h
mv confdefs.h.new confdefs.h
-VERSION=3.14
+VERSION=3.15
# Version number of Python's own shared library file.
@@ -10853,7 +10863,7 @@ then :
else case e in #(
e) as_fn_append CFLAGS_NODIST " $jit_flags"
- REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host}"
+ REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . --cflags=\"$CFLAGS_JIT\""
JIT_STENCILS_H="jit_stencils.h"
if test "x$Py_DEBUG" = xtrue
then :
@@ -12969,13 +12979,6 @@ if test "$ac_cv_sizeof_off_t" -gt "$ac_cv_sizeof_long" -a \
else
have_largefile_support="no"
fi
-case $ac_sys_system in #(
- Emscripten) :
- have_largefile_support="no"
- ;; #(
- *) :
- ;;
-esac
if test "x$have_largefile_support" = xyes
then :
@@ -14042,6 +14045,7 @@ fi
+
have_uuid=missing
for ac_header in uuid.h
@@ -14051,6 +14055,7 @@ if test "x$ac_cv_header_uuid_h" = xyes
then :
printf "%s\n" "#define HAVE_UUID_H 1" >>confdefs.h
+
for ac_func in uuid_create uuid_enc_be
do :
as_ac_var=`printf "%s\n" "ac_cv_func_$ac_func" | sed "$as_sed_sh"`
@@ -14060,7 +14065,9 @@ then :
cat >>confdefs.h <<_ACEOF
#define `printf "%s\n" "HAVE_$ac_func" | sed "$as_sed_cpp"` 1
_ACEOF
- have_uuid=yes
+
+ have_uuid=yes
+ ac_cv_have_uuid_h=yes
LIBUUID_CFLAGS=${LIBUUID_CFLAGS-""}
LIBUUID_LIBS=${LIBUUID_LIBS-""}
@@ -14150,6 +14157,7 @@ if test "x$ac_cv_header_uuid_uuid_h" = xyes
then :
printf "%s\n" "#define HAVE_UUID_UUID_H 1" >>confdefs.h
+ ac_cv_have_uuid_uuid_h=yes
py_check_lib_save_LIBS=$LIBS
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for uuid_generate_time in -luuid" >&5
printf %s "checking for uuid_generate_time in -luuid... " >&6; }
@@ -14247,8 +14255,9 @@ fi
printf "%s\n" "$ac_cv_lib_uuid_uuid_generate_time_safe" >&6; }
if test "x$ac_cv_lib_uuid_uuid_generate_time_safe" = xyes
then :
- have_uuid=yes
- printf "%s\n" "#define HAVE_UUID_GENERATE_TIME_SAFE 1" >>confdefs.h
+
+ have_uuid=yes
+ ac_cv_have_uuid_generate_time_safe=yes
fi
@@ -14292,6 +14301,7 @@ if test "x$ac_cv_header_uuid_uuid_h" = xyes
then :
printf "%s\n" "#define HAVE_UUID_UUID_H 1" >>confdefs.h
+ ac_cv_have_uuid_uuid_h=yes
py_check_lib_save_LIBS=$LIBS
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for uuid_generate_time in -luuid" >&5
printf %s "checking for uuid_generate_time in -luuid... " >&6; }
@@ -14389,8 +14399,9 @@ fi
printf "%s\n" "$ac_cv_lib_uuid_uuid_generate_time_safe" >&6; }
if test "x$ac_cv_lib_uuid_uuid_generate_time_safe" = xyes
then :
- have_uuid=yes
- printf "%s\n" "#define HAVE_UUID_GENERATE_TIME_SAFE 1" >>confdefs.h
+
+ have_uuid=yes
+ ac_cv_have_uuid_generate_time_safe=yes
fi
@@ -14421,10 +14432,16 @@ else
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; }
have_uuid=yes
- printf "%s\n" "#define HAVE_UUID_H 1" >>confdefs.h
-
- printf "%s\n" "#define HAVE_UUID_GENERATE_TIME_SAFE 1" >>confdefs.h
-
+ ac_cv_have_uuid_generate_time_safe=yes
+ # The uuid.h file to include may be <uuid.h> *or* <uuid/uuid.h>.
+ # Since pkg-config --cflags uuid may return -I/usr/include/uuid,
+ # it's possible to write '#include <uuid.h>' in _uuidmodule.c,
+ # assuming that the compiler flags are properly updated.
+ #
+ # Ideally, we should have defined HAVE_UUID_H if and only if
+ # #include <uuid.h> can be written, *without* assuming extra
+ # include path.
+ ac_cv_have_uuid_h=yes
fi
@@ -14445,6 +14462,7 @@ if test "x$ac_cv_func_uuid_generate_time" = xyes
then :
have_uuid=yes
+ ac_cv_have_uuid_uuid_h=yes
LIBUUID_CFLAGS=${LIBUUID_CFLAGS-""}
LIBUUID_LIBS=${LIBUUID_LIBS-""}
@@ -14457,6 +14475,24 @@ done
fi
+if test "x$ac_cv_have_uuid_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_UUID_H 1" >>confdefs.h
+
+fi
+if test "x$ac_cv_have_uuid_uuid_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_UUID_UUID_H 1" >>confdefs.h
+
+fi
+if test "x$ac_cv_have_uuid_generate_time_safe" = xyes
+then :
+
+ printf "%s\n" "#define HAVE_UUID_GENERATE_TIME_SAFE 1" >>confdefs.h
+
+
+fi
+
# gh-124228: While the libuuid library is available on NetBSD, it supports only UUID version 4.
# This restriction inhibits the proper generation of time-based UUIDs.
if test "$ac_sys_system" = "NetBSD"; then
@@ -14470,6 +14506,164 @@ then :
have_uuid=no
fi
+# gh-132710: The UUID node is fetched by using libuuid when possible
+# and cached. While the node is constant within the same process,
+# different interpreters may have different values as libuuid may
+# randomize the node value if the latter cannot be deduced.
+#
+# Consumers may define HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC
+# to indicate that libuuid is unstable and should not be relied
+# upon to deduce the MAC address.
+
+
+if test "$have_uuid" = "yes" -a "$HAVE_UUID_GENERATE_TIME_SAFE" = "1"
+then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if uuid_generate_time_safe() node value is stable" >&5
+printf %s "checking if uuid_generate_time_safe() node value is stable... " >&6; }
+ save_CFLAGS=$CFLAGS
+save_CPPFLAGS=$CPPFLAGS
+save_LDFLAGS=$LDFLAGS
+save_LIBS=$LIBS
+
+
+ # Be sure to add the extra include path if we used pkg-config
+ # as HAVE_UUID_H may be set even though <uuid.h> is only reachable
+ # by adding extra -I flags.
+ #
+ # If the following script does not compile, we simply assume that
+ # libuuid is missing.
+ CFLAGS="$CFLAGS $LIBUUID_CFLAGS"
+ LIBS="$LIBS $LIBUUID_LIBS"
+ if test "$cross_compiling" = yes
+then :
+
+
+else case e in #(
+ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <inttypes.h> // PRIu64
+ #include <stdint.h> // uint64_t
+ #include <stdio.h> // fopen(), fclose()
+
+ #ifdef HAVE_UUID_H
+ #include <uuid.h>
+ #else
+ #include <uuid/uuid.h>
+ #endif
+
+ #define ERR 1
+ int main(void) {
+ uuid_t uuid; // unsigned char[16]
+ (void)uuid_generate_time_safe(uuid);
+ uint64_t node = 0;
+ for (size_t i = 0; i < 6; i++) {
+ node |= (uint64_t)uuid[15 - i] << (8 * i);
+ }
+ FILE *fp = fopen("conftest.out", "w");
+ if (fp == NULL) {
+ return ERR;
+ }
+ int rc = fprintf(fp, "%" PRIu64 "\n", node) >= 0;
+ rc |= fclose(fp);
+ return rc == 0 ? 0 : ERR;
+ }
+_ACEOF
+if ac_fn_c_try_run "$LINENO"
+then :
+
+ py_cv_uuid_node1=`cat conftest.out`
+
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
+esac
+fi
+
+CFLAGS=$save_CFLAGS
+CPPFLAGS=$save_CPPFLAGS
+LDFLAGS=$save_LDFLAGS
+LIBS=$save_LIBS
+
+
+ save_CFLAGS=$CFLAGS
+save_CPPFLAGS=$CPPFLAGS
+save_LDFLAGS=$LDFLAGS
+save_LIBS=$LIBS
+
+
+ # Be sure to add the extra include path if we used pkg-config
+ # as HAVE_UUID_H may be set even though <uuid.h> is only reachable
+ # by adding extra -I flags.
+ #
+ # If the following script does not compile, we simply assume that
+ # libuuid is missing.
+ CFLAGS="$CFLAGS $LIBUUID_CFLAGS"
+ LIBS="$LIBS $LIBUUID_LIBS"
+ if test "$cross_compiling" = yes
+then :
+
+
+else case e in #(
+ e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <inttypes.h> // PRIu64
+ #include <stdint.h> // uint64_t
+ #include <stdio.h> // fopen(), fclose()
+
+ #ifdef HAVE_UUID_H
+ #include <uuid.h>
+ #else
+ #include <uuid/uuid.h>
+ #endif
+
+ #define ERR 1
+ int main(void) {
+ uuid_t uuid; // unsigned char[16]
+ (void)uuid_generate_time_safe(uuid);
+ uint64_t node = 0;
+ for (size_t i = 0; i < 6; i++) {
+ node |= (uint64_t)uuid[15 - i] << (8 * i);
+ }
+ FILE *fp = fopen("conftest.out", "w");
+ if (fp == NULL) {
+ return ERR;
+ }
+ int rc = fprintf(fp, "%" PRIu64 "\n", node) >= 0;
+ rc |= fclose(fp);
+ return rc == 0 ? 0 : ERR;
+ }
+_ACEOF
+if ac_fn_c_try_run "$LINENO"
+then :
+
+ py_cv_uuid_node2=`cat conftest.out`
+
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
+esac
+fi
+
+CFLAGS=$save_CFLAGS
+CPPFLAGS=$save_CPPFLAGS
+LDFLAGS=$save_LDFLAGS
+LIBS=$save_LIBS
+
+
+ if test -n "$py_cv_uuid_node1" -a "$py_cv_uuid_node1" = "$py_cv_uuid_node2"
+ then
+ printf "%s\n" "#define HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC 1" >>confdefs.h
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: stable" >&5
+printf "%s\n" "stable" >&6; }
+ else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unstable" >&5
+printf "%s\n" "unstable" >&6; }
+ fi
+fi
+
# 'Real Time' functions on Solaris
# posix4 on Solaris 2.6
# pthread (first!) on Linux
@@ -14644,53 +14838,6 @@ printf "%s\n" "$AIX_BUILDDATE" >&6; }
*) ;;
esac
-# check for _Complex C type
-#
-# Note that despite most compilers define __STDC_IEC_559_COMPLEX__ - almost
-# none properly support C11+ Annex G (where pure imaginary types
-# represented by _Imaginary are mandatory). This is a bug (see e.g.
-# llvm/llvm-project#60269), so we don't rely on presence
-# of __STDC_IEC_559_COMPLEX__.
-if test "$cross_compiling" = yes
-then :
- ac_cv_c_complex_supported=no
-else case e in #(
- e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-
-#include <complex.h>
-#define test(type, out) \
-{ \
- type complex z = 1 + 2*I; z = z*z; \
- (out) = (out) || creal(z) != -3 || cimag(z) != 4; \
-}
-int main(void)
-{
- int res = 0;
- test(float, res);
- test(double, res);
- test(long double, res);
- return res;
-}
-_ACEOF
-if ac_fn_c_try_run "$LINENO"
-then :
- ac_cv_c_complex_supported=yes
-else case e in #(
- e) ac_cv_c_complex_supported=no ;;
-esac
-fi
-rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
- conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
-esac
-fi
-
-if test "$ac_cv_c_complex_supported" = "yes"; then
-
-printf "%s\n" "#define Py_HAVE_C_COMPLEX 1" >>confdefs.h
-
-fi
-
# check for systems that require aligned memory access
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking aligned memory access is required" >&5
printf %s "checking aligned memory access is required... " >&6; }
@@ -15595,7 +15742,7 @@ fi
printf "%s\n" "$ac_cv_ffi_complex_double_supported" >&6; }
if test "$ac_cv_ffi_complex_double_supported" = "yes"; then
-printf "%s\n" "#define Py_FFI_SUPPORT_C_COMPLEX 1" >>confdefs.h
+printf "%s\n" "#define _Py_FFI_SUPPORT_C_COMPLEX 1" >>confdefs.h
fi
@@ -15603,10 +15750,18 @@ fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-system-libmpdec" >&5
printf %s "checking for --with-system-libmpdec... " >&6; }
+
# Check whether --with-system_libmpdec was given.
if test ${with_system_libmpdec+y}
then :
- withval=$with_system_libmpdec;
+ withval=$with_system_libmpdec; if test "x$with_system_libmpdec" = xno
+then :
+ LIBMPDEC_CFLAGS="-I\$(srcdir)/Modules/_decimal/libmpdec"
+ LIBMPDEC_LIBS="-lm \$(LIBMPDEC_A)"
+ LIBMPDEC_INTERNAL="\$(LIBMPDEC_HEADERS) \$(LIBMPDEC_A)"
+ have_mpdec=yes
+ with_system_libmpdec=no
+fi
else case e in #(
e) with_system_libmpdec="yes" ;;
esac
@@ -15615,8 +15770,6 @@ fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_system_libmpdec" >&5
printf "%s\n" "$with_system_libmpdec" >&6; }
-
-
if test "x$with_system_libmpdec" = xyes
then :
@@ -15694,13 +15847,6 @@ else
printf "%s\n" "yes" >&6; }
fi
-else case e in #(
- e) LIBMPDEC_CFLAGS="-I\$(srcdir)/Modules/_decimal/libmpdec"
- LIBMPDEC_LIBS="-lm \$(LIBMPDEC_A)"
- LIBMPDEC_INTERNAL="\$(LIBMPDEC_HEADERS) \$(LIBMPDEC_A)"
- have_mpdec=yes
- with_system_libmpdec=no ;;
-esac
fi
if test "x$with_system_libmpdec" = xyes
@@ -15747,21 +15893,6 @@ LDFLAGS=$save_LDFLAGS
LIBS=$save_LIBS
-else case e in #(
- e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: the bundled copy of libmpdecimal is scheduled for removal in Python 3.15; consider using a system installed mpdecimal library." >&5
-printf "%s\n" "$as_me: WARNING: the bundled copy of libmpdecimal is scheduled for removal in Python 3.15; consider using a system installed mpdecimal library." >&2;} ;;
-esac
-fi
-
-if test "$with_system_libmpdec" = "yes" && test "$have_mpdec" = "no"
-then :
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: no system libmpdecimal found; falling back to bundled libmpdecimal (deprecated and scheduled for removal in Python 3.15)" >&5
-printf "%s\n" "$as_me: WARNING: no system libmpdecimal found; falling back to bundled libmpdecimal (deprecated and scheduled for removal in Python 3.15)" >&2;}
- LIBMPDEC_CFLAGS="-I\$(srcdir)/Modules/_decimal/libmpdec"
- LIBMPDEC_LIBS="-lm \$(LIBMPDEC_A)"
- LIBMPDEC_INTERNAL="\$(LIBMPDEC_HEADERS) \$(LIBMPDEC_A)"
- have_mpdec=yes
- with_system_libmpdec=no
fi
# Disable forced inlining in debug builds, see GH-94847
@@ -19305,6 +19436,12 @@ then :
printf "%s\n" "#define HAVE_GETLOGIN 1" >>confdefs.h
fi
+ac_fn_c_check_func "$LINENO" "getlogin_r" "ac_cv_func_getlogin_r"
+if test "x$ac_cv_func_getlogin_r" = xyes
+then :
+ printf "%s\n" "#define HAVE_GETLOGIN_R 1" >>confdefs.h
+
+fi
ac_fn_c_check_func "$LINENO" "getpeername" "ac_cv_func_getpeername"
if test "x$ac_cv_func_getpeername" = xyes
then :
@@ -22430,6 +22567,368 @@ printf "%s\n" "yes" >&6; }
fi
+pkg_failed=no
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libzstd >= 1.4.5" >&5
+printf %s "checking for libzstd >= 1.4.5... " >&6; }
+
+if test -n "$LIBZSTD_CFLAGS"; then
+ pkg_cv_LIBZSTD_CFLAGS="$LIBZSTD_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libzstd >= 1.4.5\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libzstd >= 1.4.5") 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBZSTD_CFLAGS=`$PKG_CONFIG --cflags "libzstd >= 1.4.5" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$LIBZSTD_LIBS"; then
+ pkg_cv_LIBZSTD_LIBS="$LIBZSTD_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libzstd >= 1.4.5\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libzstd >= 1.4.5") 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBZSTD_LIBS=`$PKG_CONFIG --libs "libzstd >= 1.4.5" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+ _pkg_short_errors_supported=yes
+else
+ _pkg_short_errors_supported=no
+fi
+ if test $_pkg_short_errors_supported = yes; then
+ LIBZSTD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libzstd >= 1.4.5" 2>&1`
+ else
+ LIBZSTD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libzstd >= 1.4.5" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$LIBZSTD_PKG_ERRORS" >&5
+
+
+ save_CFLAGS=$CFLAGS
+save_CPPFLAGS=$CPPFLAGS
+save_LDFLAGS=$LDFLAGS
+save_LIBS=$LIBS
+
+
+ CPPFLAGS="$CPPFLAGS $LIBZSTD_CFLAGS"
+ CFLAGS="$CFLAGS $LIBZSTD_CFLAGS"
+ LIBS="$LIBS $LIBZSTD_LIBS"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing ZDICT_finalizeDictionary" >&5
+printf %s "checking for library containing ZDICT_finalizeDictionary... " >&6; }
+if test ${ac_cv_search_ZDICT_finalizeDictionary+y}
+then :
+ printf %s "(cached) " >&6
+else case e in #(
+ e) ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply.
+ The 'extern "C"' is for builds by C++ compilers;
+ although this is not generally supported in C code supporting it here
+ has little cost and some practical benefit (sr 110532). */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ZDICT_finalizeDictionary (void);
+int
+main (void)
+{
+return ZDICT_finalizeDictionary ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' zstd
+do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_search_ZDICT_finalizeDictionary=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext
+ if test ${ac_cv_search_ZDICT_finalizeDictionary+y}
+then :
+ break
+fi
+done
+if test ${ac_cv_search_ZDICT_finalizeDictionary+y}
+then :
+
+else case e in #(
+ e) ac_cv_search_ZDICT_finalizeDictionary=no ;;
+esac
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_ZDICT_finalizeDictionary" >&5
+printf "%s\n" "$ac_cv_search_ZDICT_finalizeDictionary" >&6; }
+ac_res=$ac_cv_search_ZDICT_finalizeDictionary
+if test "$ac_res" != no
+then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking ZSTD_VERSION_NUMBER >= 1.4.5" >&5
+printf %s "checking ZSTD_VERSION_NUMBER >= 1.4.5... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include "zstd.h"
+int
+main (void)
+{
+
+ #if ZSTD_VERSION_NUMBER < 10405
+ # error "zstd version is too old"
+ #endif
+
+ ;
+ return 0;
+}
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+ for ac_header in zstd.h zdict.h
+do :
+ as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | sed "$as_sed_sh"`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"
+then :
+ cat >>confdefs.h <<_ACEOF
+#define `printf "%s\n" "HAVE_$ac_header" | sed "$as_sed_cpp"` 1
+_ACEOF
+ have_libzstd=yes
+else case e in #(
+ e) have_libzstd=no ;;
+esac
+fi
+
+done
+
+else case e in #(
+ e)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ have_libzstd=no
+ ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+
+else case e in #(
+ e) have_libzstd=no ;;
+esac
+fi
+
+ if test "x$have_libzstd" = xyes
+then :
+
+ LIBZSTD_CFLAGS=${LIBZSTD_CFLAGS-""}
+ LIBZSTD_LIBS=${LIBZSTD_LIBS-"-lzstd"}
+
+fi
+
+CFLAGS=$save_CFLAGS
+CPPFLAGS=$save_CPPFLAGS
+LDFLAGS=$save_LDFLAGS
+LIBS=$save_LIBS
+
+
+
+elif test $pkg_failed = untried; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+ save_CFLAGS=$CFLAGS
+save_CPPFLAGS=$CPPFLAGS
+save_LDFLAGS=$LDFLAGS
+save_LIBS=$LIBS
+
+
+ CPPFLAGS="$CPPFLAGS $LIBZSTD_CFLAGS"
+ CFLAGS="$CFLAGS $LIBZSTD_CFLAGS"
+ LIBS="$LIBS $LIBZSTD_LIBS"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing ZDICT_finalizeDictionary" >&5
+printf %s "checking for library containing ZDICT_finalizeDictionary... " >&6; }
+if test ${ac_cv_search_ZDICT_finalizeDictionary+y}
+then :
+ printf %s "(cached) " >&6
+else case e in #(
+ e) ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply.
+ The 'extern "C"' is for builds by C++ compilers;
+ although this is not generally supported in C code supporting it here
+ has little cost and some practical benefit (sr 110532). */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ZDICT_finalizeDictionary (void);
+int
+main (void)
+{
+return ZDICT_finalizeDictionary ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' zstd
+do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_search_ZDICT_finalizeDictionary=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext
+ if test ${ac_cv_search_ZDICT_finalizeDictionary+y}
+then :
+ break
+fi
+done
+if test ${ac_cv_search_ZDICT_finalizeDictionary+y}
+then :
+
+else case e in #(
+ e) ac_cv_search_ZDICT_finalizeDictionary=no ;;
+esac
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_ZDICT_finalizeDictionary" >&5
+printf "%s\n" "$ac_cv_search_ZDICT_finalizeDictionary" >&6; }
+ac_res=$ac_cv_search_ZDICT_finalizeDictionary
+if test "$ac_res" != no
+then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking ZSTD_VERSION_NUMBER >= 1.4.5" >&5
+printf %s "checking ZSTD_VERSION_NUMBER >= 1.4.5... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include "zstd.h"
+int
+main (void)
+{
+
+ #if ZSTD_VERSION_NUMBER < 10405
+ # error "zstd version is too old"
+ #endif
+
+ ;
+ return 0;
+}
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+ for ac_header in zstd.h zdict.h
+do :
+ as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | sed "$as_sed_sh"`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"
+then :
+ cat >>confdefs.h <<_ACEOF
+#define `printf "%s\n" "HAVE_$ac_header" | sed "$as_sed_cpp"` 1
+_ACEOF
+ have_libzstd=yes
+else case e in #(
+ e) have_libzstd=no ;;
+esac
+fi
+
+done
+
+else case e in #(
+ e)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ have_libzstd=no
+ ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+
+else case e in #(
+ e) have_libzstd=no ;;
+esac
+fi
+
+ if test "x$have_libzstd" = xyes
+then :
+
+ LIBZSTD_CFLAGS=${LIBZSTD_CFLAGS-""}
+ LIBZSTD_LIBS=${LIBZSTD_LIBS-"-lzstd"}
+
+fi
+
+CFLAGS=$save_CFLAGS
+CPPFLAGS=$save_CPPFLAGS
+LDFLAGS=$save_LDFLAGS
+LIBS=$save_LIBS
+
+
+
+else
+ LIBZSTD_CFLAGS=$pkg_cv_LIBZSTD_CFLAGS
+ LIBZSTD_LIBS=$pkg_cv_LIBZSTD_LIBS
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+ have_libzstd=yes
+fi
+
+
@@ -23327,6 +23826,33 @@ fi
+ac_fn_check_decl "$LINENO" "MAXLOGNAME" "ac_cv_have_decl_MAXLOGNAME" "#include <sys/param.h>
+" "$ac_c_undeclared_builtin_options" "CFLAGS"
+if test "x$ac_cv_have_decl_MAXLOGNAME" = xyes
+then :
+
+printf "%s\n" "#define HAVE_MAXLOGNAME 1" >>confdefs.h
+
+fi
+
+ac_fn_check_decl "$LINENO" "UT_NAMESIZE" "ac_cv_have_decl_UT_NAMESIZE" "#include <utmp.h>
+" "$ac_c_undeclared_builtin_options" "CFLAGS"
+if test "x$ac_cv_have_decl_UT_NAMESIZE" = xyes
+then :
+ ac_have_decl=1
+else case e in #(
+ e) ac_have_decl=0 ;;
+esac
+fi
+printf "%s\n" "#define HAVE_DECL_UT_NAMESIZE $ac_have_decl" >>confdefs.h
+if test $ac_have_decl = 1
+then :
+
+printf "%s\n" "#define HAVE_UT_NAMESIZE 1" >>confdefs.h
+
+fi
+
+
# check for openpty, login_tty, and forkpty
@@ -29391,9 +29917,6 @@ printf "%s\n" "#define Py_REMOTE_DEBUG 1" >>confdefs.h
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; }
else
-
-printf "%s\n" "#define Py_REMOTE_DEBUG 0" >>confdefs.h
-
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
printf "%s\n" "no" >&6; }
fi
@@ -29429,6 +29952,7 @@ SRCDIRS="\
Modules/_testinternalcapi \
Modules/_testlimitedcapi \
Modules/_xxtestfuzz \
+ Modules/_zstd \
Modules/cjkcodecs \
Modules/expat \
Objects \
@@ -30684,7 +31208,7 @@ case $ac_sys_system in #(
py_cv_module__ctypes_test=n/a
- py_cv_module__testexternalinspection=n/a
+ py_cv_module__remote_debugging=n/a
py_cv_module__testimportmultiple=n/a
py_cv_module__testmultiphase=n/a
py_cv_module__testsinglephase=n/a
@@ -31009,6 +31533,28 @@ then :
fi
+ if test "$py_cv_module__remote_debugging" != "n/a"
+then :
+ py_cv_module__remote_debugging=yes
+fi
+ if test "$py_cv_module__remote_debugging" = yes; then
+ MODULE__REMOTE_DEBUGGING_TRUE=
+ MODULE__REMOTE_DEBUGGING_FALSE='#'
+else
+ MODULE__REMOTE_DEBUGGING_TRUE='#'
+ MODULE__REMOTE_DEBUGGING_FALSE=
+fi
+
+ as_fn_append MODULE_BLOCK "MODULE__REMOTE_DEBUGGING_STATE=$py_cv_module__remote_debugging$as_nl"
+ if test "x$py_cv_module__remote_debugging" = xyes
+then :
+
+
+
+
+fi
+
+
if test "$py_cv_module_select" != "n/a"
then :
py_cv_module_select=yes
@@ -32018,6 +32564,14 @@ LIBHACL_CFLAGS="${LIBHACL_FLAG_I} ${LIBHACL_FLAG_D} \$(PY_STDMODULE_CFLAGS) \$(C
LIBHACL_LDFLAGS= # for now, no specific linker flags are needed
+if test "$UNIVERSAL_ARCHS" = "universal2" -o \
+ \( "$build_cpu" = "aarch64" -a "$build_vendor" = "apple" \)
+then
+ use_hacl_universal2_impl=yes
+else
+ use_hacl_universal2_impl=no
+fi
+
# The SIMD files use aligned_alloc, which is not available on older versions of
# Android.
# The *mmintrin.h headers are x86-family-specific, so can't be used on WASI.
@@ -32063,7 +32617,7 @@ then :
LIBHACL_SIMD128_FLAGS="-msse -msse2 -msse3 -msse4.1 -msse4.2"
-printf "%s\n" "#define HACL_CAN_COMPILE_SIMD128 1" >>confdefs.h
+printf "%s\n" "#define _Py_HACL_CAN_COMPILE_VEC128 1" >>confdefs.h
# macOS universal2 builds *support* the -msse etc flags because they're
@@ -32071,7 +32625,7 @@ printf "%s\n" "#define HACL_CAN_COMPILE_SIMD128 1" >>confdefs.h
# isn't great, so it's disabled on ARM64.
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for HACL* SIMD128 implementation" >&5
printf %s "checking for HACL* SIMD128 implementation... " >&6; }
- if test "$UNIVERSAL_ARCHS" == "universal2"; then
+ if test "$use_hacl_universal2_impl" = "yes"; then
LIBHACL_BLAKE2_SIMD128_OBJS="Modules/_hacl/Hacl_Hash_Blake2s_Simd128_universal2.o"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: universal2" >&5
printf "%s\n" "universal2" >&6; }
@@ -32139,7 +32693,7 @@ then :
LIBHACL_SIMD256_FLAGS="-mavx2"
-printf "%s\n" "#define HACL_CAN_COMPILE_SIMD256 1" >>confdefs.h
+printf "%s\n" "#define _Py_HACL_CAN_COMPILE_VEC256 1" >>confdefs.h
# macOS universal2 builds *support* the -mavx2 compiler flag because it's
@@ -32148,7 +32702,7 @@ printf "%s\n" "#define HACL_CAN_COMPILE_SIMD256 1" >>confdefs.h
# wrapped implementation if we're building for universal2.
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for HACL* SIMD256 implementation" >&5
printf %s "checking for HACL* SIMD256 implementation... " >&6; }
- if test "$UNIVERSAL_ARCHS" == "universal2"; then
+ if test "$use_hacl_universal2_impl" = "yes"; then
LIBHACL_BLAKE2_SIMD256_OBJS="Modules/_hacl/Hacl_Hash_Blake2b_Simd256_universal2.o"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: universal2" >&5
printf "%s\n" "universal2" >&6; }
@@ -32423,7 +32977,7 @@ printf %s "checking for stdlib extension module _hmac... " >&6; }
if test "$py_cv_module__hmac" != "n/a"
then :
- if true
+ if test "$ac_sys_system" != "Emscripten"
then :
if true
then :
@@ -32624,6 +33178,18 @@ fi
printf "%s\n" "$py_cv_module__decimal" >&6; }
+if test "x$with_system_libmpdec" = xno
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: the bundled copy of libmpdec is scheduled for removal in Python 3.16; consider using a system installed mpdecimal library." >&5
+printf "%s\n" "$as_me: WARNING: the bundled copy of libmpdec is scheduled for removal in Python 3.16; consider using a system installed mpdecimal library." >&2;}
+fi
+if test "$with_system_libmpdec" = "yes" && test "$have_mpdec" = "no"
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: no system libmpdec found; falling back to pure-Python version for the decimal module" >&5
+printf "%s\n" "$as_me: WARNING: no system libmpdec found; falling back to pure-Python version for the decimal module" >&2;}
+fi
+
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _dbm" >&5
printf %s "checking for stdlib extension module _dbm... " >&6; }
if test "$py_cv_module__dbm" != "n/a"
@@ -33007,6 +33573,46 @@ fi
printf "%s\n" "$py_cv_module__lzma" >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _zstd" >&5
+printf %s "checking for stdlib extension module _zstd... " >&6; }
+ if test "$py_cv_module__zstd" != "n/a"
+then :
+
+ if true
+then :
+ if test "$have_libzstd" = yes
+then :
+ py_cv_module__zstd=yes
+else case e in #(
+ e) py_cv_module__zstd=missing ;;
+esac
+fi
+else case e in #(
+ e) py_cv_module__zstd=disabled ;;
+esac
+fi
+
+fi
+ as_fn_append MODULE_BLOCK "MODULE__ZSTD_STATE=$py_cv_module__zstd$as_nl"
+ if test "x$py_cv_module__zstd" = xyes
+then :
+
+ as_fn_append MODULE_BLOCK "MODULE__ZSTD_CFLAGS=$LIBZSTD_CFLAGS$as_nl"
+ as_fn_append MODULE_BLOCK "MODULE__ZSTD_LDFLAGS=$LIBZSTD_LIBS$as_nl"
+
+fi
+ if test "$py_cv_module__zstd" = yes; then
+ MODULE__ZSTD_TRUE=
+ MODULE__ZSTD_FALSE='#'
+else
+ MODULE__ZSTD_TRUE='#'
+ MODULE__ZSTD_FALSE=
+fi
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__zstd" >&5
+printf "%s\n" "$py_cv_module__zstd" >&6; }
+
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _ssl" >&5
printf %s "checking for stdlib extension module _ssl... " >&6; }
@@ -33449,46 +34055,6 @@ fi
printf "%s\n" "$py_cv_module__testsinglephase" >&6; }
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testexternalinspection" >&5
-printf %s "checking for stdlib extension module _testexternalinspection... " >&6; }
- if test "$py_cv_module__testexternalinspection" != "n/a"
-then :
-
- if test "$TEST_MODULES" = yes
-then :
- if true
-then :
- py_cv_module__testexternalinspection=yes
-else case e in #(
- e) py_cv_module__testexternalinspection=missing ;;
-esac
-fi
-else case e in #(
- e) py_cv_module__testexternalinspection=disabled ;;
-esac
-fi
-
-fi
- as_fn_append MODULE_BLOCK "MODULE__TESTEXTERNALINSPECTION_STATE=$py_cv_module__testexternalinspection$as_nl"
- if test "x$py_cv_module__testexternalinspection" = xyes
-then :
-
-
-
-
-fi
- if test "$py_cv_module__testexternalinspection" = yes; then
- MODULE__TESTEXTERNALINSPECTION_TRUE=
- MODULE__TESTEXTERNALINSPECTION_FALSE='#'
-else
- MODULE__TESTEXTERNALINSPECTION_TRUE='#'
- MODULE__TESTEXTERNALINSPECTION_FALSE=
-fi
-
- { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__testexternalinspection" >&5
-printf "%s\n" "$py_cv_module__testexternalinspection" >&6; }
-
-
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module xxsubtype" >&5
printf %s "checking for stdlib extension module xxsubtype... " >&6; }
if test "$py_cv_module_xxsubtype" != "n/a"
@@ -33863,6 +34429,10 @@ if test -z "${MODULE__RANDOM_TRUE}" && test -z "${MODULE__RANDOM_FALSE}"; then
as_fn_error $? "conditional \"MODULE__RANDOM\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
fi
+if test -z "${MODULE__REMOTE_DEBUGGING_TRUE}" && test -z "${MODULE__REMOTE_DEBUGGING_FALSE}"; then
+ as_fn_error $? "conditional \"MODULE__REMOTE_DEBUGGING\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
if test -z "${MODULE_SELECT_TRUE}" && test -z "${MODULE_SELECT_FALSE}"; then
as_fn_error $? "conditional \"MODULE_SELECT\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
@@ -34075,6 +34645,10 @@ if test -z "${MODULE__LZMA_TRUE}" && test -z "${MODULE__LZMA_FALSE}"; then
as_fn_error $? "conditional \"MODULE__LZMA\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
fi
+if test -z "${MODULE__ZSTD_TRUE}" && test -z "${MODULE__ZSTD_FALSE}"; then
+ as_fn_error $? "conditional \"MODULE__ZSTD\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
if test -z "${MODULE__SSL_TRUE}" && test -z "${MODULE__SSL_FALSE}"; then
as_fn_error $? "conditional \"MODULE__SSL\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
@@ -34119,10 +34693,6 @@ if test -z "${MODULE__TESTSINGLEPHASE_TRUE}" && test -z "${MODULE__TESTSINGLEPHA
as_fn_error $? "conditional \"MODULE__TESTSINGLEPHASE\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
fi
-if test -z "${MODULE__TESTEXTERNALINSPECTION_TRUE}" && test -z "${MODULE__TESTEXTERNALINSPECTION_FALSE}"; then
- as_fn_error $? "conditional \"MODULE__TESTEXTERNALINSPECTION\" was never defined.
-Usually this means the macro was only invoked conditionally." "$LINENO" 5
-fi
if test -z "${MODULE_XXSUBTYPE_TRUE}" && test -z "${MODULE_XXSUBTYPE_FALSE}"; then
as_fn_error $? "conditional \"MODULE_XXSUBTYPE\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
@@ -34536,7 +35106,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by python $as_me 3.14, which was
+This file was extended by python $as_me 3.15, which was
generated by GNU Autoconf 2.72. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -34600,7 +35170,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
-python config.status 3.14
+python config.status 3.15
configured by $0, generated by GNU Autoconf 2.72,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index 65f265045ba..cb7f2144345 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,7 +10,7 @@ dnl to regenerate the configure script.
dnl
# Set VERSION so we only need to edit in one place (i.e., here)
-m4_define([PYTHON_VERSION], [3.14])
+m4_define([PYTHON_VERSION], [3.15])
AC_PREREQ([2.72])
@@ -1716,7 +1716,7 @@ ABI_THREAD=""
# --disable-gil
AC_MSG_CHECKING([for --disable-gil])
AC_ARG_ENABLE([gil],
- [AS_HELP_STRING([--disable-gil], [enable experimental support for running without the GIL (default is no)])],
+ [AS_HELP_STRING([--disable-gil], [enable support for running without the GIL (default is no)])],
[AS_VAR_IF([enable_gil], [yes], [disable_gil=no], [disable_gil=yes])], [disable_gil=no]
)
AC_MSG_RESULT([$disable_gil])
@@ -2776,7 +2776,7 @@ AS_VAR_IF([jit_flags],
[],
[AS_VAR_APPEND([CFLAGS_NODIST], [" $jit_flags"])
AS_VAR_SET([REGEN_JIT_COMMAND],
- ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host}"])
+ ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . --cflags=\"$CFLAGS_JIT\""])
AS_VAR_SET([JIT_STENCILS_H], ["jit_stencils.h"])
AS_VAR_IF([Py_DEBUG],
[true],
@@ -3172,10 +3172,6 @@ if test "$ac_cv_sizeof_off_t" -gt "$ac_cv_sizeof_long" -a \
else
have_largefile_support="no"
fi
-dnl LFS does not work with Emscripten 3.1
-AS_CASE([$ac_sys_system],
- [Emscripten], [have_largefile_support="no"]
-)
AS_VAR_IF([have_largefile_support], [yes], [
AC_DEFINE([HAVE_LARGEFILE_SUPPORT], [1],
[Defined to enable large file support when an off_t is bigger than a long
@@ -3740,15 +3736,17 @@ dnl check for uuid dependencies
AH_TEMPLATE([HAVE_UUID_H], [Define to 1 if you have the <uuid.h> header file.])
AH_TEMPLATE([HAVE_UUID_UUID_H], [Define to 1 if you have the <uuid/uuid.h> header file.])
AH_TEMPLATE([HAVE_UUID_GENERATE_TIME_SAFE], [Define if uuid_generate_time_safe() exists.])
+AH_TEMPLATE([HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC], [Define if uuid_generate_time_safe() is able to deduce a MAC address.])
have_uuid=missing
dnl AIX provides support for RFC4122 (uuid) in libc.a starting with AIX 6.1
dnl (anno 2007). FreeBSD and OpenBSD provides support in libc as well.
dnl Little-endian FreeBSD, OpenBSD and NetBSD needs encoding into an octet
dnl stream in big-endian byte-order
-AC_CHECK_HEADERS([uuid.h],
- [AC_CHECK_FUNCS([uuid_create uuid_enc_be],
- [have_uuid=yes
+AC_CHECK_HEADERS([uuid.h], [
+ AC_CHECK_FUNCS([uuid_create uuid_enc_be], [
+ have_uuid=yes
+ ac_cv_have_uuid_h=yes
LIBUUID_CFLAGS=${LIBUUID_CFLAGS-""}
LIBUUID_LIBS=${LIBUUID_LIBS-""}
])
@@ -3758,19 +3756,29 @@ AS_VAR_IF([have_uuid], [missing], [
PKG_CHECK_MODULES(
[LIBUUID], [uuid >= 2.20],
[dnl linux-util's libuuid has uuid_generate_time_safe() since v2.20 (2011)
- dnl and provides <uuid.h>.
+ dnl and provides <uuid.h> assuming specific include paths are given
have_uuid=yes
- AC_DEFINE([HAVE_UUID_H], [1])
- AC_DEFINE([HAVE_UUID_GENERATE_TIME_SAFE], [1])
+ ac_cv_have_uuid_generate_time_safe=yes
+ # The uuid.h file to include may be <uuid.h> *or* <uuid/uuid.h>.
+ # Since pkg-config --cflags uuid may return -I/usr/include/uuid,
+ # it's possible to write '#include <uuid.h>' in _uuidmodule.c,
+ # assuming that the compiler flags are properly updated.
+ #
+ # Ideally, we should have defined HAVE_UUID_H if and only if
+ # #include <uuid.h> can be written, *without* assuming extra
+ # include path.
+ ac_cv_have_uuid_h=yes
], [
WITH_SAVE_ENV([
CPPFLAGS="$CPPFLAGS $LIBUUID_CFLAGS"
LIBS="$LIBS $LIBUUID_LIBS"
AC_CHECK_HEADERS([uuid/uuid.h], [
+ ac_cv_have_uuid_uuid_h=yes
PY_CHECK_LIB([uuid], [uuid_generate_time], [have_uuid=yes])
- PY_CHECK_LIB([uuid], [uuid_generate_time_safe],
- [have_uuid=yes
- AC_DEFINE([HAVE_UUID_GENERATE_TIME_SAFE], [1]) ]) ])
+ PY_CHECK_LIB([uuid], [uuid_generate_time_safe], [
+ have_uuid=yes
+ ac_cv_have_uuid_generate_time_safe=yes
+ ])])
AS_VAR_IF([have_uuid], [yes], [
LIBUUID_CFLAGS=${LIBUUID_CFLAGS-""}
LIBUUID_LIBS=${LIBUUID_LIBS-"-luuid"}
@@ -3785,12 +3793,19 @@ AS_VAR_IF([have_uuid], [missing], [
AC_CHECK_HEADERS([uuid/uuid.h], [
AC_CHECK_FUNC([uuid_generate_time], [
have_uuid=yes
+ ac_cv_have_uuid_uuid_h=yes
LIBUUID_CFLAGS=${LIBUUID_CFLAGS-""}
LIBUUID_LIBS=${LIBUUID_LIBS-""}
])
])
])
+AS_VAR_IF([ac_cv_have_uuid_h], [yes], [AC_DEFINE([HAVE_UUID_H], [1])])
+AS_VAR_IF([ac_cv_have_uuid_uuid_h], [yes], [AC_DEFINE([HAVE_UUID_UUID_H], [1])])
+AS_VAR_IF([ac_cv_have_uuid_generate_time_safe], [yes], [
+ AC_DEFINE([HAVE_UUID_GENERATE_TIME_SAFE], [1])
+])
+
# gh-124228: While the libuuid library is available on NetBSD, it supports only UUID version 4.
# This restriction inhibits the proper generation of time-based UUIDs.
if test "$ac_sys_system" = "NetBSD"; then
@@ -3800,6 +3815,68 @@ fi
AS_VAR_IF([have_uuid], [missing], [have_uuid=no])
+# gh-132710: The UUID node is fetched by using libuuid when possible
+# and cached. While the node is constant within the same process,
+# different interpreters may have different values as libuuid may
+# randomize the node value if the latter cannot be deduced.
+#
+# Consumers may define HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC
+# to indicate that libuuid is unstable and should not be relied
+# upon to deduce the MAC address.
+AC_DEFUN([PY_EXTRACT_UUID_GENERATE_TIME_SAFE_MAC], [WITH_SAVE_ENV([
+ # Be sure to add the extra include path if we used pkg-config
+ # as HAVE_UUID_H may be set even though <uuid.h> is only reachable
+ # by adding extra -I flags.
+ #
+ # If the following script does not compile, we simply assume that
+ # libuuid is missing.
+ CFLAGS="$CFLAGS $LIBUUID_CFLAGS"
+ LIBS="$LIBS $LIBUUID_LIBS"
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[
+ #include <inttypes.h> // PRIu64
+ #include <stdint.h> // uint64_t
+ #include <stdio.h> // fopen(), fclose()
+
+ #ifdef HAVE_UUID_H
+ #include <uuid.h>
+ #else
+ #include <uuid/uuid.h>
+ #endif
+
+ #define ERR 1
+ int main(void) {
+ uuid_t uuid; // unsigned char[16]
+ (void)uuid_generate_time_safe(uuid);
+ uint64_t node = 0;
+ for (size_t i = 0; i < 6; i++) {
+ node |= (uint64_t)uuid[15 - i] << (8 * i);
+ }
+ FILE *fp = fopen("conftest.out", "w");
+ if (fp == NULL) {
+ return ERR;
+ }
+ int rc = fprintf(fp, "%" PRIu64 "\n", node) >= 0;
+ rc |= fclose(fp);
+ return rc == 0 ? 0 : ERR;
+ }]])], [
+ AS_VAR_SET([$1], [`cat conftest.out`])
+ ], [], []
+ )])])
+
+if test "$have_uuid" = "yes" -a "$HAVE_UUID_GENERATE_TIME_SAFE" = "1"
+then
+ AC_MSG_CHECKING([if uuid_generate_time_safe() node value is stable])
+ PY_EXTRACT_UUID_GENERATE_TIME_SAFE_MAC([py_cv_uuid_node1])
+ PY_EXTRACT_UUID_GENERATE_TIME_SAFE_MAC([py_cv_uuid_node2])
+ if test -n "$py_cv_uuid_node1" -a "$py_cv_uuid_node1" = "$py_cv_uuid_node2"
+ then
+ AC_DEFINE([HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC], [1])
+ AC_MSG_RESULT([stable])
+ else
+ AC_MSG_RESULT([unstable])
+ fi
+fi
+
# 'Real Time' functions on Solaris
# posix4 on Solaris 2.6
# pthread (first!) on Linux
@@ -3838,35 +3915,6 @@ dnl The AIX_BUILDDATE is obtained from the kernel fileset - bos.mp64
*) ;;
esac
-# check for _Complex C type
-#
-# Note that despite most compilers define __STDC_IEC_559_COMPLEX__ - almost
-# none properly support C11+ Annex G (where pure imaginary types
-# represented by _Imaginary are mandatory). This is a bug (see e.g.
-# llvm/llvm-project#60269), so we don't rely on presence
-# of __STDC_IEC_559_COMPLEX__.
-AC_RUN_IFELSE([AC_LANG_SOURCE([[
-#include <complex.h>
-#define test(type, out) \
-{ \
- type complex z = 1 + 2*I; z = z*z; \
- (out) = (out) || creal(z) != -3 || cimag(z) != 4; \
-}
-int main(void)
-{
- int res = 0;
- test(float, res);
- test(double, res);
- test(long double, res);
- return res;
-}]])], [ac_cv_c_complex_supported=yes],
-[ac_cv_c_complex_supported=no],
-[ac_cv_c_complex_supported=no])
-if test "$ac_cv_c_complex_supported" = "yes"; then
- AC_DEFINE([Py_HAVE_C_COMPLEX], [1],
- [Defined if _Complex C type is available.])
-fi
-
# check for systems that require aligned memory access
AC_CACHE_CHECK([aligned memory access is required], [ac_cv_aligned_required],
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
@@ -4115,37 +4163,36 @@ int main(void)
[ac_cv_ffi_complex_double_supported=no])
])])
if test "$ac_cv_ffi_complex_double_supported" = "yes"; then
- AC_DEFINE([Py_FFI_SUPPORT_C_COMPLEX], [1],
+ AC_DEFINE([_Py_FFI_SUPPORT_C_COMPLEX], [1],
[Defined if _Complex C type can be used with libffi.])
fi
# Check for use of the system libmpdec library
AC_MSG_CHECKING([for --with-system-libmpdec])
+AC_DEFUN([USE_BUNDLED_LIBMPDEC],
+ [LIBMPDEC_CFLAGS="-I\$(srcdir)/Modules/_decimal/libmpdec"
+ LIBMPDEC_LIBS="-lm \$(LIBMPDEC_A)"
+ LIBMPDEC_INTERNAL="\$(LIBMPDEC_HEADERS) \$(LIBMPDEC_A)"
+ have_mpdec=yes
+ with_system_libmpdec=no])
AC_ARG_WITH(
[system_libmpdec],
[AS_HELP_STRING(
[--with-system-libmpdec],
[build _decimal module using an installed mpdecimal library, see Doc/library/decimal.rst (default is yes)]
)],
- [],
+ [AS_IF([test "x$with_system_libmpdec" = xno],
+ [USE_BUNDLED_LIBMPDEC()])],
[with_system_libmpdec="yes"])
AC_MSG_RESULT([$with_system_libmpdec])
-AC_DEFUN([USE_BUNDLED_LIBMPDEC],
- [LIBMPDEC_CFLAGS="-I\$(srcdir)/Modules/_decimal/libmpdec"
- LIBMPDEC_LIBS="-lm \$(LIBMPDEC_A)"
- LIBMPDEC_INTERNAL="\$(LIBMPDEC_HEADERS) \$(LIBMPDEC_A)"
- have_mpdec=yes
- with_system_libmpdec=no])
-
AS_VAR_IF(
[with_system_libmpdec], [yes],
[PKG_CHECK_MODULES(
[LIBMPDEC], [libmpdec >= 2.5.0], [],
[LIBMPDEC_CFLAGS=${LIBMPDEC_CFLAGS-""}
LIBMPDEC_LIBS=${LIBMPDEC_LIBS-"-lmpdec -lm"}
- LIBMPDEC_INTERNAL=])],
- [USE_BUNDLED_LIBMPDEC()])
+ LIBMPDEC_INTERNAL=])])
AS_VAR_IF([with_system_libmpdec], [yes],
[WITH_SAVE_ENV([
@@ -4161,16 +4208,7 @@ AS_VAR_IF([with_system_libmpdec], [yes],
], [const char *x = mpd_version();])],
[have_mpdec=yes],
[have_mpdec=no])
- ])],
- [AC_MSG_WARN([m4_normalize([
- the bundled copy of libmpdecimal is scheduled for removal in Python 3.15;
- consider using a system installed mpdecimal library.])])])
-
-AS_IF([test "$with_system_libmpdec" = "yes" && test "$have_mpdec" = "no"],
- [AC_MSG_WARN([m4_normalize([
- no system libmpdecimal found; falling back to bundled libmpdecimal
- (deprecated and scheduled for removal in Python 3.15)])])
- USE_BUNDLED_LIBMPDEC()])
+ ])])
# Disable forced inlining in debug builds, see GH-94847
AS_VAR_IF(
@@ -5167,7 +5205,7 @@ AC_CHECK_FUNCS([ \
faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \
fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \
gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \
- getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \
+ getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin getlogin_r \
getpeername getpgid getpid getppid getpriority _getpty \
getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \
getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \
@@ -5415,6 +5453,35 @@ PKG_CHECK_MODULES([LIBLZMA], [liblzma], [have_liblzma=yes], [
])
])
+dnl zstd 1.4.5 stabilised ZDICT_finalizeDictionary
+PKG_CHECK_MODULES([LIBZSTD], [libzstd >= 1.4.5], [have_libzstd=yes], [
+ WITH_SAVE_ENV([
+ CPPFLAGS="$CPPFLAGS $LIBZSTD_CFLAGS"
+ CFLAGS="$CFLAGS $LIBZSTD_CFLAGS"
+ LIBS="$LIBS $LIBZSTD_LIBS"
+ AC_SEARCH_LIBS([ZDICT_finalizeDictionary], [zstd], [
+ AC_MSG_CHECKING([ZSTD_VERSION_NUMBER >= 1.4.5])
+ AC_COMPILE_IFELSE([
+ AC_LANG_PROGRAM([@%:@include "zstd.h"], [
+ #if ZSTD_VERSION_NUMBER < 10405
+ # error "zstd version is too old"
+ #endif
+ ])
+ ], [
+ AC_MSG_RESULT([yes])
+ AC_CHECK_HEADERS([zstd.h zdict.h], [have_libzstd=yes], [have_libzstd=no])
+ ], [
+ AC_MSG_RESULT([no])
+ have_libzstd=no
+ ])
+ ], [have_libzstd=no])
+ AS_VAR_IF([have_libzstd], [yes], [
+ LIBZSTD_CFLAGS=${LIBZSTD_CFLAGS-""}
+ LIBZSTD_LIBS=${LIBZSTD_LIBS-"-lzstd"}
+ ])
+ ])
+])
+
dnl PY_CHECK_NETDB_FUNC(FUNCTION)
AC_DEFUN([PY_CHECK_NETDB_FUNC], [PY_CHECK_FUNC([$1], [@%:@include <netdb.h>])])
@@ -5457,6 +5524,18 @@ PY_CHECK_FUNC([setgroups], [
#endif
])
+AC_CHECK_DECL([MAXLOGNAME],
+ [AC_DEFINE([HAVE_MAXLOGNAME], [1],
+ [Define if you have the 'MAXLOGNAME' constant.])],
+ [],
+ [@%:@include <sys/param.h>])
+
+AC_CHECK_DECLS([UT_NAMESIZE],
+ [AC_DEFINE([HAVE_UT_NAMESIZE], [1],
+ [Define if you have the 'HAVE_UT_NAMESIZE' constant.])],
+ [],
+ [@%:@include <utmp.h>])
+
# check for openpty, login_tty, and forkpty
AC_CHECK_FUNCS([openpty], [],
@@ -7079,8 +7158,6 @@ if test "$with_remote_debug" = yes; then
[Define if you want to enable remote debugging support.])
AC_MSG_RESULT([yes])
else
- AC_DEFINE([Py_REMOTE_DEBUG], [0],
- [Define if you want to enable remote debugging support.])
AC_MSG_RESULT([no])
fi
@@ -7114,6 +7191,7 @@ SRCDIRS="\
Modules/_testinternalcapi \
Modules/_testlimitedcapi \
Modules/_xxtestfuzz \
+ Modules/_zstd \
Modules/cjkcodecs \
Modules/expat \
Objects \
@@ -7720,7 +7798,7 @@ AS_CASE([$ac_sys_system],
dnl (see Modules/Setup.stdlib.in).
PY_STDLIB_MOD_SET_NA(
[_ctypes_test],
- [_testexternalinspection],
+ [_remote_debugging],
[_testimportmultiple],
[_testmultiphase],
[_testsinglephase],
@@ -7814,6 +7892,7 @@ PY_STDLIB_MOD_SIMPLE([_pickle])
PY_STDLIB_MOD_SIMPLE([_posixsubprocess])
PY_STDLIB_MOD_SIMPLE([_queue])
PY_STDLIB_MOD_SIMPLE([_random])
+PY_STDLIB_MOD_SIMPLE([_remote_debugging])
PY_STDLIB_MOD_SIMPLE([select])
PY_STDLIB_MOD_SIMPLE([_struct])
PY_STDLIB_MOD_SIMPLE([_types])
@@ -7918,6 +7997,15 @@ AC_SUBST([LIBHACL_CFLAGS])
LIBHACL_LDFLAGS= # for now, no specific linker flags are needed
AC_SUBST([LIBHACL_LDFLAGS])
+dnl Check if universal2 HACL* implementation should be used.
+if test "$UNIVERSAL_ARCHS" = "universal2" -o \
+ \( "$build_cpu" = "aarch64" -a "$build_vendor" = "apple" \)
+then
+ use_hacl_universal2_impl=yes
+else
+ use_hacl_universal2_impl=no
+fi
+
# The SIMD files use aligned_alloc, which is not available on older versions of
# Android.
# The *mmintrin.h headers are x86-family-specific, so can't be used on WASI.
@@ -7928,13 +8016,14 @@ then
AX_CHECK_COMPILE_FLAG([-msse -msse2 -msse3 -msse4.1 -msse4.2],[
[LIBHACL_SIMD128_FLAGS="-msse -msse2 -msse3 -msse4.1 -msse4.2"]
- AC_DEFINE([HACL_CAN_COMPILE_SIMD128], [1], [HACL* library can compile SIMD128 implementations])
+ AC_DEFINE([_Py_HACL_CAN_COMPILE_VEC128], [1], [
+ HACL* library can compile SIMD128 implementations])
# macOS universal2 builds *support* the -msse etc flags because they're
# available on x86_64. However, performance of the HACL SIMD128 implementation
# isn't great, so it's disabled on ARM64.
AC_MSG_CHECKING([for HACL* SIMD128 implementation])
- if test "$UNIVERSAL_ARCHS" == "universal2"; then
+ if test "$use_hacl_universal2_impl" = "yes"; then
[LIBHACL_BLAKE2_SIMD128_OBJS="Modules/_hacl/Hacl_Hash_Blake2s_Simd128_universal2.o"]
AC_MSG_RESULT([universal2])
else
@@ -7959,14 +8048,15 @@ if test "$ac_sys_system" != "Linux-android" -a "$ac_sys_system" != "WASI" || \
then
AX_CHECK_COMPILE_FLAG([-mavx2],[
[LIBHACL_SIMD256_FLAGS="-mavx2"]
- AC_DEFINE([HACL_CAN_COMPILE_SIMD256], [1], [HACL* library can compile SIMD256 implementations])
+ AC_DEFINE([_Py_HACL_CAN_COMPILE_VEC256], [1], [
+ HACL* library can compile SIMD256 implementations])
# macOS universal2 builds *support* the -mavx2 compiler flag because it's
# available on x86_64; but the HACL SIMD256 build then fails because the
# implementation requires symbols that aren't available on ARM64. Use a
# wrapped implementation if we're building for universal2.
AC_MSG_CHECKING([for HACL* SIMD256 implementation])
- if test "$UNIVERSAL_ARCHS" == "universal2"; then
+ if test "$use_hacl_universal2_impl" = "yes"; then
[LIBHACL_BLAKE2_SIMD256_OBJS="Modules/_hacl/Hacl_Hash_Blake2b_Simd256_universal2.o"]
AC_MSG_RESULT([universal2])
else
@@ -8016,7 +8106,10 @@ PY_HACL_CREATE_MODULE([BLAKE2], [_blake2], [test "$with_builtin_blake2" = yes])
dnl HMAC builtin library does not need OpenSSL for now. In the future
dnl we might want to rely on OpenSSL EVP/NID interface or implement
dnl our own for algorithm resolution.
-PY_HACL_CREATE_MODULE([HMAC], [_hmac], [])
+dnl
+dnl For Emscripten, we disable HACL* HMAC as it is tricky to make it work.
+dnl See https://github.com/python/cpython/issues/133042.
+PY_HACL_CREATE_MODULE([HMAC], [_hmac], [test "$ac_sys_system" != "Emscripten"])
### end(cryptographic primitives)
PY_STDLIB_MOD([_ctypes],
@@ -8033,6 +8126,16 @@ PY_STDLIB_MOD([_curses_panel],
PY_STDLIB_MOD([_decimal],
[], [test "$have_mpdec" = "yes"],
[$LIBMPDEC_CFLAGS], [$LIBMPDEC_LIBS])
+
+AS_VAR_IF([with_system_libmpdec], [no],
+ [AC_MSG_WARN([m4_normalize([
+ the bundled copy of libmpdec is scheduled for removal in Python 3.16;
+ consider using a system installed mpdecimal library.])])])
+AS_IF([test "$with_system_libmpdec" = "yes" && test "$have_mpdec" = "no"],
+ [AC_MSG_WARN([m4_normalize([
+ no system libmpdec found; falling back to pure-Python version
+ for the decimal module])])])
+
PY_STDLIB_MOD([_dbm],
[test -n "$with_dbmliborder"], [test "$have_dbm" != "no"],
[$DBM_CFLAGS], [$DBM_LIBS])
@@ -8062,6 +8165,8 @@ PY_STDLIB_MOD([_bz2], [], [test "$have_bzip2" = yes],
[$BZIP2_CFLAGS], [$BZIP2_LIBS])
PY_STDLIB_MOD([_lzma], [], [test "$have_liblzma" = yes],
[$LIBLZMA_CFLAGS], [$LIBLZMA_LIBS])
+PY_STDLIB_MOD([_zstd], [], [test "$have_libzstd" = yes],
+ [$LIBZSTD_CFLAGS], [$LIBZSTD_LIBS])
dnl OpenSSL bindings
PY_STDLIB_MOD([_ssl], [], [test "$ac_cv_working_openssl_ssl" = yes],
@@ -8082,7 +8187,6 @@ PY_STDLIB_MOD([_testbuffer], [test "$TEST_MODULES" = yes])
PY_STDLIB_MOD([_testimportmultiple], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
PY_STDLIB_MOD([_testmultiphase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
PY_STDLIB_MOD([_testsinglephase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
-PY_STDLIB_MOD([_testexternalinspection], [test "$TEST_MODULES" = yes])
PY_STDLIB_MOD([xxsubtype], [test "$TEST_MODULES" = yes])
PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes])
PY_STDLIB_MOD([_ctypes_test],
diff --git a/iOS/README.rst b/iOS/README.rst
index 13b88514493..f0979ba152e 100644
--- a/iOS/README.rst
+++ b/iOS/README.rst
@@ -196,7 +196,7 @@ simulator build with a deployment target of 15.4.
Merge thin frameworks into fat frameworks
-----------------------------------------
-Once you've built a ``Python.framework`` for each ABI and and architecture, you
+Once you've built a ``Python.framework`` for each ABI and architecture, you
must produce a "fat" framework for each ABI that contains all the architectures
for that ABI.
diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-strip b/iOS/Resources/bin/arm64-apple-ios-simulator-strip
new file mode 100755
index 00000000000..fd59d309b73
--- /dev/null
+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-strip
@@ -0,0 +1,2 @@
+#!/bin/sh
+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@"
diff --git a/iOS/Resources/bin/arm64-apple-ios-strip b/iOS/Resources/bin/arm64-apple-ios-strip
new file mode 100755
index 00000000000..75e823a3d02
--- /dev/null
+++ b/iOS/Resources/bin/arm64-apple-ios-strip
@@ -0,0 +1,2 @@
+#!/bin/sh
+xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@"
diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-strip b/iOS/Resources/bin/x86_64-apple-ios-simulator-strip
new file mode 100755
index 00000000000..c5cfb289291
--- /dev/null
+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-strip
@@ -0,0 +1,2 @@
+#!/bin/sh
+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@"
diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py
index c05497ede3a..1146bf3b988 100644
--- a/iOS/testbed/__main__.py
+++ b/iOS/testbed/__main__.py
@@ -127,7 +127,7 @@ async def async_check_output(*args, **kwargs):
async def select_simulator_device():
# List the testing simulators, in JSON format
raw_json = await async_check_output(
- "xcrun", "simctl", "--set", "testing", "list", "-j"
+ "xcrun", "simctl", "list", "-j"
)
json_data = json.loads(raw_json)
diff --git a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
index dd6e76f9496..b502a6eb277 100644
--- a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
+++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
@@ -15,6 +15,11 @@
PyStatus status;
PyPreConfig preconfig;
PyConfig config;
+ PyObject *app_packages_path;
+ PyObject *method_args;
+ PyObject *result;
+ PyObject *site_module;
+ PyObject *site_addsitedir_attr;
PyObject *sys_module;
PyObject *sys_path_attr;
NSArray *test_args;
@@ -111,29 +116,55 @@
return;
}
- sys_module = PyImport_ImportModule("sys");
- if (sys_module == NULL) {
- XCTFail(@"Could not import sys module");
+ // Add app_packages as a site directory. This both adds to sys.path,
+ // and ensures that any .pth files in that directory will be executed.
+ site_module = PyImport_ImportModule("site");
+ if (site_module == NULL) {
+ XCTFail(@"Could not import site module");
return;
}
- sys_path_attr = PyObject_GetAttrString(sys_module, "path");
- if (sys_path_attr == NULL) {
- XCTFail(@"Could not access sys.path");
+ site_addsitedir_attr = PyObject_GetAttrString(site_module, "addsitedir");
+ if (site_addsitedir_attr == NULL || !PyCallable_Check(site_addsitedir_attr)) {
+ XCTFail(@"Could not access site.addsitedir");
return;
}
- // Add the app packages path
path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil];
NSLog(@"App packages path: %@", path);
wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
- failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
- if (failed) {
- XCTFail(@"Unable to add app packages to sys.path");
+ app_packages_path = PyUnicode_FromWideChar(wtmp_str, wcslen(wtmp_str));
+ if (app_packages_path == NULL) {
+ XCTFail(@"Could not convert app_packages path to unicode");
return;
}
PyMem_RawFree(wtmp_str);
+ method_args = Py_BuildValue("(O)", app_packages_path);
+ if (method_args == NULL) {
+ XCTFail(@"Could not create arguments for site.addsitedir");
+ return;
+ }
+
+ result = PyObject_CallObject(site_addsitedir_attr, method_args);
+ if (result == NULL) {
+ XCTFail(@"Could not add app_packages directory using site.addsitedir");
+ return;
+ }
+
+ // Add test code to sys.path
+ sys_module = PyImport_ImportModule("sys");
+ if (sys_module == NULL) {
+ XCTFail(@"Could not import sys module");
+ return;
+ }
+
+ sys_path_attr = PyObject_GetAttrString(sys_module, "path");
+ if (sys_path_attr == NULL) {
+ XCTFail(@"Could not access sys.path");
+ return;
+ }
+
path = [NSString stringWithFormat:@"%@/app", resourcePath, nil];
NSLog(@"App path: %@", path);
wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 6c17685e22a..d7c496fccc6 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -50,12 +50,6 @@
/* Define if getpgrp() must be called as getpgrp(0). */
#undef GETPGRP_HAVE_ARG
-/* HACL* library can compile SIMD128 implementations */
-#undef HACL_CAN_COMPILE_SIMD128
-
-/* HACL* library can compile SIMD256 implementations */
-#undef HACL_CAN_COMPILE_SIMD256
-
/* Define if you have the 'accept' function. */
#undef HAVE_ACCEPT
@@ -267,6 +261,10 @@
*/
#undef HAVE_DECL_TZNAME
+/* Define to 1 if you have the declaration of 'UT_NAMESIZE', and to 0 if you
+ don't. */
+#undef HAVE_DECL_UT_NAMESIZE
+
/* Define to 1 if you have the device macros. */
#undef HAVE_DEVICE_MACROS
@@ -539,6 +537,9 @@
/* Define to 1 if you have the 'getlogin' function. */
#undef HAVE_GETLOGIN
+/* Define to 1 if you have the 'getlogin_r' function. */
+#undef HAVE_GETLOGIN_R
+
/* Define to 1 if you have the 'getnameinfo' function. */
#undef HAVE_GETNAMEINFO
@@ -807,6 +808,9 @@
/* Define this if you have the makedev macro. */
#undef HAVE_MAKEDEV
+/* Define if you have the 'MAXLOGNAME' constant. */
+#undef HAVE_MAXLOGNAME
+
/* Define to 1 if you have the 'mbrtowc' function. */
#undef HAVE_MBRTOWC
@@ -1575,6 +1579,9 @@
/* Define to 1 if you have the <utmp.h> header file. */
#undef HAVE_UTMP_H
+/* Define if you have the 'HAVE_UT_NAMESIZE' constant. */
+#undef HAVE_UT_NAMESIZE
+
/* Define to 1 if you have the 'uuid_create' function. */
#undef HAVE_UUID_CREATE
@@ -1584,6 +1591,9 @@
/* Define if uuid_generate_time_safe() exists. */
#undef HAVE_UUID_GENERATE_TIME_SAFE
+/* Define if uuid_generate_time_safe() is able to deduce a MAC address. */
+#undef HAVE_UUID_GENERATE_TIME_SAFE_STABLE_MAC
+
/* Define to 1 if you have the <uuid.h> header file. */
#undef HAVE_UUID_H
@@ -1630,12 +1640,18 @@
/* Define to 1 if you have the 'writev' function. */
#undef HAVE_WRITEV
+/* Define to 1 if you have the <zdict.h> header file. */
+#undef HAVE_ZDICT_H
+
/* Define if the zlib library has inflateCopy */
#undef HAVE_ZLIB_COPY
/* Define to 1 if you have the <zlib.h> header file. */
#undef HAVE_ZLIB_H
+/* Define to 1 if you have the <zstd.h> header file. */
+#undef HAVE_ZSTD_H
+
/* Define to 1 if you have the '_getpty' function. */
#undef HAVE__GETPTY
@@ -1714,9 +1730,6 @@
/* Defined if Python is built as a shared library. */
#undef Py_ENABLE_SHARED
-/* Defined if _Complex C type can be used with libffi. */
-#undef Py_FFI_SUPPORT_C_COMPLEX
-
/* Define if you want to disable the GIL */
#undef Py_GIL_DISABLED
@@ -1724,9 +1737,6 @@
SipHash13: 3, externally defined: 0 */
#undef Py_HASH_ALGORITHM
-/* Defined if _Complex C type is available. */
-#undef Py_HAVE_C_COMPLEX
-
/* Define if year with century should be normalized for strftime. */
#undef Py_NORMALIZE_CENTURY
@@ -2007,6 +2017,15 @@
/* Maximum length in bytes of a thread name */
#undef _PYTHREAD_NAME_MAXLEN
+/* Defined if _Complex C type can be used with libffi. */
+#undef _Py_FFI_SUPPORT_C_COMPLEX
+
+/* HACL* library can compile SIMD128 implementations */
+#undef _Py_HACL_CAN_COMPILE_VEC128
+
+/* HACL* library can compile SIMD256 implementations */
+#undef _Py_HACL_CAN_COMPILE_VEC256
+
/* Define to force use of thread-safe errno, h_errno, and other functions */
#undef _REENTRANT