From bfe0e4d7696647a546110328510bdb98146ad2f2 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Tue, 2 Jul 2024 09:13:37 +0100 Subject: [PATCH 01/97] gh-121035: Improve logging flow diagram for dark/light modes. (GH-121254) --- Doc/howto/logging.rst | 38 ++++++++++++++++++++++++++++++++++++++ Doc/howto/logging_flow.svg | 35 ++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index 316b16aa796af4..9c55233e910f17 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -385,6 +385,44 @@ following diagram. .. raw:: html :file: logging_flow.svg +.. raw:: html + + + Loggers ^^^^^^^ diff --git a/Doc/howto/logging_flow.svg b/Doc/howto/logging_flow.svg index a5f656b1df0b42..9807323b7190d6 100644 --- a/Doc/howto/logging_flow.svg +++ b/Doc/howto/logging_flow.svg @@ -1,9 +1,9 @@ @@ -57,7 +69,7 @@ Create - LogRecord + LogRecord @@ -100,7 +112,7 @@ - Pass to + Pass record to handlers of current logger @@ -135,16 +147,17 @@ - - Use lastResort - handler + + Use + lastResort + handler Handler enabled for - level of LogRecord? + level of record? @@ -292,7 +305,7 @@ Yes - LogRecord passed + Record passed to handler From 7435f053b4a54372a2c43dee7a15c4b973f09209 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 2 Jul 2024 10:34:13 +0200 Subject: [PATCH 02/97] Move get_signal_name() to test.support (#121251) * Move get_signal_name() from test.libregrtest to test.support. * Use get_signal_name() in support.script_helper. * support.script_helper now decodes stdout and stderr from UTF-8, instead of ASCII, if a command failed. --- Lib/test/libregrtest/run_workers.py | 4 ++-- Lib/test/libregrtest/utils.py | 29 ----------------------- Lib/test/support/__init__.py | 32 +++++++++++++++++++++++++ Lib/test/support/script_helper.py | 36 +++++++++++++++-------------- Lib/test/test_regrtest.py | 10 -------- Lib/test/test_support.py | 12 ++++++++++ 6 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Lib/test/libregrtest/run_workers.py b/Lib/test/libregrtest/run_workers.py index a71050e66db3bd..387ddf9614cf79 100644 --- a/Lib/test/libregrtest/run_workers.py +++ b/Lib/test/libregrtest/run_workers.py @@ -22,7 +22,7 @@ from .single import PROGRESS_MIN_TIME from .utils import ( StrPath, TestName, - format_duration, print_warning, count, plural, get_signal_name) + format_duration, print_warning, count, plural) from .worker import create_worker_process, USE_PROCESS_GROUP if MS_WINDOWS: @@ -366,7 +366,7 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult: err_msg=None, state=State.TIMEOUT) if retcode != 0: - name = get_signal_name(retcode) + name = support.get_signal_name(retcode) if name: retcode = f"{retcode} ({name})" raise WorkerError(self.test_name, f"Exit code {retcode}", stdout, diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 0167742d388a2c..0197e50125d96e 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -685,35 +685,6 @@ def cleanup_temp_dir(tmp_dir: StrPath): print("Remove file: %s" % name) os_helper.unlink(name) -WINDOWS_STATUS = { - 0xC0000005: "STATUS_ACCESS_VIOLATION", - 0xC00000FD: "STATUS_STACK_OVERFLOW", - 0xC000013A: "STATUS_CONTROL_C_EXIT", -} - -def get_signal_name(exitcode): - if exitcode < 0: - signum = -exitcode - try: - return signal.Signals(signum).name - except ValueError: - pass - - # Shell exit code (ex: WASI build) - if 128 < exitcode < 256: - signum = exitcode - 128 - try: - return signal.Signals(signum).name - except ValueError: - pass - - try: - return WINDOWS_STATUS[exitcode] - except KeyError: - pass - - return None - ILLEGAL_XML_CHARS_RE = re.compile( '[' diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index dbea070929be9b..18455bb6e0ff52 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2632,3 +2632,35 @@ 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", + 0xC000013A: "STATUS_CONTROL_C_EXIT", +} + +def get_signal_name(exitcode): + import signal + + if exitcode < 0: + signum = -exitcode + try: + return signal.Signals(signum).name + except ValueError: + pass + + # Shell exit code (ex: WASI build) + if 128 < exitcode < 256: + signum = exitcode - 128 + try: + return signal.Signals(signum).name + except ValueError: + pass + + try: + return WINDOWS_STATUS[exitcode] + except KeyError: + pass + + return None diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py index 65e0bc199e7f0b..d0be3179b0efa3 100644 --- a/Lib/test/support/script_helper.py +++ b/Lib/test/support/script_helper.py @@ -70,23 +70,25 @@ def fail(self, cmd_line): out = b'(... truncated stdout ...)' + out[-maxlen:] if len(err) > maxlen: err = b'(... truncated stderr ...)' + err[-maxlen:] - out = out.decode('ascii', 'replace').rstrip() - err = err.decode('ascii', 'replace').rstrip() - raise AssertionError("Process return code is %d\n" - "command line: %r\n" - "\n" - "stdout:\n" - "---\n" - "%s\n" - "---\n" - "\n" - "stderr:\n" - "---\n" - "%s\n" - "---" - % (self.rc, cmd_line, - out, - err)) + out = out.decode('utf8', 'replace').rstrip() + err = err.decode('utf8', 'replace').rstrip() + + exitcode = self.rc + signame = support.get_signal_name(exitcode) + if signame: + exitcode = f"{exitcode} ({signame})" + raise AssertionError(f"Process return code is {exitcode}\n" + f"command line: {cmd_line!r}\n" + f"\n" + f"stdout:\n" + f"---\n" + f"{out}\n" + f"---\n" + f"\n" + f"stderr:\n" + f"---\n" + f"{err}\n" + f"---") # Executing the interpreter in a subprocess diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index 44fd11bfdc3fcb..d4f4a69a7a38c1 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -2329,16 +2329,6 @@ def test_normalize_test_name(self): self.assertIsNone(normalize('setUpModule (test.test_x)', is_error=True)) self.assertIsNone(normalize('tearDownModule (test.test_module)', is_error=True)) - def test_get_signal_name(self): - for exitcode, expected in ( - (-int(signal.SIGINT), 'SIGINT'), - (-int(signal.SIGSEGV), 'SIGSEGV'), - (128 + int(signal.SIGABRT), 'SIGABRT'), - (3221225477, "STATUS_ACCESS_VIOLATION"), - (0xC00000FD, "STATUS_STACK_OVERFLOW"), - ): - self.assertEqual(utils.get_signal_name(exitcode), expected, exitcode) - def test_format_resources(self): format_resources = utils.format_resources ALL_RESOURCES = utils.ALL_RESOURCES diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index d6f024a476920c..e60e5477d32e1f 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -3,6 +3,7 @@ import io import os import shutil +import signal import socket import stat import subprocess @@ -732,6 +733,17 @@ def test_copy_python_src_ignore(self): self.assertEqual(support.copy_python_src_ignore(path, os.listdir(path)), ignored) + def test_get_signal_name(self): + for exitcode, expected in ( + (-int(signal.SIGINT), 'SIGINT'), + (-int(signal.SIGSEGV), 'SIGSEGV'), + (128 + int(signal.SIGABRT), 'SIGABRT'), + (3221225477, "STATUS_ACCESS_VIOLATION"), + (0xC00000FD, "STATUS_STACK_OVERFLOW"), + ): + self.assertEqual(support.get_signal_name(exitcode), expected, + exitcode) + # XXX -follows a list of untested API # make_legacy_pyc # is_resource_enabled From 7a807c3efaa83f1e4fb9b791579b47a0a1fd47de Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 2 Jul 2024 12:40:01 +0300 Subject: [PATCH 03/97] gh-121245: Amend d611c4c8e9 (correct import) (#121255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Miro Hrončok --- Lib/site.py | 3 +-- .../Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst diff --git a/Lib/site.py b/Lib/site.py index 9381f6f510eb46..daa56e158949db 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -526,8 +526,7 @@ def register_readline(): def write_history(): try: - # _pyrepl.__main__ is executed as the __main__ module - from __main__ import CAN_USE_PYREPL + from _pyrepl.main import CAN_USE_PYREPL except ImportError: CAN_USE_PYREPL = False diff --git a/Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst b/Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst new file mode 100644 index 00000000000000..6e9dec2545166f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst @@ -0,0 +1,2 @@ +Fix a bug in the handling of the command history of the new :term:`REPL` that caused +the history file to be wiped at REPL exit. From 15232a0819a2f7e0f448f28f2e6081912d10e7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:53:17 +0200 Subject: [PATCH 04/97] gh-121210: handle nodes with missing attributes/fields in `ast.compare` (#121211) --- Lib/ast.py | 19 +++++++++++++++---- Lib/test/test_ast.py | 19 +++++++++++++++++++ ...-07-01-11-23-18.gh-issue-121210.cD0zfn.rst | 2 ++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst diff --git a/Lib/ast.py b/Lib/ast.py index fb4d21b87d8bd0..a954d4a97d3c22 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -422,6 +422,8 @@ def compare( might differ in whitespace or similar details. """ + sentinel = object() # handle the possibility of a missing attribute/field + def _compare(a, b): # Compare two fields on an AST object, which may themselves be # AST objects, lists of AST objects, or primitive ASDL types @@ -449,8 +451,14 @@ def _compare_fields(a, b): if a._fields != b._fields: return False for field in a._fields: - a_field = getattr(a, field) - b_field = getattr(b, field) + a_field = getattr(a, field, sentinel) + b_field = getattr(b, field, sentinel) + if a_field is sentinel and b_field is sentinel: + # both nodes are missing a field at runtime + continue + if a_field is sentinel or b_field is sentinel: + # one of the node is missing a field + return False if not _compare(a_field, b_field): return False else: @@ -461,8 +469,11 @@ def _compare_attributes(a, b): return False # Attributes are always ints. for attr in a._attributes: - a_attr = getattr(a, attr) - b_attr = getattr(b, attr) + a_attr = getattr(a, attr, sentinel) + b_attr = getattr(b, attr, sentinel) + if a_attr is sentinel and b_attr is sentinel: + # both nodes are missing an attribute at runtime + continue if a_attr != b_attr: return False else: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 5d71d524516df2..fbd19620311159 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -948,6 +948,15 @@ def test_compare_fieldless(self): self.assertTrue(ast.compare(ast.Add(), ast.Add())) self.assertFalse(ast.compare(ast.Sub(), ast.Add())) + # test that missing runtime fields is handled in ast.compare() + a1, a2 = ast.Name('a'), ast.Name('a') + self.assertTrue(ast.compare(a1, a2)) + self.assertTrue(ast.compare(a1, a2)) + del a1.id + self.assertFalse(ast.compare(a1, a2)) + del a2.id + self.assertTrue(ast.compare(a1, a2)) + def test_compare_modes(self): for mode, sources in ( ("exec", exec_tests), @@ -970,6 +979,16 @@ def parse(a, b): self.assertTrue(ast.compare(a, b, compare_attributes=False)) self.assertFalse(ast.compare(a, b, compare_attributes=True)) + def test_compare_attributes_option_missing_attribute(self): + # test that missing runtime attributes is handled in ast.compare() + a1, a2 = ast.Name('a', lineno=1), ast.Name('a', lineno=1) + self.assertTrue(ast.compare(a1, a2)) + self.assertTrue(ast.compare(a1, a2, compare_attributes=True)) + del a1.lineno + self.assertFalse(ast.compare(a1, a2, compare_attributes=True)) + del a2.lineno + self.assertTrue(ast.compare(a1, a2, compare_attributes=True)) + def test_positional_only_feature_version(self): ast.parse('def foo(x, /): ...', feature_version=(3, 8)) ast.parse('def bar(x=1, /): ...', feature_version=(3, 8)) diff --git a/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst b/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst new file mode 100644 index 00000000000000..55d5b221bf0765 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst @@ -0,0 +1,2 @@ +Handle AST nodes with missing runtime fields or attributes in +:func:`ast.compare`. Patch by Bénédikt Tran. From 6343486eb60ac5a9e15402a592298259c5afdee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:57:51 +0200 Subject: [PATCH 05/97] gh-121165: protect macro expansion of `ADJUST_INDICES` with do-while(0) (#121166) --- Objects/bytes_methods.c | 31 ++++++++++++++++++------------- Objects/unicodeobject.c | 31 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/Objects/bytes_methods.c b/Objects/bytes_methods.c index 55252406578774..c239ae18a593e3 100644 --- a/Objects/bytes_methods.c +++ b/Objects/bytes_methods.c @@ -432,19 +432,24 @@ parse_args_finds_byte(const char *function_name, PyObject **subobj, char *byte) } /* helper macro to fixup start/end slice values */ -#define ADJUST_INDICES(start, end, len) \ - if (end > len) \ - end = len; \ - else if (end < 0) { \ - end += len; \ - if (end < 0) \ - end = 0; \ - } \ - if (start < 0) { \ - start += len; \ - if (start < 0) \ - start = 0; \ - } +#define ADJUST_INDICES(start, end, len) \ + do { \ + if (end > len) { \ + end = len; \ + } \ + else if (end < 0) { \ + end += len; \ + if (end < 0) { \ + end = 0; \ + } \ + } \ + if (start < 0) { \ + start += len; \ + if (start < 0) { \ + start = 0; \ + } \ + } \ + } while (0) Py_LOCAL_INLINE(Py_ssize_t) find_internal(const char *str, Py_ssize_t len, diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 9738442ab962b0..394ea888fc9231 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -9315,19 +9315,24 @@ _PyUnicode_TransformDecimalAndSpaceToASCII(PyObject *unicode) /* --- Helpers ------------------------------------------------------------ */ /* helper macro to fixup start/end slice values */ -#define ADJUST_INDICES(start, end, len) \ - if (end > len) \ - end = len; \ - else if (end < 0) { \ - end += len; \ - if (end < 0) \ - end = 0; \ - } \ - if (start < 0) { \ - start += len; \ - if (start < 0) \ - start = 0; \ - } +#define ADJUST_INDICES(start, end, len) \ + do { \ + if (end > len) { \ + end = len; \ + } \ + else if (end < 0) { \ + end += len; \ + if (end < 0) { \ + end = 0; \ + } \ + } \ + if (start < 0) { \ + start += len; \ + if (start < 0) { \ + start = 0; \ + } \ + } \ + } while (0) static Py_ssize_t any_find_slice(PyObject* s1, PyObject* s2, From 1ac273224a85126c4356e355f7445206fadde7ec Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:22:08 +0100 Subject: [PATCH 06/97] gh-121272: move __future__ import validation from compiler to symtable (#121273) --- Python/compile.c | 16 ---------------- Python/symtable.c | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 69de0ec2996e00..d33db69f425361 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -77,14 +77,6 @@ typedef struct _PyCfgBuilder cfg_builder; #define LOCATION(LNO, END_LNO, COL, END_COL) \ ((const _Py_SourceLocation){(LNO), (END_LNO), (COL), (END_COL)}) -/* Return true if loc1 starts after loc2 ends. */ -static inline bool -location_is_after(location loc1, location loc2) { - return (loc1.lineno > loc2.end_lineno) || - ((loc1.lineno == loc2.end_lineno) && - (loc1.col_offset > loc2.end_col_offset)); -} - #define LOC(x) SRC_LOCATION_FROM_AST(x) typedef _PyJumpTargetLabel jump_target_label; @@ -3847,14 +3839,6 @@ compiler_from_import(struct compiler *c, stmt_ty s) PyTuple_SET_ITEM(names, i, Py_NewRef(alias->name)); } - if (location_is_after(LOC(s), c->c_future.ff_location) && - s->v.ImportFrom.module && s->v.ImportFrom.level == 0 && - _PyUnicode_EqualToASCIIString(s->v.ImportFrom.module, "__future__")) - { - Py_DECREF(names); - return compiler_error(c, LOC(s), "from __future__ imports must occur " - "at the beginning of the file"); - } ADDOP_LOAD_CONST_NEW(c, LOC(s), names); if (s->v.ImportFrom.module) { diff --git a/Python/symtable.c b/Python/symtable.c index 2e56ea6e830846..61fa5c6fdf923c 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1660,6 +1660,27 @@ has_kwonlydefaults(asdl_arg_seq *kwonlyargs, asdl_expr_seq *kw_defaults) return 0; } +static int +check_import_from(struct symtable *st, stmt_ty s) +{ + assert(s->kind == ImportFrom_kind); + _Py_SourceLocation fut = st->st_future->ff_location; + if (s->v.ImportFrom.module && s->v.ImportFrom.level == 0 && + _PyUnicode_EqualToASCIIString(s->v.ImportFrom.module, "__future__") && + ((s->lineno > fut.lineno) || + ((s->lineno == fut.end_lineno) && (s->col_offset > fut.end_col_offset)))) + { + PyErr_SetString(PyExc_SyntaxError, + "from __future__ imports must occur " + "at the beginning of the file"); + PyErr_RangedSyntaxLocationObject(st->st_filename, + s->lineno, s->col_offset + 1, + s->end_lineno, s->end_col_offset + 1); + return 0; + } + return 1; +} + static int symtable_visit_stmt(struct symtable *st, stmt_ty s) { @@ -1914,6 +1935,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) break; case ImportFrom_kind: VISIT_SEQ(st, alias, s->v.ImportFrom.names); + if (!check_import_from(st, s)) { + VISIT_QUIT(st, 0); + } break; case Global_kind: { Py_ssize_t i; From 8e8d202f552c993f40913b628139a39a5abe6a03 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 2 Jul 2024 12:30:14 -0400 Subject: [PATCH 07/97] gh-117139: Add _PyTuple_FromStackRefSteal and use it (#121244) Avoids the extra conversion from stack refs to PyObjects. --- Include/internal/pycore_stackref.h | 2 +- Include/internal/pycore_tuple.h | 1 + Objects/tupleobject.c | 21 +++++++++++++++++++++ Python/bytecodes.c | 8 +------- Python/ceval.c | 8 +------- Python/executor_cases.c.h | 10 +--------- Python/generated_cases.c.h | 10 +--------- Tools/cases_generator/analyzer.py | 1 + 8 files changed, 28 insertions(+), 33 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 32e445dd96f9a1..4301c6a7cb40b0 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -48,7 +48,7 @@ extern "C" { CPython refcounting operations on it! */ -typedef union { +typedef union _PyStackRef { uintptr_t bits; } _PyStackRef; diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index 14a9e42c3a324c..dfbbd6fd0c7de5 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -21,6 +21,7 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); #define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item) extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t); +PyAPI_FUNC(PyObject *)_PyTuple_FromStackRefSteal(const union _PyStackRef *, Py_ssize_t); PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); typedef struct { diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 5ae1ee9a89af84..994258f20b495d 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -390,6 +390,27 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n) return (PyObject *)tuple; } +PyObject * +_PyTuple_FromStackRefSteal(const _PyStackRef *src, Py_ssize_t n) +{ + if (n == 0) { + return tuple_get_empty(); + } + PyTupleObject *tuple = tuple_alloc(n); + if (tuple == NULL) { + for (Py_ssize_t i = 0; i < n; i++) { + PyStackRef_CLOSE(src[i]); + } + return NULL; + } + PyObject **dst = tuple->ob_item; + for (Py_ssize_t i = 0; i < n; i++) { + dst[i] = PyStackRef_AsPyObjectSteal(src[i]); + } + _PyObject_GC_TRACK(tuple); + return (PyObject *)tuple; +} + PyObject * _PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 343481e9313de4..76587a4f0dc695 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1780,13 +1780,7 @@ dummy_func( } inst(BUILD_TUPLE, (values[oparg] -- tup)) { - STACKREFS_TO_PYOBJECTS(values, oparg, values_o); - if (CONVERSION_FAILED(values_o)) { - DECREF_INPUTS(); - ERROR_IF(true, error); - } - PyObject *tup_o = _PyTuple_FromArraySteal(values_o, oparg); - STACKREFS_TO_PYOBJECTS_CLEANUP(values_o); + PyObject *tup_o = _PyTuple_FromStackRefSteal(values, oparg); ERROR_IF(tup_o == NULL, error); tup = PyStackRef_FromPyObjectSteal(tup_o); } diff --git a/Python/ceval.c b/Python/ceval.c index a71244676f3029..a240ed4321f7ee 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1500,13 +1500,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func, u = (PyObject *)&_Py_SINGLETON(tuple_empty); } else { - assert(args != NULL); - STACKREFS_TO_PYOBJECTS((_PyStackRef *)args, argcount, args_o); - if (args_o == NULL) { - goto fail_pre_positional; - } - u = _PyTuple_FromArraySteal((args_o + n), argcount - n); - STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); + u = _PyTuple_FromStackRefSteal(args + n, argcount - n); } if (u == NULL) { goto fail_post_positional; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index d70a57a9a8ffbe..3b999465aac815 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1816,15 +1816,7 @@ _PyStackRef tup; oparg = CURRENT_OPARG(); values = &stack_pointer[-oparg]; - STACKREFS_TO_PYOBJECTS(values, oparg, values_o); - if (CONVERSION_FAILED(values_o)) { - for (int _i = oparg; --_i >= 0;) { - PyStackRef_CLOSE(values[_i]); - } - if (true) JUMP_TO_ERROR(); - } - PyObject *tup_o = _PyTuple_FromArraySteal(values_o, oparg); - STACKREFS_TO_PYOBJECTS_CLEANUP(values_o); + PyObject *tup_o = _PyTuple_FromStackRefSteal(values, oparg); if (tup_o == NULL) JUMP_TO_ERROR(); tup = PyStackRef_FromPyObjectSteal(tup_o); stack_pointer[-oparg] = tup; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 32b22aff14a768..61057221291c0a 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -769,15 +769,7 @@ _PyStackRef *values; _PyStackRef tup; values = &stack_pointer[-oparg]; - STACKREFS_TO_PYOBJECTS(values, oparg, values_o); - if (CONVERSION_FAILED(values_o)) { - for (int _i = oparg; --_i >= 0;) { - PyStackRef_CLOSE(values[_i]); - } - if (true) { stack_pointer += -oparg; goto error; } - } - PyObject *tup_o = _PyTuple_FromArraySteal(values_o, oparg); - STACKREFS_TO_PYOBJECTS_CLEANUP(values_o); + PyObject *tup_o = _PyTuple_FromStackRefSteal(values, oparg); if (tup_o == NULL) { stack_pointer += -oparg; goto error; } tup = PyStackRef_FromPyObjectSteal(tup_o); stack_pointer[-oparg] = tup; diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 6b1af1b59f14d8..f92560bd2b76b3 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -431,6 +431,7 @@ def has_error_without_pop(op: parser.InstDef) -> bool: "CONVERSION_FAILED", "_PyList_FromArraySteal", "_PyTuple_FromArraySteal", + "_PyTuple_FromStackRefSteal", ) ESCAPING_FUNCTIONS = ( From b180788d4a927d23af54f4b4702ccaf254f64854 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Tue, 2 Jul 2024 18:54:33 +0100 Subject: [PATCH 08/97] gh-115773: Add sizes to debug offset structure (#120112) --- Include/internal/pycore_runtime.h | 10 ++++++++++ Include/internal/pycore_runtime_init.h | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index f58eccf729cb2a..341fe29a68af16 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -55,12 +55,14 @@ typedef struct _Py_DebugOffsets { uint64_t version; // Runtime state offset; struct _runtime_state { + uint64_t size; uint64_t finalizing; uint64_t interpreters_head; } runtime_state; // Interpreter state offset; struct _interpreter_state { + uint64_t size; uint64_t next; uint64_t threads_head; uint64_t gc; @@ -74,6 +76,7 @@ typedef struct _Py_DebugOffsets { // Thread state offset; struct _thread_state{ + uint64_t size; uint64_t prev; uint64_t next; uint64_t interp; @@ -84,6 +87,7 @@ typedef struct _Py_DebugOffsets { // InterpreterFrame offset; struct _interpreter_frame { + uint64_t size; uint64_t previous; uint64_t executable; uint64_t instr_ptr; @@ -93,12 +97,14 @@ typedef struct _Py_DebugOffsets { // CFrame offset; struct _cframe { + uint64_t size; uint64_t current_frame; uint64_t previous; } cframe; // Code object offset; struct _code_object { + uint64_t size; uint64_t filename; uint64_t name; uint64_t linetable; @@ -111,21 +117,25 @@ typedef struct _Py_DebugOffsets { // PyObject offset; struct _pyobject { + uint64_t size; uint64_t ob_type; } pyobject; // PyTypeObject object offset; struct _type_object { + uint64_t size; uint64_t tp_name; } type_object; // PyTuple object offset; struct _tuple_object { + uint64_t size; uint64_t ob_item; } tuple_object; // Unicode object offset; struct _unicode_object { + uint64_t size; uint64_t state; uint64_t length; size_t asciiobject_size; diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 98920dbb7c7a92..33e39c2edbe541 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -35,10 +35,12 @@ extern PyTypeObject _PyExc_MemoryError; .cookie = "xdebugpy", \ .version = PY_VERSION_HEX, \ .runtime_state = { \ + .size = sizeof(_PyRuntimeState), \ .finalizing = offsetof(_PyRuntimeState, _finalizing), \ .interpreters_head = offsetof(_PyRuntimeState, interpreters.head), \ }, \ .interpreter_state = { \ + .size = sizeof(PyInterpreterState), \ .next = offsetof(PyInterpreterState, next), \ .threads_head = offsetof(PyInterpreterState, threads.head), \ .gc = offsetof(PyInterpreterState, gc), \ @@ -50,6 +52,7 @@ extern PyTypeObject _PyExc_MemoryError; .gil_runtime_state_holder = offsetof(PyInterpreterState, _gil.last_holder), \ }, \ .thread_state = { \ + .size = sizeof(PyThreadState), \ .prev = offsetof(PyThreadState, prev), \ .next = offsetof(PyThreadState, next), \ .interp = offsetof(PyThreadState, interp), \ @@ -58,6 +61,7 @@ extern PyTypeObject _PyExc_MemoryError; .native_thread_id = offsetof(PyThreadState, native_thread_id), \ }, \ .interpreter_frame = { \ + .size = sizeof(_PyInterpreterFrame), \ .previous = offsetof(_PyInterpreterFrame, previous), \ .executable = offsetof(_PyInterpreterFrame, f_executable), \ .instr_ptr = offsetof(_PyInterpreterFrame, instr_ptr), \ @@ -65,6 +69,7 @@ extern PyTypeObject _PyExc_MemoryError; .owner = offsetof(_PyInterpreterFrame, owner), \ }, \ .code_object = { \ + .size = sizeof(PyCodeObject), \ .filename = offsetof(PyCodeObject, co_filename), \ .name = offsetof(PyCodeObject, co_name), \ .linetable = offsetof(PyCodeObject, co_linetable), \ @@ -75,15 +80,19 @@ extern PyTypeObject _PyExc_MemoryError; .co_code_adaptive = offsetof(PyCodeObject, co_code_adaptive), \ }, \ .pyobject = { \ + .size = sizeof(PyObject), \ .ob_type = offsetof(PyObject, ob_type), \ }, \ .type_object = { \ + .size = sizeof(PyTypeObject), \ .tp_name = offsetof(PyTypeObject, tp_name), \ }, \ .tuple_object = { \ + .size = sizeof(PyTupleObject), \ .ob_item = offsetof(PyTupleObject, ob_item), \ }, \ .unicode_object = { \ + .size = sizeof(PyUnicodeObject), \ .state = offsetof(PyUnicodeObject, _base._base.state), \ .length = offsetof(PyUnicodeObject, _base._base.length), \ .asciiobject_size = sizeof(PyASCIIObject), \ From 089835469d5efbea4793cd611b43cb8387f2e7e5 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Tue, 2 Jul 2024 18:57:34 +0100 Subject: [PATCH 09/97] gh-121035: Further improve logging flow diagram with respect to dark/light modes. (GH-121265) --- Doc/howto/logging.rst | 6 ++++-- Doc/howto/logging_flow.svg | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index 9c55233e910f17..cbfe93319ddaa4 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -403,11 +403,13 @@ following diagram. function updateBody(theme) { let elem = document.body; + elem.classList.remove('dark-theme'); + elem.classList.remove('light-theme'); if (theme === 'dark') { elem.classList.add('dark-theme'); } - else { - elem.classList.remove('dark-theme'); + else if (theme === 'light') { + elem.classList.add('light-theme'); } } diff --git a/Doc/howto/logging_flow.svg b/Doc/howto/logging_flow.svg index 9807323b7190d6..4974994ac6b400 100644 --- a/Doc/howto/logging_flow.svg +++ b/Doc/howto/logging_flow.svg @@ -46,6 +46,7 @@ filter: invert(100%) hue-rotate(180deg) saturate(1.25); } } + /* These rules are for when the theme selector is used, perhaps in contrast to the browser theme. */ body.dark-theme polygon, body.dark-theme rect, body.dark-theme polyline, body.dark-theme line { stroke: #ffffff; } @@ -55,6 +56,15 @@ body.dark-theme text { fill: #ffffff; } + body.light-theme polygon, body.light-theme rect, body.light-theme polyline, body.light-theme line { + stroke: #000000; + } + body.light-theme polygon.filled { + fill: #000000; + } + body.light-theme text { + fill: #000000; + } From f09d184821efd9438d092643881e28bdf8de4de5 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Wed, 3 Jul 2024 04:30:29 +0100 Subject: [PATCH 10/97] GH-73991: Support copying directory symlinks on older Windows (#120807) Check for `ERROR_INVALID_PARAMETER` when calling `_winapi.CopyFile2()` and raise `UnsupportedOperation`. In `Path.copy()`, handle this exception and fall back to the `PathBase.copy()` implementation. --- Doc/library/pathlib.rst | 5 ----- Lib/pathlib/__init__.py | 4 ++-- Lib/pathlib/_abc.py | 11 +-------- Lib/pathlib/_local.py | 19 +++++++++------- Lib/pathlib/_os.py | 27 ++++++++++++++++++++--- Lib/test/test_pathlib/test_pathlib_abc.py | 3 ++- 6 files changed, 40 insertions(+), 29 deletions(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 0918bbb47e9ea6..d7fd56f4c4ff7f 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1554,11 +1554,6 @@ Copying, renaming and deleting permissions. After the copy is complete, users may wish to call :meth:`Path.chmod` to set the permissions of the target file. - .. warning:: - On old builds of Windows (before Windows 10 build 19041), this method - raises :exc:`OSError` when a symlink to a directory is encountered and - *follow_symlinks* is false. - .. versionadded:: 3.14 diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 4b3edf535a61aa..2298a249529460 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -5,8 +5,8 @@ operating systems. """ -from ._abc import * +from ._os import * from ._local import * -__all__ = (_abc.__all__ + +__all__ = (_os.__all__ + _local.__all__) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 71973913921169..b5f903ec1f03ce 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -16,10 +16,7 @@ import posixpath from glob import _GlobberBase, _no_recurse_symlinks from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO -from ._os import copyfileobj - - -__all__ = ["UnsupportedOperation"] +from ._os import UnsupportedOperation, copyfileobj @functools.cache @@ -27,12 +24,6 @@ def _is_case_sensitive(parser): return parser.normcase('Aa') == 'Aa' -class UnsupportedOperation(NotImplementedError): - """An exception that is raised when an unsupported operation is called on - a path object. - """ - pass - class ParserBase: """Base class for path parsers, which do low-level path manipulation. diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index 0105ea3042422e..acb57214b81865 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -17,8 +17,8 @@ except ImportError: grp = None -from ._abc import UnsupportedOperation, PurePathBase, PathBase -from ._os import copyfile +from ._os import UnsupportedOperation, copyfile +from ._abc import PurePathBase, PathBase __all__ = [ @@ -791,12 +791,15 @@ def copy(self, target, follow_symlinks=True): try: target = os.fspath(target) except TypeError: - if isinstance(target, PathBase): - # Target is an instance of PathBase but not os.PathLike. - # Use generic implementation from PathBase. - return PathBase.copy(self, target, follow_symlinks=follow_symlinks) - raise - copyfile(os.fspath(self), target, follow_symlinks) + if not isinstance(target, PathBase): + raise + else: + try: + copyfile(os.fspath(self), target, follow_symlinks) + return + except UnsupportedOperation: + pass # Fall through to generic code. + PathBase.copy(self, target, follow_symlinks=follow_symlinks) def chmod(self, mode, *, follow_symlinks=True): """ diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index bbb019b6534503..61923b5e410b5c 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -20,6 +20,15 @@ _winapi = None +__all__ = ["UnsupportedOperation"] + + +class UnsupportedOperation(NotImplementedError): + """An exception that is raised when an unsupported operation is attempted. + """ + pass + + def get_copy_blocksize(infd): """Determine blocksize for fastcopying on Linux. Hopefully the whole file will be copied in a single call. @@ -106,18 +115,30 @@ def copyfile(source, target, follow_symlinks): Copy from one file to another using CopyFile2 (Windows only). """ if follow_symlinks: - flags = 0 + _winapi.CopyFile2(source, target, 0) else: + # Use COPY_FILE_COPY_SYMLINK to copy a file symlink. flags = _winapi.COPY_FILE_COPY_SYMLINK try: _winapi.CopyFile2(source, target, flags) return except OSError as err: # Check for ERROR_ACCESS_DENIED - if err.winerror != 5 or not _is_dirlink(source): + if err.winerror == 5 and _is_dirlink(source): + pass + else: raise + + # Add COPY_FILE_DIRECTORY to copy a directory symlink. flags |= _winapi.COPY_FILE_DIRECTORY - _winapi.CopyFile2(source, target, flags) + try: + _winapi.CopyFile2(source, target, flags) + except OSError as err: + # Check for ERROR_INVALID_PARAMETER + if err.winerror == 87: + raise UnsupportedOperation(err) from None + else: + raise else: copyfile = None diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index ad692e872ede0b..28c9664cc90fe1 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -5,7 +5,8 @@ import stat import unittest -from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase +from pathlib._os import UnsupportedOperation +from pathlib._abc import ParserBase, PurePathBase, PathBase import posixpath from test.support import is_wasi From ff5806c78edda1feed61254ac55193772dc9ec48 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 3 Jul 2024 09:02:15 +0300 Subject: [PATCH 11/97] gh-121027: Make the functools.partial object a method descriptor (GH-121089) Co-authored-by: d.grigonis --- Doc/whatsnew/3.14.rst | 6 +++ Lib/functools.py | 10 ++--- Lib/test/test_functools.py | 4 +- Lib/test/test_inspect/test_inspect.py | 38 ++++++++----------- ...-06-27-12-27-52.gh-issue-121027.D4K1OX.rst | 1 + Modules/_functoolsmodule.c | 9 +---- 6 files changed, 28 insertions(+), 40 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 6ebadd75092fac..9578ba0c9c9657 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -305,6 +305,12 @@ Porting to Python 3.14 This section lists previously described changes and other bugfixes that may require changes to your code. +Changes in the Python API +------------------------- + +* :class:`functools.partial` is now a method descriptor. + Wrap it in :func:`staticmethod` if you want to preserve the old behavior. + (Contributed by Serhiy Storchaka and Dominykas Grigonis in :gh:`121027`.) Build Changes ============= diff --git a/Lib/functools.py b/Lib/functools.py index d04957c555295e..a10493f0e25360 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -18,6 +18,7 @@ from collections import namedtuple # import types, weakref # Deferred to single_dispatch() from reprlib import recursive_repr +from types import MethodType from _thread import RLock # Avoid importing types, so we can speedup import time @@ -314,12 +315,7 @@ def __repr__(self): def __get__(self, obj, objtype=None): if obj is None: return self - import warnings - warnings.warn('functools.partial will be a method descriptor in ' - 'future Python versions; wrap it in staticmethod() ' - 'if you want to preserve the old behavior', - FutureWarning, 2) - return self + return MethodType(self, obj) def __reduce__(self): return type(self), (self.func,), (self.func, self.args, @@ -402,7 +398,7 @@ def _method(cls_or_self, /, *args, **keywords): def __get__(self, obj, cls=None): get = getattr(self.func, "__get__", None) result = None - if get is not None and not isinstance(self.func, partial): + if get is not None: new_func = get(obj, cls) if new_func is not self.func: # Assume __get__ returning something new indicates the diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 1ce0f4d0aea6ee..492a16a8c7ff45 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -405,9 +405,7 @@ class A: self.assertEqual(A.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) self.assertEqual(A.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) self.assertEqual(A.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) - with self.assertWarns(FutureWarning) as w: - self.assertEqual(a.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) - self.assertEqual(w.filename, __file__) + self.assertEqual(a.meth(3, b=4), ((1, a, 3), {'a': 2, 'b': 4})) self.assertEqual(a.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 308c09874fe2ac..d39c3ccdc847bd 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -3868,17 +3868,15 @@ def __init__(self, b): with self.subTest('partial'): class CM(type): - __call__ = functools.partial(lambda x, a: (x, a), 2) + __call__ = functools.partial(lambda x, a, b: (x, a, b), 2) class C(metaclass=CM): - def __init__(self, b): + def __init__(self, c): pass - with self.assertWarns(FutureWarning): - self.assertEqual(C(1), (2, 1)) - with self.assertWarns(FutureWarning): - self.assertEqual(self.signature(C), - ((('a', ..., ..., "positional_or_keyword"),), - ...)) + self.assertEqual(C(1), (2, C, 1)) + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('partialmethod'): class CM(type): @@ -4024,14 +4022,12 @@ class C: with self.subTest('partial'): class C: - __init__ = functools.partial(lambda x, a: None, 2) + __init__ = functools.partial(lambda x, a, b: None, 2) - with self.assertWarns(FutureWarning): - C(1) # does not raise - with self.assertWarns(FutureWarning): - self.assertEqual(self.signature(C), - ((('a', ..., ..., "positional_or_keyword"),), - ...)) + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('partialmethod'): class C: @@ -4284,15 +4280,13 @@ class C: with self.subTest('partial'): class C: - __call__ = functools.partial(lambda x, a: (x, a), 2) + __call__ = functools.partial(lambda x, a, b: (x, a, b), 2) c = C() - with self.assertWarns(FutureWarning): - self.assertEqual(c(1), (2, 1)) - with self.assertWarns(FutureWarning): - self.assertEqual(self.signature(c), - ((('a', ..., ..., "positional_or_keyword"),), - ...)) + self.assertEqual(c(1), (2, c, 1)) + self.assertEqual(self.signature(C()), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('partialmethod'): class C: diff --git a/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst b/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst new file mode 100644 index 00000000000000..a450726d9afed9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst @@ -0,0 +1 @@ +Make the :class:`functools.partial` object a method descriptor. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 564c271915959a..64766b474514bf 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -203,14 +203,7 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type) if (obj == Py_None || obj == NULL) { return Py_NewRef(self); } - if (PyErr_WarnEx(PyExc_FutureWarning, - "functools.partial will be a method descriptor in " - "future Python versions; wrap it in staticmethod() " - "if you want to preserve the old behavior", 1) < 0) - { - return NULL; - } - return Py_NewRef(self); + return PyMethod_New(self, obj); } /* Merging keyword arguments using the vectorcall convention is messy, so From 705a123898f1394b62076c00ab6008c18fd8e115 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 3 Jul 2024 15:35:05 +0800 Subject: [PATCH 12/97] gh-116181: Remove Py_BUILD_CORE_BUILTIN and Py_BUILD_CORE_MODULE in rotatingtree.c (#121260) --- Modules/rotatingtree.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Modules/rotatingtree.c b/Modules/rotatingtree.c index 217e495b3d2a9d..5910e25bed6389 100644 --- a/Modules/rotatingtree.c +++ b/Modules/rotatingtree.c @@ -1,9 +1,4 @@ -#ifndef Py_BUILD_CORE_BUILTIN -# define Py_BUILD_CORE_MODULE 1 -#endif - #include "Python.h" -#include "pycore_lock.h" #include "rotatingtree.h" #define KEY_LOWER_THAN(key1, key2) ((char*)(key1) < (char*)(key2)) From ff5751a208e05f9d054b6df44f7651b64d415908 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 3 Jul 2024 15:46:57 +0800 Subject: [PATCH 13/97] gh-111872: Document the max_children attribute for `socketserver.ForkingMixIn` (#118134) --- Doc/library/socketserver.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst index f1f87ea975ca42..69f06e6cf4d923 100644 --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -126,6 +126,12 @@ server is the address family. waits until all non-daemon threads complete, except if :attr:`block_on_close` attribute is ``False``. + .. attribute:: max_children + + Specify how many child processes will exist to handle requests at a time + for :class:`ForkingMixIn`. If the limit is reached, + new requests will wait until one child process has finished. + .. attribute:: daemon_threads For :class:`ThreadingMixIn` use daemonic threads by setting From f65d17bf471ae5932ebd8baacedca83bfc85bdb1 Mon Sep 17 00:00:00 2001 From: byundojin <103907292+byundojin@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:51:25 +0900 Subject: [PATCH 14/97] updated tp_flags initialization to use inplace or (#120625) --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b042e64a188d9d..447e561c0d4440 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -8395,7 +8395,7 @@ type_ready(PyTypeObject *type, int initial) } /* All done -- set the ready flag */ - type->tp_flags = type->tp_flags | Py_TPFLAGS_READY; + type->tp_flags |= Py_TPFLAGS_READY; stop_readying(type); assert(_PyType_CheckConsistency(type)); From f49c83aa6683915fe6ba0a1177c3d4e873429703 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:22:59 +0530 Subject: [PATCH 15/97] build(deps): bump hypothesis from 6.100.2 to 6.104.2 in /Tools (#121218) Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.100.2 to 6.104.2. - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.100.2...hypothesis-python-6.104.2) --- updated-dependencies: - dependency-name: hypothesis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-hypothesis.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-hypothesis.txt b/Tools/requirements-hypothesis.txt index 9d5a18c881bf36..ab3f39ac6ee087 100644 --- a/Tools/requirements-hypothesis.txt +++ b/Tools/requirements-hypothesis.txt @@ -1,4 +1,4 @@ # Requirements file for hypothesis that # we use to run our property-based tests in CI. -hypothesis==6.100.2 +hypothesis==6.104.2 From 4232976b02cb999335c6bfdec3315520b21954f2 Mon Sep 17 00:00:00 2001 From: da-woods Date: Wed, 3 Jul 2024 09:05:02 +0100 Subject: [PATCH 16/97] docs: Fix "Py_TPFLAGS_MANAGED_WEAKREF is set in tp_flags" (#112237) --- Doc/c-api/typeobj.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index c9ef076c78c66a..0091e084308245 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -1592,7 +1592,7 @@ and :c:data:`PyType_Type` effectively act as defaults.) weak references to the type object itself. It is an error to set both the :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` bit and - :c:member:`~PyTypeObject.tp_weaklist`. + :c:member:`~PyTypeObject.tp_weaklistoffset`. **Inheritance:** @@ -1604,7 +1604,7 @@ and :c:data:`PyType_Type` effectively act as defaults.) **Default:** If the :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` bit is set in the - :c:member:`~PyTypeObject.tp_dict` field, then + :c:member:`~PyTypeObject.tp_flags` field, then :c:member:`~PyTypeObject.tp_weaklistoffset` will be set to a negative value, to indicate that it is unsafe to use this field. From 9d3c9b822ce3c52cd747efe93b172f02c0d09289 Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Wed, 3 Jul 2024 10:10:57 +0200 Subject: [PATCH 17/97] Docs: Add `os.splice` flags argument (#109847) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Blaise Pabon --- Doc/library/os.rst | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 8d95d01fe55ed9..2878d425310d75 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1724,10 +1724,27 @@ or `the MSDN `_ on Windo Added support for pipes on Windows. -.. function:: splice(src, dst, count, offset_src=None, offset_dst=None) +.. function:: splice(src, dst, count, offset_src=None, offset_dst=None, flags=0) Transfer *count* bytes from file descriptor *src*, starting from offset *offset_src*, to file descriptor *dst*, starting from offset *offset_dst*. + + The splicing behaviour can be modified by specifying a *flags* value. + Any of the following variables may used, combined using bitwise OR + (the ``|`` operator): + + * If :const:`SPLICE_F_MOVE` is specified, + the kernel is asked to move pages instead of copying, + but pages may still be copied if the kernel cannot move the pages from the pipe. + + * If :const:`SPLICE_F_NONBLOCK` is specified, + the kernel is asked to not block on I/O. + This makes the splice pipe operations nonblocking, + but splice may nevertheless block because the spliced file descriptors may block. + + * If :const:`SPLICE_F_MORE` is specified, + it hints to the kernel that more data will be coming in a subsequent splice. + At least one of the file descriptors must refer to a pipe. If *offset_src* is ``None``, then *src* is read from the current position; respectively for *offset_dst*. The offset associated to the file descriptor that refers to a @@ -1746,6 +1763,8 @@ or `the MSDN `_ on Windo make sense to block because there are no writers connected to the write end of the pipe. + .. seealso:: The :manpage:`splice(2)` man page. + .. availability:: Linux >= 2.6.17 with glibc >= 2.5 .. versionadded:: 3.10 From c9bdfbe86853fcf5f2b7dce3a50b383e23384ed2 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 3 Jul 2024 09:53:44 +0100 Subject: [PATCH 18/97] gh-106597: Add more offsets to _Py_DebugOffsets (#121311) Add more offsets to _Py_DebugOffsets We add a few more offsets that are required by some out-of-process tools, such as [Austin](https://github.com/p403n1x87/austin). --- Include/internal/pycore_runtime.h | 10 ++++++++++ Include/internal/pycore_runtime_init.h | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 341fe29a68af16..bc67377a89c17f 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -63,6 +63,7 @@ typedef struct _Py_DebugOffsets { // Interpreter state offset; struct _interpreter_state { uint64_t size; + uint64_t id; uint64_t next; uint64_t threads_head; uint64_t gc; @@ -83,6 +84,8 @@ typedef struct _Py_DebugOffsets { uint64_t current_frame; uint64_t thread_id; uint64_t native_thread_id; + uint64_t datastack_chunk; + uint64_t status; } thread_state; // InterpreterFrame offset; @@ -107,6 +110,7 @@ typedef struct _Py_DebugOffsets { uint64_t size; uint64_t filename; uint64_t name; + uint64_t qualname; uint64_t linetable; uint64_t firstlineno; uint64_t argcount; @@ -140,6 +144,12 @@ typedef struct _Py_DebugOffsets { uint64_t length; size_t asciiobject_size; } unicode_object; + + // GC runtime state offset; + struct _gc { + uint64_t size; + uint64_t collecting; + } gc; } _Py_DebugOffsets; /* Reference tracer state */ diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 33e39c2edbe541..da2b8d5570de62 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -41,6 +41,7 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .interpreter_state = { \ .size = sizeof(PyInterpreterState), \ + .id = offsetof(PyInterpreterState, id), \ .next = offsetof(PyInterpreterState, next), \ .threads_head = offsetof(PyInterpreterState, threads.head), \ .gc = offsetof(PyInterpreterState, gc), \ @@ -59,6 +60,8 @@ extern PyTypeObject _PyExc_MemoryError; .current_frame = offsetof(PyThreadState, current_frame), \ .thread_id = offsetof(PyThreadState, thread_id), \ .native_thread_id = offsetof(PyThreadState, native_thread_id), \ + .datastack_chunk = offsetof(PyThreadState, datastack_chunk), \ + .status = offsetof(PyThreadState, _status), \ }, \ .interpreter_frame = { \ .size = sizeof(_PyInterpreterFrame), \ @@ -72,6 +75,7 @@ extern PyTypeObject _PyExc_MemoryError; .size = sizeof(PyCodeObject), \ .filename = offsetof(PyCodeObject, co_filename), \ .name = offsetof(PyCodeObject, co_name), \ + .qualname = offsetof(PyCodeObject, co_qualname), \ .linetable = offsetof(PyCodeObject, co_linetable), \ .firstlineno = offsetof(PyCodeObject, co_firstlineno), \ .argcount = offsetof(PyCodeObject, co_argcount), \ @@ -97,6 +101,10 @@ extern PyTypeObject _PyExc_MemoryError; .length = offsetof(PyUnicodeObject, _base._base.length), \ .asciiobject_size = sizeof(PyASCIIObject), \ }, \ + .gc = { \ + .size = sizeof(struct _gc_runtime_state), \ + .collecting = offsetof(struct _gc_runtime_state, collecting), \ + }, \ }, \ .allocators = { \ .standard = _pymem_allocators_standard_INIT(runtime), \ From 51c4a324c037fb2e31640202243fd1c8b33800d5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 3 Jul 2024 12:08:11 +0300 Subject: [PATCH 19/97] gh-61103: Support float and long double complex types in ctypes module (#121248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This amends 6988ff02a5: memory allocation for stginfo->ffi_type_pointer.elements in PyCSimpleType_init() should be more generic (perhaps someday fmt->pffi_type->elements will be not a two-elements array). It should finally resolve #61103. Co-authored-by: Victor Stinner Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/ctypes.rst | 20 ++++++++ Lib/ctypes/__init__.py | 4 ++ Lib/test/test_ctypes/test_libc.py | 14 ++++++ Lib/test/test_ctypes/test_numbers.py | 10 ++-- ...4-06-23-07-23-08.gh-issue-61103.ca_U_l.rst | 8 ++-- Modules/_complex.h | 20 ++++++++ Modules/_ctypes/_ctypes.c | 7 +-- Modules/_ctypes/_ctypes_test.c | 10 ++++ Modules/_ctypes/callproc.c | 2 + Modules/_ctypes/cfield.c | 48 +++++++++++++++++++ Modules/_ctypes/ctypes.h | 2 + 11 files changed, 135 insertions(+), 10 deletions(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index a56e0eef5d11b1..e3d74d7dc0d91c 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -272,8 +272,12 @@ complex types are available: +----------------------------------+---------------------------------+-----------------+ | ctypes type | C type | Python type | +==================================+=================================+=================+ +| :class:`c_float_complex` | :c:expr:`float complex` | complex | ++----------------------------------+---------------------------------+-----------------+ | :class:`c_double_complex` | :c:expr:`double complex` | complex | +----------------------------------+---------------------------------+-----------------+ +| :class:`c_longdouble_complex` | :c:expr:`long double complex` | complex | ++----------------------------------+---------------------------------+-----------------+ All these types can be created by calling them with an optional initializer of @@ -2302,6 +2306,22 @@ These are the fundamental ctypes data types: .. versionadded:: 3.14 +.. class:: c_float_complex + + Represents the C :c:expr:`float complex` datatype, if available. The + constructor accepts an optional :class:`complex` initializer. + + .. versionadded:: 3.14 + + +.. class:: c_longdouble_complex + + Represents the C :c:expr:`long double complex` datatype, if available. The + constructor accepts an optional :class:`complex` initializer. + + .. versionadded:: 3.14 + + .. class:: c_int Represents the C :c:expr:`signed int` datatype. The constructor accepts an diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index d2e6a8bfc8c9d4..721522caeeac92 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -208,6 +208,10 @@ class c_longdouble(_SimpleCData): try: class c_double_complex(_SimpleCData): _type_ = "C" + class c_float_complex(_SimpleCData): + _type_ = "E" + class c_longdouble_complex(_SimpleCData): + _type_ = "F" except AttributeError: pass diff --git a/Lib/test/test_ctypes/test_libc.py b/Lib/test/test_ctypes/test_libc.py index dec0afff4b38fd..cab3cc9f46003a 100644 --- a/Lib/test/test_ctypes/test_libc.py +++ b/Lib/test/test_ctypes/test_libc.py @@ -33,6 +33,20 @@ def test_csqrt(self): self.assertAlmostEqual(lib.my_csqrt(-1-0.01j), 0.004999937502734214-1.0000124996093955j) + lib.my_csqrtf.argtypes = ctypes.c_float_complex, + lib.my_csqrtf.restype = ctypes.c_float_complex + self.assertAlmostEqual(lib.my_csqrtf(-1+0.01j), + 0.004999937502734214+1.0000124996093955j) + self.assertAlmostEqual(lib.my_csqrtf(-1-0.01j), + 0.004999937502734214-1.0000124996093955j) + + lib.my_csqrtl.argtypes = ctypes.c_longdouble_complex, + lib.my_csqrtl.restype = ctypes.c_longdouble_complex + self.assertAlmostEqual(lib.my_csqrtl(-1+0.01j), + 0.004999937502734214+1.0000124996093955j) + self.assertAlmostEqual(lib.my_csqrtl(-1-0.01j), + 0.004999937502734214-1.0000124996093955j) + def test_qsort(self): comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc diff --git a/Lib/test/test_ctypes/test_numbers.py b/Lib/test/test_ctypes/test_numbers.py index b3816f61a6e7aa..1dd3f2a234b1ee 100644 --- a/Lib/test/test_ctypes/test_numbers.py +++ b/Lib/test/test_ctypes/test_numbers.py @@ -146,7 +146,8 @@ def test_floats(self): @unittest.skipUnless(hasattr(ctypes, "c_double_complex"), "requires C11 complex type") def test_complex(self): - for t in [ctypes.c_double_complex]: + for t in [ctypes.c_double_complex, ctypes.c_float_complex, + ctypes.c_longdouble_complex]: self.assertEqual(t(1).value, 1+0j) self.assertEqual(t(1.0).value, 1+0j) self.assertEqual(t(1+0.125j).value, 1+0.125j) @@ -162,9 +163,10 @@ def test_complex_round_trip(self): values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, -3, INF, -INF, NAN], 2)] for z in values: - with self.subTest(z=z): - z2 = ctypes.c_double_complex(z).value - self.assertComplexesAreIdentical(z, z2) + for t in [ctypes.c_double_complex, ctypes.c_float_complex, + ctypes.c_longdouble_complex]: + with self.subTest(z=z, type=t): + self.assertComplexesAreIdentical(z, t(z).value) def test_integers(self): f = FloatLike() diff --git a/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst b/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst index 7b11d8c303c2f6..890eb62010eb33 100644 --- a/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst +++ b/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst @@ -1,3 +1,5 @@ -Support :c:expr:`double complex` C type in :mod:`ctypes` via -:class:`~ctypes.c_double_complex` if compiler has C11 complex -arithmetic. Patch by Sergey B Kirpichev. +Support :c:expr:`float complex`, :c:expr:`double complex` and +:c:expr:`long double complex` C types in :mod:`ctypes` as +:class:`~ctypes.c_float_complex`, :class:`~ctypes.c_double_complex` and +:class:`~ctypes.c_longdouble_complex` if the compiler has C11 complex arithmetic. +Patch by Sergey B Kirpichev. diff --git a/Modules/_complex.h b/Modules/_complex.h index 1c1d1c8cae51b9..28d4a32794b97c 100644 --- a/Modules/_complex.h +++ b/Modules/_complex.h @@ -21,6 +21,8 @@ #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) @@ -30,5 +32,23 @@ CMPLX(double real, double imag) ((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/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 3647361b13a52c..db58f33511c166 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1751,7 +1751,7 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type" /*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/ #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) -static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCfuzZqQPXOv?g"; +static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCEFfuzZqQPXOv?g"; #else static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g"; #endif @@ -2234,12 +2234,13 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds) stginfo->ffi_type_pointer = *fmt->pffi_type; } else { + const size_t els_size = sizeof(fmt->pffi_type->elements); stginfo->ffi_type_pointer.size = fmt->pffi_type->size; stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; - stginfo->ffi_type_pointer.elements = PyMem_Malloc(2 * sizeof(ffi_type)); + stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); memcpy(stginfo->ffi_type_pointer.elements, - fmt->pffi_type->elements, 2 * sizeof(ffi_type)); + fmt->pffi_type->elements, els_size); } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index cbc8f8b0b453af..b8e613fd669d1b 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -454,6 +454,16 @@ EXPORT(double complex) my_csqrt(double complex a) { return csqrt(a); } + +EXPORT(float complex) my_csqrtf(float complex a) +{ + return csqrtf(a); +} + +EXPORT(long double complex) my_csqrtl(long double complex a) +{ + return csqrtl(a); +} #endif EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*)) diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index a83fa19af32402..fd89d9c67b3fc0 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -657,6 +657,8 @@ union result { void *p; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) double complex C; + float complex E; + long double complex F; #endif }; diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index 40b72d83d16aeb..2c1fb9b862e12d 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -1112,6 +1112,50 @@ C_get(void *ptr, Py_ssize_t size) memcpy(&x, ptr, sizeof(x)); return PyComplex_FromDoubles(creal(x), cimag(x)); } + +static PyObject * +E_set(void *ptr, PyObject *value, Py_ssize_t size) +{ + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + float complex x = CMPLXF((float)c.real, (float)c.imag); + memcpy(ptr, &x, sizeof(x)); + _RET(value); +} + +static PyObject * +E_get(void *ptr, Py_ssize_t size) +{ + float complex x; + + memcpy(&x, ptr, sizeof(x)); + return PyComplex_FromDoubles(crealf(x), cimagf(x)); +} + +static PyObject * +F_set(void *ptr, PyObject *value, Py_ssize_t size) +{ + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + long double complex x = CMPLXL(c.real, c.imag); + memcpy(ptr, &x, sizeof(x)); + _RET(value); +} + +static PyObject * +F_get(void *ptr, Py_ssize_t size) +{ + long double complex x; + + memcpy(&x, ptr, sizeof(x)); + return PyComplex_FromDoubles((double)creall(x), (double)cimagl(x)); +} #endif static PyObject * @@ -1621,6 +1665,8 @@ static struct fielddesc formattable[] = { { 'd', d_set, d_get, NULL, d_set_sw, d_get_sw}, #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) { 'C', C_set, C_get, NULL}, + { 'E', E_set, E_get, NULL}, + { 'F', F_set, F_get, NULL}, #endif { 'g', g_set, g_get, NULL}, { 'f', f_set, f_get, NULL, f_set_sw, f_get_sw}, @@ -1674,6 +1720,8 @@ _ctypes_init_fielddesc(void) case 'd': fd->pffi_type = &ffi_type_double; break; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) case 'C': fd->pffi_type = &ffi_type_complex_double; break; + case 'E': fd->pffi_type = &ffi_type_complex_float; break; + case 'F': fd->pffi_type = &ffi_type_complex_longdouble; break; #endif case 'g': fd->pffi_type = &ffi_type_longdouble; break; case 'f': fd->pffi_type = &ffi_type_float; break; diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 5ba5eb3851a690..a794cfe86b5f42 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -401,6 +401,8 @@ struct tagPyCArgObject { void *p; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) double complex C; + float complex E; + long double complex F; #endif } value; PyObject *obj; From 93156880efd14ad7adc7d3512552b434f5543890 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:18:34 +0100 Subject: [PATCH 20/97] gh-121272: set ste_coroutine during symtable construction (#121297) compiler no longer modifies the symtable after this. --- Python/compile.c | 6 +++--- Python/symtable.c | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index d33db69f425361..30708e1dda9d43 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -3059,7 +3059,7 @@ compiler_async_for(struct compiler *c, stmt_ty s) { location loc = LOC(s); if (IS_TOP_LEVEL_AWAIT(c)){ - c->u->u_ste->ste_coroutine = 1; + assert(c->u->u_ste->ste_coroutine == 1); } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) { return compiler_error(c, loc, "'async for' outside async function"); } @@ -5782,7 +5782,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, co = optimize_and_assemble(c, 1); compiler_exit_scope(c); if (is_top_level_await && is_async_generator){ - c->u->u_ste->ste_coroutine = 1; + assert(c->u->u_ste->ste_coroutine == 1); } if (co == NULL) { goto error; @@ -5926,7 +5926,7 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos) assert(s->kind == AsyncWith_kind); if (IS_TOP_LEVEL_AWAIT(c)){ - c->u->u_ste->ste_coroutine = 1; + assert(c->u->u_ste->ste_coroutine == 1); } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION){ return compiler_error(c, loc, "'async with' outside async function"); } diff --git a/Python/symtable.c b/Python/symtable.c index 61fa5c6fdf923c..65677f86092b0b 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1681,6 +1681,16 @@ check_import_from(struct symtable *st, stmt_ty s) return 1; } +static void +maybe_set_ste_coroutine_for_module(struct symtable *st, stmt_ty s) +{ + if ((st->st_future->ff_features & PyCF_ALLOW_TOP_LEVEL_AWAIT) && + (st->st_cur->ste_type == ModuleBlock)) + { + st->st_cur->ste_coroutine = 1; + } +} + static int symtable_visit_stmt(struct symtable *st, stmt_ty s) { @@ -2074,10 +2084,12 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) break; } case AsyncWith_kind: + maybe_set_ste_coroutine_for_module(st, s); VISIT_SEQ(st, withitem, s->v.AsyncWith.items); VISIT_SEQ(st, stmt, s->v.AsyncWith.body); break; case AsyncFor_kind: + maybe_set_ste_coroutine_for_module(st, s); VISIT(st, expr, s->v.AsyncFor.target); VISIT(st, expr, s->v.AsyncFor.iter); VISIT_SEQ(st, stmt, s->v.AsyncFor.body); From 722229e5dc1e499664966e50bb98065670033300 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Wed, 3 Jul 2024 17:49:31 +0800 Subject: [PATCH 21/97] gh-121263: Macro-ify most stackref functions for MSVC (GH-121270) Macro-ify most stackref functions for MSVC --- Include/internal/pycore_stackref.h | 88 ++++++++++-------------------- 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 4301c6a7cb40b0..8d3d559814bfd9 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -85,81 +85,67 @@ typedef union _PyStackRef { # define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) }) #endif +// Note: the following are all macros because MSVC (Windows) has trouble inlining them. -static inline int -PyStackRef_Is(_PyStackRef a, _PyStackRef b) { - return a.bits == b.bits; -} +#define PyStackRef_Is(a, b) ((a).bits == (b).bits) + +#define PyStackRef_IsDeferred(ref) (((ref).bits & Py_TAG_BITS) == Py_TAG_DEFERRED) -static inline int -PyStackRef_IsDeferred(_PyStackRef ref) -{ - return ((ref.bits & Py_TAG_BITS) == Py_TAG_DEFERRED); -} +#ifdef Py_GIL_DISABLED // Gets a PyObject * from a _PyStackRef static inline PyObject * PyStackRef_AsPyObjectBorrow(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED PyObject *cleared = ((PyObject *)((stackref).bits & (~Py_TAG_BITS))); return cleared; +} #else - return ((PyObject *)(stackref).bits); +# define PyStackRef_AsPyObjectBorrow(stackref) ((PyObject *)(stackref).bits) #endif -} // Converts a PyStackRef back to a PyObject *, stealing the // PyStackRef. +#ifdef Py_GIL_DISABLED static inline PyObject * PyStackRef_AsPyObjectSteal(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED if (!PyStackRef_IsNull(stackref) && PyStackRef_IsDeferred(stackref)) { return Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)); } return PyStackRef_AsPyObjectBorrow(stackref); +} #else - return PyStackRef_AsPyObjectBorrow(stackref); +# define PyStackRef_AsPyObjectSteal(stackref) PyStackRef_AsPyObjectBorrow(stackref) #endif -} // Converts a PyStackRef back to a PyObject *, converting the // stackref to a new reference. -static inline PyObject * -PyStackRef_AsPyObjectNew(_PyStackRef stackref) -{ - return Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)); -} +#define PyStackRef_AsPyObjectNew(stackref) Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)) -static inline PyTypeObject * -PyStackRef_TYPE(_PyStackRef stackref) -{ - return Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref)); -} +#define PyStackRef_TYPE(stackref) Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref)) // Converts a PyObject * to a PyStackRef, stealing the reference +#ifdef Py_GIL_DISABLED static inline _PyStackRef _PyStackRef_FromPyObjectSteal(PyObject *obj) { -#ifdef Py_GIL_DISABLED // Make sure we don't take an already tagged value. assert(((uintptr_t)obj & Py_TAG_BITS) == 0); int tag = (obj == NULL || _Py_IsImmortal(obj)) ? (Py_TAG_DEFERRED) : Py_TAG_PTR; return ((_PyStackRef){.bits = ((uintptr_t)(obj)) | tag}); +} +# define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj)) #else - return ((_PyStackRef){.bits = ((uintptr_t)(obj))}); +# define PyStackRef_FromPyObjectSteal(obj) ((_PyStackRef){.bits = ((uintptr_t)(obj))}) #endif -} - -#define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj)) // Converts a PyObject * to a PyStackRef, with a new reference +#ifdef Py_GIL_DISABLED static inline _PyStackRef PyStackRef_FromPyObjectNew(PyObject *obj) { -#ifdef Py_GIL_DISABLED // Make sure we don't take an already tagged value. assert(((uintptr_t)obj & Py_TAG_BITS) == 0); assert(obj != NULL); @@ -170,30 +156,27 @@ PyStackRef_FromPyObjectNew(PyObject *obj) else { return (_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) | Py_TAG_PTR }; } +} +# define PyStackRef_FromPyObjectNew(obj) PyStackRef_FromPyObjectNew(_PyObject_CAST(obj)) #else - return ((_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) }); +# define PyStackRef_FromPyObjectNew(obj) ((_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) }) #endif -} - -#define PyStackRef_FromPyObjectNew(obj) PyStackRef_FromPyObjectNew(_PyObject_CAST(obj)) +#ifdef Py_GIL_DISABLED // Same as PyStackRef_FromPyObjectNew but only for immortal objects. static inline _PyStackRef PyStackRef_FromPyObjectImmortal(PyObject *obj) { -#ifdef Py_GIL_DISABLED // 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)) #else - assert(_Py_IsImmortal(obj)); - return ((_PyStackRef){ .bits = (uintptr_t)(obj) }); +# define PyStackRef_FromPyObjectImmortal(obj) ((_PyStackRef){ .bits = (uintptr_t)(obj) }) #endif -} - -#define PyStackRef_FromPyObjectImmortal(obj) PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj)) #define PyStackRef_CLEAR(op) \ @@ -206,20 +189,20 @@ PyStackRef_FromPyObjectImmortal(PyObject *obj) } \ } while (0) +#ifdef Py_GIL_DISABLED static inline void PyStackRef_CLOSE(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED if (PyStackRef_IsDeferred(stackref)) { // No assert for being immortal or deferred here. // The GC unsets deferred objects right before clearing. return; } Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref)); +} #else - Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref)); +# define PyStackRef_CLOSE(stackref) Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref)); #endif -} #define PyStackRef_XCLOSE(stackref) \ do { \ @@ -230,10 +213,10 @@ PyStackRef_CLOSE(_PyStackRef stackref) } while (0); +#ifdef Py_GIL_DISABLED static inline _PyStackRef PyStackRef_DUP(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED if (PyStackRef_IsDeferred(stackref)) { assert(PyStackRef_IsNull(stackref) || _Py_IsImmortal(PyStackRef_AsPyObjectBorrow(stackref))); @@ -241,21 +224,10 @@ PyStackRef_DUP(_PyStackRef stackref) } Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref)); return stackref; +} #else - Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref)); - return stackref; +# define PyStackRef_DUP(stackref) PyStackRef_FromPyObjectSteal(Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref))); #endif -} - -static inline _PyStackRef -PyStackRef_XDUP(_PyStackRef stackref) -{ - if (!PyStackRef_IsNull(stackref)) { - return PyStackRef_DUP(stackref); - } - return stackref; -} - static inline void _PyObjectStack_FromStackRefStack(PyObject **dst, const _PyStackRef *src, size_t length) From afee76b6ebeefbbc2935ab4f5320355c6fa390dd Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 3 Jul 2024 13:45:43 +0300 Subject: [PATCH 22/97] gh-121245: a regression test for site.register_readline() (#121259) --- Lib/test/test_pyrepl/test_pyrepl.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index b189d3291e8181..93c80467a04546 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1,14 +1,17 @@ import io import itertools import os +import pathlib import rlcompleter import select import subprocess import sys +import tempfile from unittest import TestCase, skipUnless from unittest.mock import patch from test.support import force_not_colorized from test.support import SHORT_TIMEOUT +from test.support.os_helper import unlink from .support import ( FakeConsole, @@ -898,6 +901,30 @@ def test_python_basic_repl(self): self.assertNotIn("Exception", output) self.assertNotIn("Traceback", output) + def test_not_wiping_history_file(self): + hfile = tempfile.NamedTemporaryFile(delete=False) + self.addCleanup(unlink, hfile.name) + env = os.environ.copy() + env["PYTHON_HISTORY"] = hfile.name + commands = "123\nspam\nexit()\n" + + env.pop("PYTHON_BASIC_REPL", None) + output, exit_code = self.run_repl(commands, env=env) + self.assertEqual(exit_code, 0) + self.assertIn("123", output) + self.assertIn("spam", output) + self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0) + + hfile.file.truncate() + hfile.close() + + env["PYTHON_BASIC_REPL"] = "1" + output, exit_code = self.run_repl(commands, env=env) + self.assertEqual(exit_code, 0) + self.assertIn("123", output) + self.assertIn("spam", output) + self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0) + def run_repl(self, repl_input: str | list[str], env: dict | None = None) -> tuple[str, int]: master_fd, slave_fd = pty.openpty() process = subprocess.Popen( From 26d24eeb90d781e381b97d64b4dcb1ee4dd891fe Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Wed, 3 Jul 2024 12:33:28 +0100 Subject: [PATCH 23/97] gh-121035: Update PNG image for logging flow diagram. (GH-121323) --- Doc/howto/logging_flow.png | Bin 110388 -> 39133 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Doc/howto/logging_flow.png b/Doc/howto/logging_flow.png index c2d0befe27326c140a3cd72b60088e5c69b9e53e..d60ed7c031585a426e344961ca9e09c2b51d70a7 100644 GIT binary patch literal 39133 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 literal 110388 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 Date: Wed, 3 Jul 2024 14:22:21 +0100 Subject: [PATCH 24/97] GH-119726: Emit AArch64 trampolines out-of-line (GH-121280) --- .../2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst | 2 ++ Tools/jit/_stencils.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst new file mode 100644 index 00000000000000..cf5d61450aa3ae --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst @@ -0,0 +1,2 @@ +Optimize code layout for calls to C functions from the JIT on AArch64. +Patch by Diego Russo. diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py index 68eb1d13394170..1c6a9edb39840d 100644 --- a/Tools/jit/_stencils.py +++ b/Tools/jit/_stencils.py @@ -184,7 +184,7 @@ def pad(self, alignment: int) -> None: self.disassembly.append(f"{offset:x}: {' '.join(['00'] * padding)}") self.body.extend([0] * padding) - def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> None: + def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> Hole: """Even with the large code model, AArch64 Linux insists on 28-bit jumps.""" assert hole.symbol is not None reuse_trampoline = hole.symbol in self.trampolines @@ -194,14 +194,10 @@ def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> None: else: self.pad(alignment) base = len(self.body) - where = slice(hole.offset, hole.offset + 4) - instruction = int.from_bytes(self.body[where], sys.byteorder) - instruction &= 0xFC000000 - instruction |= ((base - hole.offset) >> 2) & 0x03FFFFFF - self.body[where] = instruction.to_bytes(4, sys.byteorder) + new_hole = hole.replace(addend=base, symbol=None, value=HoleValue.DATA) if reuse_trampoline: - return + return new_hole self.disassembly += [ f"{base + 4 * 0:x}: 58000048 ldr x8, 8", @@ -219,6 +215,7 @@ def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> None: self.body.extend(code) self.holes.append(hole.replace(offset=base + 8, kind="R_AARCH64_ABS64")) self.trampolines[hole.symbol] = base + return new_hole def remove_jump(self, *, alignment: int = 1) -> None: """Remove a zero-length continuation jump, if it exists.""" @@ -294,8 +291,9 @@ def process_relocations(self, *, alignment: int = 1) -> None: in {"R_AARCH64_CALL26", "R_AARCH64_JUMP26", "ARM64_RELOC_BRANCH26"} and hole.value is HoleValue.ZERO ): - self.code.emit_aarch64_trampoline(hole, alignment) + new_hole = self.data.emit_aarch64_trampoline(hole, alignment) self.code.holes.remove(hole) + self.code.holes.append(new_hole) self.code.remove_jump(alignment=alignment) self.code.pad(alignment) self.data.pad(8) From ca2e8765009d0d3eb9fe6c75465825c50808f4dd Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Wed, 3 Jul 2024 07:44:34 -0700 Subject: [PATCH 25/97] gh-121201: Disable perf_trampoline on riscv64 for now (#121328) Disable perf_trampoline on riscv64 for now Until support is added in perf_jit_trampoline.c gh-120089 was incomplete. --- .../NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- | 1 - .../2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst | 1 - configure | 2 -- configure.ac | 1 - 4 files changed, 5 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- b/Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- deleted file mode 100644 index 29f06d43c3598c..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- +++ /dev/null @@ -1 +0,0 @@ -Support Linux perf profiler to see Python calls on RISC-V architecture diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst deleted file mode 100644 index 8c86d4750e39a8..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst +++ /dev/null @@ -1 +0,0 @@ -Support Linux perf profiler to see Python calls on RISC-V architecture. diff --git a/configure b/configure index 922d33edc00cb5..131ca5f7f897a7 100755 --- a/configure +++ b/configure @@ -13292,8 +13292,6 @@ case $PLATFORM_TRIPLET in #( perf_trampoline=yes ;; #( aarch64-linux-gnu) : perf_trampoline=yes ;; #( - riscv64-linux-gnu) : - perf_trampoline=yes ;; #( *) : perf_trampoline=no ;; diff --git a/configure.ac b/configure.ac index a70e673623de81..705f8752597b96 100644 --- a/configure.ac +++ b/configure.ac @@ -3709,7 +3709,6 @@ AC_MSG_CHECKING([perf trampoline]) AS_CASE([$PLATFORM_TRIPLET], [x86_64-linux-gnu], [perf_trampoline=yes], [aarch64-linux-gnu], [perf_trampoline=yes], - [riscv64-linux-gnu], [perf_trampoline=yes], [perf_trampoline=no] ) AC_MSG_RESULT([$perf_trampoline]) From 7c66906802cd8534b05264bd47acf9eb9db6d09e Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Wed, 3 Jul 2024 10:03:56 -0500 Subject: [PATCH 26/97] gh-121300: Add `replace` to `copy.__all__` (#121302) --- Lib/copy.py | 7 ++++--- Lib/test/test_copy.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 7a1907d75494d7..a79976d3a658f0 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -4,8 +4,9 @@ import copy - x = copy.copy(y) # make a shallow copy of y - x = copy.deepcopy(y) # make a deep copy of y + x = copy.copy(y) # make a shallow copy of y + x = copy.deepcopy(y) # make a deep copy of y + x = copy.replace(y, a=1, b=2) # new object with fields replaced, as defined by `__replace__` For module specific errors, copy.Error is raised. @@ -56,7 +57,7 @@ class Error(Exception): pass error = Error # backward compatibility -__all__ = ["Error", "copy", "deepcopy"] +__all__ = ["Error", "copy", "deepcopy", "replace"] def copy(x): """Shallow copy operation on arbitrary Python objects. diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 89102373759ca0..3dec64cc9a2414 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -972,6 +972,10 @@ class C: copy.replace(c, x=1, error=2) +class MiscTestCase(unittest.TestCase): + def test__all__(self): + support.check__all__(self, copy, not_exported={"dispatch_table", "error"}) + def global_foo(x, y): return x+y From f8373db153920b890c2e2dd8def249e8df63bcc6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 3 Jul 2024 18:36:57 +0200 Subject: [PATCH 27/97] gh-112136: Restore removed _PyArg_Parser (#121262) Restore the private _PyArg_Parser structure and the private _PyArg_ParseTupleAndKeywordsFast() function, previously removed in Python 3.13 alpha 1. Recreate Include/cpython/modsupport.h header file. --- Include/cpython/modsupport.h | 26 +++++++++++++++++++ Include/internal/pycore_lock.h | 6 ----- Include/internal/pycore_modsupport.h | 18 ------------- Include/modsupport.h | 6 +++++ Makefile.pre.in | 1 + ...-07-02-11-03-40.gh-issue-112136.f3fiY8.rst | 3 +++ PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 +++ 8 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 Include/cpython/modsupport.h create mode 100644 Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst diff --git a/Include/cpython/modsupport.h b/Include/cpython/modsupport.h new file mode 100644 index 00000000000000..d3b88f58c82ca3 --- /dev/null +++ b/Include/cpython/modsupport.h @@ -0,0 +1,26 @@ +#ifndef Py_CPYTHON_MODSUPPORT_H +# error "this header file must not be included directly" +#endif + +// A data structure that can be used to run initialization code once in a +// thread-safe manner. The C++11 equivalent is std::call_once. +typedef struct { + uint8_t v; +} _PyOnceFlag; + +typedef struct _PyArg_Parser { + const char *format; + const char * const *keywords; + const char *fname; + const char *custom_msg; + _PyOnceFlag once; /* atomic one-time initialization flag */ + int is_kwtuple_owned; /* does this parser own the kwtuple object? */ + int pos; /* number of positional-only arguments */ + int min; /* minimal number of arguments */ + int max; /* maximal number of positional arguments */ + PyObject *kwtuple; /* tuple of keyword parameter names */ + struct _PyArg_Parser *next; +} _PyArg_Parser; + +PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywordsFast(PyObject *, PyObject *, + struct _PyArg_Parser *, ...); diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 8aa73946e2c645..3824434f3f375d 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -128,12 +128,6 @@ _PyRawMutex_Unlock(_PyRawMutex *m) _PyRawMutex_UnlockSlow(m); } -// A data structure that can be used to run initialization code once in a -// thread-safe manner. The C++11 equivalent is std::call_once. -typedef struct { - uint8_t v; -} _PyOnceFlag; - // Type signature for one-time initialization functions. The function should // return 0 on success and -1 on failure. typedef int _Py_once_fn_t(void *arg); diff --git a/Include/internal/pycore_modsupport.h b/Include/internal/pycore_modsupport.h index 3d3cd6722528e9..11fde814875938 100644 --- a/Include/internal/pycore_modsupport.h +++ b/Include/internal/pycore_modsupport.h @@ -67,24 +67,6 @@ PyAPI_FUNC(void) _PyArg_BadArgument( // --- _PyArg_Parser API --------------------------------------------------- -typedef struct _PyArg_Parser { - const char *format; - const char * const *keywords; - const char *fname; - const char *custom_msg; - _PyOnceFlag once; /* atomic one-time initialization flag */ - int is_kwtuple_owned; /* does this parser own the kwtuple object? */ - int pos; /* number of positional-only arguments */ - int min; /* minimal number of arguments */ - int max; /* maximal number of positional arguments */ - PyObject *kwtuple; /* tuple of keyword parameter names */ - struct _PyArg_Parser *next; -} _PyArg_Parser; - -// Export for '_testclinic' shared extension -PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywordsFast(PyObject *, PyObject *, - struct _PyArg_Parser *, ...); - // Export for '_dbm' shared extension PyAPI_FUNC(int) _PyArg_ParseStackAndKeywords( PyObject *const *args, diff --git a/Include/modsupport.h b/Include/modsupport.h index ea4c0fce9f4562..af995f567b004c 100644 --- a/Include/modsupport.h +++ b/Include/modsupport.h @@ -134,6 +134,12 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def, #endif /* New in 3.5 */ +#ifndef Py_LIMITED_API +# define Py_CPYTHON_MODSUPPORT_H +# include "cpython/modsupport.h" +# undef Py_CPYTHON_MODSUPPORT_H +#endif + #ifdef __cplusplus } #endif diff --git a/Makefile.pre.in b/Makefile.pre.in index e1c793ce629b02..94cfb74138a3d9 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1115,6 +1115,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/cpython/longobject.h \ $(srcdir)/Include/cpython/memoryobject.h \ $(srcdir)/Include/cpython/methodobject.h \ + $(srcdir)/Include/cpython/modsupport.h \ $(srcdir)/Include/cpython/monitoring.h \ $(srcdir)/Include/cpython/object.h \ $(srcdir)/Include/cpython/objimpl.h \ diff --git a/Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst b/Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst new file mode 100644 index 00000000000000..a240b4e852c4d1 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst @@ -0,0 +1,3 @@ +Restore the private ``_PyArg_Parser`` structure and the private +``_PyArg_ParseTupleAndKeywordsFast()`` function, previously removed in Python +3.13 alpha 1. Patch by Victor Stinner. diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 3378ed54203f18..f36fcb8caece33 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -163,6 +163,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 742d88d9e1fa7a..a1b43addf9e36a 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -432,6 +432,9 @@ Include\cpython + + Include\cpython + Include\cpython From e245ed7d1e23b5c8bc0d568bd1a2f06ae92d631a Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 3 Jul 2024 11:30:20 -0700 Subject: [PATCH 28/97] gh-118714: Make the pdb post-mortem restart/quit behavior more reasonable (#118725) --- Lib/pdb.py | 9 ++++++--- Lib/test/test_pdb.py | 17 +++++++++++++++++ ...24-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst | 2 ++ 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 4af16d0a087c8c..85a3aa2e37996f 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -2481,9 +2481,12 @@ def main(): traceback.print_exception(e, colorize=_colorize.can_colorize()) print("Uncaught exception. Entering post mortem debugging") print("Running 'cont' or 'step' will restart the program") - pdb.interaction(None, e) - print(f"Post mortem debugger finished. The {target} will " - "be restarted") + try: + pdb.interaction(None, e) + except Restart: + print("Restarting", target, "with arguments:") + print("\t" + " ".join(sys.argv[1:])) + continue if pdb._user_requested_quit: break print("The program finished and will be restarted") diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 71240157e324a1..5c7445574f5d75 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3545,6 +3545,23 @@ def change_file(content, filename): # the file as up to date self.assertNotIn("WARNING:", stdout) + def test_post_mortem_restart(self): + script = """ + def foo(): + raise ValueError("foo") + foo() + """ + + commands = """ + continue + restart + continue + quit + """ + + stdout, stderr = self.run_pdb_script(script, commands) + self.assertIn("Restarting", stdout) + def test_relative_imports(self): self.module_name = 't_main' os_helper.rmtree(self.module_name) diff --git a/Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst b/Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst new file mode 100644 index 00000000000000..f41baee303482a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst @@ -0,0 +1,2 @@ +Allow ``restart`` in post-mortem debugging of :mod:`pdb`. Removed restart message +when the user quits pdb from post-mortem mode. From 94f50f8ee6872007d46c385f7af253497273255a Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Wed, 3 Jul 2024 16:50:46 -0400 Subject: [PATCH 29/97] gh-117983: Defer import of threading for lazy module loading (#120233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As noted in gh-117983, the import importlib.util can be triggered at interpreter startup under some circumstances, so adding threading makes it a potentially obligatory load. Lazy loading is not used in the stdlib, so this removes an unnecessary load for the majority of users and slightly increases the cost of the first lazily loaded module. An obligatory threading load breaks gevent, which monkeypatches the stdlib. Although unsupported, there doesn't seem to be an offsetting benefit to breaking their use case. For reference, here are benchmarks for the current main branch: ``` ❯ hyperfine -w 8 './python -c "import importlib.util"' Benchmark 1: ./python -c "import importlib.util" Time (mean ± σ): 9.7 ms ± 0.7 ms [User: 7.7 ms, System: 1.8 ms] Range (min … max): 8.4 ms … 13.1 ms 313 runs ``` And with this patch: ``` ❯ hyperfine -w 8 './python -c "import importlib.util"' Benchmark 1: ./python -c "import importlib.util" Time (mean ± σ): 8.4 ms ± 0.7 ms [User: 6.8 ms, System: 1.4 ms] Range (min … max): 7.2 ms … 11.7 ms 352 runs ``` Compare to: ``` ❯ hyperfine -w 8 './python -c pass' Benchmark 1: ./python -c pass Time (mean ± σ): 7.6 ms ± 0.6 ms [User: 5.9 ms, System: 1.6 ms] Range (min … max): 6.7 ms … 11.3 ms 390 runs ``` This roughly halves the import time of importlib.util. --- Lib/importlib/util.py | 4 +++- .../Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 7243d052cc27f3..8403ef9b44ad1a 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -13,7 +13,6 @@ import _imp import sys -import threading import types @@ -257,6 +256,9 @@ def create_module(self, spec): def exec_module(self, module): """Make the module load lazily.""" + # Threading is only needed for lazy loading, and importlib.util can + # be pulled in at interpreter startup, so defer until needed. + import threading module.__spec__.loader = self.loader module.__loader__ = self.loader # Don't need to worry about deep-copying as trying to set an attribute diff --git a/Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst b/Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst new file mode 100644 index 00000000000000..cca97f50a20496 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst @@ -0,0 +1,2 @@ +Defer the ``threading`` import in ``importlib.util`` until lazy loading is +used. From 9728ead36181fb3f0a4b2e8a7291a3e0a702b952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 4 Jul 2024 05:10:54 +0200 Subject: [PATCH 30/97] gh-121141: add support for `copy.replace` to AST nodes (#121162) --- Doc/whatsnew/3.14.rst | 8 +- Lib/test/test_ast.py | 272 +++++++++++++++++ ...-06-29-15-21-12.gh-issue-121141.4evD6q.rst | 1 + Parser/asdl_c.py | 279 ++++++++++++++++++ Python/Python-ast.c | 279 ++++++++++++++++++ 5 files changed, 837 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 9578ba0c9c9657..d02c10ec9cf3f3 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -89,8 +89,12 @@ Improved Modules ast --- -Added :func:`ast.compare` for comparing two ASTs. -(Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.) +* Added :func:`ast.compare` for comparing two ASTs. + (Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.) + +* Add support for :func:`copy.replace` for AST nodes. + + (Contributed by Bénédikt Tran in :gh:`121141`.) os -- diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index fbd19620311159..eb3aefd5c262f6 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1149,6 +1149,25 @@ def test_none_checks(self) -> None: class CopyTests(unittest.TestCase): """Test copying and pickling AST nodes.""" + @staticmethod + def iter_ast_classes(): + """Iterate over the (native) subclasses of ast.AST recursively. + + This excludes the special class ast.Index since its constructor + returns an integer. + """ + def do(cls): + if cls.__module__ != 'ast': + return + if cls is ast.Index: + return + + yield cls + for sub in cls.__subclasses__(): + yield from do(sub) + + yield from do(ast.AST) + def test_pickling(self): import pickle @@ -1218,6 +1237,259 @@ def test_copy_with_parents(self): )): self.assertEqual(to_tuple(child.parent), to_tuple(node)) + def test_replace_interface(self): + for klass in self.iter_ast_classes(): + with self.subTest(klass=klass): + self.assertTrue(hasattr(klass, '__replace__')) + + fields = set(klass._fields) + with self.subTest(klass=klass, fields=fields): + node = klass(**dict.fromkeys(fields)) + # forbid positional arguments in replace() + self.assertRaises(TypeError, copy.replace, node, 1) + self.assertRaises(TypeError, node.__replace__, 1) + + def test_replace_native(self): + for klass in self.iter_ast_classes(): + fields = set(klass._fields) + attributes = set(klass._attributes) + + with self.subTest(klass=klass, fields=fields, attributes=attributes): + # use of object() to ensure that '==' and 'is' + # behave similarly in ast.compare(node, repl) + old_fields = {field: object() for field in fields} + old_attrs = {attr: object() for attr in attributes} + + # check shallow copy + node = klass(**old_fields) + repl = copy.replace(node) + self.assertTrue(ast.compare(node, repl, compare_attributes=True)) + # check when passing using attributes (they may be optional!) + node = klass(**old_fields, **old_attrs) + repl = copy.replace(node) + self.assertTrue(ast.compare(node, repl, compare_attributes=True)) + + for field in fields: + # check when we sometimes have attributes and sometimes not + for init_attrs in [{}, old_attrs]: + node = klass(**old_fields, **init_attrs) + # only change a single field (do not change attributes) + new_value = object() + repl = copy.replace(node, **{field: new_value}) + for f in fields: + old_value = old_fields[f] + # assert that there is no side-effect + self.assertIs(getattr(node, f), old_value) + # check the changes + if f != field: + self.assertIs(getattr(repl, f), old_value) + else: + self.assertIs(getattr(repl, f), new_value) + self.assertFalse(ast.compare(node, repl, compare_attributes=True)) + + for attribute in attributes: + node = klass(**old_fields, **old_attrs) + # only change a single attribute (do not change fields) + new_attr = object() + repl = copy.replace(node, **{attribute: new_attr}) + for a in attributes: + old_attr = old_attrs[a] + # assert that there is no side-effect + self.assertIs(getattr(node, a), old_attr) + # check the changes + if a != attribute: + self.assertIs(getattr(repl, a), old_attr) + else: + self.assertIs(getattr(repl, a), new_attr) + self.assertFalse(ast.compare(node, repl, compare_attributes=True)) + + def test_replace_accept_known_class_fields(self): + nid, ctx = object(), object() + + node = ast.Name(id=nid, ctx=ctx) + self.assertIs(node.id, nid) + self.assertIs(node.ctx, ctx) + + new_nid = object() + repl = copy.replace(node, id=new_nid) + # assert that there is no side-effect + self.assertIs(node.id, nid) + self.assertIs(node.ctx, ctx) + # check the changes + self.assertIs(repl.id, new_nid) + self.assertIs(repl.ctx, node.ctx) # no changes + + def test_replace_accept_known_class_attributes(self): + node = ast.parse('x').body[0].value + self.assertEqual(node.id, 'x') + self.assertEqual(node.lineno, 1) + + # constructor allows any type so replace() should do the same + lineno = object() + repl = copy.replace(node, lineno=lineno) + # assert that there is no side-effect + self.assertEqual(node.lineno, 1) + # check the changes + self.assertEqual(repl.id, node.id) + self.assertEqual(repl.ctx, node.ctx) + self.assertEqual(repl.lineno, lineno) + + _, _, state = node.__reduce__() + self.assertEqual(state['id'], 'x') + self.assertEqual(state['ctx'], node.ctx) + self.assertEqual(state['lineno'], 1) + + _, _, state = repl.__reduce__() + self.assertEqual(state['id'], 'x') + self.assertEqual(state['ctx'], node.ctx) + self.assertEqual(state['lineno'], lineno) + + def test_replace_accept_known_custom_class_fields(self): + class MyNode(ast.AST): + _fields = ('name', 'data') + __annotations__ = {'name': str, 'data': object} + __match_args__ = ('name', 'data') + + name, data = 'name', object() + + node = MyNode(name, data) + self.assertIs(node.name, name) + self.assertIs(node.data, data) + # check shallow copy + repl = copy.replace(node) + # assert that there is no side-effect + self.assertIs(node.name, name) + self.assertIs(node.data, data) + # check the shallow copy + self.assertIs(repl.name, name) + self.assertIs(repl.data, data) + + node = MyNode(name, data) + repl_data = object() + # replace custom but known field + repl = copy.replace(node, data=repl_data) + # assert that there is no side-effect + self.assertIs(node.name, name) + self.assertIs(node.data, data) + # check the changes + self.assertIs(repl.name, node.name) + self.assertIs(repl.data, repl_data) + + def test_replace_accept_known_custom_class_attributes(self): + class MyNode(ast.AST): + x = 0 + y = 1 + _attributes = ('x', 'y') + + node = MyNode() + self.assertEqual(node.x, 0) + self.assertEqual(node.y, 1) + + y = object() + # custom attributes are currently not supported and raise a warning + # because the allowed attributes are hard-coded ! + msg = ( + "MyNode.__init__ got an unexpected keyword argument 'y'. " + "Support for arbitrary keyword arguments is deprecated and " + "will be removed in Python 3.15" + ) + with self.assertWarnsRegex(DeprecationWarning, re.escape(msg)): + repl = copy.replace(node, y=y) + # assert that there is no side-effect + self.assertEqual(node.x, 0) + self.assertEqual(node.y, 1) + # check the changes + self.assertEqual(repl.x, 0) + self.assertEqual(repl.y, y) + + def test_replace_ignore_known_custom_instance_fields(self): + node = ast.parse('x').body[0].value + node.extra = extra = object() # add instance 'extra' field + context = node.ctx + + # assert initial values + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + # shallow copy, but drops extra fields + repl = copy.replace(node) + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + # verify that the 'extra' field is not kept + self.assertIs(repl.id, 'x') + self.assertIs(repl.ctx, context) + self.assertRaises(AttributeError, getattr, repl, 'extra') + + # change known native field + repl = copy.replace(node, id='y') + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + # verify that the 'extra' field is not kept + self.assertIs(repl.id, 'y') + self.assertIs(repl.ctx, context) + self.assertRaises(AttributeError, getattr, repl, 'extra') + + def test_replace_reject_missing_field(self): + # case: warn if deleted field is not replaced + node = ast.parse('x').body[0].value + context = node.ctx + del node.id + + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + msg = "Name.__replace__ missing 1 keyword argument: 'id'." + with self.assertRaisesRegex(TypeError, re.escape(msg)): + copy.replace(node) + # assert that there is no side-effect + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + + # case: do not raise if deleted field is replaced + node = ast.parse('x').body[0].value + context = node.ctx + del node.id + + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + repl = copy.replace(node, id='y') + # assert that there is no side-effect + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + self.assertIs(repl.id, 'y') + self.assertIs(repl.ctx, context) + + 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')) + msg = "Name.__replace__ got an unexpected keyword argument 'extra'." + with self.assertRaisesRegex(TypeError, re.escape(msg)): + copy.replace(node, extra=1) + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + + def test_replace_reject_unknown_instance_fields(self): + node = ast.parse('x').body[0].value + context = node.ctx + + # explicit rejection of unknown extra fields + self.assertRaises(AttributeError, getattr, node, 'unknown') + msg = "Name.__replace__ got an unexpected keyword argument 'unknown'." + with self.assertRaisesRegex(TypeError, re.escape(msg)): + copy.replace(node, unknown=1) + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertRaises(AttributeError, getattr, node, 'unknown') class ASTHelpers_Test(unittest.TestCase): maxDiff = None diff --git a/Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst b/Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst new file mode 100644 index 00000000000000..f2dc621050ff4b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst @@ -0,0 +1 @@ +Add support for :func:`copy.replace` to AST nodes. Patch by Bénédikt Tran. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index e338656a5b1eb9..f3667801782f2b 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -1132,6 +1132,279 @@ def visitModule(self, mod): return result; } +/* + * Perform the following validations: + * + * - All keyword arguments are known 'fields' or 'attributes'. + * - No field or attribute would be left unfilled after copy.replace(). + * + * On success, this returns 1. Otherwise, set a TypeError + * exception and returns -1 (no exception is set if some + * other internal errors occur). + * + * Parameters + * + * self The AST node instance. + * dict The AST node instance dictionary (self.__dict__). + * fields The list of fields (self._fields). + * attributes The list of attributes (self._attributes). + * kwargs Keyword arguments passed to ast_type_replace(). + * + * The 'dict', 'fields', 'attributes' and 'kwargs' arguments can be NULL. + * + * Note: this function can be removed in 3.15 since the verification + * will be done inside the constructor. + */ +static inline int +ast_type_replace_check(PyObject *self, + PyObject *dict, + PyObject *fields, + PyObject *attributes, + PyObject *kwargs) +{ + // While it is possible to make some fast paths that would avoid + // allocating objects on the stack, this would cost us readability. + // For instance, if 'fields' and 'attributes' are both empty, and + // 'kwargs' is not empty, we could raise a TypeError immediately. + PyObject *expecting = PySet_New(fields); + if (expecting == NULL) { + return -1; + } + if (attributes) { + if (_PySet_Update(expecting, attributes) < 0) { + Py_DECREF(expecting); + return -1; + } + } + // Any keyword argument that is neither a field nor attribute is rejected. + // We first need to check whether a keyword argument is accepted or not. + // If all keyword arguments are accepted, we compute the required fields + // and attributes. A field or attribute is not needed if: + // + // 1) it is given in 'kwargs', or + // 2) it already exists on 'self'. + if (kwargs) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(kwargs, &pos, &key, &value)) { + int rc = PySet_Discard(expecting, key); + if (rc < 0) { + Py_DECREF(expecting); + return -1; + } + if (rc == 0) { + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ got an unexpected keyword " + "argument '%U'.", Py_TYPE(self)->tp_name, key); + Py_DECREF(expecting); + return -1; + } + } + } + // check that the remaining fields or attributes would be filled + if (dict) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(dict, &pos, &key, &value)) { + // Mark fields or attributes that are found on the instance + // as non-mandatory. If they are not given in 'kwargs', they + // will be shallow-coied; otherwise, they would be replaced + // (not in this function). + if (PySet_Discard(expecting, key) < 0) { + Py_DECREF(expecting); + return -1; + } + } + if (attributes) { + // Some attributes may or may not be present at runtime. + // In particular, now that we checked whether 'kwargs' + // is correct or not, we allow any attribute to be missing. + // + // Note that fields must still be entirely determined when + // calling the constructor later. + PyObject *unused = PyObject_CallMethodOneArg(expecting, + &_Py_ID(difference_update), + attributes); + if (unused == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_DECREF(unused); + } + } + // Now 'expecting' contains the fields or attributes + // that would not be filled inside ast_type_replace(). + Py_ssize_t m = PySet_GET_SIZE(expecting); + if (m > 0) { + PyObject *names = PyList_New(m); + if (names == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_ssize_t i = 0, pos = 0; + PyObject *item; + Py_hash_t hash; + while (_PySet_NextEntry(expecting, &pos, &item, &hash)) { + PyObject *name = PyObject_Repr(item); + if (name == NULL) { + Py_DECREF(expecting); + Py_DECREF(names); + return -1; + } + // steal the reference 'name' + PyList_SET_ITEM(names, i++, name); + } + Py_DECREF(expecting); + if (PyList_Sort(names) < 0) { + Py_DECREF(names); + return -1; + } + PyObject *sep = PyUnicode_FromString(", "); + if (sep == NULL) { + Py_DECREF(names); + return -1; + } + PyObject *str_names = PyUnicode_Join(sep, names); + Py_DECREF(sep); + Py_DECREF(names); + if (str_names == NULL) { + return -1; + } + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ missing %ld keyword argument%s: %U.", + Py_TYPE(self)->tp_name, m, m == 1 ? "" : "s", str_names); + Py_DECREF(str_names); + return -1; + } + else { + Py_DECREF(expecting); + return 1; + } +} + +/* + * Python equivalent: + * + * for key in keys: + * if hasattr(self, key): + * payload[key] = getattr(self, key) + * + * The 'keys' argument is a sequence corresponding to + * the '_fields' or the '_attributes' of an AST node. + * + * This returns -1 if an error occurs and 0 otherwise. + * + * Parameters + * + * payload A dictionary to fill. + * keys A sequence of keys or NULL for an empty sequence. + * dict The AST node instance dictionary (must not be NULL). + */ +static inline int +ast_type_replace_update_payload(PyObject *payload, + PyObject *keys, + PyObject *dict) +{ + assert(dict != NULL); + if (keys == NULL) { + return 0; + } + Py_ssize_t n = PySequence_Size(keys); + if (n == -1) { + return -1; + } + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *key = PySequence_GetItem(keys, i); + if (key == NULL) { + return -1; + } + PyObject *value; + if (PyDict_GetItemRef(dict, key, &value) < 0) { + Py_DECREF(key); + return -1; + } + if (value == NULL) { + Py_DECREF(key); + // If a field or attribute is not present at runtime, it should + // be explicitly given in 'kwargs'. If not, the constructor will + // issue a warning (which becomes an error in 3.15). + continue; + } + int rc = PyDict_SetItem(payload, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (rc < 0) { + return -1; + } + } + return 0; +} + +/* copy.replace() support (shallow copy) */ +static PyObject * +ast_type_replace(PyObject *self, PyObject *args, PyObject *kwargs) +{ + if (!_PyArg_NoPositional("__replace__", args)) { + return NULL; + } + + struct ast_state *state = get_ast_state(); + if (state == NULL) { + return NULL; + } + + PyObject *result = NULL; + // known AST class fields and attributes + PyObject *fields = NULL, *attributes = NULL; + // current instance dictionary + PyObject *dict = NULL; + // constructor positional and keyword arguments + PyObject *empty_tuple = NULL, *payload = NULL; + + PyObject *type = (PyObject *)Py_TYPE(self); + if (PyObject_GetOptionalAttr(type, state->_fields, &fields) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(type, state->_attributes, &attributes) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { + goto cleanup; + } + if (ast_type_replace_check(self, dict, fields, attributes, kwargs) < 0) { + goto cleanup; + } + empty_tuple = PyTuple_New(0); + if (empty_tuple == NULL) { + goto cleanup; + } + payload = PyDict_New(); + if (payload == NULL) { + goto cleanup; + } + if (dict) { // in case __dict__ is missing (for some obscure reason) + // copy the instance's fields (possibly NULL) + if (ast_type_replace_update_payload(payload, fields, dict) < 0) { + goto cleanup; + } + // copy the instance's attributes (possibly NULL) + if (ast_type_replace_update_payload(payload, attributes, dict) < 0) { + goto cleanup; + } + } + if (kwargs && PyDict_Update(payload, kwargs) < 0) { + goto cleanup; + } + result = PyObject_Call(type, empty_tuple, payload); +cleanup: + Py_XDECREF(payload); + Py_XDECREF(empty_tuple); + Py_XDECREF(dict); + Py_XDECREF(attributes); + Py_XDECREF(fields); + return result; +} + static PyMemberDef ast_type_members[] = { {"__dictoffset__", Py_T_PYSSIZET, offsetof(AST_object, dict), Py_READONLY}, {NULL} /* Sentinel */ @@ -1139,6 +1412,10 @@ def visitModule(self, mod): static PyMethodDef ast_type_methods[] = { {"__reduce__", ast_type_reduce, METH_NOARGS, NULL}, + {"__replace__", _PyCFunction_CAST(ast_type_replace), METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("__replace__($self, /, **fields)\\n--\\n\\n" + "Return a copy of the AST node with new values " + "for the specified fields.")}, {NULL} }; @@ -1773,7 +2050,9 @@ def generate_module_def(mod, metadata, f, internal_h): #include "pycore_ceval.h" // _Py_EnterRecursiveCall #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast + #include "pycore_modsupport.h" // _PyArg_NoPositional() #include "pycore_pystate.h" // _PyInterpreterState_GET() + #include "pycore_setobject.h" // _PySet_NextEntry(), _PySet_Update() #include "pycore_unionobject.h" // _Py_union_type_or #include "structmember.h" #include diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 01ffea1869350b..cca2ee409e7978 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -6,7 +6,9 @@ #include "pycore_ceval.h" // _Py_EnterRecursiveCall #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast +#include "pycore_modsupport.h" // _PyArg_NoPositional() #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_setobject.h" // _PySet_NextEntry(), _PySet_Update() #include "pycore_unionobject.h" // _Py_union_type_or #include "structmember.h" #include @@ -5331,6 +5333,279 @@ ast_type_reduce(PyObject *self, PyObject *unused) return result; } +/* + * Perform the following validations: + * + * - All keyword arguments are known 'fields' or 'attributes'. + * - No field or attribute would be left unfilled after copy.replace(). + * + * On success, this returns 1. Otherwise, set a TypeError + * exception and returns -1 (no exception is set if some + * other internal errors occur). + * + * Parameters + * + * self The AST node instance. + * dict The AST node instance dictionary (self.__dict__). + * fields The list of fields (self._fields). + * attributes The list of attributes (self._attributes). + * kwargs Keyword arguments passed to ast_type_replace(). + * + * The 'dict', 'fields', 'attributes' and 'kwargs' arguments can be NULL. + * + * Note: this function can be removed in 3.15 since the verification + * will be done inside the constructor. + */ +static inline int +ast_type_replace_check(PyObject *self, + PyObject *dict, + PyObject *fields, + PyObject *attributes, + PyObject *kwargs) +{ + // While it is possible to make some fast paths that would avoid + // allocating objects on the stack, this would cost us readability. + // For instance, if 'fields' and 'attributes' are both empty, and + // 'kwargs' is not empty, we could raise a TypeError immediately. + PyObject *expecting = PySet_New(fields); + if (expecting == NULL) { + return -1; + } + if (attributes) { + if (_PySet_Update(expecting, attributes) < 0) { + Py_DECREF(expecting); + return -1; + } + } + // Any keyword argument that is neither a field nor attribute is rejected. + // We first need to check whether a keyword argument is accepted or not. + // If all keyword arguments are accepted, we compute the required fields + // and attributes. A field or attribute is not needed if: + // + // 1) it is given in 'kwargs', or + // 2) it already exists on 'self'. + if (kwargs) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(kwargs, &pos, &key, &value)) { + int rc = PySet_Discard(expecting, key); + if (rc < 0) { + Py_DECREF(expecting); + return -1; + } + if (rc == 0) { + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ got an unexpected keyword " + "argument '%U'.", Py_TYPE(self)->tp_name, key); + Py_DECREF(expecting); + return -1; + } + } + } + // check that the remaining fields or attributes would be filled + if (dict) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(dict, &pos, &key, &value)) { + // Mark fields or attributes that are found on the instance + // as non-mandatory. If they are not given in 'kwargs', they + // will be shallow-coied; otherwise, they would be replaced + // (not in this function). + if (PySet_Discard(expecting, key) < 0) { + Py_DECREF(expecting); + return -1; + } + } + if (attributes) { + // Some attributes may or may not be present at runtime. + // In particular, now that we checked whether 'kwargs' + // is correct or not, we allow any attribute to be missing. + // + // Note that fields must still be entirely determined when + // calling the constructor later. + PyObject *unused = PyObject_CallMethodOneArg(expecting, + &_Py_ID(difference_update), + attributes); + if (unused == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_DECREF(unused); + } + } + // Now 'expecting' contains the fields or attributes + // that would not be filled inside ast_type_replace(). + Py_ssize_t m = PySet_GET_SIZE(expecting); + if (m > 0) { + PyObject *names = PyList_New(m); + if (names == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_ssize_t i = 0, pos = 0; + PyObject *item; + Py_hash_t hash; + while (_PySet_NextEntry(expecting, &pos, &item, &hash)) { + PyObject *name = PyObject_Repr(item); + if (name == NULL) { + Py_DECREF(expecting); + Py_DECREF(names); + return -1; + } + // steal the reference 'name' + PyList_SET_ITEM(names, i++, name); + } + Py_DECREF(expecting); + if (PyList_Sort(names) < 0) { + Py_DECREF(names); + return -1; + } + PyObject *sep = PyUnicode_FromString(", "); + if (sep == NULL) { + Py_DECREF(names); + return -1; + } + PyObject *str_names = PyUnicode_Join(sep, names); + Py_DECREF(sep); + Py_DECREF(names); + if (str_names == NULL) { + return -1; + } + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ missing %ld keyword argument%s: %U.", + Py_TYPE(self)->tp_name, m, m == 1 ? "" : "s", str_names); + Py_DECREF(str_names); + return -1; + } + else { + Py_DECREF(expecting); + return 1; + } +} + +/* + * Python equivalent: + * + * for key in keys: + * if hasattr(self, key): + * payload[key] = getattr(self, key) + * + * The 'keys' argument is a sequence corresponding to + * the '_fields' or the '_attributes' of an AST node. + * + * This returns -1 if an error occurs and 0 otherwise. + * + * Parameters + * + * payload A dictionary to fill. + * keys A sequence of keys or NULL for an empty sequence. + * dict The AST node instance dictionary (must not be NULL). + */ +static inline int +ast_type_replace_update_payload(PyObject *payload, + PyObject *keys, + PyObject *dict) +{ + assert(dict != NULL); + if (keys == NULL) { + return 0; + } + Py_ssize_t n = PySequence_Size(keys); + if (n == -1) { + return -1; + } + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *key = PySequence_GetItem(keys, i); + if (key == NULL) { + return -1; + } + PyObject *value; + if (PyDict_GetItemRef(dict, key, &value) < 0) { + Py_DECREF(key); + return -1; + } + if (value == NULL) { + Py_DECREF(key); + // If a field or attribute is not present at runtime, it should + // be explicitly given in 'kwargs'. If not, the constructor will + // issue a warning (which becomes an error in 3.15). + continue; + } + int rc = PyDict_SetItem(payload, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (rc < 0) { + return -1; + } + } + return 0; +} + +/* copy.replace() support (shallow copy) */ +static PyObject * +ast_type_replace(PyObject *self, PyObject *args, PyObject *kwargs) +{ + if (!_PyArg_NoPositional("__replace__", args)) { + return NULL; + } + + struct ast_state *state = get_ast_state(); + if (state == NULL) { + return NULL; + } + + PyObject *result = NULL; + // known AST class fields and attributes + PyObject *fields = NULL, *attributes = NULL; + // current instance dictionary + PyObject *dict = NULL; + // constructor positional and keyword arguments + PyObject *empty_tuple = NULL, *payload = NULL; + + PyObject *type = (PyObject *)Py_TYPE(self); + if (PyObject_GetOptionalAttr(type, state->_fields, &fields) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(type, state->_attributes, &attributes) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { + goto cleanup; + } + if (ast_type_replace_check(self, dict, fields, attributes, kwargs) < 0) { + goto cleanup; + } + empty_tuple = PyTuple_New(0); + if (empty_tuple == NULL) { + goto cleanup; + } + payload = PyDict_New(); + if (payload == NULL) { + goto cleanup; + } + if (dict) { // in case __dict__ is missing (for some obscure reason) + // copy the instance's fields (possibly NULL) + if (ast_type_replace_update_payload(payload, fields, dict) < 0) { + goto cleanup; + } + // copy the instance's attributes (possibly NULL) + if (ast_type_replace_update_payload(payload, attributes, dict) < 0) { + goto cleanup; + } + } + if (kwargs && PyDict_Update(payload, kwargs) < 0) { + goto cleanup; + } + result = PyObject_Call(type, empty_tuple, payload); +cleanup: + Py_XDECREF(payload); + Py_XDECREF(empty_tuple); + Py_XDECREF(dict); + Py_XDECREF(attributes); + Py_XDECREF(fields); + return result; +} + static PyMemberDef ast_type_members[] = { {"__dictoffset__", Py_T_PYSSIZET, offsetof(AST_object, dict), Py_READONLY}, {NULL} /* Sentinel */ @@ -5338,6 +5613,10 @@ static PyMemberDef ast_type_members[] = { static PyMethodDef ast_type_methods[] = { {"__reduce__", ast_type_reduce, METH_NOARGS, NULL}, + {"__replace__", _PyCFunction_CAST(ast_type_replace), METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("__replace__($self, /, **fields)\n--\n\n" + "Return a copy of the AST node with new values " + "for the specified fields.")}, {NULL} }; From 2f5f19e783385ec5312f7054827ccf1cdb6e14ef Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 4 Jul 2024 00:17:00 -0700 Subject: [PATCH 31/97] gh-120754: Reduce system calls in full-file FileIO.readall() case (#120755) This reduces the system call count of a simple program[0] that reads all the `.rst` files in Doc by over 10% (5706 -> 4734 system calls on my linux system, 5813 -> 4875 on my macOS) This reduces the number of `fstat()` calls always and seek calls most the time. Stat was always called twice, once at open (to error early on directories), and a second time to get the size of the file to be able to read the whole file in one read. Now the size is cached with the first call. The code keeps an optimization that if the user had previously read a lot of data, the current position is subtracted from the number of bytes to read. That is somewhat expensive so only do it on larger files, otherwise just try and read the extra bytes and resize the PyBytes as needeed. I built a little test program to validate the behavior + assumptions around relative costs and then ran it under `strace` to get a log of the system calls. Full samples below[1]. After the changes, this is everything in one `filename.read_text()`: ```python3 openat(AT_FDCWD, "cpython/Doc/howto/clinic.rst", O_RDONLY|O_CLOEXEC) = 3` fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0` ioctl(3, TCGETS, 0x7ffdfac04b40) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 read(3, ":orphan:\n\n.. This page is retain"..., 344) = 343 read(3, "", 1) = 0 close(3) = 0 ``` This does make some tradeoffs 1. If the file size changes between open() and readall(), this will still get all the data but might have more read calls. 2. I experimented with avoiding the stat + cached result for small files in general, but on my dev workstation at least that tended to reduce performance compared to using the fstat(). [0] ```python3 from pathlib import Path nlines = [] for filename in Path("cpython/Doc").glob("**/*.rst"): nlines.append(len(filename.read_text())) ``` [1] Before small file: ``` openat(AT_FDCWD, "cpython/Doc/howto/clinic.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0 ioctl(3, TCGETS, 0x7ffe52525930) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 lseek(3, 0, SEEK_CUR) = 0 fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0 read(3, ":orphan:\n\n.. This page is retain"..., 344) = 343 read(3, "", 1) = 0 close(3) = 0 ``` After small file: ``` openat(AT_FDCWD, "cpython/Doc/howto/clinic.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0 ioctl(3, TCGETS, 0x7ffdfac04b40) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 read(3, ":orphan:\n\n.. This page is retain"..., 344) = 343 read(3, "", 1) = 0 close(3) = 0 ``` Before large file: ``` openat(AT_FDCWD, "cpython/Doc/c-api/typeobj.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=133104, ...}) = 0 ioctl(3, TCGETS, 0x7ffe52525930) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 lseek(3, 0, SEEK_CUR) = 0 fstat(3, {st_mode=S_IFREG|0644, st_size=133104, ...}) = 0 read(3, ".. highlight:: c\n\n.. _type-struc"..., 133105) = 133104 read(3, "", 1) = 0 close(3) = 0 ``` After large file: ``` openat(AT_FDCWD, "cpython/Doc/c-api/typeobj.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=133104, ...}) = 0 ioctl(3, TCGETS, 0x7ffdfac04b40) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 lseek(3, 0, SEEK_CUR) = 0 read(3, ".. highlight:: c\n\n.. _type-struc"..., 133105) = 133104 read(3, "", 1) = 0 close(3) = 0 ``` Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Co-authored-by: Erlend E. Aasland Co-authored-by: Victor Stinner --- Lib/_pyio.py | 22 +++--- ...-06-19-19-54-35.gh-issue-120754.uF29sj.rst | 1 + Modules/_io/fileio.c | 70 ++++++++++++------- 3 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 7d298e1674b49a..75b5ad1b1a47d2 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1577,6 +1577,7 @@ def __init__(self, file, mode='r', closefd=True, opener=None): self._blksize = getattr(fdfstat, 'st_blksize', 0) if self._blksize <= 1: self._blksize = DEFAULT_BUFFER_SIZE + self._estimated_size = fdfstat.st_size if _setmode: # don't translate newlines (\r\n <=> \n) @@ -1654,14 +1655,18 @@ def readall(self): """ self._checkClosed() self._checkReadable() - bufsize = DEFAULT_BUFFER_SIZE - try: - pos = os.lseek(self._fd, 0, SEEK_CUR) - end = os.fstat(self._fd).st_size - if end >= pos: - bufsize = end - pos + 1 - except OSError: - pass + if self._estimated_size <= 0: + bufsize = DEFAULT_BUFFER_SIZE + else: + bufsize = self._estimated_size + 1 + + if self._estimated_size > 65536: + try: + pos = os.lseek(self._fd, 0, SEEK_CUR) + if self._estimated_size >= pos: + bufsize = self._estimated_size - pos + 1 + except OSError: + pass result = bytearray() while True: @@ -1737,6 +1742,7 @@ def truncate(self, size=None): if size is None: size = self.tell() os.ftruncate(self._fd, size) + self._estimated_size = size return size def close(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst new file mode 100644 index 00000000000000..46481d8f31aaba --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst @@ -0,0 +1 @@ +Reduce the number of system calls invoked when reading a whole file (ex. ``open('a.txt').read()``). For a sample program that reads the contents of the 400+ ``.rst`` files in the cpython repository ``Doc`` folder, there is an over 10% reduction in system call count. diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index b5129ffcbffdcf..d5bf328eee9c10 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -54,6 +54,9 @@ # define SMALLCHUNK BUFSIZ #endif +/* Size at which a buffer is considered "large" and behavior should change to + avoid excessive memory allocation */ +#define LARGE_BUFFER_CUTOFF_SIZE 65536 /*[clinic input] module _io @@ -72,6 +75,7 @@ typedef struct { unsigned int closefd : 1; char finalizing; unsigned int blksize; + Py_off_t estimated_size; PyObject *weakreflist; PyObject *dict; } fileio; @@ -196,6 +200,7 @@ fileio_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->appending = 0; self->seekable = -1; self->blksize = 0; + self->estimated_size = -1; self->closefd = 1; self->weakreflist = NULL; } @@ -482,6 +487,9 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode, if (fdfstat.st_blksize > 1) self->blksize = fdfstat.st_blksize; #endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ + if (fdfstat.st_size < PY_SSIZE_T_MAX) { + self->estimated_size = (Py_off_t)fdfstat.st_size; + } } #if defined(MS_WINDOWS) || defined(__CYGWIN__) @@ -684,7 +692,7 @@ new_buffersize(fileio *self, size_t currentsize) giving us amortized linear-time behavior. For bigger sizes, use a less-than-double growth factor to avoid excessive allocation. */ assert(currentsize <= PY_SSIZE_T_MAX); - if (currentsize > 65536) + if (currentsize > LARGE_BUFFER_CUTOFF_SIZE) addend = currentsize >> 3; else addend = 256 + currentsize; @@ -707,43 +715,56 @@ static PyObject * _io_FileIO_readall_impl(fileio *self) /*[clinic end generated code: output=faa0292b213b4022 input=dbdc137f55602834]*/ { - struct _Py_stat_struct status; Py_off_t pos, end; PyObject *result; Py_ssize_t bytes_read = 0; Py_ssize_t n; size_t bufsize; - int fstat_result; - if (self->fd < 0) + if (self->fd < 0) { return err_closed(); + } - Py_BEGIN_ALLOW_THREADS - _Py_BEGIN_SUPPRESS_IPH -#ifdef MS_WINDOWS - pos = _lseeki64(self->fd, 0L, SEEK_CUR); -#else - pos = lseek(self->fd, 0L, SEEK_CUR); -#endif - _Py_END_SUPPRESS_IPH - fstat_result = _Py_fstat_noraise(self->fd, &status); - Py_END_ALLOW_THREADS - - if (fstat_result == 0) - end = status.st_size; - else - end = (Py_off_t)-1; - - if (end > 0 && end >= pos && pos >= 0 && end - pos < PY_SSIZE_T_MAX) { + end = self->estimated_size; + if (end <= 0) { + /* Use a default size and resize as needed. */ + bufsize = SMALLCHUNK; + } + else { /* This is probably a real file, so we try to allocate a buffer one byte larger than the rest of the file. If the calculation is right then we should get EOF without having to enlarge the buffer. */ - bufsize = (size_t)(end - pos + 1); - } else { - bufsize = SMALLCHUNK; + if (end > _PY_READ_MAX - 1) { + bufsize = _PY_READ_MAX; + } + else { + bufsize = (size_t)end + 1; + } + + /* While a lot of code does open().read() to get the whole contents + of a file it is possible a caller seeks/reads a ways into the file + then calls readall() to get the rest, which would result in allocating + more than required. Guard against that for larger files where we expect + the I/O time to dominate anyways while keeping small files fast. */ + if (bufsize > LARGE_BUFFER_CUTOFF_SIZE) { + Py_BEGIN_ALLOW_THREADS + _Py_BEGIN_SUPPRESS_IPH +#ifdef MS_WINDOWS + pos = _lseeki64(self->fd, 0L, SEEK_CUR); +#else + pos = lseek(self->fd, 0L, SEEK_CUR); +#endif + _Py_END_SUPPRESS_IPH + Py_END_ALLOW_THREADS + + if (end >= pos && pos >= 0 && (end - pos) < (_PY_READ_MAX - 1)) { + bufsize = (size_t)(end - pos) + 1; + } + } } + result = PyBytes_FromStringAndSize(NULL, bufsize); if (result == NULL) return NULL; @@ -783,7 +804,6 @@ _io_FileIO_readall_impl(fileio *self) return NULL; } bytes_read += n; - pos += n; } if (PyBytes_GET_SIZE(result) > bytes_read) { From 19d1e43e43df97d14c5ab415520b6ccd941e1c88 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:28:44 +0100 Subject: [PATCH 32/97] gh-121352: use _Py_SourceLocation in symtable (#121353) --- Include/internal/pycore_symtable.h | 7 +- Python/symtable.c | 195 ++++++++++------------------- 2 files changed, 69 insertions(+), 133 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 4cfdf92459c70a..d9ed16a3d2321f 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -127,12 +127,7 @@ typedef struct _symtable_entry { unsigned ste_can_see_class_scope : 1; /* true if this block can see names bound in an enclosing class scope */ int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */ - int ste_lineno; /* first line of block */ - int ste_col_offset; /* offset of first line of block */ - int ste_end_lineno; /* end line of block */ - int ste_end_col_offset; /* end offset of first line of block */ - int ste_opt_lineno; /* lineno of last exec or import * */ - int ste_opt_col_offset; /* offset of last exec or import * */ + _Py_SourceLocation ste_loc; /* source location of block */ struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */ struct symtable *ste_table; } PySTEntryObject; diff --git a/Python/symtable.c b/Python/symtable.c index 65677f86092b0b..6ff07077d4d0ed 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -71,16 +71,15 @@ "duplicate type parameter '%U'" -#define LOCATION(x) \ - (x)->lineno, (x)->col_offset, (x)->end_lineno, (x)->end_col_offset +#define LOCATION(x) SRC_LOCATION_FROM_AST(x) -#define ST_LOCATION(x) \ - (x)->ste_lineno, (x)->ste_col_offset, (x)->ste_end_lineno, (x)->ste_end_col_offset +#define SET_ERROR_LOCATION(FNAME, L) \ + PyErr_RangedSyntaxLocationObject((FNAME), \ + (L).lineno, (L).col_offset + 1, (L).end_lineno, (L).end_col_offset + 1) static PySTEntryObject * ste_new(struct symtable *st, identifier name, _Py_block_ty block, - void *key, int lineno, int col_offset, - int end_lineno, int end_col_offset) + void *key, _Py_SourceLocation loc) { PySTEntryObject *ste = NULL; PyObject *k = NULL; @@ -112,13 +111,8 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block, ste->ste_free = 0; ste->ste_varargs = 0; ste->ste_varkeywords = 0; - ste->ste_opt_lineno = 0; - ste->ste_opt_col_offset = 0; ste->ste_annotations_used = 0; - ste->ste_lineno = lineno; - ste->ste_col_offset = col_offset; - ste->ste_end_lineno = end_lineno; - ste->ste_end_col_offset = end_col_offset; + ste->ste_loc = loc; if (st->st_cur != NULL && (st->st_cur->ste_nested || @@ -158,7 +152,7 @@ static PyObject * ste_repr(PySTEntryObject *ste) { return PyUnicode_FromFormat("", - ste->ste_name, ste->ste_id, ste->ste_lineno); + ste->ste_name, ste->ste_id, ste->ste_loc.lineno); } static void @@ -186,7 +180,7 @@ static PyMemberDef ste_memberlist[] = { {"children", _Py_T_OBJECT, OFF(ste_children), Py_READONLY}, {"nested", Py_T_INT, OFF(ste_nested), Py_READONLY}, {"type", Py_T_INT, OFF(ste_type), Py_READONLY}, - {"lineno", Py_T_INT, OFF(ste_lineno), Py_READONLY}, + {"lineno", Py_T_INT, OFF(ste_loc.lineno), Py_READONLY}, {NULL} }; @@ -233,9 +227,7 @@ PyTypeObject PySTEntry_Type = { static int symtable_analyze(struct symtable *st); static int symtable_enter_block(struct symtable *st, identifier name, - _Py_block_ty block, void *ast, - int lineno, int col_offset, - int end_lineno, int end_col_offset); + _Py_block_ty block, void *ast, _Py_SourceLocation loc); static int symtable_exit_block(struct symtable *st); static int symtable_visit_stmt(struct symtable *st, stmt_ty s); static int symtable_visit_expr(struct symtable *st, expr_ty s); @@ -311,8 +303,8 @@ static void _dump_symtable(PySTEntryObject* ste, PyObject* prefix) ste->ste_comp_iter_target ? " comp_iter_target" : "", ste->ste_can_see_class_scope ? " can_see_class_scope" : "", prefix, - ste->ste_lineno, - ste->ste_col_offset, + ste->ste_loc.lineno, + ste->ste_loc.col_offset, prefix ); assert(msg != NULL); @@ -424,7 +416,9 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, _PyFutureFeatures *future) st->recursion_limit = Py_C_RECURSION_LIMIT; /* Make the initial symbol information gathering pass */ - if (!symtable_enter_block(st, &_Py_ID(top), ModuleBlock, (void *)mod, 0, 0, 0, 0)) { + + _Py_SourceLocation loc0 = {0, 0, 0, 0}; + if (!symtable_enter_block(st, &_Py_ID(top), ModuleBlock, (void *)mod, loc0)) { _PySymtable_Free(st); return NULL; } @@ -1379,11 +1373,9 @@ symtable_enter_existing_block(struct symtable *st, PySTEntryObject* ste) static int symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, - void *ast, int lineno, int col_offset, - int end_lineno, int end_col_offset) + void *ast, _Py_SourceLocation loc) { - PySTEntryObject *ste = ste_new(st, name, block, ast, - lineno, col_offset, end_lineno, end_col_offset); + PySTEntryObject *ste = ste_new(st, name, block, ast, loc); if (ste == NULL) return 0; int result = symtable_enter_existing_block(st, ste); @@ -1410,7 +1402,7 @@ symtable_lookup(struct symtable *st, PyObject *name) static int symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _symtable_entry *ste, - int lineno, int col_offset, int end_lineno, int end_col_offset) + _Py_SourceLocation loc) { PyObject *o; PyObject *dict; @@ -1425,16 +1417,12 @@ 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_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, loc); goto error; } if ((flag & DEF_TYPE_PARAM) && (val & DEF_TYPE_PARAM)) { PyErr_Format(PyExc_SyntaxError, DUPLICATE_TYPE_PARAM, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, loc); goto error; } val |= flag; @@ -1454,9 +1442,7 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s if (val & (DEF_GLOBAL | DEF_NONLOCAL)) { PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_INNER_LOOP_CONFLICT, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, loc); goto error; } val |= DEF_COMP_ITER; @@ -1501,33 +1487,28 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s } static int -symtable_add_def(struct symtable *st, PyObject *name, int flag, - int lineno, int col_offset, int end_lineno, int end_col_offset) +symtable_add_def(struct symtable *st, PyObject *name, int flag, _Py_SourceLocation loc) { if ((flag & DEF_TYPE_PARAM) && st->st_cur->ste_mangled_names != NULL) { if(PySet_Add(st->st_cur->ste_mangled_names, name) < 0) { return 0; } } - return symtable_add_def_helper(st, name, flag, st->st_cur, - lineno, col_offset, end_lineno, end_col_offset); + return symtable_add_def_helper(st, name, flag, st->st_cur, loc); } static int symtable_enter_type_param_block(struct symtable *st, identifier name, void *ast, int has_defaults, int has_kwdefaults, - enum _stmt_kind kind, - int lineno, int col_offset, - int end_lineno, int end_col_offset) + enum _stmt_kind kind, _Py_SourceLocation loc) { _Py_block_ty current_type = st->st_cur->ste_type; - if(!symtable_enter_block(st, name, TypeParametersBlock, ast, lineno, - col_offset, end_lineno, end_col_offset)) { + if(!symtable_enter_block(st, name, TypeParametersBlock, ast, loc)) { return 0; } if (current_type == ClassBlock) { st->st_cur->ste_can_see_class_scope = 1; - if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, loc)) { return 0; } } @@ -1535,36 +1516,30 @@ symtable_enter_type_param_block(struct symtable *st, identifier name, _Py_DECLARE_STR(type_params, ".type_params"); // It gets "set" when we create the type params tuple and // "used" when we build up the bases. - if (!symtable_add_def(st, &_Py_STR(type_params), DEF_LOCAL, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(type_params), DEF_LOCAL, loc)) { return 0; } - if (!symtable_add_def(st, &_Py_STR(type_params), USE, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(type_params), USE, loc)) { return 0; } // This is used for setting the generic base _Py_DECLARE_STR(generic_base, ".generic_base"); - if (!symtable_add_def(st, &_Py_STR(generic_base), DEF_LOCAL, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(generic_base), DEF_LOCAL, loc)) { return 0; } - if (!symtable_add_def(st, &_Py_STR(generic_base), USE, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(generic_base), USE, loc)) { return 0; } } if (has_defaults) { _Py_DECLARE_STR(defaults, ".defaults"); - if (!symtable_add_def(st, &_Py_STR(defaults), DEF_PARAM, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(defaults), DEF_PARAM, loc)) { return 0; } } if (has_kwdefaults) { _Py_DECLARE_STR(kwdefaults, ".kwdefaults"); - if (!symtable_add_def(st, &_Py_STR(kwdefaults), DEF_PARAM, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(kwdefaults), DEF_PARAM, loc)) { return 0; } } @@ -1627,8 +1602,7 @@ symtable_enter_type_param_block(struct symtable *st, identifier name, } while(0) static int -symtable_record_directive(struct symtable *st, identifier name, int lineno, - int col_offset, int end_lineno, int end_col_offset) +symtable_record_directive(struct symtable *st, identifier name, _Py_SourceLocation loc) { PyObject *data, *mangled; int res; @@ -1640,7 +1614,8 @@ symtable_record_directive(struct symtable *st, identifier name, int lineno, mangled = _Py_MaybeMangle(st->st_private, st->st_cur, name); if (!mangled) return 0; - data = Py_BuildValue("(Niiii)", mangled, lineno, col_offset, end_lineno, end_col_offset); + data = Py_BuildValue("(Niiii)", mangled, loc.lineno, loc.col_offset, + loc.end_lineno, loc.end_col_offset); if (!data) return 0; res = PyList_Append(st->st_cur->ste_directives, data); @@ -1673,9 +1648,7 @@ check_import_from(struct symtable *st, stmt_ty s) PyErr_SetString(PyExc_SyntaxError, "from __future__ imports must occur " "at the beginning of the file"); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, s->col_offset + 1, - s->end_lineno, s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); return 0; } return 1; @@ -1772,9 +1745,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) VISIT_SEQ(st, expr, s->v.ClassDef.bases); VISIT_SEQ(st, keyword, s->v.ClassDef.keywords); if (!symtable_enter_block(st, s->v.ClassDef.name, ClassBlock, - (void *)s, s->lineno, s->col_offset, - s->end_lineno, s->end_col_offset)) + (void *)s, LOCATION(s))) { VISIT_QUIT(st, 0); + } st->st_private = s->v.ClassDef.name; if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) { if (!symtable_add_def(st, &_Py_ID(__type_params__), @@ -1814,8 +1787,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) VISIT_SEQ(st, type_param, s->v.TypeAlias.type_params); } if (!symtable_enter_block(st, name, TypeAliasBlock, - (void *)s, LOCATION(s))) + (void *)s, LOCATION(s))) { VISIT_QUIT(st, 0); + } st->st_cur->ste_can_see_class_scope = is_in_class; if (is_in_class && !symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(s->v.TypeAlias.value))) { VISIT_QUIT(st, 0); @@ -1856,11 +1830,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) PyErr_Format(PyExc_SyntaxError, cur & DEF_GLOBAL ? GLOBAL_ANNOT : NONLOCAL_ANNOT, e_name->v.Name.id); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, - s->col_offset + 1, - s->end_lineno, - s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); VISIT_QUIT(st, 0); } if (s->v.AnnAssign.simple && @@ -1970,18 +1940,15 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) } PyErr_Format(PyExc_SyntaxError, msg, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, - s->col_offset + 1, - s->end_lineno, - s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); VISIT_QUIT(st, 0); } - if (!symtable_add_def(st, name, DEF_GLOBAL, LOCATION(s))) + if (!symtable_add_def(st, name, DEF_GLOBAL, LOCATION(s))) { VISIT_QUIT(st, 0); - if (!symtable_record_directive(st, name, s->lineno, s->col_offset, - s->end_lineno, s->end_col_offset)) + } + if (!symtable_record_directive(st, name, LOCATION(s))) { VISIT_QUIT(st, 0); + } } break; } @@ -2005,18 +1972,14 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) msg = NONLOCAL_AFTER_ASSIGN; } PyErr_Format(PyExc_SyntaxError, msg, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, - s->col_offset + 1, - s->end_lineno, - s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); VISIT_QUIT(st, 0); } if (!symtable_add_def(st, name, DEF_NONLOCAL, LOCATION(s))) VISIT_QUIT(st, 0); - if (!symtable_record_directive(st, name, s->lineno, s->col_offset, - s->end_lineno, s->end_col_offset)) + if (!symtable_record_directive(st, name, LOCATION(s))) { VISIT_QUIT(st, 0); + } } break; } @@ -2124,11 +2087,7 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) if ((target_in_scope & DEF_COMP_ITER) && (target_in_scope & DEF_LOCAL)) { PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_CONFLICT, target_name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); VISIT_QUIT(st, 0); } continue; @@ -2141,20 +2100,24 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) if (!symtable_add_def(st, target_name, DEF_GLOBAL, LOCATION(e))) VISIT_QUIT(st, 0); } else { - if (!symtable_add_def(st, target_name, DEF_NONLOCAL, LOCATION(e))) + if (!symtable_add_def(st, target_name, DEF_NONLOCAL, LOCATION(e))) { VISIT_QUIT(st, 0); + } } - if (!symtable_record_directive(st, target_name, LOCATION(e))) + if (!symtable_record_directive(st, target_name, LOCATION(e))) { VISIT_QUIT(st, 0); + } return symtable_add_def_helper(st, target_name, DEF_LOCAL, ste, LOCATION(e)); } /* If we find a ModuleBlock entry, add as GLOBAL */ if (ste->ste_type == ModuleBlock) { - if (!symtable_add_def(st, target_name, DEF_GLOBAL, LOCATION(e))) + if (!symtable_add_def(st, target_name, DEF_GLOBAL, LOCATION(e))) { VISIT_QUIT(st, 0); - if (!symtable_record_directive(st, target_name, LOCATION(e))) + } + if (!symtable_record_directive(st, target_name, LOCATION(e))) { VISIT_QUIT(st, 0); + } return symtable_add_def_helper(st, target_name, DEF_GLOBAL, ste, LOCATION(e)); } @@ -2179,11 +2142,7 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) default: Py_UNREACHABLE(); } - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); VISIT_QUIT(st, 0); } } @@ -2201,11 +2160,7 @@ symtable_handle_namedexpr(struct symtable *st, expr_ty e) if (st->st_cur->ste_comp_iter_expr > 0) { /* Assignment isn't allowed in a comprehension iterable expression */ PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_ITER_EXPR); - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); return 0; } if (st->st_cur->ste_comprehension) { @@ -2250,10 +2205,9 @@ symtable_visit_expr(struct symtable *st, expr_ty e) if (e->v.Lambda.args->kw_defaults) VISIT_SEQ_WITH_NULL(st, expr, e->v.Lambda.args->kw_defaults); if (!symtable_enter_block(st, &_Py_ID(lambda), - FunctionBlock, (void *)e, - e->lineno, e->col_offset, - e->end_lineno, e->end_col_offset)) + FunctionBlock, (void *)e, LOCATION(e))) { VISIT_QUIT(st, 0); + } VISIT(st, arguments, e->v.Lambda.args); VISIT(st, expr, e->v.Lambda.body); if (!symtable_exit_block(st)) @@ -2385,8 +2339,9 @@ symtable_visit_type_param_bound_or_default( { if (e) { int is_in_class = st->st_cur->ste_can_see_class_scope; - if (!symtable_enter_block(st, name, TypeVariableBlock, key, LOCATION(e))) + if (!symtable_enter_block(st, name, TypeVariableBlock, key, LOCATION(e))) { return 0; + } st->st_cur->ste_can_see_class_scope = is_in_class; if (is_in_class && !symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(e))) { @@ -2519,7 +2474,7 @@ symtable_implicit_arg(struct symtable *st, int pos) PyObject *id = PyUnicode_FromFormat(".%d", pos); if (id == NULL) return 0; - if (!symtable_add_def(st, id, DEF_PARAM, ST_LOCATION(st->st_cur))) { + if (!symtable_add_def(st, id, DEF_PARAM, st->st_cur->ste_loc)) { Py_DECREF(id); return 0; } @@ -2740,14 +2695,8 @@ symtable_visit_alias(struct symtable *st, alias_ty a) } else { if (st->st_cur->ste_type != ModuleBlock) { - int lineno = a->lineno; - int col_offset = a->col_offset; - int end_lineno = a->end_lineno; - int end_col_offset = a->end_col_offset; PyErr_SetString(PyExc_SyntaxError, IMPORT_STAR_WARNING); - PyErr_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(a)); Py_DECREF(store_name); return 0; } @@ -2796,9 +2745,7 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e, st->st_cur->ste_comp_iter_expr--; /* Create comprehension scope for the rest */ if (!scope_name || - !symtable_enter_block(st, scope_name, FunctionBlock, (void *)e, - e->lineno, e->col_offset, - e->end_lineno, e->end_col_offset)) { + !symtable_enter_block(st, scope_name, FunctionBlock, (void *)e, LOCATION(e))) { return 0; } switch(e->kind) { @@ -2902,11 +2849,7 @@ symtable_raise_if_annotation_block(struct symtable *st, const char *name, expr_t else return 1; - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); return 0; } @@ -2918,9 +2861,7 @@ symtable_raise_if_comprehension_block(struct symtable *st, expr_ty e) { (type == SetComprehension) ? "'yield' inside set comprehension" : (type == DictComprehension) ? "'yield' inside dict comprehension" : "'yield' inside generator expression"); - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, e->col_offset + 1, - e->end_lineno, e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); VISIT_QUIT(st, 0); } From 06a1c3fb24c4be9ce3b432022ebaf3f913f86ba7 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 4 Jul 2024 05:59:18 -0700 Subject: [PATCH 33/97] gh-120754: Update estimated_size in C truncate (#121357) Sometimes a large file is truncated (test_largefile). While estimated_size is used as a estimate (the read will stil get the number of bytes in the file), that it is much larger than the actual size of data can result in a significant over allocation and sometimes lead to a MemoryError / running out of memory. This brings the C implementation to match the Python _pyio implementation. --- Modules/_io/fileio.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index d5bf328eee9c10..5d9d87d6118a75 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -1094,6 +1094,12 @@ _io_FileIO_truncate_impl(fileio *self, PyTypeObject *cls, PyObject *posobj) return NULL; } + /* Sometimes a large file is truncated. While estimated_size is used as a + estimate, that it is much larger than the actual size can result in a + significant over allocation and sometimes a MemoryError / running out of + memory. */ + self->estimated_size = pos; + return posobj; } #endif /* HAVE_FTRUNCATE */ From 715ec630dd78819ed79cad5ac28617daefe1e745 Mon Sep 17 00:00:00 2001 From: Jongbum Won <71166964+Wondaeng@users.noreply.github.com> Date: Thu, 4 Jul 2024 22:34:34 +0900 Subject: [PATCH 34/97] gh-121355: Fix incorrect word in simple_stmts.rst (#121356) --- Doc/reference/simple_stmts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 4f6c0c63ae42be..618664b23f0680 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -293,7 +293,7 @@ statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target. The target is only evaluated once. -An augmented assignment expression like ``x += 1`` can be rewritten as ``x = x + +An augmented assignment statement like ``x += 1`` can be rewritten as ``x = x + 1`` to achieve a similar, but not exactly equal effect. In the augmented version, ``x`` is only evaluated once. Also, when possible, the actual operation is performed *in-place*, meaning that rather than creating a new object and From 67a05de17ca811459e0e856d8e51d0eaf0f76232 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 4 Jul 2024 14:47:21 +0100 Subject: [PATCH 35/97] gh-121272: move async for/with validation from compiler to symtable (#121361) --- Python/compile.c | 13 ------------- Python/symtable.c | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 30708e1dda9d43..1d6b54d411daf1 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -3058,11 +3058,6 @@ static int compiler_async_for(struct compiler *c, stmt_ty s) { location loc = LOC(s); - if (IS_TOP_LEVEL_AWAIT(c)){ - assert(c->u->u_ste->ste_coroutine == 1); - } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) { - return compiler_error(c, loc, "'async for' outside async function"); - } NEW_JUMP_TARGET_LABEL(c, start); NEW_JUMP_TARGET_LABEL(c, except); @@ -5781,9 +5776,6 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, co = optimize_and_assemble(c, 1); compiler_exit_scope(c); - if (is_top_level_await && is_async_generator){ - assert(c->u->u_ste->ste_coroutine == 1); - } if (co == NULL) { goto error; } @@ -5925,11 +5917,6 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos) withitem_ty item = asdl_seq_GET(s->v.AsyncWith.items, pos); assert(s->kind == AsyncWith_kind); - if (IS_TOP_LEVEL_AWAIT(c)){ - assert(c->u->u_ste->ste_coroutine == 1); - } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION){ - return compiler_error(c, loc, "'async with' outside async function"); - } NEW_JUMP_TARGET_LABEL(c, block); NEW_JUMP_TARGET_LABEL(c, final); diff --git a/Python/symtable.c b/Python/symtable.c index 6ff07077d4d0ed..10103dbc2582a2 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -70,6 +70,11 @@ #define DUPLICATE_TYPE_PARAM \ "duplicate type parameter '%U'" +#define ASYNC_WITH_OUTISDE_ASYNC_FUNC \ +"'async with' outside async function" + +#define ASYNC_FOR_OUTISDE_ASYNC_FUNC \ +"'async for' outside async function" #define LOCATION(x) SRC_LOCATION_FROM_AST(x) @@ -251,6 +256,7 @@ static int symtable_visit_withitem(struct symtable *st, withitem_ty item); static int symtable_visit_match_case(struct symtable *st, match_case_ty m); static int symtable_visit_pattern(struct symtable *st, pattern_ty s); static int symtable_raise_if_annotation_block(struct symtable *st, const char *, expr_ty); +static int symtable_raise_if_not_coroutine(struct symtable *st, const char *msg, _Py_SourceLocation loc); static int symtable_raise_if_comprehension_block(struct symtable *st, expr_ty); /* For debugging purposes only */ @@ -2048,11 +2054,17 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) } case AsyncWith_kind: maybe_set_ste_coroutine_for_module(st, s); + if (!symtable_raise_if_not_coroutine(st, ASYNC_WITH_OUTISDE_ASYNC_FUNC, LOCATION(s))) { + VISIT_QUIT(st, 0); + } VISIT_SEQ(st, withitem, s->v.AsyncWith.items); VISIT_SEQ(st, stmt, s->v.AsyncWith.body); break; case AsyncFor_kind: maybe_set_ste_coroutine_for_module(st, s); + if (!symtable_raise_if_not_coroutine(st, ASYNC_FOR_OUTISDE_ASYNC_FUNC, LOCATION(s))) { + VISIT_QUIT(st, 0); + } VISIT(st, expr, s->v.AsyncFor.target); VISIT(st, expr, s->v.AsyncFor.iter); VISIT_SEQ(st, stmt, s->v.AsyncFor.body); @@ -2865,6 +2877,16 @@ symtable_raise_if_comprehension_block(struct symtable *st, expr_ty e) { VISIT_QUIT(st, 0); } +static int +symtable_raise_if_not_coroutine(struct symtable *st, const char *msg, _Py_SourceLocation loc) { + if (!st->st_cur->ste_coroutine) { + PyErr_SetString(PyExc_SyntaxError, msg); + SET_ERROR_LOCATION(st->st_filename, loc); + return 0; + } + return 1; +} + struct symtable * _Py_SymtableStringObjectFlags(const char *str, PyObject *filename, int start, PyCompilerFlags *flags) From db1729143de0775c99e0453cb0fc01a799f289a0 Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Thu, 4 Jul 2024 16:56:06 +0200 Subject: [PATCH 36/97] gh-118507: Amend news entry to mention ntpath.isfile bugfix (GH-120817) --- .../next/Library/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/NEWS.d/next/Library/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst b/Misc/NEWS.d/next/Library/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst index de1462f0d24fce..67b1fea4f83cb4 100644 --- a/Misc/NEWS.d/next/Library/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst +++ b/Misc/NEWS.d/next/Library/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst @@ -1 +1,2 @@ +Fix :func:`os.path.isfile` on Windows for pipes. Speedup :func:`os.path.isjunction` and :func:`os.path.lexists` on Windows with a native implementation. From 17d5b9df10f53ae3c09c8b22f27d25d9e83b4b7e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 4 Jul 2024 18:04:24 +0300 Subject: [PATCH 37/97] gh-59110: zipimport: support namespace packages when no directory entry exists (GH-121233) --- .../test_importlib/test_namespace_pkgs.py | 27 ++-- Lib/test/test_zipimport.py | 125 +++++++++++++++--- Lib/zipimport.py | 16 +++ ...4-07-04-17-36-03.gh-issue-59110.IlI9Fz.rst | 2 + 4 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-04-17-36-03.gh-issue-59110.IlI9Fz.rst diff --git a/Lib/test/test_importlib/test_namespace_pkgs.py b/Lib/test/test_importlib/test_namespace_pkgs.py index 072e198795d394..cbbdada3b010a7 100644 --- a/Lib/test/test_importlib/test_namespace_pkgs.py +++ b/Lib/test/test_importlib/test_namespace_pkgs.py @@ -286,25 +286,24 @@ def test_project3_succeeds(self): class ZipWithMissingDirectory(NamespacePackageTest): paths = ['missing_directory.zip'] + # missing_directory.zip contains: + # Length Date Time Name + # --------- ---------- ----- ---- + # 29 2012-05-03 18:13 foo/one.py + # 0 2012-05-03 20:57 bar/ + # 38 2012-05-03 20:57 bar/two.py + # --------- ------- + # 67 3 files - @unittest.expectedFailure def test_missing_directory(self): - # This will fail because missing_directory.zip contains: - # Length Date Time Name - # --------- ---------- ----- ---- - # 29 2012-05-03 18:13 foo/one.py - # 0 2012-05-03 20:57 bar/ - # 38 2012-05-03 20:57 bar/two.py - # --------- ------- - # 67 3 files - - # Because there is no 'foo/', the zipimporter currently doesn't - # know that foo is a namespace package - import foo.one + self.assertEqual(foo.one.attr, 'portion1 foo one') + + def test_missing_directory2(self): + import foo + self.assertFalse(hasattr(foo, 'one')) def test_present_directory(self): - # This succeeds because there is a "bar/" in the zip file import bar.two self.assertEqual(bar.two.attr, 'missing_directory foo two') diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py index 0bae54d26c64f1..d70fa3e863b98c 100644 --- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -296,6 +296,81 @@ def testSubNamespacePackage(self): packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD) + def testPackageExplicitDirectories(self): + # Test explicit namespace packages with explicit directory entries. + self.addCleanup(os_helper.unlink, TEMP_ZIP) + with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z: + z.mkdir('a') + z.writestr('a/__init__.py', test_src) + z.mkdir('a/b') + z.writestr('a/b/__init__.py', test_src) + z.mkdir('a/b/c') + z.writestr('a/b/c/__init__.py', test_src) + z.writestr('a/b/c/d.py', test_src) + self._testPackage(initfile='__init__.py') + + def testPackageImplicitDirectories(self): + # Test explicit namespace packages without explicit directory entries. + self.addCleanup(os_helper.unlink, TEMP_ZIP) + with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z: + z.writestr('a/__init__.py', test_src) + z.writestr('a/b/__init__.py', test_src) + z.writestr('a/b/c/__init__.py', test_src) + z.writestr('a/b/c/d.py', test_src) + self._testPackage(initfile='__init__.py') + + def testNamespacePackageExplicitDirectories(self): + # Test implicit namespace packages with explicit directory entries. + self.addCleanup(os_helper.unlink, TEMP_ZIP) + with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z: + z.mkdir('a') + z.mkdir('a/b') + z.mkdir('a/b/c') + z.writestr('a/b/c/d.py', test_src) + self._testPackage(initfile=None) + + def testNamespacePackageImplicitDirectories(self): + # Test implicit namespace packages without explicit directory entries. + self.addCleanup(os_helper.unlink, TEMP_ZIP) + with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z: + z.writestr('a/b/c/d.py', test_src) + self._testPackage(initfile=None) + + def _testPackage(self, initfile): + zi = zipimport.zipimporter(os.path.join(TEMP_ZIP, 'a')) + if initfile is None: + # XXX Should it work? + self.assertRaises(zipimport.ZipImportError, zi.is_package, 'b') + self.assertRaises(zipimport.ZipImportError, zi.get_source, 'b') + self.assertRaises(zipimport.ZipImportError, zi.get_code, 'b') + else: + self.assertTrue(zi.is_package('b')) + self.assertEqual(zi.get_source('b'), test_src) + self.assertEqual(zi.get_code('b').co_filename, + os.path.join(TEMP_ZIP, 'a', 'b', initfile)) + + sys.path.insert(0, TEMP_ZIP) + self.assertNotIn('a', sys.modules) + + mod = importlib.import_module(f'a.b') + self.assertIn('a', sys.modules) + self.assertIs(sys.modules['a.b'], mod) + if initfile is None: + self.assertIsNone(mod.__file__) + else: + self.assertEqual(mod.__file__, + os.path.join(TEMP_ZIP, 'a', 'b', initfile)) + self.assertEqual(len(mod.__path__), 1, mod.__path__) + self.assertEqual(mod.__path__[0], os.path.join(TEMP_ZIP, 'a', 'b')) + + mod2 = importlib.import_module(f'a.b.c.d') + self.assertIn('a.b.c', sys.modules) + self.assertIn('a.b.c.d', sys.modules) + self.assertIs(sys.modules['a.b.c.d'], mod2) + self.assertIs(mod.c.d, mod2) + self.assertEqual(mod2.__file__, + os.path.join(TEMP_ZIP, 'a', 'b', 'c', 'd.py')) + def testMixedNamespacePackage(self): # Test implicit namespace packages spread between a # real filesystem and a zip archive. @@ -520,6 +595,7 @@ def testInvalidateCaches(self): packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc), "spam" + pyc_ext: (NOW, test_pyc)} + extra_files = [packdir, packdir2] self.addCleanup(os_helper.unlink, TEMP_ZIP) with ZipFile(TEMP_ZIP, "w") as z: for name, (mtime, data) in files.items(): @@ -529,10 +605,10 @@ def testInvalidateCaches(self): z.writestr(zinfo, data) zi = zipimport.zipimporter(TEMP_ZIP) - self.assertEqual(zi._get_files().keys(), files.keys()) + self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) # Check that the file information remains accurate after reloading zi.invalidate_caches() - self.assertEqual(zi._get_files().keys(), files.keys()) + self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) # Add a new file to the ZIP archive newfile = {"spam2" + pyc_ext: (NOW, test_pyc)} files.update(newfile) @@ -544,7 +620,7 @@ def testInvalidateCaches(self): z.writestr(zinfo, data) # Check that we can detect the new file after invalidating the cache zi.invalidate_caches() - self.assertEqual(zi._get_files().keys(), files.keys()) + self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) spec = zi.find_spec('spam2') self.assertIsNotNone(spec) self.assertIsInstance(spec.loader, zipimport.zipimporter) @@ -562,6 +638,7 @@ def testInvalidateCachesWithMultipleZipimports(self): packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc), "spam" + pyc_ext: (NOW, test_pyc)} + extra_files = [packdir, packdir2] self.addCleanup(os_helper.unlink, TEMP_ZIP) with ZipFile(TEMP_ZIP, "w") as z: for name, (mtime, data) in files.items(): @@ -571,10 +648,10 @@ def testInvalidateCachesWithMultipleZipimports(self): z.writestr(zinfo, data) zi = zipimport.zipimporter(TEMP_ZIP) - self.assertEqual(zi._get_files().keys(), files.keys()) + self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) # Zipimporter for the same path. zi2 = zipimport.zipimporter(TEMP_ZIP) - self.assertEqual(zi2._get_files().keys(), files.keys()) + self.assertEqual(sorted(zi2._get_files()), sorted([*files, *extra_files])) # Add a new file to the ZIP archive to make the cache wrong. newfile = {"spam2" + pyc_ext: (NOW, test_pyc)} files.update(newfile) @@ -587,7 +664,7 @@ def testInvalidateCachesWithMultipleZipimports(self): # Invalidate the cache of the first zipimporter. zi.invalidate_caches() # Check that the second zipimporter detects the new file and isn't using a stale cache. - self.assertEqual(zi2._get_files().keys(), files.keys()) + self.assertEqual(sorted(zi2._get_files()), sorted([*files, *extra_files])) spec = zi2.find_spec('spam2') self.assertIsNotNone(spec) self.assertIsInstance(spec.loader, zipimport.zipimporter) @@ -650,17 +727,33 @@ def testZipImporterMethodsInSubDirectory(self): self.assertIsNone(loader.get_source(mod_name)) self.assertEqual(loader.get_filename(mod_name), mod.__file__) - def testGetData(self): + def testGetDataExplicitDirectories(self): self.addCleanup(os_helper.unlink, TEMP_ZIP) - with ZipFile(TEMP_ZIP, "w") as z: - z.compression = self.compression - name = "testdata.dat" - data = bytes(x for x in range(256)) - z.writestr(name, data) - - zi = zipimport.zipimporter(TEMP_ZIP) - self.assertEqual(data, zi.get_data(name)) - self.assertIn('zipimporter object', repr(zi)) + with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z: + z.mkdir('a') + z.mkdir('a/b') + z.mkdir('a/b/c') + data = bytes(range(256)) + z.writestr('a/b/c/testdata.dat', data) + self._testGetData() + + def testGetDataImplicitDirectories(self): + self.addCleanup(os_helper.unlink, TEMP_ZIP) + with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z: + data = bytes(range(256)) + z.writestr('a/b/c/testdata.dat', data) + self._testGetData() + + def _testGetData(self): + zi = zipimport.zipimporter(os.path.join(TEMP_ZIP, 'ignored')) + pathname = os.path.join('a', 'b', 'c', 'testdata.dat') + data = bytes(range(256)) + self.assertEqual(zi.get_data(pathname), data) + self.assertEqual(zi.get_data(os.path.join(TEMP_ZIP, pathname)), data) + self.assertEqual(zi.get_data(os.path.join('a', 'b', '')), b'') + self.assertEqual(zi.get_data(os.path.join(TEMP_ZIP, 'a', 'b', '')), b'') + self.assertRaises(OSError, zi.get_data, os.path.join('a', 'b')) + self.assertRaises(OSError, zi.get_data, os.path.join(TEMP_ZIP, 'a', 'b')) def testImporterAttr(self): src = """if 1: # indent hack diff --git a/Lib/zipimport.py b/Lib/zipimport.py index a49a21f0799df2..a79862f1de7564 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py @@ -155,6 +155,8 @@ def get_data(self, pathname): toc_entry = self._get_files()[key] except KeyError: raise OSError(0, '', key) + if toc_entry is None: + return b'' return _get_data(self.archive, toc_entry) @@ -554,6 +556,20 @@ def _read_directory(archive): finally: fp.seek(start_offset) _bootstrap._verbose_message('zipimport: found {} names in {!r}', count, archive) + + # Add implicit directories. + for name in list(files): + while True: + i = name.rstrip(path_sep).rfind(path_sep) + if i < 0: + break + name = name[:i + 1] + if name in files: + break + files[name] = None + count += 1 + _bootstrap._verbose_message('zipimport: added {} implicit directories in {!r}', + count, archive) return files # During bootstrap, we may need to load the encodings diff --git a/Misc/NEWS.d/next/Library/2024-07-04-17-36-03.gh-issue-59110.IlI9Fz.rst b/Misc/NEWS.d/next/Library/2024-07-04-17-36-03.gh-issue-59110.IlI9Fz.rst new file mode 100644 index 00000000000000..b8e3ee0720cfe6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-04-17-36-03.gh-issue-59110.IlI9Fz.rst @@ -0,0 +1,2 @@ +:mod:`zipimport` supports now namespace packages when no directory entry +exists. From f5c8d67de6c68bea2860d5d96af747c5e95dbf22 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 4 Jul 2024 18:28:23 +0100 Subject: [PATCH 38/97] gh-106597: Remove unnecessary CFrame offsets (#121369) --- Include/internal/pycore_runtime.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index bc67377a89c17f..d4ffd977940a02 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -98,13 +98,6 @@ typedef struct _Py_DebugOffsets { uint64_t owner; } interpreter_frame; - // CFrame offset; - struct _cframe { - uint64_t size; - uint64_t current_frame; - uint64_t previous; - } cframe; - // Code object offset; struct _code_object { uint64_t size; From 5f660e8e2ca3acfb89ccbdd990f072149b6baa6a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 4 Jul 2024 19:38:30 +0200 Subject: [PATCH 39/97] gh-121084: Fix test_typing random leaks (#121360) Clear typing ABC caches when running tests for refleaks (-R option): call _abc_caches_clear() on typing abstract classes and their subclasses. --- Lib/test/libregrtest/utils.py | 6 ++++++ .../Tests/2024-07-04-15-10-29.gh-issue-121084.qxcd5d.rst | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 Misc/NEWS.d/next/Tests/2024-07-04-15-10-29.gh-issue-121084.qxcd5d.rst diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 0197e50125d96e..2a3449016fe951 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -264,6 +264,12 @@ def clear_caches(): for f in typing._cleanups: f() + import inspect + abs_classes = filter(inspect.isabstract, typing.__dict__.values()) + for abc in abs_classes: + for obj in abc.__subclasses__() + [abc]: + obj._abc_caches_clear() + try: fractions = sys.modules['fractions'] except KeyError: diff --git a/Misc/NEWS.d/next/Tests/2024-07-04-15-10-29.gh-issue-121084.qxcd5d.rst b/Misc/NEWS.d/next/Tests/2024-07-04-15-10-29.gh-issue-121084.qxcd5d.rst new file mode 100644 index 00000000000000..b91ea8acfadbf1 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-07-04-15-10-29.gh-issue-121084.qxcd5d.rst @@ -0,0 +1,3 @@ +Fix test_typing random leaks. Clear typing ABC caches when running tests for +refleaks (``-R`` option): call ``_abc_caches_clear()`` on typing abstract +classes and their subclasses. Patch by Victor Stinner. From cb688bab08559079d0ee9ffd841dd6eb11116181 Mon Sep 17 00:00:00 2001 From: Ali Tavallaie Date: Fri, 5 Jul 2024 02:19:14 +0330 Subject: [PATCH 40/97] gh-90437: Fix __main__.py documentation wording (GH-116309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Éric Co-authored-by: Frank Dana --- Doc/library/__main__.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/__main__.rst b/Doc/library/__main__.rst index 6232e173d9537d..647ff9da04d10d 100644 --- a/Doc/library/__main__.rst +++ b/Doc/library/__main__.rst @@ -251,9 +251,9 @@ attribute will include the package's path if imported:: >>> asyncio.__main__.__name__ 'asyncio.__main__' -This won't work for ``__main__.py`` files in the root directory of a .zip file -though. Hence, for consistency, minimal ``__main__.py`` like the :mod:`venv` -one mentioned below are preferred. +This won't work for ``__main__.py`` files in the root directory of a +``.zip`` file though. Hence, for consistency, a minimal ``__main__.py`` +without a ``__name__`` check is preferred. .. seealso:: From db39bc42f90c151b298f97b780e62703adbf1221 Mon Sep 17 00:00:00 2001 From: Josh Brobst Date: Fri, 5 Jul 2024 02:39:48 -0400 Subject: [PATCH 41/97] gh-121390: tracemalloc: Fix tracebacks memory leak (#121391) The tracemalloc_tracebacks hash table has traceback keys and NULL values, but its destructors do not reflect this -- key_destroy_func is NULL while value_destroy_func is raw_free. Swap these to free the traceback keys instead. --- Python/tracemalloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c index fee7dd0e56d96d..e58b60ddd5e484 100644 --- a/Python/tracemalloc.c +++ b/Python/tracemalloc.c @@ -838,7 +838,7 @@ _PyTraceMalloc_Init(void) tracemalloc_tracebacks = hashtable_new(hashtable_hash_traceback, hashtable_compare_traceback, - NULL, raw_free); + raw_free, NULL); tracemalloc_traces = tracemalloc_create_traces_table(); tracemalloc_domains = tracemalloc_create_domains_table(); From cecd6012b0ed5dca3916ae341e705ae44172991d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 5 Jul 2024 11:44:07 +0300 Subject: [PATCH 42/97] gh-59110: Fix a debug output for implicit directories (GH-121375) --- Lib/zipimport.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/zipimport.py b/Lib/zipimport.py index a79862f1de7564..68f031f89c9996 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py @@ -558,6 +558,7 @@ def _read_directory(archive): _bootstrap._verbose_message('zipimport: found {} names in {!r}', count, archive) # Add implicit directories. + count = 0 for name in list(files): while True: i = name.rstrip(path_sep).rfind(path_sep) @@ -568,8 +569,9 @@ def _read_directory(archive): break files[name] = None count += 1 - _bootstrap._verbose_message('zipimport: added {} implicit directories in {!r}', - count, archive) + if count: + _bootstrap._verbose_message('zipimport: added {} implicit directories in {!r}', + count, archive) return files # During bootstrap, we may need to load the encodings From d4faa7bd321a7016f3e987d65962e02c778d708f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 5 Jul 2024 18:01:05 +0300 Subject: [PATCH 43/97] gh-121149: improve accuracy of builtin sum() for complex inputs (gh-121176) --- Doc/library/functions.rst | 4 + Lib/test/test_builtin.py | 9 ++ ...-06-30-03-48-10.gh-issue-121149.lLBMKe.rst | 2 + Python/bltinmodule.c | 131 ++++++++++++++---- 4 files changed, 120 insertions(+), 26 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-30-03-48-10.gh-issue-121149.lLBMKe.rst diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 1d82f92ea67857..17348dd907bf67 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1934,6 +1934,10 @@ are always available. They are listed here in alphabetical order. .. versionchanged:: 3.12 Summation of floats switched to an algorithm that gives higher accuracy and better commutativity on most builds. + .. versionchanged:: 3.14 + Added specialization for summation of complexes, + using same algorithm as for summation of floats. + .. class:: super() super(type, object_or_type=None) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 9ff0f488dc4fa9..5818e96d61f480 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1768,6 +1768,11 @@ def __getitem__(self, index): sum(([x] for x in range(10)), empty) self.assertEqual(empty, []) + xs = [complex(random.random() - .5, random.random() - .5) + for _ in range(10000)] + self.assertEqual(sum(xs), complex(sum(z.real for z in xs), + sum(z.imag for z in xs))) + @requires_IEEE_754 @unittest.skipIf(HAVE_DOUBLE_ROUNDING, "sum accuracy not guaranteed on machines with double rounding") @@ -1775,6 +1780,10 @@ def __getitem__(self, index): def test_sum_accuracy(self): self.assertEqual(sum([0.1] * 10), 1.0) self.assertEqual(sum([1.0, 10E100, 1.0, -10E100]), 2.0) + self.assertEqual(sum([1.0, 10E100, 1.0, -10E100, 2j]), 2+2j) + self.assertEqual(sum([2+1j, 10E100j, 1j, -10E100j]), 2+2j) + self.assertEqual(sum([1j, 1, 10E100j, 1j, 1.0, -10E100j]), 2+2j) + self.assertEqual(sum([0.1j]*10 + [fractions.Fraction(1, 10)]), 0.1+1j) def test_type(self): self.assertEqual(type(''), type('123')) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-30-03-48-10.gh-issue-121149.lLBMKe.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-30-03-48-10.gh-issue-121149.lLBMKe.rst new file mode 100644 index 00000000000000..38d618f06090fd --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-30-03-48-10.gh-issue-121149.lLBMKe.rst @@ -0,0 +1,2 @@ +Added specialization for summation of complexes, this also improves accuracy +of builtin :func:`sum` for such inputs. Patch by Sergey B Kirpichev. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 6e50623cafa4ed..a5b45e358d9efb 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2516,6 +2516,49 @@ Without arguments, equivalent to locals().\n\ With an argument, equivalent to object.__dict__."); +/* Improved Kahan–Babuška algorithm by Arnold Neumaier + Neumaier, A. (1974), Rundungsfehleranalyse einiger Verfahren + zur Summation endlicher Summen. Z. angew. Math. Mech., + 54: 39-51. https://doi.org/10.1002/zamm.19740540106 + https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements + */ + +typedef struct { + double hi; /* high-order bits for a running sum */ + double lo; /* a running compensation for lost low-order bits */ +} CompensatedSum; + +static inline CompensatedSum +cs_from_double(double x) +{ + return (CompensatedSum) {x}; +} + +static inline CompensatedSum +cs_add(CompensatedSum total, double x) +{ + double t = total.hi + x; + if (fabs(total.hi) >= fabs(x)) { + total.lo += (total.hi - t) + x; + } + else { + total.lo += (x - t) + total.hi; + } + return (CompensatedSum) {t, total.lo}; +} + +static inline double +cs_to_double(CompensatedSum total) +{ + /* Avoid losing the sign on a negative result, + and don't let adding the compensation convert + an infinite or overflowed sum to a NaN. */ + if (total.lo && isfinite(total.lo)) { + return total.hi + total.lo; + } + return total.hi; +} + /*[clinic input] sum as builtin_sum @@ -2628,8 +2671,7 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) } if (PyFloat_CheckExact(result)) { - double f_result = PyFloat_AS_DOUBLE(result); - double c = 0.0; + CompensatedSum re_sum = cs_from_double(PyFloat_AS_DOUBLE(result)); Py_SETREF(result, NULL); while(result == NULL) { item = PyIter_Next(iter); @@ -2637,28 +2679,10 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) Py_DECREF(iter); if (PyErr_Occurred()) return NULL; - /* Avoid losing the sign on a negative result, - and don't let adding the compensation convert - an infinite or overflowed sum to a NaN. */ - if (c && isfinite(c)) { - f_result += c; - } - return PyFloat_FromDouble(f_result); + return PyFloat_FromDouble(cs_to_double(re_sum)); } if (PyFloat_CheckExact(item)) { - // Improved Kahan–Babuška algorithm by Arnold Neumaier - // Neumaier, A. (1974), Rundungsfehleranalyse einiger Verfahren - // zur Summation endlicher Summen. Z. angew. Math. Mech., - // 54: 39-51. https://doi.org/10.1002/zamm.19740540106 - // https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements - double x = PyFloat_AS_DOUBLE(item); - double t = f_result + x; - if (fabs(f_result) >= fabs(x)) { - c += (f_result - t) + x; - } else { - c += (x - t) + f_result; - } - f_result = t; + re_sum = cs_add(re_sum, PyFloat_AS_DOUBLE(item)); _Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc); continue; } @@ -2667,15 +2691,70 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) int overflow; value = PyLong_AsLongAndOverflow(item, &overflow); if (!overflow) { - f_result += (double)value; + re_sum.hi += (double)value; + Py_DECREF(item); + continue; + } + } + result = PyFloat_FromDouble(cs_to_double(re_sum)); + if (result == NULL) { + Py_DECREF(item); + Py_DECREF(iter); + return NULL; + } + temp = PyNumber_Add(result, item); + Py_DECREF(result); + Py_DECREF(item); + result = temp; + if (result == NULL) { + Py_DECREF(iter); + return NULL; + } + } + } + + if (PyComplex_CheckExact(result)) { + Py_complex z = PyComplex_AsCComplex(result); + CompensatedSum re_sum = cs_from_double(z.real); + CompensatedSum im_sum = cs_from_double(z.imag); + Py_SETREF(result, NULL); + while (result == NULL) { + item = PyIter_Next(iter); + if (item == NULL) { + Py_DECREF(iter); + if (PyErr_Occurred()) { + return NULL; + } + return PyComplex_FromDoubles(cs_to_double(re_sum), + cs_to_double(im_sum)); + } + if (PyComplex_CheckExact(item)) { + z = PyComplex_AsCComplex(item); + re_sum = cs_add(re_sum, z.real); + im_sum = cs_add(im_sum, z.imag); + Py_DECREF(item); + continue; + } + if (PyLong_Check(item)) { + long value; + int overflow; + value = PyLong_AsLongAndOverflow(item, &overflow); + if (!overflow) { + re_sum.hi += (double)value; + im_sum.hi += 0.0; Py_DECREF(item); continue; } } - if (c && isfinite(c)) { - f_result += c; + if (PyFloat_Check(item)) { + double value = PyFloat_AS_DOUBLE(item); + re_sum.hi += value; + im_sum.hi += 0.0; + _Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc); + continue; } - result = PyFloat_FromDouble(f_result); + result = PyComplex_FromDoubles(cs_to_double(re_sum), + cs_to_double(im_sum)); if (result == NULL) { Py_DECREF(item); Py_DECREF(iter); From 0e77540d86833f0a0ef964ab51f35be9bfb533f9 Mon Sep 17 00:00:00 2001 From: AraHaan Date: Fri, 5 Jul 2024 12:33:52 -0400 Subject: [PATCH 44/97] Fixed regenerating files in a checkout path with spaces (GH-121384) --- PCbuild/regen.targets | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/PCbuild/regen.targets b/PCbuild/regen.targets index 4aa14ed1fad9eb..416241d9d0df10 100644 --- a/PCbuild/regen.targets +++ b/PCbuild/regen.targets @@ -90,23 +90,23 @@ Inputs="@(_CasesSources)" Outputs="@(_CasesOutputs)" DependsOnTargets="FindPythonForBuild"> - - - - - - - - - From 8ecb8962e33930dd56d72004a59336d4b00fce22 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 5 Jul 2024 20:50:45 +0300 Subject: [PATCH 45/97] gh-121288: Make error message for index() methods consistent (GH-121395) Make error message for index() methods consistent Remove the repr of the searched value (which can be arbitrary large) from ValueError messages for list.index(), range.index(), deque.index(), deque.remove() and ShareableList.index(). Make the error messages consistent with error messages for other index() and remove() methods. --- Lib/multiprocessing/shared_memory.py | 2 +- .../2024-07-05-11-29-27.gh-issue-121288.lYKYYP.rst | 5 +++++ Modules/_collectionsmodule.c | 4 ++-- Objects/listobject.c | 2 +- Objects/rangeobject.c | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-07-05-11-29-27.gh-issue-121288.lYKYYP.rst diff --git a/Lib/multiprocessing/shared_memory.py b/Lib/multiprocessing/shared_memory.py index 67e70fdc27cf31..99a8ce3320ad4e 100644 --- a/Lib/multiprocessing/shared_memory.py +++ b/Lib/multiprocessing/shared_memory.py @@ -539,6 +539,6 @@ def index(self, value): if value == entry: return position else: - raise ValueError(f"{value!r} not in this container") + raise ValueError("ShareableList.index(x): x not in list") __class_getitem__ = classmethod(types.GenericAlias) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-05-11-29-27.gh-issue-121288.lYKYYP.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-05-11-29-27.gh-issue-121288.lYKYYP.rst new file mode 100644 index 00000000000000..bd3e20b5658562 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-05-11-29-27.gh-issue-121288.lYKYYP.rst @@ -0,0 +1,5 @@ +:exc:`ValueError` messages for :meth:`!list.index()`, :meth:`!range.index()`, +:meth:`!deque.index()`, :meth:`!deque.remove()` and +:meth:`!ShareableList.index()` no longer contain the repr of the searched +value (which can be arbitrary large) and are consistent with error messages +for other :meth:`!index()` and :meth:`!remove()` methods. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 0bc61db4117c5d..fbfed59995c21e 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -1293,7 +1293,7 @@ deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, index = 0; } } - PyErr_Format(PyExc_ValueError, "%R is not in deque", v); + PyErr_SetString(PyExc_ValueError, "deque.index(x): x not in deque"); return NULL; } @@ -1462,7 +1462,7 @@ deque_remove_impl(dequeobject *deque, PyObject *value) } } if (i == n) { - PyErr_Format(PyExc_ValueError, "%R is not in deque", value); + PyErr_SetString(PyExc_ValueError, "deque.remove(x): x not in deque"); return NULL; } rv = deque_del_item(deque, i); diff --git a/Objects/listobject.c b/Objects/listobject.c index 9eae9626f7c1f1..f29f58dc25be04 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3244,7 +3244,7 @@ list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start, else if (cmp < 0) return NULL; } - PyErr_Format(PyExc_ValueError, "%R is not in list", value); + PyErr_SetString(PyExc_ValueError, "list.index(x): x not in list"); return NULL; } diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index d5db48c143324f..9727b4f47b53a1 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -655,7 +655,7 @@ range_index(rangeobject *r, PyObject *ob) } /* object is not in the range */ - PyErr_Format(PyExc_ValueError, "%R is not in range", ob); + PyErr_SetString(PyExc_ValueError, "range.index(x): x not in range"); return NULL; } From 892e3a1b708391cb43517a141f9b9712e047b8a4 Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Fri, 5 Jul 2024 13:08:29 -0700 Subject: [PATCH 46/97] Update example of str.split, bytes.split (#121287) In `{str,bytes}.strip(chars)`, multiple characters are not treated as a prefix/suffix, but as individual characters. This may make users confuse whether `split` has similar behavior. Users may incorrectly expect that `'Good morning, John.'.split(', .') == ['Good', 'morning', 'John']` Adding a bit of clarification in the doc. Co-authored-by: Yuxin Wu --- Doc/library/stdtypes.rst | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 54cc7d1333d34e..d3f7cfb01d3c21 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -2095,8 +2095,9 @@ expression support in the :mod:`re` module). If *sep* is given, consecutive delimiters are not grouped together and are deemed to delimit empty strings (for example, ``'1,,2'.split(',')`` returns ``['1', '', '2']``). The *sep* argument may consist of multiple characters - (for example, ``'1<>2<>3'.split('<>')`` returns ``['1', '2', '3']``). - Splitting an empty string with a specified separator returns ``['']``. + as a single delimiter (to split with multiple delimiters, use + :func:`re.split`). Splitting an empty string with a specified separator + returns ``['']``. For example:: @@ -2106,6 +2107,8 @@ expression support in the :mod:`re` module). ['1', '2,3'] >>> '1,2,,3,'.split(',') ['1', '2', '', '3', ''] + >>> '1<>2<>3<4'.split('<>') + ['1', '2', '3<4'] If *sep* is not specified or is ``None``, a different splitting algorithm is applied: runs of consecutive whitespace are regarded as a single separator, @@ -3149,10 +3152,9 @@ produce new objects. If *sep* is given, consecutive delimiters are not grouped together and are deemed to delimit empty subsequences (for example, ``b'1,,2'.split(b',')`` returns ``[b'1', b'', b'2']``). The *sep* argument may consist of a - multibyte sequence (for example, ``b'1<>2<>3'.split(b'<>')`` returns - ``[b'1', b'2', b'3']``). Splitting an empty sequence with a specified - separator returns ``[b'']`` or ``[bytearray(b'')]`` depending on the type - of object being split. The *sep* argument may be any + multibyte sequence as a single delimiter. Splitting an empty sequence with + a specified separator returns ``[b'']`` or ``[bytearray(b'')]`` depending + on the type of object being split. The *sep* argument may be any :term:`bytes-like object`. For example:: @@ -3163,6 +3165,8 @@ produce new objects. [b'1', b'2,3'] >>> b'1,2,,3,'.split(b',') [b'1', b'2', b'', b'3', b''] + >>> b'1<>2<>3<4'.split(b'<>') + [b'1', b'2', b'3<4'] If *sep* is not specified or is ``None``, a different splitting algorithm is applied: runs of consecutive ASCII whitespace are regarded as a single From 6239d41527d5977aa5d44e4b894d719bc045860e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 5 Jul 2024 22:30:08 +0200 Subject: [PATCH 47/97] gh-121359: Run test_pyrepl in isolated mode (#121414) run_repl() now pass the -I option (isolated mode) to Python if the 'env' parameter is not set. --- Lib/test/test_pyrepl/test_pyrepl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 93c80467a04546..7621b8af808f53 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -927,8 +927,11 @@ def test_not_wiping_history_file(self): def run_repl(self, repl_input: str | list[str], env: dict | None = None) -> tuple[str, int]: master_fd, slave_fd = pty.openpty() + cmd = [sys.executable, "-i", "-u"] + if env is None: + cmd.append("-I") process = subprocess.Popen( - [sys.executable, "-i", "-u"], + cmd, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, From 88fc0655d4a487233efce293277690a799706bf9 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 6 Jul 2024 17:18:39 +0100 Subject: [PATCH 48/97] GH-73991: Support preserving metadata in `pathlib.Path.copy()` (#120806) Add *preserve_metadata* keyword-only argument to `pathlib.Path.copy()`, defaulting to false. When set to true, we copy timestamps, permissions, extended attributes and flags where available, like `shutil.copystat()`. The argument has no effect on Windows, where metadata is always copied. Internally (in the pathlib ABCs), path types gain `_readable_metadata` and `_writable_metadata` attributes. These sets of strings describe what kinds of metadata can be retrieved and stored. We take an intersection of `source._readable_metadata` and `target._writable_metadata` to minimise reads/writes. A new `_read_metadata()` method accepts a set of metadata keys and returns a dict with those keys, and a new `_write_metadata()` method accepts a dict of metadata. We *might* make these public in future, but it's hard to justify while the ABCs are still private. --- Doc/library/pathlib.rst | 12 ++-- Lib/pathlib/_abc.py | 31 ++++++++- Lib/pathlib/_local.py | 12 +++- Lib/pathlib/_os.py | 99 ++++++++++++++++++++++++++- Lib/test/test_pathlib/test_pathlib.py | 44 ++++++++++++ 5 files changed, 187 insertions(+), 11 deletions(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index d7fd56f4c4ff7f..f139abd2454d69 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1539,7 +1539,7 @@ Creating files and directories Copying, renaming and deleting ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. method:: Path.copy(target, *, follow_symlinks=True) +.. method:: Path.copy(target, *, follow_symlinks=True, preserve_metadata=False) Copy the contents of this file to the *target* file. If *target* specifies a file that already exists, it will be replaced. @@ -1548,11 +1548,11 @@ Copying, renaming and deleting will be created as a symbolic link. If *follow_symlinks* is true and this file is a symbolic link, *target* will be a copy of the symlink target. - .. note:: - This method uses operating system functionality to copy file content - efficiently. The OS might also copy some metadata, such as file - permissions. After the copy is complete, users may wish to call - :meth:`Path.chmod` to set the permissions of the target file. + If *preserve_metadata* is false (the default), only the file data is + guaranteed to be copied. Set *preserve_metadata* to true to ensure that the + file mode (permissions), flags, last access and modification times, and + extended attributes are copied where supported. This argument has no effect + on Windows, where metadata is always preserved when copying. .. versionadded:: 3.14 diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index b5f903ec1f03ce..05f55badd77c58 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -781,7 +781,32 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False): """ raise UnsupportedOperation(self._unsupported_msg('mkdir()')) - def copy(self, target, follow_symlinks=True): + # Metadata keys supported by this path type. + _readable_metadata = _writable_metadata = frozenset() + + def _read_metadata(self, keys=None, *, follow_symlinks=True): + """ + Returns path metadata as a dict with string keys. + """ + raise UnsupportedOperation(self._unsupported_msg('_read_metadata()')) + + def _write_metadata(self, metadata, *, follow_symlinks=True): + """ + Sets path metadata from the given dict with string keys. + """ + raise UnsupportedOperation(self._unsupported_msg('_write_metadata()')) + + def _copy_metadata(self, target, *, follow_symlinks=True): + """ + Copies metadata (permissions, timestamps, etc) from this path to target. + """ + # Metadata types supported by both source and target. + keys = self._readable_metadata & target._writable_metadata + if keys: + metadata = self._read_metadata(keys, follow_symlinks=follow_symlinks) + target._write_metadata(metadata, follow_symlinks=follow_symlinks) + + def copy(self, target, *, follow_symlinks=True, preserve_metadata=False): """ Copy the contents of this file to the given target. If this file is a symlink and follow_symlinks is false, a symlink will be created at the @@ -793,6 +818,8 @@ def copy(self, target, follow_symlinks=True): raise OSError(f"{self!r} and {target!r} are the same file") if not follow_symlinks and self.is_symlink(): target.symlink_to(self.readlink()) + if preserve_metadata: + self._copy_metadata(target, follow_symlinks=False) return with self.open('rb') as source_f: try: @@ -805,6 +832,8 @@ def copy(self, target, follow_symlinks=True): f'Directory does not exist: {target}') from e else: raise + if preserve_metadata: + self._copy_metadata(target) def copytree(self, target, *, follow_symlinks=True, dirs_exist_ok=False, ignore=None, on_error=None): diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index acb57214b81865..eae8a30c876f19 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -17,7 +17,8 @@ except ImportError: grp = None -from ._os import UnsupportedOperation, copyfile +from ._os import (UnsupportedOperation, copyfile, file_metadata_keys, + read_file_metadata, write_file_metadata) from ._abc import PurePathBase, PathBase @@ -781,8 +782,12 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False): if not exist_ok or not self.is_dir(): raise + _readable_metadata = _writable_metadata = file_metadata_keys + _read_metadata = read_file_metadata + _write_metadata = write_file_metadata + if copyfile: - def copy(self, target, follow_symlinks=True): + def copy(self, target, *, follow_symlinks=True, preserve_metadata=False): """ Copy the contents of this file to the given target. If this file is a symlink and follow_symlinks is false, a symlink will be created at the @@ -799,7 +804,8 @@ def copy(self, target, follow_symlinks=True): return except UnsupportedOperation: pass # Fall through to generic code. - PathBase.copy(self, target, follow_symlinks=follow_symlinks) + PathBase.copy(self, target, follow_symlinks=follow_symlinks, + preserve_metadata=preserve_metadata) def chmod(self, mode, *, follow_symlinks=True): """ diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index 61923b5e410b5c..164ee8e9034427 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -2,7 +2,7 @@ Low-level OS functionality wrappers used by pathlib. """ -from errno import EBADF, EOPNOTSUPP, ETXTBSY, EXDEV +from errno import * import os import stat import sys @@ -178,3 +178,100 @@ def copyfileobj(source_f, target_f): write_target = target_f.write while buf := read_source(1024 * 1024): write_target(buf) + + +# Kinds of metadata supported by the operating system. +file_metadata_keys = {'mode', 'times_ns'} +if hasattr(os.stat_result, 'st_flags'): + file_metadata_keys.add('flags') +if hasattr(os, 'listxattr'): + file_metadata_keys.add('xattrs') +file_metadata_keys = frozenset(file_metadata_keys) + + +def read_file_metadata(path, keys=None, *, follow_symlinks=True): + """ + Returns local path metadata as a dict with string keys. + """ + if keys is None: + keys = file_metadata_keys + assert keys.issubset(file_metadata_keys) + result = {} + for key in keys: + if key == 'xattrs': + try: + result['xattrs'] = [ + (attr, os.getxattr(path, attr, follow_symlinks=follow_symlinks)) + for attr in os.listxattr(path, follow_symlinks=follow_symlinks)] + except OSError as err: + if err.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): + raise + continue + st = os.stat(path, follow_symlinks=follow_symlinks) + if key == 'mode': + result['mode'] = stat.S_IMODE(st.st_mode) + elif key == 'times_ns': + result['times_ns'] = st.st_atime_ns, st.st_mtime_ns + elif key == 'flags': + result['flags'] = st.st_flags + return result + + +def write_file_metadata(path, metadata, *, follow_symlinks=True): + """ + Sets local path metadata from the given dict with string keys. + """ + assert frozenset(metadata.keys()).issubset(file_metadata_keys) + + def _nop(*args, ns=None, follow_symlinks=None): + pass + + if follow_symlinks: + # use the real function if it exists + def lookup(name): + return getattr(os, name, _nop) + else: + # use the real function only if it exists + # *and* it supports follow_symlinks + def lookup(name): + fn = getattr(os, name, _nop) + if fn in os.supports_follow_symlinks: + return fn + return _nop + + times_ns = metadata.get('times_ns') + if times_ns is not None: + lookup("utime")(path, ns=times_ns, follow_symlinks=follow_symlinks) + # We must copy extended attributes before the file is (potentially) + # chmod()'ed read-only, otherwise setxattr() will error with -EACCES. + xattrs = metadata.get('xattrs') + if xattrs is not None: + for attr, value in xattrs: + try: + os.setxattr(path, attr, value, follow_symlinks=follow_symlinks) + except OSError as e: + if e.errno not in (EPERM, ENOTSUP, ENODATA, EINVAL, EACCES): + raise + mode = metadata.get('mode') + if mode is not None: + try: + lookup("chmod")(path, mode, follow_symlinks=follow_symlinks) + except NotImplementedError: + # if we got a NotImplementedError, it's because + # * follow_symlinks=False, + # * lchown() is unavailable, and + # * either + # * fchownat() is unavailable or + # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW. + # (it returned ENOSUP.) + # therefore we're out of options--we simply cannot chown the + # symlink. give up, suppress the error. + # (which is what shutil always did in this circumstance.) + pass + flags = metadata.get('flags') + if flags is not None: + try: + lookup("chflags")(path, flags, follow_symlinks=follow_symlinks) + except OSError as why: + if why.errno not in (EOPNOTSUPP, ENOTSUP): + raise diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index da6d82465d29cf..234e5746e544cd 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -653,6 +653,50 @@ def test_open_unbuffered(self): self.assertIsInstance(f, io.RawIOBase) self.assertEqual(f.read().strip(), b"this is file A") + def test_copy_file_preserve_metadata(self): + base = self.cls(self.base) + source = base / 'fileA' + if hasattr(os, 'setxattr'): + os.setxattr(source, b'user.foo', b'42') + if hasattr(os, 'chmod'): + os.chmod(source, stat.S_IRWXU | stat.S_IRWXO) + if hasattr(os, 'chflags') and hasattr(stat, 'UF_NODUMP'): + os.chflags(source, stat.UF_NODUMP) + source_st = source.stat() + target = base / 'copyA' + source.copy(target, preserve_metadata=True) + self.assertTrue(target.exists()) + self.assertEqual(source.read_text(), target.read_text()) + target_st = target.stat() + self.assertLessEqual(source_st.st_atime, target_st.st_atime) + self.assertLessEqual(source_st.st_mtime, target_st.st_mtime) + if hasattr(os, 'getxattr'): + self.assertEqual(os.getxattr(target, b'user.foo'), b'42') + self.assertEqual(source_st.st_mode, target_st.st_mode) + if hasattr(source_st, 'st_flags'): + self.assertEqual(source_st.st_flags, target_st.st_flags) + + @needs_symlinks + def test_copy_link_preserve_metadata(self): + base = self.cls(self.base) + source = base / 'linkA' + if hasattr(os, 'lchmod'): + os.lchmod(source, stat.S_IRWXU | stat.S_IRWXO) + if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'): + os.lchflags(source, stat.UF_NODUMP) + source_st = source.lstat() + target = base / 'copyA' + source.copy(target, follow_symlinks=False, preserve_metadata=True) + self.assertTrue(target.exists()) + self.assertTrue(target.is_symlink()) + self.assertEqual(source.readlink(), target.readlink()) + target_st = target.lstat() + self.assertLessEqual(source_st.st_atime, target_st.st_atime) + self.assertLessEqual(source_st.st_mtime, target_st.st_mtime) + self.assertEqual(source_st.st_mode, target_st.st_mode) + if hasattr(source_st, 'st_flags'): + self.assertEqual(source_st.st_flags, target_st.st_flags) + @unittest.skipIf(sys.platform == "win32" or sys.platform == "wasi", "directories are always readable on Windows and WASI") @unittest.skipIf(root_in_posix, "test fails with root privilege") def test_copytree_no_read_permission(self): From ada964fba06c0d142b8746d140817ea151314d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:57:26 +0200 Subject: [PATCH 49/97] [docs] fix a Sphinx directive in `c-api/object.rst` (#121430) --- Doc/c-api/object.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 8eeac3fc8a1e58..2103a64d8ffbb7 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -52,6 +52,7 @@ Object Protocol The reference is borrowed from the interpreter, and is valid until the interpreter finalization. + .. versionadded:: 3.13 From 53e12025cd7d7ee46ce10cc8f1b722c55716b892 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sat, 6 Jul 2024 21:04:41 +0300 Subject: [PATCH 50/97] Regen ``Doc/requirements-oldest-sphinx.txt`` (#121437) regen dependencies --- Doc/requirements-oldest-sphinx.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/requirements-oldest-sphinx.txt b/Doc/requirements-oldest-sphinx.txt index 4e49ba1a8ededd..068fe0cb426ecd 100644 --- a/Doc/requirements-oldest-sphinx.txt +++ b/Doc/requirements-oldest-sphinx.txt @@ -14,7 +14,7 @@ python-docs-theme>=2022.1 alabaster==0.7.16 Babel==2.15.0 -certifi==2024.6.2 +certifi==2024.7.4 charset-normalizer==3.3.2 docutils==0.19 idna==3.7 From 114389470ec3db457c589b3991b695258d23ce5a Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sat, 6 Jul 2024 23:49:33 +0300 Subject: [PATCH 51/97] gh-119909: Fix ``NameError`` in ``asyncio`` REPL (#121341) --- Lib/asyncio/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 91fff9aaee337b..3e2fe93943d4ed 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -116,7 +116,7 @@ def run(self): if err := check(): raise RuntimeError(err) except Exception as e: - console.interact(banner="", exitmsg=exit_message) + console.interact(banner="", exitmsg="") else: try: run_multiline_interactive_console(console=console) From 68e279b37aae3019979a05ca55f462b11aac14be Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 7 Jul 2024 01:53:54 +0300 Subject: [PATCH 52/97] gh-121351: Skip test_not_wiping_history_file() if no readline (#121422) --- Lib/test/test_pyrepl/test_pyrepl.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 7621b8af808f53..015b690566223d 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -11,6 +11,7 @@ from unittest.mock import patch from test.support import force_not_colorized from test.support import SHORT_TIMEOUT +from test.support.import_helper import import_module from test.support.os_helper import unlink from .support import ( @@ -902,6 +903,9 @@ def test_python_basic_repl(self): self.assertNotIn("Traceback", output) def test_not_wiping_history_file(self): + # skip, if readline module is not available + import_module('readline') + hfile = tempfile.NamedTemporaryFile(delete=False) self.addCleanup(unlink, hfile.name) env = os.environ.copy() From 3bddd07c2ada7cdadb55ea23a15037bd650e20ef Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Sun, 7 Jul 2024 21:45:06 +0800 Subject: [PATCH 53/97] Add Fidget-Spinner to stackrefs CODEOWNERS (GH-121455) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e8f4a4693a814c..95e30ac3001c9c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -72,6 +72,7 @@ Include/internal/pycore_freelist.h @ericsnowcurrently Include/internal/pycore_global_objects.h @ericsnowcurrently Include/internal/pycore_obmalloc.h @ericsnowcurrently Include/internal/pycore_pymem.h @ericsnowcurrently +Include/internal/pycore_stackref.h @Fidget-Spinner Modules/main.c @ericsnowcurrently Programs/_bootstrap_python.c @ericsnowcurrently Programs/python.c @ericsnowcurrently From b765e4adf858ff8a8646f38933a5a355b6d72760 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 7 Jul 2024 17:27:52 +0100 Subject: [PATCH 54/97] GH-73991: Fix "Operation not supported" on Fedora buildbot. (#121444) Follow-up to #120806. Use `os_helper.skip_unless_xattr` to skip testing xattr preservation when unsupported. --- Lib/test/test_pathlib/test_pathlib.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 234e5746e544cd..1328a8695b0cca 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -656,8 +656,6 @@ def test_open_unbuffered(self): def test_copy_file_preserve_metadata(self): base = self.cls(self.base) source = base / 'fileA' - if hasattr(os, 'setxattr'): - os.setxattr(source, b'user.foo', b'42') if hasattr(os, 'chmod'): os.chmod(source, stat.S_IRWXU | stat.S_IRWXO) if hasattr(os, 'chflags') and hasattr(stat, 'UF_NODUMP'): @@ -670,12 +668,19 @@ def test_copy_file_preserve_metadata(self): target_st = target.stat() self.assertLessEqual(source_st.st_atime, target_st.st_atime) self.assertLessEqual(source_st.st_mtime, target_st.st_mtime) - if hasattr(os, 'getxattr'): - self.assertEqual(os.getxattr(target, b'user.foo'), b'42') self.assertEqual(source_st.st_mode, target_st.st_mode) if hasattr(source_st, 'st_flags'): self.assertEqual(source_st.st_flags, target_st.st_flags) + @os_helper.skip_unless_xattr + def test_copy_file_preserve_metadata_xattrs(self): + base = self.cls(self.base) + source = base / 'fileA' + os.setxattr(source, b'user.foo', b'42') + target = base / 'copyA' + source.copy(target, preserve_metadata=True) + self.assertEqual(os.getxattr(target, b'user.foo'), b'42') + @needs_symlinks def test_copy_link_preserve_metadata(self): base = self.cls(self.base) From c8669489d45f22a8c6de7e05b7625db10befb8db Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 7 Jul 2024 17:18:28 -0700 Subject: [PATCH 55/97] Fix sphinx reference target (#121470) This was introduced in https://github.com/python/cpython/pull/121164 and appears to be causing test failures on main --- .../Library/2024-06-29-19-30-15.gh-issue-121163.SJKDFq.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2024-06-29-19-30-15.gh-issue-121163.SJKDFq.rst b/Misc/NEWS.d/next/Library/2024-06-29-19-30-15.gh-issue-121163.SJKDFq.rst index 029838030278a6..50f945ab9f1436 100644 --- a/Misc/NEWS.d/next/Library/2024-06-29-19-30-15.gh-issue-121163.SJKDFq.rst +++ b/Misc/NEWS.d/next/Library/2024-06-29-19-30-15.gh-issue-121163.SJKDFq.rst @@ -1,3 +1,2 @@ Add support for ``all`` as an valid ``action`` for :func:`warnings.simplefilter` -and :func:`warnings.filterswarnings`. - +and :func:`warnings.filterwarnings`. From 5aa1e60e0cc9c48abdd8d2c03fcc71927cf95204 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 8 Jul 2024 02:45:21 +0200 Subject: [PATCH 56/97] gh-121467: Fix makefile to include mimalloc headers (#121469) --- Makefile.pre.in | 4 ++-- .../next/Build/2024-07-08-01-11-54.gh-issue-121467.3qWRQj.rst | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-07-08-01-11-54.gh-issue-121467.3qWRQj.rst diff --git a/Makefile.pre.in b/Makefile.pre.in index 94cfb74138a3d9..0bece8717ef4c0 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2652,7 +2652,7 @@ inclinstall: $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(INCLUDEPY)/internal; \ else true; \ fi - @if test "$(INSTALL_MIMALLOC)" == "yes"; then \ + @if test "$(INSTALL_MIMALLOC)" = "yes"; then \ if test ! -d $(DESTDIR)$(INCLUDEPY)/internal/mimalloc/mimalloc; then \ echo "Creating directory $(DESTDIR)$(INCLUDEPY)/internal/mimalloc/mimalloc"; \ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(INCLUDEPY)/internal/mimalloc/mimalloc; \ @@ -2673,7 +2673,7 @@ inclinstall: echo $(INSTALL_DATA) $$i $(INCLUDEPY)/internal; \ $(INSTALL_DATA) $$i $(DESTDIR)$(INCLUDEPY)/internal; \ done - @if test "$(INSTALL_MIMALLOC)" == "yes"; then \ + @if test "$(INSTALL_MIMALLOC)" = "yes"; then \ echo $(INSTALL_DATA) $(srcdir)/Include/internal/mimalloc/mimalloc.h $(DESTDIR)$(INCLUDEPY)/internal/mimalloc/mimalloc.h; \ $(INSTALL_DATA) $(srcdir)/Include/internal/mimalloc/mimalloc.h $(DESTDIR)$(INCLUDEPY)/internal/mimalloc/mimalloc.h; \ for i in $(srcdir)/Include/internal/mimalloc/mimalloc/*.h; \ diff --git a/Misc/NEWS.d/next/Build/2024-07-08-01-11-54.gh-issue-121467.3qWRQj.rst b/Misc/NEWS.d/next/Build/2024-07-08-01-11-54.gh-issue-121467.3qWRQj.rst new file mode 100644 index 00000000000000..a2238475546eaa --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-07-08-01-11-54.gh-issue-121467.3qWRQj.rst @@ -0,0 +1 @@ +Fix a Makefile bug that prevented mimalloc header files from being installed. From bf74db731bf108e880348f2925160af61570dbf4 Mon Sep 17 00:00:00 2001 From: CBerJun <121291537+CBerJun@users.noreply.github.com> Date: Sun, 7 Jul 2024 23:51:03 -0400 Subject: [PATCH 57/97] gh-121461: Fix os.path.normpath documentation indentation (#121466) --- Doc/library/os.path.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index b582321515db56..52487b4737ae2f 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -389,7 +389,7 @@ the :mod:`glob` module.) that contains symbolic links. On Windows, it converts forward slashes to backward slashes. To normalize case, use :func:`normcase`. - .. note:: + .. note:: On POSIX systems, in accordance with `IEEE Std 1003.1 2013 Edition; 4.13 Pathname Resolution `_, if a pathname begins with exactly two slashes, the first component From 59be79ae60073f7b6bdf6ce921560c279937e4ab Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Mon, 8 Jul 2024 15:24:31 +1000 Subject: [PATCH 58/97] gh-108297: Update crashers README for test_crashers removal (#121475) Update Lib/test/crashers/README for test_crashers removal --- Lib/test/crashers/README | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/test/crashers/README b/Lib/test/crashers/README index d844385113eb45..7111946b93b280 100644 --- a/Lib/test/crashers/README +++ b/Lib/test/crashers/README @@ -15,7 +15,3 @@ what the variables are. Once the crash is fixed, the test case should be moved into an appropriate test (even if it was originally from the test suite). This ensures the regression doesn't happen again. And if it does, it should be easier to track down. - -Also see Lib/test_crashers.py which exercises the crashers in this directory. -In particular, make sure to add any new infinite loop crashers to the black -list so it doesn't try to run them. From d69529d31ccd1510843cfac1ab53bb8cb027541f Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 8 Jul 2024 08:48:42 -0400 Subject: [PATCH 59/97] gh-121338: Remove #pragma optimize (#121340) --- Python/ceval.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index a240ed4321f7ee..d8bc830f8e80c1 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -730,15 +730,6 @@ _PyObjectArray_Free(PyObject **array, PyObject **scratch) * so consume 3 units of C stack */ #define PY_EVAL_C_STACK_UNITS 2 -#if defined(_MSC_VER) && defined(_Py_USING_PGO) -/* gh-111786: _PyEval_EvalFrameDefault is too large to optimize for speed with - PGO on MSVC. Disable that optimization temporarily. If this is fixed - upstream, we should gate this on the version of MSVC. - */ -# pragma optimize("t", off) -/* This setting is reversed below following _PyEval_EvalFrameDefault */ -#endif - PyObject* _Py_HOT_FUNCTION _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) { @@ -1158,7 +1149,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int # pragma GCC diagnostic pop #elif defined(_MSC_VER) /* MS_WINDOWS */ # pragma warning(pop) -# pragma optimize("", on) #endif static void From 8ad6067bd4556afddc86004f8e350aa672fda217 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 8 Jul 2024 14:20:13 +0100 Subject: [PATCH 60/97] GH-121012: Set index to -1 when list iterators become exhausted in tier 2 (GH-121483) --- Lib/test/test_list.py | 9 +++++++++ .../2024-07-08-10-31-08.gh-issue-121012.M5hHk-.rst | 2 ++ Python/bytecodes.c | 5 ++++- Python/executor_cases.c.h | 7 +++++-- 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-07-08-10-31-08.gh-issue-121012.M5hHk-.rst diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 4d2d54705fc894..ad7accf2099f43 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -299,6 +299,15 @@ def __eq__(self, other): lst = [X(), X()] X() in lst + def test_tier2_invalidates_iterator(self): + # GH-121012 + for _ in range(100): + a = [1, 2, 3] + it = iter(a) + for _ in it: + pass + a.append(4) + self.assertEqual(list(it), []) if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-08-10-31-08.gh-issue-121012.M5hHk-.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-08-10-31-08.gh-issue-121012.M5hHk-.rst new file mode 100644 index 00000000000000..7b04eb68b03752 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-08-10-31-08.gh-issue-121012.M5hHk-.rst @@ -0,0 +1,2 @@ +Tier 2 execution now ensures that list iterators remain exhausted, once they +become exhausted. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 76587a4f0dc695..84241c64ffae88 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2967,7 +2967,10 @@ dummy_func( assert(Py_TYPE(iter_o) == &PyListIter_Type); PyListObject *seq = it->it_seq; EXIT_IF(seq == NULL); - EXIT_IF((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)); + if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { + it->it_index = -1; + EXIT_IF(1); + } } op(_ITER_NEXT_LIST, (iter -- iter, next)) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 3b999465aac815..8f6bc75b528d9b 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3055,8 +3055,11 @@ JUMP_TO_JUMP_TARGET(); } if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) { - UOP_STAT_INC(uopcode, miss); - JUMP_TO_JUMP_TARGET(); + it->it_index = -1; + if (1) { + UOP_STAT_INC(uopcode, miss); + JUMP_TO_JUMP_TARGET(); + } } break; } From 5289550b33de3d56f89a5d44a665283f7c8483a7 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Mon, 8 Jul 2024 11:32:17 -0500 Subject: [PATCH 61/97] gh-121374: Correct docstrings in `_interpchannels` (gh-121418) --- Modules/_interpchannelsmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_interpchannelsmodule.c b/Modules/_interpchannelsmodule.c index ff8dacf5bd1ad0..47dbdeb9a37c44 100644 --- a/Modules/_interpchannelsmodule.c +++ b/Modules/_interpchannelsmodule.c @@ -2977,7 +2977,7 @@ channelsmod_send(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(channelsmod_send_doc, -"channel_send(cid, obj, blocking=True)\n\ +"channel_send(cid, obj, *, blocking=True, timeout=None)\n\ \n\ Add the object's data to the channel's queue.\n\ By default this waits for the object to be received."); @@ -3027,7 +3027,7 @@ channelsmod_send_buffer(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(channelsmod_send_buffer_doc, -"channel_send_buffer(cid, obj, blocking=True)\n\ +"channel_send_buffer(cid, obj, *, blocking=True, timeout=None)\n\ \n\ Add the object's buffer to the channel's queue.\n\ By default this waits for the object to be received."); From db00fee3a22db1c4b893b432c64a8123d7e92322 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Mon, 8 Jul 2024 17:41:01 +0100 Subject: [PATCH 62/97] GH-119169: Simplify `os.walk()` exception handling (#121435) Handle errors from `os.scandir()` and `ScandirIterator` similarly, which lets us loop over directory entries with `for`. --- Lib/os.py | 84 ++++++++----------- ...-07-06-16-08-39.gh-issue-119169.o0YymL.rst | 1 + 2 files changed, 35 insertions(+), 50 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-06-16-08-39.gh-issue-119169.o0YymL.rst diff --git a/Lib/os.py b/Lib/os.py index 4b48afb040e565..aaa758d955fe4c 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -373,61 +373,45 @@ def walk(top, topdown=True, onerror=None, followlinks=False): # minor reason when (say) a thousand readable directories are still # left to visit. try: - scandir_it = scandir(top) + with scandir(top) as entries: + for entry in entries: + try: + if followlinks is _walk_symlinks_as_files: + is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction() + else: + is_dir = entry.is_dir() + except OSError: + # If is_dir() raises an OSError, consider the entry not to + # be a directory, same behaviour as os.path.isdir(). + is_dir = False + + if is_dir: + dirs.append(entry.name) + else: + nondirs.append(entry.name) + + if not topdown and is_dir: + # Bottom-up: traverse into sub-directory, but exclude + # symlinks to directories if followlinks is False + if followlinks: + walk_into = True + else: + try: + is_symlink = entry.is_symlink() + except OSError: + # If is_symlink() raises an OSError, consider the + # entry not to be a symbolic link, same behaviour + # as os.path.islink(). + is_symlink = False + walk_into = not is_symlink + + if walk_into: + walk_dirs.append(entry.path) except OSError as error: if onerror is not None: onerror(error) continue - cont = False - with scandir_it: - while True: - try: - try: - entry = next(scandir_it) - except StopIteration: - break - except OSError as error: - if onerror is not None: - onerror(error) - cont = True - break - - try: - if followlinks is _walk_symlinks_as_files: - is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction() - else: - is_dir = entry.is_dir() - except OSError: - # If is_dir() raises an OSError, consider the entry not to - # be a directory, same behaviour as os.path.isdir(). - is_dir = False - - if is_dir: - dirs.append(entry.name) - else: - nondirs.append(entry.name) - - if not topdown and is_dir: - # Bottom-up: traverse into sub-directory, but exclude - # symlinks to directories if followlinks is False - if followlinks: - walk_into = True - else: - try: - is_symlink = entry.is_symlink() - except OSError: - # If is_symlink() raises an OSError, consider the - # entry not to be a symbolic link, same behaviour - # as os.path.islink(). - is_symlink = False - walk_into = not is_symlink - - if walk_into: - walk_dirs.append(entry.path) - if cont: - continue - if topdown: # Yield before sub-directory traversal if going top down yield top, dirs, nondirs diff --git a/Misc/NEWS.d/next/Library/2024-07-06-16-08-39.gh-issue-119169.o0YymL.rst b/Misc/NEWS.d/next/Library/2024-07-06-16-08-39.gh-issue-119169.o0YymL.rst new file mode 100644 index 00000000000000..5d9b50d452a9cd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-06-16-08-39.gh-issue-119169.o0YymL.rst @@ -0,0 +1 @@ +Slightly speed up :func:`os.walk` by simplifying exception handling. From 31873bea471020ca5deaf735d9acb0f1abeb1d3c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:32:30 +0200 Subject: [PATCH 63/97] gh-121487: Fix deprecation warning for ATOMIC_VAR_INIT in mimalloc (gh-121488) --- Include/internal/mimalloc/mimalloc/atomic.h | 8 ++++++-- .../Build/2024-07-08-14-01-17.gh-issue-121487.ekHmpR.rst | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-07-08-14-01-17.gh-issue-121487.ekHmpR.rst diff --git a/Include/internal/mimalloc/mimalloc/atomic.h b/Include/internal/mimalloc/mimalloc/atomic.h index 52f82487685cdb..cdd9c372beafd5 100644 --- a/Include/internal/mimalloc/mimalloc/atomic.h +++ b/Include/internal/mimalloc/mimalloc/atomic.h @@ -23,7 +23,9 @@ terms of the MIT license. A copy of the license can be found in the file #define _Atomic(tp) std::atomic #define mi_atomic(name) std::atomic_##name #define mi_memory_order(name) std::memory_order_##name -#if !defined(ATOMIC_VAR_INIT) || (__cplusplus >= 202002L) // c++20, see issue #571 +#if (__cplusplus >= 202002L) // c++20, see issue #571 + #define MI_ATOMIC_VAR_INIT(x) x +#elif !defined(ATOMIC_VAR_INIT) #define MI_ATOMIC_VAR_INIT(x) x #else #define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x) @@ -39,7 +41,9 @@ terms of the MIT license. A copy of the license can be found in the file #include #define mi_atomic(name) atomic_##name #define mi_memory_order(name) memory_order_##name -#if !defined(ATOMIC_VAR_INIT) || (__STDC_VERSION__ >= 201710L) // c17, see issue #735 +#if (__STDC_VERSION__ >= 201710L) // c17, see issue #735 + #define MI_ATOMIC_VAR_INIT(x) x +#elif !defined(ATOMIC_VAR_INIT) #define MI_ATOMIC_VAR_INIT(x) x #else #define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x) diff --git a/Misc/NEWS.d/next/Build/2024-07-08-14-01-17.gh-issue-121487.ekHmpR.rst b/Misc/NEWS.d/next/Build/2024-07-08-14-01-17.gh-issue-121487.ekHmpR.rst new file mode 100644 index 00000000000000..e30d4dcdbfe779 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-07-08-14-01-17.gh-issue-121487.ekHmpR.rst @@ -0,0 +1 @@ +Fix deprecation warning for ATOMIC_VAR_INIT in mimalloc. From 1d3cf79a501a93a7a488fc75d4db3060c5ee7d1a Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 8 Jul 2024 14:52:07 -0400 Subject: [PATCH 64/97] gh-121368: Fix seq lock memory ordering in _PyType_Lookup (#121388) The `_PySeqLock_EndRead` function needs an acquire fence to ensure that the load of the sequence happens after any loads within the read side critical section. The missing fence can trigger bugs on macOS arm64. Additionally, we need a release fence in `_PySeqLock_LockWrite` to ensure that the sequence update is visible before any modifications to the cache entry. --- Include/cpython/pyatomic.h | 3 +++ Include/cpython/pyatomic_gcc.h | 4 +++ Include/cpython/pyatomic_msc.h | 12 +++++++++ Include/cpython/pyatomic_std.h | 7 +++++ Include/internal/pycore_lock.h | 8 +++--- ...-07-04-23-38-30.gh-issue-121368.m3EF9E.rst | 3 +++ Modules/_testcapi/pyatomic.c | 1 + Objects/typeobject.c | 2 +- Python/lock.c | 26 +++++++++++-------- 9 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-07-04-23-38-30.gh-issue-121368.m3EF9E.rst diff --git a/Include/cpython/pyatomic.h b/Include/cpython/pyatomic.h index 55a139bb9158db..4ecef4f56edf42 100644 --- a/Include/cpython/pyatomic.h +++ b/Include/cpython/pyatomic.h @@ -510,6 +510,9 @@ _Py_atomic_load_ssize_acquire(const Py_ssize_t *obj); // See https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence static inline void _Py_atomic_fence_seq_cst(void); +// Acquire fence +static inline void _Py_atomic_fence_acquire(void); + // Release fence static inline void _Py_atomic_fence_release(void); diff --git a/Include/cpython/pyatomic_gcc.h b/Include/cpython/pyatomic_gcc.h index f2ebdeeb5524e4..ef09954d53ac1d 100644 --- a/Include/cpython/pyatomic_gcc.h +++ b/Include/cpython/pyatomic_gcc.h @@ -542,6 +542,10 @@ static inline void _Py_atomic_fence_seq_cst(void) { __atomic_thread_fence(__ATOMIC_SEQ_CST); } + static inline void +_Py_atomic_fence_acquire(void) +{ __atomic_thread_fence(__ATOMIC_ACQUIRE); } + static inline void _Py_atomic_fence_release(void) { __atomic_thread_fence(__ATOMIC_RELEASE); } diff --git a/Include/cpython/pyatomic_msc.h b/Include/cpython/pyatomic_msc.h index f32995c1f578ac..84da21bdcbff4f 100644 --- a/Include/cpython/pyatomic_msc.h +++ b/Include/cpython/pyatomic_msc.h @@ -1066,6 +1066,18 @@ _Py_atomic_fence_seq_cst(void) #else # error "no implementation of _Py_atomic_fence_seq_cst" #endif +} + + static inline void +_Py_atomic_fence_acquire(void) +{ +#if defined(_M_ARM64) + __dmb(_ARM64_BARRIER_ISHLD); +#elif defined(_M_X64) || defined(_M_IX86) + _ReadBarrier(); +#else +# error "no implementation of _Py_atomic_fence_acquire" +#endif } static inline void diff --git a/Include/cpython/pyatomic_std.h b/Include/cpython/pyatomic_std.h index 0cdce4e6dd39f0..7c71e94c68f8e6 100644 --- a/Include/cpython/pyatomic_std.h +++ b/Include/cpython/pyatomic_std.h @@ -961,6 +961,13 @@ _Py_atomic_fence_seq_cst(void) atomic_thread_fence(memory_order_seq_cst); } + static inline void +_Py_atomic_fence_acquire(void) +{ + _Py_USING_STD; + atomic_thread_fence(memory_order_acquire); +} + static inline void _Py_atomic_fence_release(void) { diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 3824434f3f375d..e6da083b807ce5 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -228,12 +228,12 @@ PyAPI_FUNC(void) _PySeqLock_AbandonWrite(_PySeqLock *seqlock); PyAPI_FUNC(uint32_t) _PySeqLock_BeginRead(_PySeqLock *seqlock); // End the read operation and confirm that the sequence number has not changed. -// Returns 1 if the read was successful or 0 if the read should be re-tried. -PyAPI_FUNC(uint32_t) _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous); +// Returns 1 if the read was successful or 0 if the read should be retried. +PyAPI_FUNC(int) _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous); // Check if the lock was held during a fork and clear the lock. Returns 1 -// if the lock was held and any associated datat should be cleared. -PyAPI_FUNC(uint32_t) _PySeqLock_AfterFork(_PySeqLock *seqlock); +// if the lock was held and any associated data should be cleared. +PyAPI_FUNC(int) _PySeqLock_AfterFork(_PySeqLock *seqlock); #ifdef __cplusplus } diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-04-23-38-30.gh-issue-121368.m3EF9E.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-04-23-38-30.gh-issue-121368.m3EF9E.rst new file mode 100644 index 00000000000000..3df5b216cbc0af --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-04-23-38-30.gh-issue-121368.m3EF9E.rst @@ -0,0 +1,3 @@ +Fix race condition in ``_PyType_Lookup`` in the free-threaded build due to +a missing memory fence. This could lead to ``_PyType_Lookup`` returning +incorrect results on arm64. diff --git a/Modules/_testcapi/pyatomic.c b/Modules/_testcapi/pyatomic.c index 4f72844535ebd6..850de6f9c3366b 100644 --- a/Modules/_testcapi/pyatomic.c +++ b/Modules/_testcapi/pyatomic.c @@ -125,6 +125,7 @@ test_atomic_fences(PyObject *self, PyObject *obj) { // Just make sure that the fences compile. We are not // testing any synchronizing ordering. _Py_atomic_fence_seq_cst(); + _Py_atomic_fence_acquire(); _Py_atomic_fence_release(); Py_RETURN_NONE; } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 447e561c0d4440..df895bc65983c0 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5387,7 +5387,7 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name) #ifdef Py_GIL_DISABLED // synchronize-with other writing threads by doing an acquire load on the sequence while (1) { - int sequence = _PySeqLock_BeginRead(&entry->sequence); + uint32_t sequence = _PySeqLock_BeginRead(&entry->sequence); uint32_t entry_version = _Py_atomic_load_uint32_relaxed(&entry->version); uint32_t type_version = _Py_atomic_load_uint32_acquire(&type->tp_version_tag); if (entry_version == type_version && diff --git a/Python/lock.c b/Python/lock.c index 7c6a5175e88ff1..57675fe1873fa2 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -514,6 +514,7 @@ void _PySeqLock_LockWrite(_PySeqLock *seqlock) } else if (_Py_atomic_compare_exchange_uint32(&seqlock->sequence, &prev, prev + 1)) { // We've locked the cache + _Py_atomic_fence_release(); break; } else { @@ -547,28 +548,31 @@ uint32_t _PySeqLock_BeginRead(_PySeqLock *seqlock) return sequence; } -uint32_t _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous) +int _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous) { - // Synchronize again and validate that the entry hasn't been updated - // while we were readying the values. - if (_Py_atomic_load_uint32_acquire(&seqlock->sequence) == previous) { + // gh-121368: We need an explicit acquire fence here to ensure that + // this load of the sequence number is not reordered before any loads + // within the read lock. + _Py_atomic_fence_acquire(); + + if (_Py_atomic_load_uint32_relaxed(&seqlock->sequence) == previous) { return 1; - } + } - _Py_yield(); - return 0; + _Py_yield(); + return 0; } -uint32_t _PySeqLock_AfterFork(_PySeqLock *seqlock) +int _PySeqLock_AfterFork(_PySeqLock *seqlock) { // Synchronize again and validate that the entry hasn't been updated // while we were readying the values. - if (SEQLOCK_IS_UPDATING(seqlock->sequence)) { + if (SEQLOCK_IS_UPDATING(seqlock->sequence)) { seqlock->sequence = 0; return 1; - } + } - return 0; + return 0; } #undef PyMutex_Lock From 2be37ec8e2f68d2c2fce7ea7b9eef73d218a22f9 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:02:01 +0100 Subject: [PATCH 65/97] gh-121404: remove direct accesses to u_private from codegen functions (#121500) --- Python/compile.c | 69 +++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 1d6b54d411daf1..fa70ca101c40e5 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -74,6 +74,10 @@ typedef _Py_SourceLocation location; typedef struct _PyCfgBuilder cfg_builder; +struct compiler; + +static PyObject *compiler_maybe_mangle(struct compiler *c, PyObject *name); + #define LOCATION(LNO, END_LNO, COL, END_COL) \ ((const _Py_SourceLocation){(LNO), (END_LNO), (COL), (END_COL)}) @@ -887,10 +891,10 @@ compiler_addop_o(struct compiler_unit *u, location loc, #define LOAD_ZERO_SUPER_METHOD -4 static int -compiler_addop_name(struct compiler_unit *u, location loc, +compiler_addop_name(struct compiler *c, location loc, int opcode, PyObject *dict, PyObject *o) { - PyObject *mangled = _Py_MaybeMangle(u->u_private, u->u_ste, o); + PyObject *mangled = compiler_maybe_mangle(c, o); if (!mangled) { return ERROR; } @@ -925,7 +929,7 @@ compiler_addop_name(struct compiler_unit *u, location loc, arg <<= 2; arg |= 1; } - return codegen_addop_i(u->u_instr_sequence, opcode, arg, loc); + return codegen_addop_i(INSTR_SEQUENCE(c), opcode, arg, loc); } /* Add an opcode with an integer argument */ @@ -993,7 +997,7 @@ codegen_addop_j(instr_sequence *seq, location loc, } #define ADDOP_NAME(C, LOC, OP, O, TYPE) \ - RETURN_IF_ERROR(compiler_addop_name((C)->u, (LOC), (OP), (C)->u->u_metadata.u_ ## TYPE, (O))) + RETURN_IF_ERROR(compiler_addop_name((C), (LOC), (OP), (C)->u->u_metadata.u_ ## TYPE, (O))) #define ADDOP_I(C, LOC, OP, O) \ RETURN_IF_ERROR(codegen_addop_i(INSTR_SEQUENCE(C), (OP), (O), (LOC))) @@ -1052,8 +1056,8 @@ codegen_addop_j(instr_sequence *seq, location loc, static int -compiler_enter_scope(struct compiler *c, identifier name, - int scope_type, void *key, int lineno) +compiler_enter_scope(struct compiler *c, identifier name, int scope_type, + void *key, int lineno, PyObject *private) { location loc = LOCATION(lineno, lineno, 0, 0); @@ -1132,7 +1136,6 @@ compiler_enter_scope(struct compiler *c, identifier name, return ERROR; } - u->u_private = NULL; u->u_deferred_annotations = NULL; if (scope_type == COMPILER_SCOPE_CLASS) { u->u_static_attributes = PySet_New(0); @@ -1146,6 +1149,10 @@ compiler_enter_scope(struct compiler *c, identifier name, } u->u_instr_sequence = (instr_sequence*)_PyInstructionSequence_New(); + if (!u->u_instr_sequence) { + compiler_unit_free(u); + return ERROR; + } /* Push the old compiler_unit on the stack. */ if (c->u) { @@ -1156,8 +1163,13 @@ compiler_enter_scope(struct compiler *c, identifier name, return ERROR; } Py_DECREF(capsule); - u->u_private = Py_XNewRef(c->u->u_private); + if (private == NULL) { + private = c->u->u_private; + } } + + u->u_private = Py_XNewRef(private); + c->u = u; c->c_nestlevel++; @@ -1436,7 +1448,7 @@ compiler_setup_annotations_scope(struct compiler *c, location loc, void *key, PyObject *name) { if (compiler_enter_scope(c, name, COMPILER_SCOPE_ANNOTATIONS, - key, loc.lineno) == -1) { + key, loc.lineno, NULL) == -1) { return ERROR; } c->u->u_metadata.u_posonlyargcount = 1; @@ -1597,7 +1609,7 @@ compiler_enter_anonymous_scope(struct compiler* c, mod_ty mod) _Py_DECLARE_STR(anon_module, ""); RETURN_IF_ERROR( compiler_enter_scope(c, &_Py_STR(anon_module), COMPILER_SCOPE_MODULE, - mod, 1)); + mod, 1, NULL)); return SUCCESS; } @@ -1770,7 +1782,7 @@ compiler_kwonlydefaults(struct compiler *c, location loc, arg_ty arg = asdl_seq_GET(kwonlyargs, i); expr_ty default_ = asdl_seq_GET(kw_defaults, i); if (default_) { - PyObject *mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, arg->arg); + PyObject *mangled = compiler_maybe_mangle(c, arg->arg); if (!mangled) { goto error; } @@ -1827,7 +1839,7 @@ compiler_argannotation(struct compiler *c, identifier id, if (!annotation) { return SUCCESS; } - PyObject *mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, id); + PyObject *mangled = compiler_maybe_mangle(c, id); if (!mangled) { return ERROR; } @@ -2052,7 +2064,7 @@ compiler_type_param_bound_or_default(struct compiler *c, expr_ty e, bool allow_starred) { if (compiler_enter_scope(c, name, COMPILER_SCOPE_ANNOTATIONS, - key, e->lineno) == -1) { + key, e->lineno, NULL) == -1) { return ERROR; } if (allow_starred && e->kind == Starred_kind) { @@ -2197,7 +2209,7 @@ compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t f } RETURN_IF_ERROR( - compiler_enter_scope(c, name, scope_type, (void *)s, firstlineno)); + compiler_enter_scope(c, name, scope_type, (void *)s, firstlineno, NULL)); Py_ssize_t first_instr = 0; PyObject *docstring = _PyAST_GetDocString(body); @@ -2324,7 +2336,7 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) return ERROR; } if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_ANNOTATIONS, - (void *)type_params, firstlineno) == -1) { + (void *)type_params, firstlineno, NULL) == -1) { Py_DECREF(type_params_name); return ERROR; } @@ -2407,12 +2419,10 @@ compiler_class_body(struct compiler *c, stmt_ty s, int firstlineno) /* 1. compile the class body into a code object */ RETURN_IF_ERROR( - compiler_enter_scope(c, s->v.ClassDef.name, - COMPILER_SCOPE_CLASS, (void *)s, firstlineno)); + compiler_enter_scope(c, s->v.ClassDef.name, COMPILER_SCOPE_CLASS, + (void *)s, firstlineno, s->v.ClassDef.name)); location loc = LOCATION(firstlineno, firstlineno, 0, 0); - /* use the class name for name mangling */ - Py_XSETREF(c->u->u_private, Py_NewRef(s->v.ClassDef.name)); /* load (global) __name__ ... */ if (compiler_nameop(c, loc, &_Py_ID(__name__), Load) < 0) { compiler_exit_scope(c); @@ -2558,12 +2568,11 @@ compiler_class(struct compiler *c, stmt_ty s) return ERROR; } if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_ANNOTATIONS, - (void *)type_params, firstlineno) == -1) { + (void *)type_params, firstlineno, s->v.ClassDef.name) == -1) { Py_DECREF(type_params_name); return ERROR; } Py_DECREF(type_params_name); - Py_XSETREF(c->u->u_private, Py_NewRef(s->v.ClassDef.name)); RETURN_IF_ERROR_IN_SCOPE(c, compiler_type_params(c, type_params)); _Py_DECLARE_STR(type_params, ".type_params"); RETURN_IF_ERROR_IN_SCOPE(c, compiler_nameop(c, loc, &_Py_STR(type_params), Store)); @@ -2643,7 +2652,7 @@ compiler_typealias_body(struct compiler *c, stmt_ty s) location loc = LOC(s); PyObject *name = s->v.TypeAlias.name->v.Name.id; RETURN_IF_ERROR( - compiler_enter_scope(c, name, COMPILER_SCOPE_FUNCTION, s, loc.lineno)); + compiler_enter_scope(c, name, COMPILER_SCOPE_FUNCTION, s, loc.lineno, NULL)); /* Make None the first constant, so the evaluate function can't have a docstring. */ RETURN_IF_ERROR(compiler_add_const(c->c_const_cache, c->u, Py_None)); @@ -2678,7 +2687,7 @@ compiler_typealias(struct compiler *c, stmt_ty s) return ERROR; } if (compiler_enter_scope(c, type_params_name, COMPILER_SCOPE_ANNOTATIONS, - (void *)type_params, loc.lineno) == -1) { + (void *)type_params, loc.lineno, NULL) == -1) { Py_DECREF(type_params_name); return ERROR; } @@ -2947,7 +2956,7 @@ compiler_lambda(struct compiler *c, expr_ty e) _Py_DECLARE_STR(anon_lambda, ""); RETURN_IF_ERROR( compiler_enter_scope(c, &_Py_STR(anon_lambda), COMPILER_SCOPE_LAMBDA, - (void *)e, e->lineno)); + (void *)e, e->lineno, NULL)); /* Make None the first constant, so the lambda can't have a docstring. */ @@ -4115,7 +4124,7 @@ compiler_nameop(struct compiler *c, location loc, return ERROR; } - mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, name); + mangled = compiler_maybe_mangle(c, name); if (!mangled) { return ERROR; } @@ -5712,7 +5721,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, } else { if (compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION, - (void *)e, e->lineno) < 0) + (void *)e, e->lineno, NULL) < 0) { goto error; } @@ -6416,7 +6425,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) if (future_annotations) { VISIT(c, annexpr, s->v.AnnAssign.annotation); ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names); - mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, targ->v.Name.id); + mangled = compiler_maybe_mangle(c, targ->v.Name.id); ADDOP_LOAD_CONST_NEW(c, loc, mangled); ADDOP(c, loc, STORE_SUBSCR); } @@ -7458,6 +7467,12 @@ consts_dict_keys_inorder(PyObject *dict) return consts; } +static PyObject * +compiler_maybe_mangle(struct compiler *c, PyObject *name) +{ + return _Py_MaybeMangle(c->u->u_private, c->u->u_ste, name); +} + static int compute_code_flags(struct compiler *c) { From 006b53a42f72be83ecdfc39f3603cdd66bfcdc45 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:30:05 -0600 Subject: [PATCH 66/97] NEWS: Fix Sphinx warnings and increase threshold for new news nits (#121482) Co-authored-by: Alex Waygood --- Doc/conf.py | 3 ++ Doc/library/profile.rst | 2 +- Doc/tools/check-warnings.py | 2 +- Doc/whatsnew/3.4.rst | 2 +- Doc/whatsnew/3.5.rst | 4 +-- Doc/whatsnew/3.7.rst | 4 +-- Misc/HISTORY | 2 +- Misc/NEWS.d/3.10.0a1.rst | 30 +++++++++---------- Misc/NEWS.d/3.10.0a2.rst | 2 +- Misc/NEWS.d/3.10.0a3.rst | 2 +- Misc/NEWS.d/3.10.0a4.rst | 8 ++--- Misc/NEWS.d/3.10.0a5.rst | 2 +- Misc/NEWS.d/3.10.0a6.rst | 2 +- Misc/NEWS.d/3.10.0a7.rst | 2 +- Misc/NEWS.d/3.10.0b1.rst | 8 ++--- Misc/NEWS.d/3.11.0a1.rst | 14 ++++----- Misc/NEWS.d/3.11.0a2.rst | 4 +-- Misc/NEWS.d/3.11.0a3.rst | 2 +- Misc/NEWS.d/3.11.0a4.rst | 2 +- Misc/NEWS.d/3.11.0b1.rst | 2 +- Misc/NEWS.d/3.12.0a1.rst | 16 +++++----- Misc/NEWS.d/3.12.0a2.rst | 8 ++--- Misc/NEWS.d/3.12.0a3.rst | 2 +- Misc/NEWS.d/3.12.0a4.rst | 2 +- Misc/NEWS.d/3.12.0b1.rst | 2 +- Misc/NEWS.d/3.13.0a1.rst | 10 +++---- Misc/NEWS.d/3.13.0a2.rst | 2 +- Misc/NEWS.d/3.13.0a3.rst | 4 +-- Misc/NEWS.d/3.13.0a5.rst | 6 ++-- Misc/NEWS.d/3.13.0a6.rst | 2 +- Misc/NEWS.d/3.13.0b1.rst | 20 ++++++------- Misc/NEWS.d/3.5.0a1.rst | 3 +- Misc/NEWS.d/3.6.0a1.rst | 6 ++-- Misc/NEWS.d/3.8.0a1.rst | 4 +-- Misc/NEWS.d/3.8.0a4.rst | 4 +-- Misc/NEWS.d/3.8.0b1.rst | 2 +- Misc/NEWS.d/3.9.0a1.rst | 16 +++++----- Misc/NEWS.d/3.9.0a6.rst | 6 ++-- ...-04-24-05-34-23.gh-issue-103194.GwBwWL.rst | 2 +- 39 files changed, 110 insertions(+), 106 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 8a14646801ebac..29b1b2db32718b 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -272,6 +272,9 @@ ('c:data', 'PyExc_UnicodeWarning'), ('c:data', 'PyExc_UserWarning'), ('c:data', 'PyExc_Warning'), + # Undocumented public C macros + ('c:macro', 'Py_BUILD_ASSERT'), + ('c:macro', 'Py_BUILD_ASSERT_EXPR'), # Do not error nit-picky mode builds when _SubParsersAction.add_parser cannot # be resolved, as the method is currently undocumented. For context, see # https://github.com/python/cpython/pull/103289. diff --git a/Doc/library/profile.rst b/Doc/library/profile.rst index 9721da7220d54d..d7940b3040bbdb 100644 --- a/Doc/library/profile.rst +++ b/Doc/library/profile.rst @@ -699,7 +699,7 @@ you are using :class:`profile.Profile` or :class:`cProfile.Profile`, As the :class:`cProfile.Profile` class cannot be calibrated, custom timer functions should be used with care and should be as fast as possible. For the best results with a custom timer, it might be necessary to hard-code it - in the C source of the internal :mod:`_lsprof` module. + in the C source of the internal :mod:`!_lsprof` module. Python 3.3 adds several new functions in :mod:`time` that can be used to make precise measurements of process or wall-clock time. For example, see diff --git a/Doc/tools/check-warnings.py b/Doc/tools/check-warnings.py index c50b00636c36ce..67623b83d3a67d 100644 --- a/Doc/tools/check-warnings.py +++ b/Doc/tools/check-warnings.py @@ -14,7 +14,7 @@ from typing import TextIO # Fail if NEWS nit found before this line number -NEWS_NIT_THRESHOLD = 200 +NEWS_NIT_THRESHOLD = 1700 # Exclude these whether they're dirty or clean, # because they trigger a rebuild of dirty files. diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index 8aef0f5ac26728..938dd273e7e102 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -1495,7 +1495,7 @@ The dictionary returned by :meth:`.SSLSocket.getpeercert` contains additional stat ---- -The :mod:`stat` module is now backed by a C implementation in :mod:`_stat`. A C +The :mod:`stat` module is now backed by a C implementation in :mod:`!_stat`. A C implementation is required as most of the values aren't standardized and are platform-dependent. (Contributed by Christian Heimes in :issue:`11016`.) diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index cd8a903327cc2f..75654f3e78eb16 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -1935,8 +1935,8 @@ specifying the namespace in which the code will be running. tkinter ------- -The :mod:`tkinter._fix` module used for setting up the Tcl/Tk environment -on Windows has been replaced by a private function in the :mod:`_tkinter` +The :mod:`!tkinter._fix` module used for setting up the Tcl/Tk environment +on Windows has been replaced by a private function in the :mod:`!_tkinter` module which makes no permanent changes to environment variables. (Contributed by Zachary Ware in :issue:`20035`.) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 69d043bcf7efd5..ae750cb9bba696 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -2048,7 +2048,7 @@ The :mod:`macpath` is now deprecated and will be removed in Python 3.8. threading --------- -:mod:`dummy_threading` and :mod:`_dummy_thread` have been deprecated. It is +:mod:`!dummy_threading` and :mod:`!_dummy_thread` have been deprecated. It is no longer possible to build Python with threading disabled. Use :mod:`threading` instead. (Contributed by Antoine Pitrou in :issue:`31370`.) @@ -2184,7 +2184,7 @@ The following features and APIs have been removed from Python 3.7: ``socket.socketpair`` on Python 3.5 and newer. * :mod:`asyncio` no longer exports the :mod:`selectors` and - :mod:`_overlapped` modules as ``asyncio.selectors`` and + :mod:`!_overlapped` modules as ``asyncio.selectors`` and ``asyncio._overlapped``. Replace ``from asyncio import selectors`` with ``import selectors``. diff --git a/Misc/HISTORY b/Misc/HISTORY index 8ca35e1af62c05..a74d7e06acd071 100644 --- a/Misc/HISTORY +++ b/Misc/HISTORY @@ -3952,7 +3952,7 @@ Library - Issue #18626: the inspect module now offers a basic command line introspection interface (Initial patch by Claudiu Popa) -- Issue #3015: Fixed tkinter with wantobject=False. Any Tcl command call +- Issue #3015: Fixed tkinter with ``wantobjects=False``. Any Tcl command call returned empty string. - Issue #19037: The mailbox module now makes all changes to maildir files diff --git a/Misc/NEWS.d/3.10.0a1.rst b/Misc/NEWS.d/3.10.0a1.rst index 9a729a45b160eb..f30ed548e7e033 100644 --- a/Misc/NEWS.d/3.10.0a1.rst +++ b/Misc/NEWS.d/3.10.0a1.rst @@ -97,7 +97,7 @@ convention. Patch by Donghee Na. .. nonce: aJS9B3 .. section: Core and Builtins -Port the :mod:`_bisect` module to the multi-phase initialization API +Port the :mod:`!_bisect` module to the multi-phase initialization API (:pep:`489`). .. @@ -128,7 +128,7 @@ Taskaya. .. nonce: lh335O .. section: Core and Builtins -Port the :mod:`_lsprof` extension module to multi-phase initialization +Port the :mod:`!_lsprof` extension module to multi-phase initialization (:pep:`489`). .. @@ -148,7 +148,7 @@ Port the :mod:`cmath` extension module to multi-phase initialization .. nonce: jiXmyT .. section: Core and Builtins -Port the :mod:`_scproxy` extension module to multi-phase initialization +Port the :mod:`!_scproxy` extension module to multi-phase initialization (:pep:`489`). .. @@ -168,7 +168,7 @@ Port the :mod:`termios` extension module to multi-phase initialization .. nonce: QuDIut .. section: Core and Builtins -Convert the :mod:`_sha256` extension module types to heap types. +Convert the :mod:`!_sha256` extension module types to heap types. .. @@ -187,7 +187,7 @@ classes with a huge amount of arguments. Patch by Pablo Galindo. .. nonce: CnRME3 .. section: Core and Builtins -Port the :mod:`_overlapped` extension module to multi-phase initialization +Port the :mod:`!_overlapped` extension module to multi-phase initialization (:pep:`489`). .. @@ -197,7 +197,7 @@ Port the :mod:`_overlapped` extension module to multi-phase initialization .. nonce: X9CZgo .. section: Core and Builtins -Port the :mod:`_curses_panel` extension module to multi-phase initialization +Port the :mod:`!_curses_panel` extension module to multi-phase initialization (:pep:`489`). .. @@ -207,7 +207,7 @@ Port the :mod:`_curses_panel` extension module to multi-phase initialization .. nonce: 5jZymK .. section: Core and Builtins -Port the :mod:`_opcode` extension module to multi-phase initialization +Port the :mod:`!_opcode` extension module to multi-phase initialization (:pep:`489`). .. @@ -282,7 +282,7 @@ initialized ``_ast`` module. .. nonce: vcxSUa .. section: Core and Builtins -Convert :mod:`_operator` to use :c:func:`PyType_FromSpec`. +Convert :mod:`!_operator` to use :c:func:`PyType_FromSpec`. .. @@ -291,7 +291,7 @@ Convert :mod:`_operator` to use :c:func:`PyType_FromSpec`. .. nonce: fubBkb .. section: Core and Builtins -Port :mod:`_sha3` to multi-phase init. Convert static types to heap types. +Port :mod:`!_sha3` to multi-phase init. Convert static types to heap types. .. @@ -300,7 +300,7 @@ Port :mod:`_sha3` to multi-phase init. Convert static types to heap types. .. nonce: FC13e7 .. section: Core and Builtins -Port the :mod:`_blake2` extension module to the multi-phase initialization +Port the :mod:`!_blake2` extension module to the multi-phase initialization API (:pep:`489`). .. @@ -339,7 +339,7 @@ The output of ``python --help`` contains now only ASCII characters. .. nonce: O0d3ym .. section: Core and Builtins -Port the :mod:`_sha1`, :mod:`_sha512`, and :mod:`_md5` extension modules to +Port the :mod:`!_sha1`, :mod:`!_sha512`, and :mod:`!_md5` extension modules to multi-phase initialization API (:pep:`489`). .. @@ -636,7 +636,7 @@ Remove the remaining files from the old parser and the :mod:`symbol` module. .. nonce: _yI-ax .. section: Core and Builtins -Convert :mod:`_bz2` to use :c:func:`PyType_FromSpec`. +Convert :mod:`!_bz2` to use :c:func:`PyType_FromSpec`. .. @@ -666,7 +666,7 @@ by Brandt Bucher. .. nonce: 61iyYh .. section: Core and Builtins -Port :mod:`_gdbm` to multiphase initialization. +Port :mod:`!_gdbm` to multiphase initialization. .. @@ -696,7 +696,7 @@ for emitting syntax errors. Patch by Pablo Galindo. .. nonce: mmlp3Q .. section: Core and Builtins -Port :mod:`_dbm` to multiphase initialization. +Port :mod:`!_dbm` to multiphase initialization. .. @@ -1010,7 +1010,7 @@ Port :mod:`mmap` to multiphase initialization. .. nonce: Kfe9fT .. section: Core and Builtins -Port :mod:`_lzma` to multiphase initialization. +Port :mod:`!_lzma` to multiphase initialization. .. diff --git a/Misc/NEWS.d/3.10.0a2.rst b/Misc/NEWS.d/3.10.0a2.rst index 79f570439b52b8..bdf9488c81bae1 100644 --- a/Misc/NEWS.d/3.10.0a2.rst +++ b/Misc/NEWS.d/3.10.0a2.rst @@ -362,7 +362,7 @@ plistlib: fix parsing XML plists with hexadecimal integer values .. nonce: 85BsRA .. section: Library -Fix an incorrectly formatted error from :meth:`_codecs.charmap_decode` when +Fix an incorrectly formatted error from :meth:`!_codecs.charmap_decode` when called with a mapped value outside the range of valid Unicode code points. PR by Max Bernstein. diff --git a/Misc/NEWS.d/3.10.0a3.rst b/Misc/NEWS.d/3.10.0a3.rst index 179cf3e9cfb08c..2aef87ab929aab 100644 --- a/Misc/NEWS.d/3.10.0a3.rst +++ b/Misc/NEWS.d/3.10.0a3.rst @@ -1386,7 +1386,7 @@ Python already implicitly installs signal handlers: see The ``Py_TRASHCAN_BEGIN`` macro no longer accesses PyTypeObject attributes, but now can get the condition by calling the new private -:c:func:`_PyTrash_cond()` function which hides implementation details. +:c:func:`!_PyTrash_cond()` function which hides implementation details. .. diff --git a/Misc/NEWS.d/3.10.0a4.rst b/Misc/NEWS.d/3.10.0a4.rst index ae667f2bffe192..5cea16c259d5ee 100644 --- a/Misc/NEWS.d/3.10.0a4.rst +++ b/Misc/NEWS.d/3.10.0a4.rst @@ -193,7 +193,7 @@ subinterpreters. Patch by Victor Stinner. .. nonce: j7nl6A .. section: Core and Builtins -Make :c:func:`_PyUnicode_FromId` function compatible with subinterpreters. +Make :c:func:`!_PyUnicode_FromId` function compatible with subinterpreters. Each interpreter now has an array of identifier objects (interned strings decoded from UTF-8). Patch by Victor Stinner. @@ -367,7 +367,7 @@ uses "options" instead. .. nonce: Quy3zn .. section: Library -Port the :mod:`_thread` extension module to the multiphase initialization +Port the :mod:`!_thread` extension module to the multiphase initialization API (:pep:`489`) and convert its static types to heap types. .. @@ -960,8 +960,8 @@ explicitly and so not exported. .. nonce: Je08Ny .. section: C API -Remove the private :c:func:`_Py_fopen` function which is no longer needed. -Use :c:func:`_Py_wfopen` or :c:func:`_Py_fopen_obj` instead. Patch by Victor +Remove the private :c:func:`!_Py_fopen` function which is no longer needed. +Use :c:func:`!_Py_wfopen` or :c:func:`!_Py_fopen_obj` instead. Patch by Victor Stinner. .. diff --git a/Misc/NEWS.d/3.10.0a5.rst b/Misc/NEWS.d/3.10.0a5.rst index dc95e8ce072fd9..a85ea1ff1c2817 100644 --- a/Misc/NEWS.d/3.10.0a5.rst +++ b/Misc/NEWS.d/3.10.0a5.rst @@ -108,7 +108,7 @@ a slice at the start of the ``bytearray`` to a shorter byte string). .. nonce: WfTdfg .. section: Core and Builtins -Fix the :c:func:`_PyUnicode_FromId` function (_Py_IDENTIFIER(var) API) when +Fix the :c:func:`!_PyUnicode_FromId` function (_Py_IDENTIFIER(var) API) when :c:func:`Py_Initialize` / :c:func:`Py_Finalize` is called multiple times: preserve ``_PyRuntime.unicode_ids.next_index`` value. diff --git a/Misc/NEWS.d/3.10.0a6.rst b/Misc/NEWS.d/3.10.0a6.rst index bad3528084897b..31b7df2c61158e 100644 --- a/Misc/NEWS.d/3.10.0a6.rst +++ b/Misc/NEWS.d/3.10.0a6.rst @@ -315,7 +315,7 @@ Adds :const:`resource.RLIMIT_KQUEUES` constant from FreeBSD to the .. section: Library Make the pure Python implementation of :mod:`xml.etree.ElementTree` behave -the same as the C implementation (:mod:`_elementree`) regarding default +the same as the C implementation (:mod:`!_elementree`) regarding default attribute values (by not setting ``specified_attributes=1``). .. diff --git a/Misc/NEWS.d/3.10.0a7.rst b/Misc/NEWS.d/3.10.0a7.rst index fe6213d95a88bb..32ee34d9a68910 100644 --- a/Misc/NEWS.d/3.10.0a7.rst +++ b/Misc/NEWS.d/3.10.0a7.rst @@ -83,7 +83,7 @@ instruction dispatch a bit. .. nonce: PhaT-B .. section: Core and Builtins -Fix reference leak in the :mod:`_hashopenssl` extension. Patch by Pablo +Fix reference leak in the :mod:`!_hashopenssl` extension. Patch by Pablo Galindo. .. diff --git a/Misc/NEWS.d/3.10.0b1.rst b/Misc/NEWS.d/3.10.0b1.rst index 640f3ee58adbae..306e987a41612e 100644 --- a/Misc/NEWS.d/3.10.0b1.rst +++ b/Misc/NEWS.d/3.10.0b1.rst @@ -182,7 +182,7 @@ normally be possible, but might occur in some unusual circumstances. .. nonce: u5Y6bS .. section: Core and Builtins -Importing the :mod:`_signal` module in a subinterpreter has no longer side +Importing the :mod:`!_signal` module in a subinterpreter has no longer side effects. .. @@ -776,11 +776,11 @@ builtins.open() is now io.open(). .. nonce: o1zEk_ .. section: Library -The Python :func:`_pyio.open` function becomes a static method to behave as +The Python :func:`!_pyio.open` function becomes a static method to behave as :func:`io.open` built-in function: don't become a bound method when stored as a class variable. It becomes possible since static methods are now -callable in Python 3.10. Moreover, :func:`_pyio.OpenWrapper` becomes a -simple alias to :func:`_pyio.open`. Patch by Victor Stinner. +callable in Python 3.10. Moreover, :func:`!_pyio.OpenWrapper` becomes a +simple alias to :func:`!_pyio.open`. Patch by Victor Stinner. .. diff --git a/Misc/NEWS.d/3.11.0a1.rst b/Misc/NEWS.d/3.11.0a1.rst index 40fbb9d42b7944..23b13c058f96bd 100644 --- a/Misc/NEWS.d/3.11.0a1.rst +++ b/Misc/NEWS.d/3.11.0a1.rst @@ -613,7 +613,7 @@ Rename ``types.Union`` to ``types.UnionType``. .. section: Core and Builtins Expose specialization stats in python via -:func:`_opcode.get_specialization_stats`. +:func:`!_opcode.get_specialization_stats`. .. @@ -1701,7 +1701,7 @@ Remove many old deprecated :mod:`unittest` features: .. nonce: y1kEfP .. section: Library -Remove the deprecated ``split()`` method of :class:`_tkinter.TkappType`. +Remove the deprecated ``split()`` method of :class:`!_tkinter.TkappType`. Patch by Erlend E. Aasland. .. @@ -2298,9 +2298,9 @@ Adopt *binacii.a2b_base64*'s strict mode in *base64.b64decode*. .. nonce: ThuDMI .. section: Library -Fixed a bug in the :mod:`_ssl` module that was throwing :exc:`OverflowError` -when using :meth:`_ssl._SSLSocket.write` and :meth:`_ssl._SSLSocket.read` -for a big value of the ``len`` parameter. Patch by Pablo Galindo +Fixed a bug in the :mod:`!_ssl` module that was throwing :exc:`OverflowError` +when using :meth:`!_ssl._SSLSocket.write` and :meth:`!_ssl._SSLSocket.read` +for a big value of the ``len`` parameter. Patch by Pablo Galindo. .. @@ -2398,7 +2398,7 @@ class in the interactive session. Instead of :exc:`TypeError`, it should be .. nonce: R3IcM1 .. section: Library -Fix memory leak in :func:`_tkinter._flatten` if it is called with a sequence +Fix memory leak in :func:`!_tkinter._flatten` if it is called with a sequence or set, but not list or tuple. .. @@ -4187,7 +4187,7 @@ Add calls of :func:`gc.collect` in tests to support PyPy. .. nonce: mQZdXU .. section: Tests -Made tests relying on the :mod:`_asyncio` C extension module optional to +Made tests relying on the :mod:`!_asyncio` C extension module optional to allow running on alternative Python implementations. Patch by Serhiy Storchaka. diff --git a/Misc/NEWS.d/3.11.0a2.rst b/Misc/NEWS.d/3.11.0a2.rst index 05644d0a4639b1..48cf2c1e428d87 100644 --- a/Misc/NEWS.d/3.11.0a2.rst +++ b/Misc/NEWS.d/3.11.0a2.rst @@ -15,7 +15,7 @@ Improve the :exc:`SyntaxError` message when using ``True``, ``None`` or .. section: Core and Builtins :data:`sys.stdlib_module_names` now contains the macOS-specific module -:mod:`_scproxy`. +:mod:`!_scproxy`. .. @@ -1023,7 +1023,7 @@ compile shared modules. .. nonce: 61gM2A .. section: Build -:mod:`pyexpat` and :mod:`_elementtree` no longer define obsolete macros +:mod:`pyexpat` and :mod:`!_elementtree` no longer define obsolete macros ``HAVE_EXPAT_CONFIG_H`` and ``USE_PYEXPAT_CAPI``. ``XML_POOR_ENTROPY`` is now defined in ``expat_config.h``. diff --git a/Misc/NEWS.d/3.11.0a3.rst b/Misc/NEWS.d/3.11.0a3.rst index 2842aad0e163d6..6a0ae20d1fb5ed 100644 --- a/Misc/NEWS.d/3.11.0a3.rst +++ b/Misc/NEWS.d/3.11.0a3.rst @@ -27,7 +27,7 @@ invalid targets. Patch by Pablo Galindo .. nonce: 3TmTSw .. section: Core and Builtins -:c:func:`_PyErr_ChainStackItem` no longer normalizes ``exc_info`` (including +:c:func:`!_PyErr_ChainStackItem` no longer normalizes ``exc_info`` (including setting the traceback on the exception instance) because ``exc_info`` is always normalized. diff --git a/Misc/NEWS.d/3.11.0a4.rst b/Misc/NEWS.d/3.11.0a4.rst index a5ce7620016cc7..64e2f39ad9db18 100644 --- a/Misc/NEWS.d/3.11.0a4.rst +++ b/Misc/NEWS.d/3.11.0a4.rst @@ -258,7 +258,7 @@ instruction which performs the same operation, but without the loop. .. nonce: ADVaPT .. section: Core and Builtins -The code called from :c:func:`_PyErr_Display` was refactored to improve +The code called from :c:func:`!_PyErr_Display` was refactored to improve error handling. It now exits immediately upon an unrecoverable error. .. diff --git a/Misc/NEWS.d/3.11.0b1.rst b/Misc/NEWS.d/3.11.0b1.rst index c35e8e2c1caf07..a035d0f5addbf2 100644 --- a/Misc/NEWS.d/3.11.0b1.rst +++ b/Misc/NEWS.d/3.11.0b1.rst @@ -285,7 +285,7 @@ macros. .. nonce: 11YXHQ .. section: Core and Builtins -Add a new :c:func:`_PyFrame_IsEntryFrame` API function, to check if a +Add a new :c:func:`!_PyFrame_IsEntryFrame` API function, to check if a :c:type:`PyFrameObject` is an entry frame. Patch by Pablo Galindo. .. diff --git a/Misc/NEWS.d/3.12.0a1.rst b/Misc/NEWS.d/3.12.0a1.rst index 84d9d4e017609d..77a34124fb39e6 100644 --- a/Misc/NEWS.d/3.12.0a1.rst +++ b/Misc/NEWS.d/3.12.0a1.rst @@ -102,7 +102,7 @@ well as generator expressions. .. section: Core and Builtins Added unicode check for ``name`` attribute of ``spec`` argument passed in -:func:`_imp.create_builtin` function. +:func:`!_imp.create_builtin` function. .. @@ -483,7 +483,7 @@ Fix case of undefined behavior in ceval.c .. nonce: AfCi36 .. section: Core and Builtins -Convert :mod:`_functools` to argument clinic. +Convert :mod:`!_functools` to argument clinic. .. @@ -492,7 +492,7 @@ Convert :mod:`_functools` to argument clinic. .. nonce: wky0Fc .. section: Core and Builtins -Do not expose ``KeyWrapper`` in :mod:`_functools`. +Do not expose ``KeyWrapper`` in :mod:`!_functools`. .. @@ -1731,7 +1731,7 @@ tracing functions implemented in C. .. nonce: lenv9h .. section: Core and Builtins -:meth:`_warnings.warn_explicit` is ported to Argument Clinic. +:meth:`!_warnings.warn_explicit` is ported to Argument Clinic. .. @@ -3142,8 +3142,8 @@ test.test_codecs.EncodedFileTest`` instead. .. nonce: VhS1eS .. section: Library -Made :class:`_struct.Struct` GC-tracked in order to fix a reference leak in -the :mod:`_struct` module. +Made :class:`!_struct.Struct` GC-tracked in order to fix a reference leak in +the :mod:`!_struct` module. .. @@ -3258,7 +3258,7 @@ on the main thread Remove ``io.OpenWrapper`` and ``_pyio.OpenWrapper``, deprecated in Python 3.10: just use :func:`open` instead. The :func:`open` (:func:`io.open`) -function is a built-in function. Since Python 3.10, :func:`_pyio.open` is +function is a built-in function. Since Python 3.10, :func:`!_pyio.open` is also a static method. Patch by Victor Stinner. .. @@ -5610,7 +5610,7 @@ Accept os.PathLike for the argument to winsound.PlaySound Support native Windows case-insensitive path comparisons by using ``LCMapStringEx`` instead of :func:`str.lower` in :func:`ntpath.normcase`. -Add ``LCMapStringEx`` to the :mod:`_winapi` module. +Add ``LCMapStringEx`` to the :mod:`!_winapi` module. .. diff --git a/Misc/NEWS.d/3.12.0a2.rst b/Misc/NEWS.d/3.12.0a2.rst index 88d84ad93b35b5..3626f8b1e20809 100644 --- a/Misc/NEWS.d/3.12.0a2.rst +++ b/Misc/NEWS.d/3.12.0a2.rst @@ -527,7 +527,7 @@ Stinner. .. nonce: Ai2KDh .. section: Library -Now :mod:`_pyio` is consistent with :mod:`_io` in raising ``ValueError`` +Now :mod:`!_pyio` is consistent with :mod:`!_io` in raising ``ValueError`` when executing methods over closed buffers. .. @@ -537,7 +537,7 @@ when executing methods over closed buffers. .. nonce: 0v8iyw .. section: Library -Clean up refleak on failed module initialisation in :mod:`_zoneinfo` +Clean up refleak on failed module initialisation in :mod:`!_zoneinfo` .. @@ -546,7 +546,7 @@ Clean up refleak on failed module initialisation in :mod:`_zoneinfo` .. nonce: qc_KHr .. section: Library -Clean up refleaks on failed module initialisation in :mod:`_pickle` +Clean up refleaks on failed module initialisation in :mod:`!_pickle` .. @@ -555,7 +555,7 @@ Clean up refleaks on failed module initialisation in :mod:`_pickle` .. nonce: LBl79O .. section: Library -Clean up refleak on failed module initialisation in :mod:`_io`. +Clean up refleak on failed module initialisation in :mod:`!_io`. .. diff --git a/Misc/NEWS.d/3.12.0a3.rst b/Misc/NEWS.d/3.12.0a3.rst index 07593998d80891..f6a4dc75d456f4 100644 --- a/Misc/NEWS.d/3.12.0a3.rst +++ b/Misc/NEWS.d/3.12.0a3.rst @@ -70,7 +70,7 @@ Fix bug where compiler crashes on an if expression with an empty body block. .. nonce: DcKoBJ .. section: Core and Builtins -Fix a reference bug in :func:`_imp.create_builtin()` after the creation of +Fix a reference bug in :func:`!_imp.create_builtin` after the creation of the first sub-interpreter for modules ``builtins`` and ``sys``. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/3.12.0a4.rst b/Misc/NEWS.d/3.12.0a4.rst index d7af30f6c09b2b..53e1688b802bae 100644 --- a/Misc/NEWS.d/3.12.0a4.rst +++ b/Misc/NEWS.d/3.12.0a4.rst @@ -241,7 +241,7 @@ are now always dumped, even if switched off. Improve ``BUILD_LIST`` opcode so that it works similarly to the ``BUILD_TUPLE`` opcode, by stealing references from the stack rather than repeatedly using stack operations to set list elements. Implementation -details are in a new private API :c:func:`_PyList_FromArraySteal`. +details are in a new private API :c:func:`!_PyList_FromArraySteal`. .. diff --git a/Misc/NEWS.d/3.12.0b1.rst b/Misc/NEWS.d/3.12.0b1.rst index 9f3095b224233e..7126e08a20c7fd 100644 --- a/Misc/NEWS.d/3.12.0b1.rst +++ b/Misc/NEWS.d/3.12.0b1.rst @@ -1828,7 +1828,7 @@ is relative. .. nonce: 511Tbh .. section: Library -Convert private :meth:`_posixsubprocess.fork_exec` to use Argument Clinic. +Convert private :meth:`!_posixsubprocess.fork_exec` to use Argument Clinic. .. diff --git a/Misc/NEWS.d/3.13.0a1.rst b/Misc/NEWS.d/3.13.0a1.rst index 9a321f779c24ff..0ba61b43411792 100644 --- a/Misc/NEWS.d/3.13.0a1.rst +++ b/Misc/NEWS.d/3.13.0a1.rst @@ -2888,9 +2888,9 @@ documented and were not intended to be used externally. .. nonce: vMbmj_ .. section: Library -:data:`opcode.ENABLE_SPECIALIZATION` (which was added in 3.12 but never +:data:`!opcode.ENABLE_SPECIALIZATION` (which was added in 3.12 but never documented or intended for external usage) is moved to -:data:`_opcode.ENABLE_SPECIALIZATION` where tests can access it. +:data:`!_opcode.ENABLE_SPECIALIZATION` where tests can access it. .. @@ -3053,7 +3053,7 @@ Donghee Na. .. nonce: U9nD_B .. section: Library -Optimize :meth:`_PollLikeSelector.select` for many iteration case. +Optimize :meth:`!_PollLikeSelector.select` for many iteration case. .. @@ -3173,7 +3173,7 @@ Disable tab completion in multiline mode of :mod:`pdb` .. nonce: pYSwMj .. section: Library -Expose opcode metadata through :mod:`_opcode`. +Expose opcode metadata through :mod:`!_opcode`. .. @@ -3735,7 +3735,7 @@ overwritten. .. nonce: _sZilh .. section: Library -Fix bugs in :mod:`_ctypes` where exceptions could end up being overwritten. +Fix bugs in :mod:`!_ctypes` where exceptions could end up being overwritten. .. diff --git a/Misc/NEWS.d/3.13.0a2.rst b/Misc/NEWS.d/3.13.0a2.rst index c6b2b1b263ffab..f4a637bf624d03 100644 --- a/Misc/NEWS.d/3.13.0a2.rst +++ b/Misc/NEWS.d/3.13.0a2.rst @@ -777,7 +777,7 @@ Add error checking during :mod:`!_socket` module init. .. nonce: urFYtn .. section: Library -Fix :mod:`_blake2` not checking for errors when initializing. +Fix :mod:`!_blake2` not checking for errors when initializing. .. diff --git a/Misc/NEWS.d/3.13.0a3.rst b/Misc/NEWS.d/3.13.0a3.rst index 2c660192dcd5b3..29fbe00efef76d 100644 --- a/Misc/NEWS.d/3.13.0a3.rst +++ b/Misc/NEWS.d/3.13.0a3.rst @@ -449,8 +449,8 @@ well-formed for surrogateescape encoding. Patch by Sidney Markowitz. .. nonce: N8E1zw .. section: Core and Builtins -Use the object's actual class name in :meth:`_io.FileIO.__repr__`, -:meth:`_io._WindowsConsoleIO` and :meth:`_io.TextIOWrapper.__repr__`, to +Use the object's actual class name in :meth:`!_io.FileIO.__repr__`, +:meth:`!_io._WindowsConsoleIO` and :meth:`!_io.TextIOWrapper.__repr__`, to make these methods subclass friendly. .. diff --git a/Misc/NEWS.d/3.13.0a5.rst b/Misc/NEWS.d/3.13.0a5.rst index 6d74c6bc5c4d55..d8cc88c8756a17 100644 --- a/Misc/NEWS.d/3.13.0a5.rst +++ b/Misc/NEWS.d/3.13.0a5.rst @@ -541,7 +541,7 @@ descriptors in :meth:`inspect.Signature.from_callable`. .. nonce: sGMKr0 .. section: Library -Isolate :mod:`_lsprof` (apply :pep:`687`). +Isolate :mod:`!_lsprof` (apply :pep:`687`). .. @@ -773,8 +773,8 @@ combination with unicode encoding. .. section: Library Fix :func:`io.BufferedReader.tell`, :func:`io.BufferedReader.seek`, -:func:`_pyio.BufferedReader.tell`, :func:`io.BufferedRandom.tell`, -:func:`io.BufferedRandom.seek` and :func:`_pyio.BufferedRandom.tell` being +:func:`!_pyio.BufferedReader.tell`, :func:`io.BufferedRandom.tell`, +:func:`io.BufferedRandom.seek` and :func:`!_pyio.BufferedRandom.tell` being able to return negative offsets. .. diff --git a/Misc/NEWS.d/3.13.0a6.rst b/Misc/NEWS.d/3.13.0a6.rst index fff29083e0dab7..0cdbb8232250d7 100644 --- a/Misc/NEWS.d/3.13.0a6.rst +++ b/Misc/NEWS.d/3.13.0a6.rst @@ -550,7 +550,7 @@ or DuplicateOptionError. .. nonce: PBiRQB .. section: Library -:class:`_io.WindowsConsoleIO` now emit a warning if a boolean value is +:class:`!_io.WindowsConsoleIO` now emit a warning if a boolean value is passed as a filedescriptor argument. .. diff --git a/Misc/NEWS.d/3.13.0b1.rst b/Misc/NEWS.d/3.13.0b1.rst index ab5f24fe345af9..831ba623765df7 100644 --- a/Misc/NEWS.d/3.13.0b1.rst +++ b/Misc/NEWS.d/3.13.0b1.rst @@ -666,7 +666,7 @@ by :pep:`738`. .. section: Library Allow to specify the signature of custom callable instances of extension -type by the :attr:`__text_signature__` attribute. Specify signatures of +type by the ``__text_signature__`` attribute. Specify signatures of :class:`operator.attrgetter`, :class:`operator.itemgetter`, and :class:`operator.methodcaller` instances. @@ -687,10 +687,10 @@ padding is not detected when no padding is necessary. .. nonce: 5N2Xcy .. section: Library -Add the :class:`!PhotoImage` methods :meth:`~tkinter.PhotoImage.read` to -read an image from a file and :meth:`~tkinter.PhotoImage.data` to get the +Add the :class:`!PhotoImage` methods :meth:`!read` to +read an image from a file and :meth:`!data` to get the image data. Add *background* and *grayscale* parameters to -:class:`!PhotoImage` method :meth:`~tkinter.PhotoImage.write`. +:class:`!PhotoImage` method :meth:`!write`. .. @@ -855,7 +855,7 @@ is used to bind indexed, nameless placeholders. See also :gh:`100668`. .. nonce: RstWg- .. section: Library -Fix TypeError in :func:`email.Message.get_payload` when the charset is +Fix TypeError in :func:`email.message.Message.get_payload` when the charset is :rfc:`2231` encoded. .. @@ -953,7 +953,7 @@ Speed up :meth:`pathlib.Path.walk` by working with strings internally. .. nonce: oxIUEI .. section: Library -Change the new multi-separator support in :meth:`asyncio.Stream.readuntil` +Change the new multi-separator support in :meth:`asyncio.StreamReader.readuntil` to only accept tuples of separators rather than arbitrary iterables. .. @@ -1260,7 +1260,7 @@ Support opcode events in :mod:`bdb` .. nonce: YoI8TV .. section: Library -:mod:`ncurses`: fixed a crash that could occur on macOS 13 or earlier when +:mod:`!ncurses`: fixed a crash that could occur on macOS 13 or earlier when Python was built with Apple Xcode 15's SDK. .. @@ -1347,13 +1347,13 @@ urllib. .. nonce: du4UKW .. section: Library -Setting the :mod:`!tkinter` module global :data:`~tkinter.wantobject` to ``2`` +Setting the :mod:`!tkinter` module global :data:`!wantobjects` to ``2`` before creating the :class:`~tkinter.Tk` object or call the -:meth:`~tkinter.Tk.wantobject` method of the :class:`!Tk` object with argument +:meth:`!wantobjects` method of the :class:`!Tk` object with argument ``2`` makes now arguments to callbacks registered in the :mod:`tkinter` module to be passed as various Python objects (``int``, ``float``, ``bytes``, ``tuple``), depending on their internal representation in Tcl, instead of always ``str``. -:data:`!tkinter.wantobject` is now set to ``2`` by default. +:data:`!tkinter.wantobjects` is now set to ``2`` by default. .. diff --git a/Misc/NEWS.d/3.5.0a1.rst b/Misc/NEWS.d/3.5.0a1.rst index 442ab62fee8185..35f340f503df18 100644 --- a/Misc/NEWS.d/3.5.0a1.rst +++ b/Misc/NEWS.d/3.5.0a1.rst @@ -3447,7 +3447,8 @@ tkinter.ttk now works when default root window is not set. .. nonce: FE_PII .. section: Library -_tkinter.create() now creates tkapp object with wantobject=1 by default. +``_tkinter.create()`` now creates ``tkapp`` object with ``wantobjects=1`` by +default. .. diff --git a/Misc/NEWS.d/3.6.0a1.rst b/Misc/NEWS.d/3.6.0a1.rst index 5c9a6e5d64b469..803c9fc5925fa6 100644 --- a/Misc/NEWS.d/3.6.0a1.rst +++ b/Misc/NEWS.d/3.6.0a1.rst @@ -1484,9 +1484,9 @@ on UNIX signals (SIGSEGV, SIGFPE, SIGABRT). .. nonce: RWN1jR .. section: Library -Add C functions :c:func:`_PyTraceMalloc_Track` and -:c:func:`_PyTraceMalloc_Untrack` to track memory blocks using the -:mod:`tracemalloc` module. Add :c:func:`_PyTraceMalloc_GetTraceback` to get +Add C functions :c:func:`!_PyTraceMalloc_Track` and +:c:func:`!_PyTraceMalloc_Untrack` to track memory blocks using the +:mod:`tracemalloc` module. Add :c:func:`!_PyTraceMalloc_GetTraceback` to get the traceback of an object. .. diff --git a/Misc/NEWS.d/3.8.0a1.rst b/Misc/NEWS.d/3.8.0a1.rst index 9decc4034d6b87..35b9e7fca27a7b 100644 --- a/Misc/NEWS.d/3.8.0a1.rst +++ b/Misc/NEWS.d/3.8.0a1.rst @@ -2519,7 +2519,7 @@ non-Windows systems. .. nonce: dQS1ng .. section: Library -Fix incorrect parsing of :class:`_io.IncrementalNewlineDecoder`'s +Fix incorrect parsing of :class:`io.IncrementalNewlineDecoder`'s *translate* argument. .. @@ -8051,7 +8051,7 @@ Update macOS 10.9+ installer to Tcl/Tk 8.6.8. .. nonce: K6jCVG .. section: macOS -In :mod:`_scproxy`, drop the GIL when calling into ``SystemConfiguration`` +In :mod:`!_scproxy`, drop the GIL when calling into ``SystemConfiguration`` to avoid deadlocks. .. diff --git a/Misc/NEWS.d/3.8.0a4.rst b/Misc/NEWS.d/3.8.0a4.rst index 7bf0de1210935b..edce71b2555a89 100644 --- a/Misc/NEWS.d/3.8.0a4.rst +++ b/Misc/NEWS.d/3.8.0a4.rst @@ -945,7 +945,7 @@ P. Hemsley. .. nonce: __FTq9 .. section: Tests -Add a new :mod:`_testinternalcapi` module to test the internal C API. +Add a new :mod:`!_testinternalcapi` module to test the internal C API. .. @@ -1383,7 +1383,7 @@ Since Python 3.7.0, calling :c:func:`Py_DecodeLocale` before coerced and/or if the UTF-8 Mode is enabled by the user configuration. The LC_CTYPE coercion and UTF-8 Mode are now disabled by default to fix the mojibake issue. They must now be enabled explicitly (opt-in) using the new -:c:func:`_Py_PreInitialize` API with ``_PyPreConfig``. +:c:func:`!_Py_PreInitialize` API with ``_PyPreConfig``. .. diff --git a/Misc/NEWS.d/3.8.0b1.rst b/Misc/NEWS.d/3.8.0b1.rst index 4174ab8fac6192..fc4e3a9bd887fb 100644 --- a/Misc/NEWS.d/3.8.0b1.rst +++ b/Misc/NEWS.d/3.8.0b1.rst @@ -600,7 +600,7 @@ default. .. nonce: sLULGQ .. section: Library -Fix destructor :class:`_pyio.BytesIO` and :class:`_pyio.TextIOWrapper`: +Fix destructor :class:`!_pyio.BytesIO` and :class:`!_pyio.TextIOWrapper`: initialize their ``_buffer`` attribute as soon as possible (in the class body), because it's used by ``__del__()`` which calls ``close()``. diff --git a/Misc/NEWS.d/3.9.0a1.rst b/Misc/NEWS.d/3.9.0a1.rst index a38b93e4b76d17..b0f63c3b9c3537 100644 --- a/Misc/NEWS.d/3.9.0a1.rst +++ b/Misc/NEWS.d/3.9.0a1.rst @@ -1384,7 +1384,7 @@ Nested subclasses of :class:`typing.NamedTuple` are now pickleable. .. nonce: hwrPN7 .. section: Library -Prevent :exc:`KeyError` thrown by :func:`_encoded_words.decode` when given +Prevent :exc:`KeyError` thrown by :func:`!_encoded_words.decode` when given an encoded-word with invalid content-type encoding from propagating all the way to :func:`email.message.get`. @@ -1395,7 +1395,7 @@ way to :func:`email.message.get`. .. nonce: S6Klvm .. section: Library -Deprecated the ``split()`` method in :class:`_tkinter.TkappType` in favour +Deprecated the ``split()`` method in :class:`!_tkinter.TkappType` in favour of the ``splitlist()`` method which has more consistent and predictable behavior. @@ -3013,7 +3013,7 @@ thread was still running. .. section: Library Allow pure Python implementation of :mod:`pickle` to work even when the C -:mod:`_pickle` module is unavailable. +:mod:`!_pickle` module is unavailable. .. @@ -3064,8 +3064,8 @@ internal tasks weak set is changed by another thread during iteration. .. nonce: ADqCkq .. section: Library -:class:`_pyio.IOBase` destructor now does nothing if getting the ``closed`` -attribute fails to better mimic :class:`_io.IOBase` finalizer. +:class:`!_pyio.IOBase` destructor now does nothing if getting the ``closed`` +attribute fails to better mimic :class:`!_io.IOBase` finalizer. .. @@ -4993,7 +4993,7 @@ Make :const:`winreg.REG_MULTI_SZ` support zero-length strings. .. section: Windows Replace use of :c:func:`strcasecmp` for the system function -:c:func:`_stricmp`. Patch by Minmin Gong. +:c:func:`!_stricmp`. Patch by Minmin Gong. .. @@ -5696,8 +5696,8 @@ Add :c:func:`PyConfig_SetWideStringList` function. .. section: C API Add fast functions for calling methods: -:c:func:`_PyObject_VectorcallMethod`, :c:func:`_PyObject_CallMethodNoArgs` -and :c:func:`_PyObject_CallMethodOneArg`. +:c:func:`!_PyObject_VectorcallMethod`, :c:func:`!_PyObject_CallMethodNoArgs` +and :c:func:`!_PyObject_CallMethodOneArg`. .. diff --git a/Misc/NEWS.d/3.9.0a6.rst b/Misc/NEWS.d/3.9.0a6.rst index b7ea1051c314f2..4ba4cfe818c2d0 100644 --- a/Misc/NEWS.d/3.9.0a6.rst +++ b/Misc/NEWS.d/3.9.0a6.rst @@ -111,7 +111,7 @@ str.decode(). .. nonce: m15TTX .. section: Core and Builtins -Fix possible refleaks in :mod:`_json`, memo of PyScannerObject should be +Fix possible refleaks in :mod:`!_json`, memo of PyScannerObject should be traversed. .. @@ -666,8 +666,8 @@ for _main_thread, instead of a _DummyThread instance. .. nonce: VTq_8s .. section: Library -Add a private ``_at_fork_reinit()`` method to :class:`_thread.Lock`, -:class:`_thread.RLock`, :class:`threading.RLock` and +Add a private ``_at_fork_reinit()`` method to :class:`!_thread.Lock`, +:class:`!_thread.RLock`, :class:`threading.RLock` and :class:`threading.Condition` classes: reinitialize the lock at fork in the child process, reset the lock to the unlocked state. Rename also the private ``_reset_internal_locks()`` method of :class:`threading.Event` to diff --git a/Misc/NEWS.d/next/Library/2023-04-24-05-34-23.gh-issue-103194.GwBwWL.rst b/Misc/NEWS.d/next/Library/2023-04-24-05-34-23.gh-issue-103194.GwBwWL.rst index 3f70168b81069e..bc9187309c6a53 100644 --- a/Misc/NEWS.d/next/Library/2023-04-24-05-34-23.gh-issue-103194.GwBwWL.rst +++ b/Misc/NEWS.d/next/Library/2023-04-24-05-34-23.gh-issue-103194.GwBwWL.rst @@ -1,4 +1,4 @@ Prepare Tkinter for C API changes in Tcl 8.7/9.0 to avoid -:class:`_tkinter.Tcl_Obj` being unexpectedly returned +:class:`!_tkinter.Tcl_Obj` being unexpectedly returned instead of :class:`bool`, :class:`str`, :class:`bytearray`, or :class:`int`. From 218edaf0ffe6ef38349047f378649f93d280e23e Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 8 Jul 2024 16:44:56 -0400 Subject: [PATCH 67/97] gh-121018: Fix typo in NEWS entry (#121510) --- .../next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst b/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst index eac5bab3e9fe6d..346a89879cad41 100644 --- a/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst +++ b/Misc/NEWS.d/next/Library/2024-06-26-03-04-24.gh-issue-121018.clVSc4.rst @@ -1,3 +1,3 @@ -Fixed issues where :meth:`!argparse.ArgumentParser.parses_args` did not honor +Fixed issues where :meth:`!argparse.ArgumentParser.parse_args` did not honor ``exit_on_error=False``. Based on patch by Ben Hsing. From 15d48aea02099ffc5bdc5511cc53ced460cb31b9 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 8 Jul 2024 15:10:00 -0600 Subject: [PATCH 68/97] gh-121110: Fix Extension Module Tests Under Py_TRACE_REFS Builds (gh-121503) The change in gh-118157 (b2cd54a) should have also updated clear_singlephase_extension() but didn't. We fix that here. Note that clear_singlephase_extension() (AKA _PyImport_ClearExtension()) is only used in tests. --- Lib/test/test_import/__init__.py | 7 ---- Python/import.c | 62 ++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index c10f689c4ea34b..e29097baaf53ae 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -3034,13 +3034,6 @@ def test_basic_multiple_interpreters_deleted_no_reset(self): def test_basic_multiple_interpreters_reset_each(self): # resetting between each interpreter - if Py_TRACE_REFS: - # It's a Py_TRACE_REFS build. - # This test breaks interpreter isolation a little, - # which causes problems on Py_TRACE_REF builds. - # See gh-121110. - raise unittest.SkipTest('crashes on Py_TRACE_REFS builds') - # At this point: # * alive in 0 interpreters # * module def may or may not be loaded already diff --git a/Python/import.c b/Python/import.c index 20ad10020044df..40b7feac001d6e 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1532,6 +1532,35 @@ switch_to_main_interpreter(PyThreadState *tstate) return main_tstate; } +static void +switch_back_from_main_interpreter(PyThreadState *tstate, + PyThreadState *main_tstate, + PyObject *tempobj) +{ + assert(main_tstate == PyThreadState_GET()); + assert(_Py_IsMainInterpreter(main_tstate->interp)); + assert(tstate->interp != main_tstate->interp); + + /* Handle any exceptions, which we cannot propagate directly + * to the subinterpreter. */ + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_MemoryError)) { + /* We trust it will be caught again soon. */ + PyErr_Clear(); + } + else { + /* Printing the exception should be sufficient. */ + PyErr_PrintEx(0); + } + } + + Py_XDECREF(tempobj); + + PyThreadState_Clear(main_tstate); + (void)PyThreadState_Swap(tstate); + PyThreadState_Delete(main_tstate); +} + static PyObject * get_core_module_dict(PyInterpreterState *interp, PyObject *name, PyObject *path) @@ -2027,27 +2056,10 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0, /* Switch back to the subinterpreter. */ if (switched) { assert(main_tstate != tstate); - - /* Handle any exceptions, which we cannot propagate directly - * to the subinterpreter. */ - if (PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_MemoryError)) { - /* We trust it will be caught again soon. */ - PyErr_Clear(); - } - else { - /* Printing the exception should be sufficient. */ - PyErr_PrintEx(0); - } - } - + switch_back_from_main_interpreter(tstate, main_tstate, mod); /* Any module we got from the init function will have to be * reloaded in the subinterpreter. */ - Py_CLEAR(mod); - - PyThreadState_Clear(main_tstate); - (void)PyThreadState_Swap(tstate); - PyThreadState_Delete(main_tstate); + mod = NULL; } /*****************************************************************/ @@ -2141,9 +2153,21 @@ clear_singlephase_extension(PyInterpreterState *interp, } } + /* We must use the main interpreter to clean up the cache. + * See the note in import_run_extension(). */ + PyThreadState *tstate = PyThreadState_GET(); + PyThreadState *main_tstate = switch_to_main_interpreter(tstate); + if (main_tstate == NULL) { + return -1; + } + /* Clear the cached module def. */ _extensions_cache_delete(path, name); + if (main_tstate != tstate) { + switch_back_from_main_interpreter(tstate, main_tstate, NULL); + } + return 0; } From facf9862da0cf9331550747197800d682cd371fb Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 9 Jul 2024 17:22:07 +0800 Subject: [PATCH 69/97] gh-121333: Clarify what is the default executor for asyncio.run_in_executor (#121335) Co-authored-by: Kumar Aditya --- Doc/library/asyncio-eventloop.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 1d79f78e8e1b67..70bdd154d6c406 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -1262,6 +1262,9 @@ Executing code in thread or process pools The *executor* argument should be an :class:`concurrent.futures.Executor` instance. The default executor is used if *executor* is ``None``. + The default executor can be set by :meth:`loop.set_default_executor`, + otherwise, a :class:`concurrent.futures.ThreadPoolExecutor` will be + lazy-initialized and used by :func:`run_in_executor` if needed. Example:: From bf8686e1ea389427157c754c5e522ee040f166ad Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 9 Jul 2024 11:33:56 +0100 Subject: [PATCH 70/97] GH-118926: Better distinguish between pointer and arrays in interpreter generator (GH-121496) --- Lib/test/test_generated_cases.py | 44 +++++++++++++++++++++- Tools/cases_generator/analyzer.py | 8 ++-- Tools/cases_generator/generators_common.py | 14 ++++++- Tools/cases_generator/parsing.py | 1 - Tools/cases_generator/stack.py | 11 +++--- Tools/cases_generator/tier1_generator.py | 11 +++--- Tools/cases_generator/tier2_generator.py | 3 +- 7 files changed, 73 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 30e39e7720e6d1..00def509a219c3 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -815,7 +815,6 @@ def test_annotated_op(self): """ self.run_cases_test(input, output) - def test_deopt_and_exit(self): input = """ pure op(OP, (arg1 -- out)) { @@ -827,6 +826,49 @@ def test_deopt_and_exit(self): with self.assertRaises(Exception): self.run_cases_test(input, output) + def test_array_of_one(self): + input = """ + inst(OP, (arg[1] -- out[1])) { + out[0] = arg[0]; + } + """ + output = """ + TARGET(OP) { + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(OP); + _PyStackRef *arg; + _PyStackRef *out; + arg = &stack_pointer[-1]; + out = &stack_pointer[-1]; + out[0] = arg[0]; + DISPATCH(); + } + """ + self.run_cases_test(input, output) + + def test_pointer_to_stackref(self): + input = """ + inst(OP, (arg: _PyStackRef * -- out)) { + out = *arg; + } + """ + output = """ + TARGET(OP) { + 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) + + class TestGeneratedAbstractCases(unittest.TestCase): def setUp(self) -> None: super().setUp() diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index f92560bd2b76b3..ec365bad3992d5 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -106,13 +106,15 @@ class StackItem: def __str__(self) -> str: cond = f" if ({self.condition})" if self.condition else "" - size = f"[{self.size}]" if self.size != "1" else "" + size = f"[{self.size}]" if self.size else "" type = "" if self.type is None else f"{self.type} " return f"{type}{self.name}{size}{cond} {self.peek}" def is_array(self) -> bool: - return self.type == "_PyStackRef *" + return self.size != "" + def get_size(self) -> str: + return self.size if self.size else "1" @dataclass class StackEffect: @@ -293,7 +295,7 @@ def convert_stack_item(item: parser.StackEffect, replace_op_arg_1: str | None) - if replace_op_arg_1 and OPARG_AND_1.match(item.cond): cond = replace_op_arg_1 return StackItem( - item.name, item.type, cond, (item.size or "1") + item.name, item.type, cond, item.size ) def analyze_stack(op: parser.InstDef | parser.Pseudo, replace_op_arg_1: str | None = None) -> StackEffect: diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index e4e0c9b658c19d..9314bb9e79687f 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -5,9 +5,10 @@ Instruction, Uop, Properties, + StackItem, ) from cwriter import CWriter -from typing import Callable, Mapping, TextIO, Iterator +from typing import Callable, Mapping, TextIO, Iterator, Tuple from lexer import Token from stack import Stack @@ -24,6 +25,15 @@ def root_relative_path(filename: str) -> str: return filename +def type_and_null(var: StackItem) -> Tuple[str, str]: + if var.type: + return var.type, "NULL" + elif var.is_array(): + return "_PyStackRef *", "NULL" + else: + return "_PyStackRef", "PyStackRef_NULL" + + def write_header( generator: str, sources: list[str], outfile: TextIO, comment: str = "//" ) -> None: @@ -126,7 +136,7 @@ def replace_decrefs( for var in uop.stack.inputs: if var.name == "unused" or var.name == "null" or var.peek: continue - if var.size != "1": + if var.size: out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") out.emit(f"PyStackRef_CLOSE({var.name}[_i]);\n") out.emit("}\n") diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py index 0bd4229e2beaf2..8957838f7a90a1 100644 --- a/Tools/cases_generator/parsing.py +++ b/Tools/cases_generator/parsing.py @@ -285,7 +285,6 @@ def stack_effect(self) -> StackEffect | None: if not (size := self.expression()): raise self.make_syntax_error("Expected expression") self.require(lx.RBRACKET) - type_text = "_PyStackRef *" size_text = size.text.strip() return StackEffect(tkn.text, type_text, cond_text, size_text) return None diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index c0e1278e519143..ebe62df537f15f 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -28,14 +28,15 @@ def var_size(var: StackItem) -> str: if var.condition == "0": return "0" elif var.condition == "1": - return var.size - elif var.condition == "oparg & 1" and var.size == "1": + return var.get_size() + elif var.condition == "oparg & 1" and not var.size: return f"({var.condition})" else: - return f"(({var.condition}) ? {var.size} : 0)" - else: + return f"(({var.condition}) ? {var.get_size()} : 0)" + elif var.size: return var.size - + else: + return "1" @dataclass class StackOffset: diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index c9dce1d5f1804e..85be673b1c396c 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -13,12 +13,14 @@ analyze_files, Skip, analysis_error, + StackItem, ) from generators_common import ( DEFAULT_INPUT, ROOT, write_header, emit_tokens, + type_and_null, ) from cwriter import CWriter from typing import TextIO @@ -38,19 +40,16 @@ def declare_variables(inst: Instruction, out: CWriter) -> None: for var in reversed(uop.stack.inputs): if var.name not in variables: variables.add(var.name) - type, null = (var.type, "NULL") if var.type else ("_PyStackRef", "PyStackRef_NULL") + type, null = type_and_null(var) space = " " if type[-1].isalnum() else "" if var.condition: out.emit(f"{type}{space}{var.name} = {null};\n") else: - if var.is_array(): - out.emit(f"{var.type}{space}{var.name};\n") - else: - out.emit(f"{type}{space}{var.name};\n") + out.emit(f"{type}{space}{var.name};\n") for var in uop.stack.outputs: if var.name not in variables: variables.add(var.name) - type, null = (var.type, "NULL") if var.type else ("_PyStackRef", "PyStackRef_NULL") + type, null = type_and_null(var) space = " " if type[-1].isalnum() else "" if var.condition: out.emit(f"{type}{space}{var.name} = {null};\n") diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index f3769bd31c295d..7a69aa6e121fa7 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -20,6 +20,7 @@ emit_tokens, emit_to, REPLACEMENT_FUNCTIONS, + type_and_null, ) from cwriter import CWriter from typing import TextIO, Iterator @@ -35,7 +36,7 @@ def declare_variable( if var.name in variables: return variables.add(var.name) - type, null = (var.type, "NULL") if var.type else ("_PyStackRef", "PyStackRef_NULL") + type, null = type_and_null(var) space = " " if type[-1].isalnum() else "" if var.condition: out.emit(f"{type}{space}{var.name} = {null};\n") From 9ba2a4638d7b620c939face7642b2f53a9fadc4b Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Tue, 9 Jul 2024 09:24:37 -0300 Subject: [PATCH 71/97] Docs: fix typo and duplicate word in configure.rst (#121410) --- Doc/using/configure.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 2a1f06e2d286ff..2c73c224e4e8a1 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -427,7 +427,7 @@ Options for third-party dependencies .. option:: PANEL_CFLAGS .. option:: PANEL_LIBS - C compiler and Linker flags for PANEL, overriding ``pkg-config``. + C compiler and linker flags for PANEL, overriding ``pkg-config``. C compiler and linker flags for ``libpanel`` or ``libpanelw``, used by :mod:`curses.panel` module, overriding ``pkg-config``. @@ -615,7 +615,7 @@ also be used to improve performance. .. option:: --without-mimalloc - Disable the fast mimalloc allocator :ref:`mimalloc ` + Disable the fast :ref:`mimalloc ` allocator (enabled by default). See also :envvar:`PYTHONMALLOC` environment variable. From 649d5b6d7b04607dd17810ac73e8f16720c6dc78 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 9 Jul 2024 18:47:35 +0300 Subject: [PATCH 72/97] gh-121533: Improve `PyCell_[Get,Set]` docs: mention the exceptions (#121534) Co-authored-by: Victor Stinner --- Doc/c-api/cell.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/cell.rst b/Doc/c-api/cell.rst index f8cd0344fdd1c0..61eb994c370946 100644 --- a/Doc/c-api/cell.rst +++ b/Doc/c-api/cell.rst @@ -39,7 +39,8 @@ Cell objects are not likely to be useful elsewhere. .. c:function:: PyObject* PyCell_Get(PyObject *cell) - Return the contents of the cell *cell*. + Return the contents of the cell *cell*, which can be ``NULL``. + If *cell* is not a cell object, returns ``NULL`` with an exception set. .. c:function:: PyObject* PyCell_GET(PyObject *cell) @@ -52,8 +53,10 @@ Cell objects are not likely to be useful elsewhere. Set the contents of the cell object *cell* to *value*. This releases the reference to any current content of the cell. *value* may be ``NULL``. *cell* - must be non-``NULL``; if it is not a cell object, ``-1`` will be returned. On - success, ``0`` will be returned. + must be non-``NULL``. + + On success, return ``0``. + If *cell* is not a cell object, set an exception and return ``-1``. .. c:function:: void PyCell_SET(PyObject *cell, PyObject *value) From 9c08f40a613d9aee78de4ce4ec3e125d1496d148 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 9 Jul 2024 12:11:43 -0400 Subject: [PATCH 73/97] gh-117657: Fix TSAN races in setobject.c (#121511) The `used` field must be written using atomic stores because `set_len` and iterators may access the field concurrently without holding the per-object lock. --- Objects/setobject.c | 18 ++++++++++-------- Tools/tsan/suppressions_free_threading.txt | 3 --- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index eb0c404bf6b8e0..5d7ad395d08c90 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -184,14 +184,14 @@ set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash) found_unused_or_dummy: if (freeslot == NULL) goto found_unused; - so->used++; + FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used + 1); freeslot->key = key; freeslot->hash = hash; return 0; found_unused: so->fill++; - so->used++; + FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used + 1); entry->key = key; entry->hash = hash; if ((size_t)so->fill*5 < mask*3) @@ -357,7 +357,7 @@ set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t hash) old_key = entry->key; entry->key = dummy; entry->hash = -1; - so->used--; + FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used - 1); Py_DECREF(old_key); return DISCARD_FOUND; } @@ -397,7 +397,7 @@ set_empty_to_minsize(PySetObject *so) { memset(so->smalltable, 0, sizeof(so->smalltable)); so->fill = 0; - so->used = 0; + FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, 0); so->mask = PySet_MINSIZE - 1; so->table = so->smalltable; so->hash = -1; @@ -615,7 +615,7 @@ set_merge_lock_held(PySetObject *so, PyObject *otherset) } } so->fill = other->fill; - so->used = other->used; + FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, other->used); return 0; } @@ -624,7 +624,7 @@ set_merge_lock_held(PySetObject *so, PyObject *otherset) setentry *newtable = so->table; size_t newmask = (size_t)so->mask; so->fill = other->used; - so->used = other->used; + FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, other->used); for (i = other->mask + 1; i > 0 ; i--, other_entry++) { key = other_entry->key; if (key != NULL && key != dummy) { @@ -678,7 +678,7 @@ set_pop_impl(PySetObject *so) key = entry->key; entry->key = dummy; entry->hash = -1; - so->used--; + FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used - 1); so->finger = entry - so->table + 1; /* next place to start */ return key; } @@ -1173,7 +1173,9 @@ set_swap_bodies(PySetObject *a, PySetObject *b) Py_hash_t h; t = a->fill; a->fill = b->fill; b->fill = t; - t = a->used; a->used = b->used; b->used = t; + t = a->used; + FT_ATOMIC_STORE_SSIZE_RELAXED(a->used, b->used); + FT_ATOMIC_STORE_SSIZE_RELAXED(b->used, t); t = a->mask; a->mask = b->mask; b->mask = t; u = a->table; diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index 534a0cedb743dd..fb97bdc128a4e1 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -30,8 +30,6 @@ race_top:assign_version_tag race_top:insertdict race_top:lookup_tp_dict race_top:new_reference -# https://gist.github.com/colesbury/d13d033f413b4ad07929d044bed86c35 -race_top:set_discard_entry race_top:_PyDict_CheckConsistency race_top:_Py_dict_lookup_threadsafe race_top:_multiprocessing_SemLock_acquire_impl @@ -41,7 +39,6 @@ race_top:insert_to_emptydict race_top:insertdict race_top:list_get_item_ref race_top:make_pending_calls -race_top:set_add_entry race_top:_Py_slot_tp_getattr_hook race_top:add_threadstate race_top:dump_traceback From 04397434aad9b31328785e17ac7b3a2d5097269b Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 9 Jul 2024 17:12:45 -0400 Subject: [PATCH 74/97] gh-117657: Skip test when running under TSan (GH-121549) The ProcessPoolForkserver combined with resource_tracker starts a thread after forking, which is not supported by TSan. Also skip test_multiprocessing_fork for the same reason --- Lib/test/test_concurrent_futures/test_init.py | 1 + Lib/test/test_multiprocessing_fork/__init__.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Lib/test/test_concurrent_futures/test_init.py b/Lib/test/test_concurrent_futures/test_init.py index a36f592b79b7cf..df640929309318 100644 --- a/Lib/test/test_concurrent_futures/test_init.py +++ b/Lib/test/test_concurrent_futures/test_init.py @@ -139,6 +139,7 @@ def _test(self, test_class): def test_spawn(self): self._test(ProcessPoolSpawnFailingInitializerTest) + @support.skip_if_sanitizer("TSAN doesn't support threads after fork", thread=True) def test_forkserver(self): self._test(ProcessPoolForkserverFailingInitializerTest) diff --git a/Lib/test/test_multiprocessing_fork/__init__.py b/Lib/test/test_multiprocessing_fork/__init__.py index aa1fff50b28f5f..b35e82879d7fe2 100644 --- a/Lib/test/test_multiprocessing_fork/__init__.py +++ b/Lib/test/test_multiprocessing_fork/__init__.py @@ -12,5 +12,8 @@ if sys.platform == 'darwin': raise unittest.SkipTest("test may crash on macOS (bpo-33725)") +if support.check_sanitizer(thread=True): + raise unittest.SkipTest("TSAN doesn't support threads after fork") + def load_tests(*args): return support.load_package_tests(os.path.dirname(__file__), *args) From 80209468144fbd1af5cd31f152a6631627a9acab Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 9 Jul 2024 15:02:25 -0700 Subject: [PATCH 75/97] GH-120372: Switch to wasmtime 22 (GH-121523) Along the way, make the cache key in GitHub Actions for `config.cache` be more robust in the face of potential env var changes from `Tools/wasm/wasi.py`. --- .devcontainer/Dockerfile | 2 +- .github/workflows/reusable-wasi.yml | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6f8fe005621c88..98ab4008bed7cf 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,7 +6,7 @@ ENV WASI_SDK_VERSION=21 ENV WASI_SDK_PATH=/opt/wasi-sdk ENV WASMTIME_HOME=/opt/wasmtime -ENV WASMTIME_VERSION=18.0.3 +ENV WASMTIME_VERSION=22.0.0 ENV WASMTIME_CPU_ARCH=x86_64 RUN dnf -y --nodocs --setopt=install_weak_deps=False install /usr/bin/{blurb,clang,curl,git,ln,tar,xz} 'dnf-command(builddep)' && \ diff --git a/.github/workflows/reusable-wasi.yml b/.github/workflows/reusable-wasi.yml index c389fe9e173b38..db6c04ec2ac1c5 100644 --- a/.github/workflows/reusable-wasi.yml +++ b/.github/workflows/reusable-wasi.yml @@ -11,7 +11,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-22.04 env: - WASMTIME_VERSION: 18.0.3 + WASMTIME_VERSION: 22.0.0 WASI_SDK_VERSION: 21 WASI_SDK_PATH: /opt/wasi-sdk CROSS_BUILD_PYTHON: cross-build/build @@ -20,9 +20,9 @@ jobs: - uses: actions/checkout@v4 # No problem resolver registered as one doesn't currently exist for Clang. - name: "Install wasmtime" - uses: jcbhmr/setup-wasmtime@v2 + uses: bytecodealliance/actions/wasmtime/setup@v1 with: - wasmtime-version: ${{ env.WASMTIME_VERSION }} + version: ${{ env.WASMTIME_VERSION }} - name: "Restore WASI SDK" id: cache-wasi-sdk uses: actions/cache@v4 @@ -50,8 +50,10 @@ jobs: uses: actions/cache@v4 with: path: ${{ env.CROSS_BUILD_PYTHON }}/config.cache - # Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python - key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }}-${{ env.pythonLocation }} + # Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python. + # Include the hash of `Tools/wasm/wasi.py` as it may change the environment variables. + # (Make sure to keep the key in sync with the other config.cache step below.) + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ env.WASI_SDK_VERSION }}-${{ env.WASMTIME_VERSION }}-${{ inputs.config_hash }}-${{ hashFiles('Tools/wasm/wasi.py') }}-${{ env.pythonLocation }} - name: "Configure build Python" run: python3 Tools/wasm/wasi.py configure-build-python -- --config-cache --with-pydebug - name: "Make build Python" @@ -60,8 +62,8 @@ jobs: uses: actions/cache@v4 with: path: ${{ env.CROSS_BUILD_WASI }}/config.cache - # Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python - key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-wasi-sdk-${{ env.WASI_SDK_VERSION }}-${{ inputs.config_hash }}-${{ env.pythonLocation }} + # Should be kept in sync with the other config.cache step above. + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ env.WASI_SDK_VERSION }}-${{ env.WASMTIME_VERSION }}-${{ inputs.config_hash }}-${{ hashFiles('Tools/wasm/wasi.py') }}-${{ env.pythonLocation }} - name: "Configure host" # `--with-pydebug` inferred from configure-build-python run: python3 Tools/wasm/wasi.py configure-host -- --config-cache From f62161837e68c1c77961435f1b954412dd5c2b65 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 9 Jul 2024 15:08:01 -0700 Subject: [PATCH 76/97] GH-121521: Detect when wasmtime is not installed in `Tools/wasm/wasi.py` (GH-121522) --- Tools/wasm/wasi.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Tools/wasm/wasi.py b/Tools/wasm/wasi.py index f69299fd662806..a14f58bdac0cb2 100644 --- a/Tools/wasm/wasi.py +++ b/Tools/wasm/wasi.py @@ -26,6 +26,9 @@ 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. @@ -215,11 +218,20 @@ def configure_wasi_python(context, working_dir): # Use PYTHONPATH to include sysconfig data which must be anchored to the # WASI guest's `/` directory. - host_runner = context.host_runner.format(GUEST_DIR="/", - HOST_DIR=CHECKOUT, - ENV_VAR_NAME="PYTHONPATH", - ENV_VAR_VALUE=f"/{sysconfig_data}", - PYTHON_WASM=working_dir / "python.wasm") + 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 @@ -277,7 +289,7 @@ def clean_contents(context): def main(): - default_host_runner = (f"{shutil.which('wasmtime')} run " + default_host_runner = (f"{WASMTIME_HOST_RUNNER_VAR} run " # Make sure the stack size will work for a pydebug # build. # Use 16 MiB stack. From 9585a1a2a251aaa15baf6579e13dd3be0cb05f1f Mon Sep 17 00:00:00 2001 From: satori1995 <132636720+satori1995@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:48:25 +0800 Subject: [PATCH 77/97] GH-121439: Allow PyTupleObjects with an ob_size of 20 in the free_list to be reused (gh-121428) --- .../2024-07-08-02-24-55.gh-issue-121439.jDHod3.rst | 1 + Objects/tupleobject.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-07-08-02-24-55.gh-issue-121439.jDHod3.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-08-02-24-55.gh-issue-121439.jDHod3.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-08-02-24-55.gh-issue-121439.jDHod3.rst new file mode 100644 index 00000000000000..361f9fc71186c6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-08-02-24-55.gh-issue-121439.jDHod3.rst @@ -0,0 +1 @@ +Allow tuples of length 20 in the freelist to be reused. diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 994258f20b495d..3704d095a977ea 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -1153,7 +1153,7 @@ maybe_freelist_pop(Py_ssize_t size) return NULL; } assert(size > 0); - if (size < PyTuple_MAXSAVESIZE) { + if (size <= PyTuple_MAXSAVESIZE) { Py_ssize_t index = size - 1; PyTupleObject *op = TUPLE_FREELIST.items[index]; if (op != NULL) { From 22a0bdbf9a63f92f45106c7dc4377e45e0278e60 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 10 Jul 2024 11:29:03 +0300 Subject: [PATCH 78/97] Improve zipimport tests (GH-121535) --- Lib/test/test_zipimport.py | 296 +++++++++++++++++-------------------- 1 file changed, 138 insertions(+), 158 deletions(-) diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py index d70fa3e863b98c..1861616d5ec3bf 100644 --- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -52,8 +52,11 @@ def module_path_to_dotted_name(path): TESTMOD = "ziptestmodule" +TESTMOD2 = "ziptestmodule2" +TESTMOD3 = "ziptestmodule3" TESTPACK = "ziptestpackage" TESTPACK2 = "ziptestpackage2" +TESTPACK3 = "ziptestpackage3" TEMP_DIR = os.path.abspath("junk95142") TEMP_ZIP = os.path.abspath("junk95142.zip") TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "zipimport_data") @@ -95,8 +98,10 @@ def makeTree(self, files, dirName=TEMP_DIR): # defined by files under the directory dirName. self.addCleanup(os_helper.rmtree, dirName) - for name, (mtime, data) in files.items(): - path = os.path.join(dirName, name) + for name, data in files.items(): + if isinstance(data, tuple): + mtime, data = data + path = os.path.join(dirName, *name.split('/')) if path[-1] == os.sep: if not os.path.isdir(path): os.makedirs(path) @@ -107,22 +112,18 @@ def makeTree(self, files, dirName=TEMP_DIR): with open(path, 'wb') as fp: fp.write(data) - def makeZip(self, files, zipName=TEMP_ZIP, **kw): + def makeZip(self, files, zipName=TEMP_ZIP, *, + comment=None, file_comment=None, stuff=None, prefix='', **kw): # Create a zip archive based set of modules/packages - # defined by files in the zip file zipName. If the - # key 'stuff' exists in kw it is prepended to the archive. + # defined by files in the zip file zipName. + # If stuff is not None, it is prepended to the archive. self.addCleanup(os_helper.unlink, zipName) - with ZipFile(zipName, "w") as z: - for name, (mtime, data) in files.items(): - zinfo = ZipInfo(name, time.localtime(mtime)) - zinfo.compress_type = self.compression - z.writestr(zinfo, data) - comment = kw.get("comment", None) + with ZipFile(zipName, "w", compression=self.compression) as z: + self.writeZip(z, files, file_comment=file_comment, prefix=prefix) if comment is not None: z.comment = comment - stuff = kw.get("stuff", None) if stuff is not None: # Prepend 'stuff' to the start of the zipfile with open(zipName, "rb") as f: @@ -131,26 +132,47 @@ def makeZip(self, files, zipName=TEMP_ZIP, **kw): f.write(stuff) f.write(data) + def writeZip(self, z, files, *, file_comment=None, prefix=''): + for name, data in files.items(): + if isinstance(data, tuple): + mtime, data = data + else: + mtime = NOW + name = name.replace(os.sep, '/') + zinfo = ZipInfo(prefix + name, time.localtime(mtime)) + zinfo.compress_type = self.compression + if file_comment is not None: + zinfo.comment = file_comment + if data is None: + zinfo.CRC = 0 + z.mkdir(zinfo) + else: + assert name[-1] != '/' + z.writestr(zinfo, data) + def getZip64Files(self): # This is the simplest way to make zipfile generate the zip64 EOCD block - return {f"f{n}.py": (NOW, test_src) for n in range(65537)} + return {f"f{n}.py": test_src for n in range(65537)} def doTest(self, expected_ext, files, *modules, **kw): + if 'prefix' not in kw: + kw['prefix'] = 'pre/fix/' self.makeZip(files, **kw) self.doTestWithPreBuiltZip(expected_ext, *modules, **kw) - def doTestWithPreBuiltZip(self, expected_ext, *modules, **kw): - sys.path.insert(0, TEMP_ZIP) + def doTestWithPreBuiltZip(self, expected_ext, *modules, + call=None, prefix='', **kw): + zip_path = os.path.join(TEMP_ZIP, *prefix.split('/')[:-1]) + sys.path.insert(0, zip_path) mod = importlib.import_module(".".join(modules)) - call = kw.get('call') if call is not None: call(mod) if expected_ext: file = mod.get_file() - self.assertEqual(file, os.path.join(TEMP_ZIP, + self.assertEqual(file, os.path.join(zip_path, *modules) + expected_ext) def testAFakeZlib(self): @@ -176,7 +198,7 @@ def testAFakeZlib(self): self.skipTest('zlib is a builtin module') if "zlib" in sys.modules: del sys.modules["zlib"] - files = {"zlib.py": (NOW, test_src)} + files = {"zlib.py": test_src} try: self.doTest(".py", files, "zlib") except ImportError: @@ -187,16 +209,16 @@ def testAFakeZlib(self): self.fail("expected test to raise ImportError") def testPy(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD) def testPyc(self): - files = {TESTMOD + pyc_ext: (NOW, test_pyc)} + files = {TESTMOD + pyc_ext: test_pyc} self.doTest(pyc_ext, files, TESTMOD) def testBoth(self): - files = {TESTMOD + ".py": (NOW, test_src), - TESTMOD + pyc_ext: (NOW, test_pyc)} + files = {TESTMOD + ".py": test_src, + TESTMOD + pyc_ext: test_pyc} self.doTest(pyc_ext, files, TESTMOD) def testUncheckedHashBasedPyc(self): @@ -229,22 +251,22 @@ def check(mod): self.doTest(None, files, TESTMOD, call=check) def testEmptyPy(self): - files = {TESTMOD + ".py": (NOW, "")} + files = {TESTMOD + ".py": ""} self.doTest(None, files, TESTMOD) def testBadMagic(self): # make pyc magic word invalid, forcing loading from .py badmagic_pyc = bytearray(test_pyc) badmagic_pyc[0] ^= 0x04 # flip an arbitrary bit - files = {TESTMOD + ".py": (NOW, test_src), - TESTMOD + pyc_ext: (NOW, badmagic_pyc)} + files = {TESTMOD + ".py": test_src, + TESTMOD + pyc_ext: badmagic_pyc} self.doTest(".py", files, TESTMOD) def testBadMagic2(self): # make pyc magic word invalid, causing an ImportError badmagic_pyc = bytearray(test_pyc) badmagic_pyc[0] ^= 0x04 # flip an arbitrary bit - files = {TESTMOD + pyc_ext: (NOW, badmagic_pyc)} + files = {TESTMOD + pyc_ext: badmagic_pyc} try: self.doTest(".py", files, TESTMOD) self.fail("This should not be reached") @@ -257,22 +279,22 @@ def testBadMTime(self): # flip the second bit -- not the first as that one isn't stored in the # .py's mtime in the zip archive. badtime_pyc[11] ^= 0x02 - files = {TESTMOD + ".py": (NOW, test_src), - TESTMOD + pyc_ext: (NOW, badtime_pyc)} + files = {TESTMOD + ".py": test_src, + TESTMOD + pyc_ext: badtime_pyc} self.doTest(".py", files, TESTMOD) def test2038MTime(self): # Make sure we can handle mtimes larger than what a 32-bit signed number # can hold. twenty_thirty_eight_pyc = make_pyc(test_co, 2**32 - 1, len(test_src)) - files = {TESTMOD + ".py": (NOW, test_src), - TESTMOD + pyc_ext: (NOW, twenty_thirty_eight_pyc)} + files = {TESTMOD + ".py": test_src, + TESTMOD + pyc_ext: twenty_thirty_eight_pyc} self.doTest(".py", files, TESTMOD) def testPackage(self): packdir = TESTPACK + os.sep - files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), - packdir + TESTMOD + pyc_ext: (NOW, test_pyc)} + files = {packdir + "__init__" + pyc_ext: test_pyc, + packdir + TESTMOD + pyc_ext: test_pyc} self.doTest(pyc_ext, files, TESTPACK, TESTMOD) def testSubPackage(self): @@ -280,9 +302,9 @@ def testSubPackage(self): # archives. packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep - files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} + files = {packdir + "__init__" + pyc_ext: test_pyc, + packdir2 + "__init__" + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc} self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD) def testSubNamespacePackage(self): @@ -291,9 +313,9 @@ def testSubNamespacePackage(self): packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep # The first two files are just directory entries (so have no data). - files = {packdir: (NOW, ""), - packdir2: (NOW, ""), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} + files = {packdir: None, + packdir2: None, + packdir2 + TESTMOD + pyc_ext: test_pyc} self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD) def testPackageExplicitDirectories(self): @@ -376,19 +398,19 @@ def testMixedNamespacePackage(self): # real filesystem and a zip archive. packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep - packdir3 = packdir2 + TESTPACK + '3' + os.sep - files1 = {packdir: (NOW, ""), - packdir + TESTMOD + pyc_ext: (NOW, test_pyc), - packdir2: (NOW, ""), - packdir3: (NOW, ""), - packdir3 + TESTMOD + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + '3' + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} - files2 = {packdir: (NOW, ""), - packdir + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), - packdir2: (NOW, ""), - packdir2 + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} + packdir3 = packdir2 + TESTPACK3 + os.sep + files1 = {packdir: None, + packdir + TESTMOD + pyc_ext: test_pyc, + packdir2: None, + packdir3: None, + packdir3 + TESTMOD + pyc_ext: test_pyc, + packdir2 + TESTMOD3 + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc} + files2 = {packdir: None, + packdir + TESTMOD2 + pyc_ext: test_pyc, + packdir2: None, + packdir2 + TESTMOD2 + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc} zip1 = os.path.abspath("path1.zip") self.makeZip(files1, zip1) @@ -421,8 +443,8 @@ def testMixedNamespacePackage(self): mod = importlib.import_module('.'.join((TESTPACK, TESTMOD))) self.assertEqual("path1.zip", mod.__file__.split(os.sep)[-3]) - # And TESTPACK/(TESTMOD + '2') only exists in path2. - mod = importlib.import_module('.'.join((TESTPACK, TESTMOD + '2'))) + # And TESTPACK/(TESTMOD2) only exists in path2. + mod = importlib.import_module('.'.join((TESTPACK, TESTMOD2))) self.assertEqual(os.path.basename(TEMP_DIR), mod.__file__.split(os.sep)[-3]) @@ -439,13 +461,13 @@ def testMixedNamespacePackage(self): self.assertEqual(os.path.basename(TEMP_DIR), mod.__file__.split(os.sep)[-4]) - # subpkg.TESTMOD + '2' only exists in zip2. - mod = importlib.import_module('.'.join((subpkg, TESTMOD + '2'))) + # subpkg.TESTMOD2 only exists in zip2. + mod = importlib.import_module('.'.join((subpkg, TESTMOD2))) self.assertEqual(os.path.basename(TEMP_DIR), mod.__file__.split(os.sep)[-4]) - # Finally subpkg.TESTMOD + '3' only exists in zip1. - mod = importlib.import_module('.'.join((subpkg, TESTMOD + '3'))) + # Finally subpkg.TESTMOD3 only exists in zip1. + mod = importlib.import_module('.'.join((subpkg, TESTMOD3))) self.assertEqual('path1.zip', mod.__file__.split(os.sep)[-4]) def testNamespacePackage(self): @@ -453,22 +475,22 @@ def testNamespacePackage(self): # archives. packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep - packdir3 = packdir2 + TESTPACK + '3' + os.sep - files1 = {packdir: (NOW, ""), - packdir + TESTMOD + pyc_ext: (NOW, test_pyc), - packdir2: (NOW, ""), - packdir3: (NOW, ""), - packdir3 + TESTMOD + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + '3' + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} + packdir3 = packdir2 + TESTPACK3 + os.sep + files1 = {packdir: None, + packdir + TESTMOD + pyc_ext: test_pyc, + packdir2: None, + packdir3: None, + packdir3 + TESTMOD + pyc_ext: test_pyc, + packdir2 + TESTMOD3 + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc} zip1 = os.path.abspath("path1.zip") self.makeZip(files1, zip1) - files2 = {packdir: (NOW, ""), - packdir + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), - packdir2: (NOW, ""), - packdir2 + TESTMOD + '2' + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} + files2 = {packdir: None, + packdir + TESTMOD2 + pyc_ext: test_pyc, + packdir2: None, + packdir2 + TESTMOD2 + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc} zip2 = os.path.abspath("path2.zip") self.makeZip(files2, zip2) @@ -497,8 +519,8 @@ def testNamespacePackage(self): mod = importlib.import_module('.'.join((TESTPACK, TESTMOD))) self.assertEqual("path1.zip", mod.__file__.split(os.sep)[-3]) - # And TESTPACK/(TESTMOD + '2') only exists in path2. - mod = importlib.import_module('.'.join((TESTPACK, TESTMOD + '2'))) + # And TESTPACK/(TESTMOD2) only exists in path2. + mod = importlib.import_module('.'.join((TESTPACK, TESTMOD2))) self.assertEqual("path2.zip", mod.__file__.split(os.sep)[-3]) # One level deeper... @@ -513,29 +535,22 @@ def testNamespacePackage(self): mod = importlib.import_module('.'.join((subpkg, TESTMOD))) self.assertEqual('path2.zip', mod.__file__.split(os.sep)[-4]) - # subpkg.TESTMOD + '2' only exists in zip2. - mod = importlib.import_module('.'.join((subpkg, TESTMOD + '2'))) + # subpkg.TESTMOD2 only exists in zip2. + mod = importlib.import_module('.'.join((subpkg, TESTMOD2))) self.assertEqual('path2.zip', mod.__file__.split(os.sep)[-4]) - # Finally subpkg.TESTMOD + '3' only exists in zip1. - mod = importlib.import_module('.'.join((subpkg, TESTMOD + '3'))) + # Finally subpkg.TESTMOD3 only exists in zip1. + mod = importlib.import_module('.'.join((subpkg, TESTMOD3))) self.assertEqual('path1.zip', mod.__file__.split(os.sep)[-4]) def testZipImporterMethods(self): packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep - files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc), - "spam" + pyc_ext: (NOW, test_pyc)} - - self.addCleanup(os_helper.unlink, TEMP_ZIP) - with ZipFile(TEMP_ZIP, "w") as z: - for name, (mtime, data) in files.items(): - zinfo = ZipInfo(name, time.localtime(mtime)) - zinfo.compress_type = self.compression - zinfo.comment = b"spam" - z.writestr(zinfo, data) + files = {packdir + "__init__" + pyc_ext: test_pyc, + packdir2 + "__init__" + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc, + "spam" + pyc_ext: test_pyc} + self.makeZip(files, file_comment=b"spam") zi = zipimport.zipimporter(TEMP_ZIP) self.assertEqual(zi.archive, TEMP_ZIP) @@ -591,18 +606,12 @@ def testZipImporterMethods(self): def testInvalidateCaches(self): packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep - files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc), - "spam" + pyc_ext: (NOW, test_pyc)} + files = {packdir + "__init__" + pyc_ext: test_pyc, + packdir2 + "__init__" + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc, + "spam" + pyc_ext: test_pyc} extra_files = [packdir, packdir2] - self.addCleanup(os_helper.unlink, TEMP_ZIP) - with ZipFile(TEMP_ZIP, "w") as z: - for name, (mtime, data) in files.items(): - zinfo = ZipInfo(name, time.localtime(mtime)) - zinfo.compress_type = self.compression - zinfo.comment = b"spam" - z.writestr(zinfo, data) + self.makeZip(files, file_comment=b"spam") zi = zipimport.zipimporter(TEMP_ZIP) self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) @@ -610,14 +619,10 @@ def testInvalidateCaches(self): zi.invalidate_caches() self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) # Add a new file to the ZIP archive - newfile = {"spam2" + pyc_ext: (NOW, test_pyc)} + newfile = {"spam2" + pyc_ext: test_pyc} files.update(newfile) - with ZipFile(TEMP_ZIP, "a") as z: - for name, (mtime, data) in newfile.items(): - zinfo = ZipInfo(name, time.localtime(mtime)) - zinfo.compress_type = self.compression - zinfo.comment = b"spam" - z.writestr(zinfo, data) + with ZipFile(TEMP_ZIP, "a", compression=self.compression) as z: + self.writeZip(z, newfile, file_comment=b"spam") # Check that we can detect the new file after invalidating the cache zi.invalidate_caches() self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) @@ -634,18 +639,12 @@ def testInvalidateCaches(self): def testInvalidateCachesWithMultipleZipimports(self): packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep - files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc), - "spam" + pyc_ext: (NOW, test_pyc)} + files = {packdir + "__init__" + pyc_ext: test_pyc, + packdir2 + "__init__" + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc, + "spam" + pyc_ext: test_pyc} extra_files = [packdir, packdir2] - self.addCleanup(os_helper.unlink, TEMP_ZIP) - with ZipFile(TEMP_ZIP, "w") as z: - for name, (mtime, data) in files.items(): - zinfo = ZipInfo(name, time.localtime(mtime)) - zinfo.compress_type = self.compression - zinfo.comment = b"spam" - z.writestr(zinfo, data) + self.makeZip(files, file_comment=b"spam") zi = zipimport.zipimporter(TEMP_ZIP) self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files])) @@ -653,14 +652,10 @@ def testInvalidateCachesWithMultipleZipimports(self): zi2 = zipimport.zipimporter(TEMP_ZIP) self.assertEqual(sorted(zi2._get_files()), sorted([*files, *extra_files])) # Add a new file to the ZIP archive to make the cache wrong. - newfile = {"spam2" + pyc_ext: (NOW, test_pyc)} + newfile = {"spam2" + pyc_ext: test_pyc} files.update(newfile) - with ZipFile(TEMP_ZIP, "a") as z: - for name, (mtime, data) in newfile.items(): - zinfo = ZipInfo(name, time.localtime(mtime)) - zinfo.compress_type = self.compression - zinfo.comment = b"spam" - z.writestr(zinfo, data) + with ZipFile(TEMP_ZIP, "a", compression=self.compression) as z: + self.writeZip(z, newfile, file_comment=b"spam") # Invalidate the cache of the first zipimporter. zi.invalidate_caches() # Check that the second zipimporter detects the new file and isn't using a stale cache. @@ -672,16 +667,9 @@ def testInvalidateCachesWithMultipleZipimports(self): def testZipImporterMethodsInSubDirectory(self): packdir = TESTPACK + os.sep packdir2 = packdir + TESTPACK2 + os.sep - files = {packdir2 + "__init__" + pyc_ext: (NOW, test_pyc), - packdir2 + TESTMOD + pyc_ext: (NOW, test_pyc)} - - self.addCleanup(os_helper.unlink, TEMP_ZIP) - with ZipFile(TEMP_ZIP, "w") as z: - for name, (mtime, data) in files.items(): - zinfo = ZipInfo(name, time.localtime(mtime)) - zinfo.compress_type = self.compression - zinfo.comment = b"eggs" - z.writestr(zinfo, data) + files = {packdir2 + "__init__" + pyc_ext: test_pyc, + packdir2 + TESTMOD + pyc_ext: test_pyc} + self.makeZip(files, file_comment=b"eggs") zi = zipimport.zipimporter(TEMP_ZIP + os.sep + packdir) self.assertEqual(zi.archive, TEMP_ZIP) @@ -762,9 +750,9 @@ def get_file(): if __loader__.get_data("some.data") != b"some data": raise AssertionError("bad data")\n""" pyc = make_pyc(compile(src, "", "exec"), NOW, len(src)) - files = {TESTMOD + pyc_ext: (NOW, pyc), - "some.data": (NOW, "some data")} - self.doTest(pyc_ext, files, TESTMOD) + files = {TESTMOD + pyc_ext: pyc, + "some.data": "some data"} + self.doTest(pyc_ext, files, TESTMOD, prefix='') def testDefaultOptimizationLevel(self): # zipimport should use the default optimization level (#28131) @@ -772,7 +760,7 @@ def testDefaultOptimizationLevel(self): def test(val): assert(val) return val\n""" - files = {TESTMOD + '.py': (NOW, src)} + files = {TESTMOD + '.py': src} self.makeZip(files) sys.path.insert(0, TEMP_ZIP) mod = importlib.import_module(TESTMOD) @@ -785,7 +773,7 @@ def test(val): def testImport_WithStuff(self): # try importing from a zipfile which contains additional # stuff at the beginning of the file - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, stuff=b"Some Stuff"*31) @@ -793,18 +781,18 @@ def assertModuleSource(self, module): self.assertEqual(inspect.getsource(module), test_src) def testGetSource(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, call=self.assertModuleSource) def testGetCompiledSource(self): pyc = make_pyc(compile(test_src, "", "exec"), NOW, len(test_src)) - files = {TESTMOD + ".py": (NOW, test_src), - TESTMOD + pyc_ext: (NOW, pyc)} + files = {TESTMOD + ".py": test_src, + TESTMOD + pyc_ext: pyc} self.doTest(pyc_ext, files, TESTMOD, call=self.assertModuleSource) def runDoctest(self, callback): - files = {TESTMOD + ".py": (NOW, test_src), - "xyz.txt": (NOW, ">>> log.append(True)\n")} + files = {TESTMOD + ".py": test_src, + "xyz.txt": ">>> log.append(True)\n"} self.doTest(".py", files, TESTMOD, call=callback) def doDoctestFile(self, module): @@ -856,29 +844,21 @@ def doTraceback(self, module): raise AssertionError("This ought to be impossible") def testTraceback(self): - files = {TESTMOD + ".py": (NOW, raise_src)} + files = {TESTMOD + ".py": raise_src} self.doTest(None, files, TESTMOD, call=self.doTraceback) @unittest.skipIf(os_helper.TESTFN_UNENCODABLE is None, "need an unencodable filename") def testUnencodable(self): filename = os_helper.TESTFN_UNENCODABLE + ".zip" - self.addCleanup(os_helper.unlink, filename) - with ZipFile(filename, "w") as z: - zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW)) - zinfo.compress_type = self.compression - z.writestr(zinfo, test_src) + self.makeZip({TESTMOD + ".py": test_src}, filename) spec = zipimport.zipimporter(filename).find_spec(TESTMOD) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) def testBytesPath(self): filename = os_helper.TESTFN + ".zip" - self.addCleanup(os_helper.unlink, filename) - with ZipFile(filename, "w") as z: - zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW)) - zinfo.compress_type = self.compression - z.writestr(zinfo, test_src) + self.makeZip({TESTMOD + ".py": test_src}, filename) zipimport.zipimporter(filename) with self.assertRaises(TypeError): @@ -889,15 +869,15 @@ def testBytesPath(self): zipimport.zipimporter(memoryview(os.fsencode(filename))) def testComment(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, comment=b"comment") def testBeginningCruftAndComment(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, stuff=b"cruft" * 64, comment=b"hi") def testLargestPossibleComment(self): - files = {TESTMOD + ".py": (NOW, test_src)} + files = {TESTMOD + ".py": test_src} self.doTest(".py", files, TESTMOD, comment=b"c" * ((1 << 16) - 1)) @support.requires_resource('cpu') From e2822360da30853f092d8a50ad83e52f6ea2ced9 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 10 Jul 2024 13:11:46 +0300 Subject: [PATCH 79/97] gh-121571: Do not use `EnvironmentError` in tests, use `OSError` instead (#121572) --- Lib/test/support/__init__.py | 2 +- Lib/test/test_subprocess.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 18455bb6e0ff52..7f6579319589b4 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -387,7 +387,7 @@ def skip_if_buildbot(reason=None): reason = 'not suitable for buildbots' try: isbuildbot = getpass.getuser().lower() == 'buildbot' - except (KeyError, EnvironmentError) as err: + except (KeyError, OSError) as err: warnings.warn(f'getpass.getuser() failed {err}.', RuntimeWarning) isbuildbot = False return unittest.skipIf(isbuildbot, reason) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 8b69cd03ba7f24..9412a2d737bb2e 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1407,7 +1407,7 @@ def open_fds(): t = threading.Thread(target=open_fds) t.start() try: - with self.assertRaises(EnvironmentError): + with self.assertRaises(OSError): subprocess.Popen(NONEXISTING_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE, From cced22c760a9398c27f0381048a1b3eb2e8ef50d Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:34:54 +0100 Subject: [PATCH 80/97] gh-121547: deduplicate the code of const_cache update functions (#121548) --- Python/compile.c | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index fa70ca101c40e5..bc7923440e1413 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -741,9 +741,11 @@ dict_add_o(PyObject *dict, PyObject *o) return arg; } -// Merge const *o* recursively and return constant key object. +/* Merge const *o* and return constant key object. + * If recursive, insert all elements if o is a tuple or frozen set. + */ static PyObject* -merge_consts_recursive(PyObject *const_cache, PyObject *o) +const_cache_insert(PyObject *const_cache, PyObject *o, bool recursive) { assert(PyDict_CheckExact(const_cache)); // None and Ellipsis are immortal objects, and key is the singleton. @@ -767,6 +769,10 @@ merge_consts_recursive(PyObject *const_cache, PyObject *o) } Py_DECREF(t); + if (!recursive) { + return key; + } + // We registered o in const_cache. // When o is a tuple or frozenset, we want to merge its // items too. @@ -774,7 +780,7 @@ merge_consts_recursive(PyObject *const_cache, PyObject *o) Py_ssize_t len = PyTuple_GET_SIZE(o); for (Py_ssize_t i = 0; i < len; i++) { PyObject *item = PyTuple_GET_ITEM(o, i); - PyObject *u = merge_consts_recursive(const_cache, item); + PyObject *u = const_cache_insert(const_cache, item, recursive); if (u == NULL) { Py_DECREF(key); return NULL; @@ -816,7 +822,7 @@ merge_consts_recursive(PyObject *const_cache, PyObject *o) PyObject *item; Py_hash_t hash; while (_PySet_NextEntry(o, &pos, &item, &hash)) { - PyObject *k = merge_consts_recursive(const_cache, item); + PyObject *k = const_cache_insert(const_cache, item, recursive); if (k == NULL) { Py_DECREF(tuple); Py_DECREF(key); @@ -850,6 +856,12 @@ merge_consts_recursive(PyObject *const_cache, PyObject *o) return key; } +static PyObject* +merge_consts_recursive(PyObject *const_cache, PyObject *o) +{ + return const_cache_insert(const_cache, o, true); +} + static Py_ssize_t compiler_add_const(PyObject *const_cache, struct compiler_unit *u, PyObject *o) { @@ -7506,37 +7518,22 @@ compute_code_flags(struct compiler *c) return flags; } -// Merge *obj* with constant cache. -// Unlike merge_consts_recursive(), this function doesn't work recursively. +// Merge *obj* with constant cache, without recursion. int _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj) { - assert(PyDict_CheckExact(const_cache)); - PyObject *key = _PyCode_ConstantKey(*obj); + PyObject *key = const_cache_insert(const_cache, *obj, false); if (key == NULL) { return ERROR; } - - PyObject *t; - int res = PyDict_SetDefaultRef(const_cache, key, key, &t); - Py_DECREF(key); - if (res < 0) { - return ERROR; - } - if (res == 0) { // inserted: obj is new constant. - Py_DECREF(t); - return SUCCESS; - } - - if (PyTuple_CheckExact(t)) { - PyObject *item = PyTuple_GET_ITEM(t, 1); + if (PyTuple_CheckExact(key)) { + PyObject *item = PyTuple_GET_ITEM(key, 1); Py_SETREF(*obj, Py_NewRef(item)); - Py_DECREF(t); + Py_DECREF(key); } else { - Py_SETREF(*obj, t); + Py_SETREF(*obj, key); } - return SUCCESS; } From 84a5597b08b7d53aced2fbd0048271ce762807a8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 10 Jul 2024 13:56:44 +0300 Subject: [PATCH 81/97] gh-121567: Improve `slice` C-API docs by mentioning exceptions (#121568) Co-authored-by: Erlend E. Aasland --- Doc/c-api/slice.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/slice.rst b/Doc/c-api/slice.rst index 27a1757c745d8b..8adf6a961378a3 100644 --- a/Doc/c-api/slice.rst +++ b/Doc/c-api/slice.rst @@ -23,7 +23,9 @@ Slice Objects Return a new slice object with the given values. The *start*, *stop*, and *step* parameters are used as the values of the slice object attributes of the same names. Any of the values may be ``NULL``, in which case the - ``None`` will be used for the corresponding attribute. Return ``NULL`` if + ``None`` will be used for the corresponding attribute. + + Return ``NULL`` with an exception set if the new object could not be allocated. @@ -52,7 +54,7 @@ Slice Objects of bounds indices are clipped in a manner consistent with the handling of normal slices. - Returns ``0`` on success and ``-1`` on error with exception set. + Return ``0`` on success and ``-1`` on error with an exception set. .. note:: This function is considered not safe for resizable sequences. @@ -95,7 +97,7 @@ Slice Objects ``PY_SSIZE_T_MIN`` to ``PY_SSIZE_T_MIN``, and silently boost the step values less than ``-PY_SSIZE_T_MAX`` to ``-PY_SSIZE_T_MAX``. - Return ``-1`` on error, ``0`` on success. + Return ``-1`` with an exception set on error, ``0`` on success. .. versionadded:: 3.6.1 From ca0fb3423c13822d909d75eb616ecf1965e619ae Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 10 Jul 2024 15:47:08 +0200 Subject: [PATCH 82/97] gh-89364: Export PySignal_SetWakeupFd() function (#121537) Export the PySignal_SetWakeupFd() function. Previously, the function was documented but it couldn't be used in 3rd party code. --- Include/cpython/pyerrors.h | 2 +- .../next/C_API/2024-07-09-15-55-20.gh-issue-89364.yYYroI.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/C_API/2024-07-09-15-55-20.gh-issue-89364.yYYroI.rst diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index 42b4b03b10ca20..b36b4681f5dddb 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -100,7 +100,7 @@ PyAPI_FUNC(PyObject*) PyUnstable_Exc_PrepReraiseStar( /* In signalmodule.c */ -int PySignal_SetWakeupFd(int fd); +PyAPI_FUNC(int) PySignal_SetWakeupFd(int fd); /* Support for adding program text to SyntaxErrors */ diff --git a/Misc/NEWS.d/next/C_API/2024-07-09-15-55-20.gh-issue-89364.yYYroI.rst b/Misc/NEWS.d/next/C_API/2024-07-09-15-55-20.gh-issue-89364.yYYroI.rst new file mode 100644 index 00000000000000..b82e78446e4e87 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-07-09-15-55-20.gh-issue-89364.yYYroI.rst @@ -0,0 +1,3 @@ +Export the :c:func:`PySignal_SetWakeupFd` function. Previously, the function +was documented but it couldn't be used in 3rd party code. Patch by Victor +Stinner. From af9f6de6ea930b607f948f2c91a87fe4ca9d64db Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 10 Jul 2024 10:36:52 -0400 Subject: [PATCH 83/97] gh-107851: Fix spurious failures in fcntl eintr tests (#121556) On heavily loaded machines, the subprocess may finish its sleep before the parent process manages to synchronize with it via a failed lock. This leads to errors like: Exception: failed to sync child in 300.3 sec Use pipes instead to mutually synchronize between parent and child. --- Lib/test/_test_eintr.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Lib/test/_test_eintr.py b/Lib/test/_test_eintr.py index 15586f15dfab30..493932d6c6d441 100644 --- a/Lib/test/_test_eintr.py +++ b/Lib/test/_test_eintr.py @@ -18,6 +18,7 @@ import socket import subprocess import sys +import textwrap import time import unittest @@ -492,29 +493,31 @@ def test_devpoll(self): self.check_elapsed_time(dt) -class FNTLEINTRTest(EINTRBaseTest): +class FCNTLEINTRTest(EINTRBaseTest): def _lock(self, lock_func, lock_name): self.addCleanup(os_helper.unlink, os_helper.TESTFN) - code = '\n'.join(( - "import fcntl, time", - "with open('%s', 'wb') as f:" % os_helper.TESTFN, - " fcntl.%s(f, fcntl.LOCK_EX)" % lock_name, - " time.sleep(%s)" % self.sleep_time)) - start_time = time.monotonic() - proc = self.subprocess(code) + rd1, wr1 = os.pipe() + rd2, wr2 = os.pipe() + for fd in (rd1, wr1, rd2, wr2): + self.addCleanup(os.close, fd) + code = textwrap.dedent(f""" + import fcntl, os, time + with open('{os_helper.TESTFN}', 'wb') as f: + fcntl.{lock_name}(f, fcntl.LOCK_EX) + os.write({wr1}, b"ok") + _ = os.read({rd2}, 2) # wait for parent process + time.sleep({self.sleep_time}) + """) + proc = self.subprocess(code, pass_fds=[wr1, rd2]) with kill_on_error(proc): with open(os_helper.TESTFN, 'wb') as f: # synchronize the subprocess + ok = os.read(rd1, 2) + self.assertEqual(ok, b"ok") + + # notify the child that the parent is ready start_time = time.monotonic() - for _ in support.sleeping_retry(support.LONG_TIMEOUT, error=False): - try: - lock_func(f, fcntl.LOCK_EX | fcntl.LOCK_NB) - lock_func(f, fcntl.LOCK_UN) - except BlockingIOError: - break - else: - dt = time.monotonic() - start_time - raise Exception("failed to sync child in %.1f sec" % dt) + os.write(wr2, b"go") # the child locked the file just a moment ago for 'sleep_time' seconds # that means that the lock below will block for 'sleep_time' minus some From 0177a343353d88ca8475dccabf6e98e164abb0e8 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:09:45 +0100 Subject: [PATCH 84/97] gh-121404: remove some accesses to compiler internals from codegen functions (#121538) --- Python/compile.c | 221 +++++++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 101 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index bc7923440e1413..83157743ee29cd 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -71,11 +71,24 @@ ((C)->c_flags.cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT) \ && ((C)->u->u_ste->ste_type == ModuleBlock)) +struct compiler; + +typedef _PyInstruction instruction; +typedef _PyInstructionSequence instr_sequence; + +static instr_sequence *compiler_instr_sequence(struct compiler *c); +static int compiler_future_features(struct compiler *c); +static struct symtable *compiler_symtable(struct compiler *c); +static PySTEntryObject *compiler_symtable_entry(struct compiler *c); + +#define INSTR_SEQUENCE(C) compiler_instr_sequence(C) +#define FUTURE_FEATURES(C) compiler_future_features(C) +#define SYMTABLE(C) compiler_symtable(C) +#define SYMTABLE_ENTRY(C) compiler_symtable_entry(C) + typedef _Py_SourceLocation location; typedef struct _PyCfgBuilder cfg_builder; -struct compiler; - static PyObject *compiler_maybe_mangle(struct compiler *c, PyObject *name); #define LOCATION(LNO, END_LNO, COL, END_COL) \ @@ -133,12 +146,6 @@ enum { }; -typedef _PyInstruction instruction; -typedef _PyInstructionSequence instr_sequence; - -#define INITIAL_INSTR_SEQUENCE_SIZE 100 -#define INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE 10 - static const int compare_masks[] = { [Py_LT] = COMPARISON_LESS_THAN, [Py_LE] = COMPARISON_LESS_THAN | COMPARISON_EQUALS, @@ -258,8 +265,6 @@ struct compiler { */ }; -#define INSTR_SEQUENCE(C) ((C)->u->u_instr_sequence) - typedef struct { // A list of strings corresponding to name captures. It is used to track: @@ -317,7 +322,6 @@ static int compiler_call_helper(struct compiler *c, location loc, asdl_keyword_seq *keywords); static int compiler_try_except(struct compiler *, stmt_ty); static int compiler_try_star_except(struct compiler *, stmt_ty); -static int compiler_set_qualname(struct compiler *); static int compiler_sync_comprehension_generator( struct compiler *c, location loc, @@ -558,8 +562,8 @@ compiler_unit_free(struct compiler_unit *u) PyMem_Free(u); } -static struct compiler_unit * -get_class_compiler_unit(struct compiler *c) +static int +compiler_add_static_attribute_to_class(struct compiler *c, PyObject *attr) { Py_ssize_t stack_size = PyList_GET_SIZE(c->c_stack); for (Py_ssize_t i = stack_size - 1; i >= 0; i--) { @@ -568,10 +572,12 @@ get_class_compiler_unit(struct compiler *c) capsule, CAPSULE_NAME); assert(u); if (u->u_scope_type == COMPILER_SCOPE_CLASS) { - return u; + assert(u->u_static_attributes); + RETURN_IF_ERROR(PySet_Add(u->u_static_attributes, attr)); + break; } } - return NULL; + return SUCCESS; } static int @@ -863,38 +869,37 @@ merge_consts_recursive(PyObject *const_cache, PyObject *o) } static Py_ssize_t -compiler_add_const(PyObject *const_cache, struct compiler_unit *u, PyObject *o) +compiler_add_const(struct compiler *c, PyObject *o) { - assert(PyDict_CheckExact(const_cache)); - PyObject *key = merge_consts_recursive(const_cache, o); + PyObject *key = merge_consts_recursive(c->c_const_cache, o); if (key == NULL) { return ERROR; } - Py_ssize_t arg = dict_add_o(u->u_metadata.u_consts, key); + Py_ssize_t arg = dict_add_o(c->u->u_metadata.u_consts, key); Py_DECREF(key); return arg; } static int -compiler_addop_load_const(PyObject *const_cache, struct compiler_unit *u, location loc, PyObject *o) +compiler_addop_load_const(struct compiler *c, location loc, PyObject *o) { - Py_ssize_t arg = compiler_add_const(const_cache, u, o); + Py_ssize_t arg = compiler_add_const(c, o); if (arg < 0) { return ERROR; } - return codegen_addop_i(u->u_instr_sequence, LOAD_CONST, arg, loc); + return codegen_addop_i(INSTR_SEQUENCE(c), LOAD_CONST, arg, loc); } static int -compiler_addop_o(struct compiler_unit *u, location loc, +compiler_addop_o(struct compiler *c, location loc, int opcode, PyObject *dict, PyObject *o) { Py_ssize_t arg = dict_add_o(dict, o); if (arg < 0) { return ERROR; } - return codegen_addop_i(u->u_instr_sequence, opcode, arg, loc); + return codegen_addop_i(INSTR_SEQUENCE(c), opcode, arg, loc); } #define LOAD_METHOD -1 @@ -984,7 +989,7 @@ codegen_addop_j(instr_sequence *seq, location loc, #define ADDOP_IN_SCOPE(C, LOC, OP) RETURN_IF_ERROR_IN_SCOPE((C), codegen_addop_noarg(INSTR_SEQUENCE(C), (OP), (LOC))) #define ADDOP_LOAD_CONST(C, LOC, O) \ - RETURN_IF_ERROR(compiler_addop_load_const((C)->c_const_cache, (C)->u, (LOC), (O))) + RETURN_IF_ERROR(compiler_addop_load_const((C), (LOC), (O))) /* Same as ADDOP_LOAD_CONST, but steals a reference. */ #define ADDOP_LOAD_CONST_NEW(C, LOC, O) { \ @@ -992,7 +997,7 @@ codegen_addop_j(instr_sequence *seq, location loc, if (__new_const == NULL) { \ return ERROR; \ } \ - if (compiler_addop_load_const((C)->c_const_cache, (C)->u, (LOC), __new_const) < 0) { \ + if (compiler_addop_load_const((C), (LOC), __new_const) < 0) { \ Py_DECREF(__new_const); \ return ERROR; \ } \ @@ -1001,7 +1006,7 @@ codegen_addop_j(instr_sequence *seq, location loc, #define ADDOP_N(C, LOC, OP, O, TYPE) { \ assert(!OPCODE_HAS_CONST(OP)); /* use ADDOP_LOAD_CONST_NEW */ \ - if (compiler_addop_o((C)->u, (LOC), (OP), (C)->u->u_metadata.u_ ## TYPE, (O)) < 0) { \ + if (compiler_addop_o((C), (LOC), (OP), (C)->u->u_metadata.u_ ## TYPE, (O)) < 0) { \ Py_DECREF((O)); \ return ERROR; \ } \ @@ -1514,7 +1519,7 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) /* If from __future__ import annotations is active, * every annotated class and module should have __annotations__. * Else __annotate__ is created when necessary. */ - if ((c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && c->u->u_ste->ste_annotations_used) { + if ((FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) && SYMTABLE_ENTRY(c)->ste_annotations_used) { ADDOP(c, loc, SETUP_ANNOTATIONS); } if (!asdl_seq_LEN(stmts)) { @@ -1546,7 +1551,7 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) // If there are annotations and the future import is not on, we // collect the annotations in a separate pass and generate an // __annotate__ function. See PEP 649. - if (!(c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) && + if (!(FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) && c->u->u_deferred_annotations != NULL) { // It's possible that ste_annotations_block is set but @@ -1554,11 +1559,12 @@ compiler_body(struct compiler *c, location loc, asdl_stmt_seq *stmts) // set if there are only non-simple annotations (i.e., annotations // for attributes, subscripts, or parenthesized names). However, the // reverse should not be possible. - assert(c->u->u_ste->ste_annotation_block != NULL); + PySTEntryObject *ste = SYMTABLE_ENTRY(c); + assert(ste->ste_annotation_block != NULL); PyObject *deferred_anno = Py_NewRef(c->u->u_deferred_annotations); - void *key = (void *)((uintptr_t)c->u->u_ste->ste_id + 1); + void *key = (void *)((uintptr_t)ste->ste_id + 1); if (compiler_setup_annotations_scope(c, loc, key, - c->u->u_ste->ste_annotation_block->ste_name) == -1) { + ste->ste_annotation_block->ste_name) == -1) { Py_DECREF(deferred_anno); return ERROR; } @@ -1642,13 +1648,8 @@ compiler_mod(struct compiler *c, mod_ty mod) return co; } -/* The test for LOCAL must come before the test for FREE in order to - handle classes where name is both local and free. The local var is - a method and the free var is a free var referenced within a method. -*/ - static int -get_ref_type(struct compiler *c, PyObject *name) +compiler_get_ref_type(struct compiler *c, PyObject *name) { int scope; if (c->u->u_scope_type == COMPILER_SCOPE_CLASS && @@ -1656,15 +1657,16 @@ get_ref_type(struct compiler *c, PyObject *name) _PyUnicode_EqualToASCIIString(name, "__classdict__"))) { return CELL; } - scope = _PyST_GetScope(c->u->u_ste, name); + PySTEntryObject *ste = SYMTABLE_ENTRY(c); + scope = _PyST_GetScope(ste, name); if (scope == 0) { PyErr_Format(PyExc_SystemError, "_PyST_GetScope(name=%R) failed: " "unknown scope in unit %S (%R); " "symbols: %R; locals: %R; globals: %R", name, - c->u->u_metadata.u_name, c->u->u_ste->ste_id, - c->u->u_ste->ste_symbols, c->u->u_metadata.u_varnames, c->u->u_metadata.u_names); + c->u->u_metadata.u_name, ste->ste_id, + ste->ste_symbols, c->u->u_metadata.u_varnames, c->u->u_metadata.u_names); return ERROR; } return scope; @@ -1698,7 +1700,7 @@ compiler_make_closure(struct compiler *c, location loc, class. It should be handled by the closure, as well as by the normal name lookup logic. */ - int reftype = get_ref_type(c, name); + int reftype = compiler_get_ref_type(c, name); if (reftype == -1) { return ERROR; } @@ -1858,7 +1860,7 @@ compiler_argannotation(struct compiler *c, identifier id, ADDOP_LOAD_CONST(c, loc, mangled); Py_DECREF(mangled); - if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) { + if (FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) { VISIT(c, annexpr, annotation); } else { @@ -1940,7 +1942,7 @@ compiler_annotations(struct compiler *c, location loc, Py_ssize_t annotations_len = 0; PySTEntryObject *ste; - if (_PySymtable_LookupOptional(c->c_st, args, &ste) < 0) { + if (_PySymtable_LookupOptional(SYMTABLE(c), args, &ste) < 0) { return ERROR; } assert(ste != NULL); @@ -2239,7 +2241,7 @@ compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t f docstring = NULL; } } - if (compiler_add_const(c->c_const_cache, c->u, docstring ? docstring : Py_None) < 0) { + if (compiler_add_const(c, docstring ? docstring : Py_None) < 0) { Py_XDECREF(docstring); compiler_exit_scope(c); return ERROR; @@ -2252,7 +2254,8 @@ compiler_function_body(struct compiler *c, stmt_ty s, int is_async, Py_ssize_t f NEW_JUMP_TARGET_LABEL(c, start); USE_LABEL(c, start); - bool add_stopiteration_handler = c->u->u_ste->ste_coroutine || c->u->u_ste->ste_generator; + PySTEntryObject *ste = SYMTABLE_ENTRY(c); + bool add_stopiteration_handler = ste->ste_coroutine || ste->ste_generator; if (add_stopiteration_handler) { /* wrap_in_stopiteration_handler will push a block, so we need to account for that */ RETURN_IF_ERROR( @@ -2463,14 +2466,14 @@ compiler_class_body(struct compiler *c, stmt_ty s, int firstlineno) return ERROR; } } - if (c->u->u_ste->ste_needs_classdict) { + if (SYMTABLE_ENTRY(c)->ste_needs_classdict) { ADDOP(c, loc, LOAD_LOCALS); // We can't use compiler_nameop here because we need to generate a // STORE_DEREF in a class namespace, and compiler_nameop() won't do // that by default. PyObject *cellvars = c->u->u_metadata.u_cellvars; - if (compiler_addop_o(c->u, loc, STORE_DEREF, cellvars, + if (compiler_addop_o(c, loc, STORE_DEREF, cellvars, &_Py_ID(__classdict__)) < 0) { compiler_exit_scope(c); return ERROR; @@ -2495,7 +2498,7 @@ compiler_class_body(struct compiler *c, stmt_ty s, int firstlineno) } /* The following code is artificial */ /* Set __classdictcell__ if necessary */ - if (c->u->u_ste->ste_needs_classdict) { + if (SYMTABLE_ENTRY(c)->ste_needs_classdict) { /* Store __classdictcell__ into class namespace */ int i = compiler_lookup_arg(c->u->u_metadata.u_cellvars, &_Py_ID(__classdict__)); if (i < 0) { @@ -2509,7 +2512,7 @@ compiler_class_body(struct compiler *c, stmt_ty s, int firstlineno) } } /* Return __classcell__ if it is referenced, otherwise return None */ - if (c->u->u_ste->ste_needs_class_closure) { + if (SYMTABLE_ENTRY(c)->ste_needs_class_closure) { /* Store __classcell__ into class namespace & return it */ int i = compiler_lookup_arg(c->u->u_metadata.u_cellvars, &_Py_ID(__class__)); if (i < 0) { @@ -2667,7 +2670,7 @@ compiler_typealias_body(struct compiler *c, stmt_ty s) compiler_enter_scope(c, name, COMPILER_SCOPE_FUNCTION, s, loc.lineno, NULL)); /* Make None the first constant, so the evaluate function can't have a docstring. */ - RETURN_IF_ERROR(compiler_add_const(c->c_const_cache, c->u, Py_None)); + RETURN_IF_ERROR(compiler_add_const(c, Py_None)); VISIT_IN_SCOPE(c, expr, s->v.TypeAlias.value); ADDOP_IN_SCOPE(c, loc, RETURN_VALUE); PyCodeObject *co = optimize_and_assemble(c, 0); @@ -2705,7 +2708,7 @@ compiler_typealias(struct compiler *c, stmt_ty s) } Py_DECREF(type_params_name); RETURN_IF_ERROR_IN_SCOPE( - c, compiler_addop_load_const(c->c_const_cache, c->u, loc, name) + c, compiler_addop_load_const(c, loc, name) ); RETURN_IF_ERROR_IN_SCOPE(c, compiler_type_params(c, type_params)); } @@ -2972,13 +2975,13 @@ compiler_lambda(struct compiler *c, expr_ty e) /* Make None the first constant, so the lambda can't have a docstring. */ - RETURN_IF_ERROR(compiler_add_const(c->c_const_cache, c->u, Py_None)); + RETURN_IF_ERROR(compiler_add_const(c, Py_None)); c->u->u_metadata.u_argcount = asdl_seq_LEN(args->args); c->u->u_metadata.u_posonlyargcount = asdl_seq_LEN(args->posonlyargs); c->u->u_metadata.u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs); VISIT_IN_SCOPE(c, expr, e->v.Lambda.body); - if (c->u->u_ste->ste_generator) { + if (SYMTABLE_ENTRY(c)->ste_generator) { co = optimize_and_assemble(c, 0); } else { @@ -3154,12 +3157,12 @@ compiler_return(struct compiler *c, stmt_ty s) location loc = LOC(s); int preserve_tos = ((s->v.Return.value != NULL) && (s->v.Return.value->kind != Constant_kind)); - if (!_PyST_IsFunctionLike(c->u->u_ste)) { + + PySTEntryObject *ste = SYMTABLE_ENTRY(c); + if (!_PyST_IsFunctionLike(ste)) { return compiler_error(c, loc, "'return' outside function"); } - if (s->v.Return.value != NULL && - c->u->u_ste->ste_coroutine && c->u->u_ste->ste_generator) - { + if (s->v.Return.value != NULL && ste->ste_coroutine && ste->ste_generator) { return compiler_error(c, loc, "'return' with value in async generator"); } @@ -4109,7 +4112,8 @@ addop_binary(struct compiler *c, location loc, operator_ty binop, static int addop_yield(struct compiler *c, location loc) { - if (c->u->u_ste->ste_generator && c->u->u_ste->ste_coroutine) { + PySTEntryObject *ste = SYMTABLE_ENTRY(c); + if (ste->ste_generator && ste->ste_coroutine) { ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_ASYNC_GEN_WRAP); } ADDOP_I(c, loc, YIELD_VALUE, 0); @@ -4143,7 +4147,7 @@ compiler_nameop(struct compiler *c, location loc, op = 0; optype = OP_NAME; - scope = _PyST_GetScope(c->u->u_ste, mangled); + scope = _PyST_GetScope(SYMTABLE_ENTRY(c), mangled); switch (scope) { case FREE: dict = c->u->u_metadata.u_freevars; @@ -4154,7 +4158,7 @@ compiler_nameop(struct compiler *c, location loc, optype = OP_DEREF; break; case LOCAL: - if (_PyST_IsFunctionLike(c->u->u_ste)) { + if (_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) { optype = OP_FAST; } else { @@ -4170,7 +4174,7 @@ compiler_nameop(struct compiler *c, location loc, } break; case GLOBAL_IMPLICIT: - if (_PyST_IsFunctionLike(c->u->u_ste)) + if (_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) optype = OP_GLOBAL; break; case GLOBAL_EXPLICIT: @@ -4188,17 +4192,17 @@ compiler_nameop(struct compiler *c, location loc, case OP_DEREF: switch (ctx) { case Load: - if (c->u->u_ste->ste_type == ClassBlock && !c->u->u_in_inlined_comp) { + if (SYMTABLE_ENTRY(c)->ste_type == ClassBlock && !c->u->u_in_inlined_comp) { op = LOAD_FROM_DICT_OR_DEREF; // First load the locals if (codegen_addop_noarg(INSTR_SEQUENCE(c), LOAD_LOCALS, loc) < 0) { goto error; } } - else if (c->u->u_ste->ste_can_see_class_scope) { + else if (SYMTABLE_ENTRY(c)->ste_can_see_class_scope) { op = LOAD_FROM_DICT_OR_DEREF; // First load the classdict - if (compiler_addop_o(c->u, loc, LOAD_DEREF, + if (compiler_addop_o(c, loc, LOAD_DEREF, c->u->u_metadata.u_freevars, &_Py_ID(__classdict__)) < 0) { goto error; } @@ -4222,10 +4226,10 @@ compiler_nameop(struct compiler *c, location loc, case OP_GLOBAL: switch (ctx) { case Load: - if (c->u->u_ste->ste_can_see_class_scope && scope == GLOBAL_IMPLICIT) { + if (SYMTABLE_ENTRY(c)->ste_can_see_class_scope && scope == GLOBAL_IMPLICIT) { op = LOAD_FROM_DICT_OR_GLOBALS; // First load the classdict - if (compiler_addop_o(c->u, loc, LOAD_DEREF, + if (compiler_addop_o(c, loc, LOAD_DEREF, c->u->u_metadata.u_freevars, &_Py_ID(__classdict__)) < 0) { goto error; } @@ -4240,7 +4244,7 @@ compiler_nameop(struct compiler *c, location loc, case OP_NAME: switch (ctx) { case Load: - op = (c->u->u_ste->ste_type == ClassBlock + op = (SYMTABLE_ENTRY(c)->ste_type == ClassBlock && c->u->u_in_inlined_comp) ? LOAD_GLOBAL : LOAD_NAME; @@ -4745,7 +4749,7 @@ is_import_originated(struct compiler *c, expr_ty e) return 0; } - long flags = _PyST_GetSymbol(c->c_st->st_top, e->v.Name.id); + long flags = _PyST_GetSymbol(SYMTABLE(c)->st_top, e->v.Name.id); return flags & DEF_IMPORT; } @@ -4764,11 +4768,11 @@ can_optimize_super_call(struct compiler *c, expr_ty attr) PyObject *super_name = e->v.Call.func->v.Name.id; // detect statically-visible shadowing of 'super' name - int scope = _PyST_GetScope(c->u->u_ste, super_name); + int scope = _PyST_GetScope(SYMTABLE_ENTRY(c), super_name); if (scope != GLOBAL_IMPLICIT) { return 0; } - scope = _PyST_GetScope(c->c_st->st_top, super_name); + scope = _PyST_GetScope(SYMTABLE(c)->st_top, super_name); if (scope != 0) { return 0; } @@ -4796,7 +4800,7 @@ can_optimize_super_call(struct compiler *c, expr_ty attr) return 0; } // __class__ cell should be available - if (get_ref_type(c, &_Py_ID(__class__)) == FREE) { + if (compiler_get_ref_type(c, &_Py_ID(__class__)) == FREE) { return 1; } return 0; @@ -4818,7 +4822,7 @@ load_args_for_super(struct compiler *c, expr_ty e) { // load __class__ cell PyObject *name = &_Py_ID(__class__); - assert(get_ref_type(c, name) == FREE); + assert(compiler_get_ref_type(c, name) == FREE); RETURN_IF_ERROR(compiler_nameop(c, loc, name, Load)); // load self (first argument) @@ -5490,7 +5494,7 @@ push_inlined_comprehension_state(struct compiler *c, location loc, PySTEntryObject *entry, inlined_comprehension_state *state) { - int in_class_block = (c->u->u_ste->ste_type == ClassBlock) && !c->u->u_in_inlined_comp; + int in_class_block = (SYMTABLE_ENTRY(c)->ste_type == ClassBlock) && !c->u->u_in_inlined_comp; c->u->u_in_inlined_comp++; // iterate over names bound in the comprehension and ensure we isolate // them from the outer scope as needed @@ -5500,7 +5504,7 @@ push_inlined_comprehension_state(struct compiler *c, location loc, assert(PyLong_Check(v)); long symbol = PyLong_AS_LONG(v); long scope = (symbol >> SCOPE_OFFSET) & SCOPE_MASK; - PyObject *outv = PyDict_GetItemWithError(c->u->u_ste->ste_symbols, k); + PyObject *outv = PyDict_GetItemWithError(SYMTABLE_ENTRY(c)->ste_symbols, k); if (outv == NULL) { if (PyErr_Occurred()) { return ERROR; @@ -5529,7 +5533,7 @@ push_inlined_comprehension_state(struct compiler *c, location loc, // the outer version; we'll restore it after running the // comprehension Py_INCREF(outv); - if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v) < 0) { + if (PyDict_SetItem(SYMTABLE_ENTRY(c)->ste_symbols, k, v) < 0) { Py_DECREF(outv); return ERROR; } @@ -5542,7 +5546,7 @@ push_inlined_comprehension_state(struct compiler *c, location loc, // locals handling for names bound in comprehension (DEF_LOCAL | // DEF_NONLOCAL occurs in assignment expression to nonlocal) if ((symbol & DEF_LOCAL && !(symbol & DEF_NONLOCAL)) || in_class_block) { - if (!_PyST_IsFunctionLike(c->u->u_ste)) { + if (!_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) { // non-function scope: override this name to use fast locals PyObject *orig; if (PyDict_GetItemRef(c->u->u_metadata.u_fasthidden, k, &orig) < 0) { @@ -5644,7 +5648,7 @@ pop_inlined_comprehension_state(struct compiler *c, location loc, Py_ssize_t pos = 0; if (state.temp_symbols) { while (PyDict_Next(state.temp_symbols, &pos, &k, &v)) { - if (PyDict_SetItem(c->u->u_ste->ste_symbols, k, v)) { + if (PyDict_SetItem(SYMTABLE_ENTRY(c)->ste_symbols, k, v)) { return ERROR; } } @@ -5713,7 +5717,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, comprehension_ty outermost; int scope_type = c->u->u_scope_type; int is_top_level_await = IS_TOP_LEVEL_AWAIT(c); - PySTEntryObject *entry = _PySymtable_Lookup(c->c_st, (void *)e); + PySTEntryObject *entry = _PySymtable_Lookup(SYMTABLE(c), (void *)e); if (entry == NULL) { goto error; } @@ -6150,7 +6154,7 @@ compiler_visit_expr(struct compiler *c, expr_ty e) case DictComp_kind: return compiler_dictcomp(c, e); case Yield_kind: - if (!_PyST_IsFunctionLike(c->u->u_ste)) { + if (!_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) { return compiler_error(c, loc, "'yield' outside function"); } if (e->v.Yield.value) { @@ -6162,7 +6166,7 @@ compiler_visit_expr(struct compiler *c, expr_ty e) ADDOP_YIELD(c, loc); break; case YieldFrom_kind: - if (!_PyST_IsFunctionLike(c->u->u_ste)) { + if (!_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) { return compiler_error(c, loc, "'yield' outside function"); } if (c->u->u_scope_type == COMPILER_SCOPE_ASYNC_FUNCTION) { @@ -6175,7 +6179,7 @@ compiler_visit_expr(struct compiler *c, expr_ty e) break; case Await_kind: if (!IS_TOP_LEVEL_AWAIT(c)){ - if (!_PyST_IsFunctionLike(c->u->u_ste)) { + if (!_PyST_IsFunctionLike(SYMTABLE_ENTRY(c))) { return compiler_error(c, loc, "'await' outside function"); } @@ -6215,13 +6219,7 @@ compiler_visit_expr(struct compiler *c, expr_ty e) if (e->v.Attribute.value->kind == Name_kind && _PyUnicode_EqualToASCIIString(e->v.Attribute.value->v.Name.id, "self")) { - struct compiler_unit *class_u = get_class_compiler_unit(c); - if (class_u != NULL) { - assert(class_u->u_scope_type == COMPILER_SCOPE_CLASS); - assert(class_u->u_static_attributes); - RETURN_IF_ERROR( - PySet_Add(class_u->u_static_attributes, e->v.Attribute.attr)); - } + RETURN_IF_ERROR(compiler_add_static_attribute_to_class(c, e->v.Attribute.attr)); } VISIT(c, expr, e->v.Attribute.value); loc = LOC(e); @@ -6368,7 +6366,7 @@ check_annotation(struct compiler *c, stmt_ty s) { /* Annotations of complex targets does not produce anything under annotations future */ - if (c->c_future.ff_features & CO_FUTURE_ANNOTATIONS) { + if (FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS) { return SUCCESS; } @@ -6415,7 +6413,7 @@ compiler_annassign(struct compiler *c, stmt_ty s) { location loc = LOC(s); expr_ty targ = s->v.AnnAssign.target; - bool future_annotations = c->c_future.ff_features & CO_FUTURE_ANNOTATIONS; + bool future_annotations = FUTURE_FEATURES(c) & CO_FUTURE_ANNOTATIONS; PyObject *mangled; assert(s->kind == AnnAssign_kind); @@ -7485,19 +7483,41 @@ compiler_maybe_mangle(struct compiler *c, PyObject *name) return _Py_MaybeMangle(c->u->u_private, c->u->u_ste, name); } +static instr_sequence * +compiler_instr_sequence(struct compiler *c) +{ + return c->u->u_instr_sequence; +} + +static int +compiler_future_features(struct compiler *c) +{ + return c->c_future.ff_features; +} + +static struct symtable * +compiler_symtable(struct compiler *c) +{ + return c->c_st; +} + +static PySTEntryObject * +compiler_symtable_entry(struct compiler *c) +{ + return c->u->u_ste; +} + static int compute_code_flags(struct compiler *c) { - PySTEntryObject *ste = c->u->u_ste; + PySTEntryObject *ste = SYMTABLE_ENTRY(c); int flags = 0; - if (_PyST_IsFunctionLike(c->u->u_ste)) { + if (_PyST_IsFunctionLike(ste)) { flags |= CO_NEWLOCALS | CO_OPTIMIZED; if (ste->ste_nested) flags |= CO_NESTED; if (ste->ste_generator && !ste->ste_coroutine) flags |= CO_GENERATOR; - if (!ste->ste_generator && ste->ste_coroutine) - flags |= CO_COROUTINE; if (ste->ste_generator && ste->ste_coroutine) flags |= CO_ASYNC_GENERATOR; if (ste->ste_varargs) @@ -7506,15 +7526,14 @@ compute_code_flags(struct compiler *c) flags |= CO_VARKEYWORDS; } - /* (Only) inherit compilerflags in PyCF_MASK */ - flags |= (c->c_flags.cf_flags & PyCF_MASK); - - if ((IS_TOP_LEVEL_AWAIT(c)) && - ste->ste_coroutine && - !ste->ste_generator) { + if (ste->ste_coroutine && !ste->ste_generator) { + assert (IS_TOP_LEVEL_AWAIT(c) || _PyST_IsFunctionLike(ste)); flags |= CO_COROUTINE; } + /* (Only) inherit compilerflags in PyCF_MASK */ + flags |= (c->c_flags.cf_flags & PyCF_MASK); + return flags; } From a802277914405786f6425f2776605c44bd407fc0 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Wed, 10 Jul 2024 09:40:55 -0700 Subject: [PATCH 85/97] gh-121460: Skip freeing unallocated arenas (gh-121491) `munmap(NULL)` is not noop, like `free(NULL)` is. Fixes an observed testsuite hang on 32-bit ARM systems. --- Objects/obmalloc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index d033e2bad1891a..a6a71802ef8e01 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -386,8 +386,16 @@ _PyMem_ArenaFree(void *Py_UNUSED(ctx), void *ptr, ) { #ifdef MS_WINDOWS + /* Unlike free(), VirtualFree() does not special-case NULL to noop. */ + if (ptr == NULL) { + return; + } VirtualFree(ptr, 0, MEM_RELEASE); #elif defined(ARENAS_USE_MMAP) + /* Unlike free(), munmap() does not special-case NULL to noop. */ + if (ptr == NULL) { + return; + } munmap(ptr, size); #else free(ptr); From 3bfc9c831ad9a3dcf4457e842f1e612e93014a17 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Thu, 11 Jul 2024 02:02:08 +0800 Subject: [PATCH 86/97] gh-120198: Stop the world when setting __class__ on free-threaded build (GH-120672) --- Include/internal/pycore_dict.h | 2 + Include/object.h | 8 -- Lib/test/test_free_threading/test_type.py | 2 +- Objects/dictobject.c | 15 ++-- Objects/typeobject.c | 96 ++++++++++++----------- 5 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 9e0e1237915e82..56cc49432cc61e 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -322,6 +322,8 @@ _PyInlineValuesSize(PyTypeObject *tp) int _PyDict_DetachFromObject(PyDictObject *dict, PyObject *obj); +PyDictObject *_PyObject_MaterializeManagedDict_LockHeld(PyObject *); + #ifdef __cplusplus } #endif diff --git a/Include/object.h b/Include/object.h index a1e5b33b0fdaae..abfdb6ce24df21 100644 --- a/Include/object.h +++ b/Include/object.h @@ -249,11 +249,7 @@ PyAPI_FUNC(PyTypeObject*) Py_TYPE(PyObject *ob); #else static inline PyTypeObject* _Py_TYPE(PyObject *ob) { - #if defined(Py_GIL_DISABLED) - return (PyTypeObject *)_Py_atomic_load_ptr_relaxed(&ob->ob_type); - #else return ob->ob_type; - #endif } #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 # define Py_TYPE(ob) _Py_TYPE(_PyObject_CAST(ob)) @@ -284,11 +280,7 @@ static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) { -#ifdef Py_GIL_DISABLED - _Py_atomic_store_ptr(&ob->ob_type, type); -#else ob->ob_type = type; -#endif } #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 # define Py_SET_TYPE(ob, type) Py_SET_TYPE(_PyObject_CAST(ob), type) diff --git a/Lib/test/test_free_threading/test_type.py b/Lib/test/test_free_threading/test_type.py index 75259795e81bcb..649676db9c08a5 100644 --- a/Lib/test/test_free_threading/test_type.py +++ b/Lib/test/test_free_threading/test_type.py @@ -106,7 +106,7 @@ class Bar: thing = Foo() def work(): foo = thing - for _ in range(10000): + for _ in range(5000): foo.__class__ = Bar type(foo) foo.__class__ = Foo diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2b11a01595b0bc..989c7bb624f488 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -158,6 +158,10 @@ ASSERT_DICT_LOCKED(PyObject *op) if (!_PyInterpreterState_GET()->stoptheworld.world_stopped) { \ ASSERT_DICT_LOCKED(op); \ } +#define ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(op) \ + if (!_PyInterpreterState_GET()->stoptheworld.world_stopped) { \ + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); \ + } #define IS_DICT_SHARED(mp) _PyObject_GC_IS_SHARED(mp) #define SET_DICT_SHARED(mp) _PyObject_GC_SET_SHARED(mp) @@ -227,6 +231,7 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys) #define ASSERT_DICT_LOCKED(op) #define ASSERT_WORLD_STOPPED_OR_DICT_LOCKED(op) +#define ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(op) #define LOCK_KEYS(keys) #define UNLOCK_KEYS(keys) #define ASSERT_KEYS_LOCKED(keys) @@ -6667,10 +6672,10 @@ make_dict_from_instance_attributes(PyInterpreterState *interp, return res; } -static PyDictObject * -materialize_managed_dict_lock_held(PyObject *obj) +PyDictObject * +_PyObject_MaterializeManagedDict_LockHeld(PyObject *obj) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj); + ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj); PyDictValues *values = _PyObject_InlineValues(obj); PyInterpreterState *interp = _PyInterpreterState_GET(); @@ -6699,7 +6704,7 @@ _PyObject_MaterializeManagedDict(PyObject *obj) goto exit; } #endif - dict = materialize_managed_dict_lock_held(obj); + dict = _PyObject_MaterializeManagedDict_LockHeld(obj); #ifdef Py_GIL_DISABLED exit: @@ -7132,7 +7137,7 @@ PyObject_ClearManagedDict(PyObject *obj) int _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj); + ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj); assert(_PyObject_ManagedDictPointer(obj)->dict == mp); assert(_PyObject_InlineValuesConsistencyCheck(obj)); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index df895bc65983c0..587632cecfba9d 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6540,28 +6540,11 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char* return 0; } -static int -object_set_class(PyObject *self, PyObject *value, void *closure) -{ - - if (value == NULL) { - PyErr_SetString(PyExc_TypeError, - "can't delete __class__ attribute"); - return -1; - } - if (!PyType_Check(value)) { - PyErr_Format(PyExc_TypeError, - "__class__ must be set to a class, not '%s' object", - Py_TYPE(value)->tp_name); - return -1; - } - PyTypeObject *newto = (PyTypeObject *)value; - if (PySys_Audit("object.__setattr__", "OsO", - self, "__class__", value) < 0) { - return -1; - } +static int +object_set_class_world_stopped(PyObject *self, PyTypeObject *newto) +{ PyTypeObject *oldto = Py_TYPE(self); /* In versions of CPython prior to 3.5, the code in @@ -6627,39 +6610,66 @@ object_set_class(PyObject *self, PyObject *value, void *closure) /* Changing the class will change the implicit dict keys, * so we must materialize the dictionary first. */ if (oldto->tp_flags & Py_TPFLAGS_INLINE_VALUES) { - PyDictObject *dict = _PyObject_MaterializeManagedDict(self); + PyDictObject *dict = _PyObject_GetManagedDict(self); if (dict == NULL) { - return -1; + dict = _PyObject_MaterializeManagedDict_LockHeld(self); + if (dict == NULL) { + return -1; + } } - bool error = false; - - Py_BEGIN_CRITICAL_SECTION2(self, dict); - - // If we raced after materialization and replaced the dict - // then the materialized dict should no longer have the - // inline values in which case detach is a nop. - assert(_PyObject_GetManagedDict(self) == dict || - dict->ma_values != _PyObject_InlineValues(self)); + assert(_PyObject_GetManagedDict(self) == dict); if (_PyDict_DetachFromObject(dict, self) < 0) { - error = true; - } - - Py_END_CRITICAL_SECTION2(); - if (error) { return -1; } + } if (newto->tp_flags & Py_TPFLAGS_HEAPTYPE) { Py_INCREF(newto); } - Py_BEGIN_CRITICAL_SECTION(self); - // The real Py_TYPE(self) (`oldto`) may have changed from - // underneath us in another thread, so we re-fetch it here. - oldto = Py_TYPE(self); + Py_SET_TYPE(self, newto); - Py_END_CRITICAL_SECTION(); + + return 0; + } + else { + return -1; + } +} + +static int +object_set_class(PyObject *self, PyObject *value, void *closure) +{ + + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "can't delete __class__ attribute"); + return -1; + } + if (!PyType_Check(value)) { + PyErr_Format(PyExc_TypeError, + "__class__ must be set to a class, not '%s' object", + Py_TYPE(value)->tp_name); + return -1; + } + PyTypeObject *newto = (PyTypeObject *)value; + + if (PySys_Audit("object.__setattr__", "OsO", + self, "__class__", value) < 0) { + return -1; + } + +#ifdef Py_GIL_DISABLED + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyEval_StopTheWorld(interp); +#endif + PyTypeObject *oldto = Py_TYPE(self); + int res = object_set_class_world_stopped(self, newto); +#ifdef Py_GIL_DISABLED + _PyEval_StartTheWorld(interp); +#endif + if (res == 0) { if (oldto->tp_flags & Py_TPFLAGS_HEAPTYPE) { Py_DECREF(oldto); } @@ -6667,9 +6677,7 @@ object_set_class(PyObject *self, PyObject *value, void *closure) RARE_EVENT_INC(set_class); return 0; } - else { - return -1; - } + return res; } static PyGetSetDef object_getsets[] = { From 3ec719fabf936ea7a012a76445b860759155de86 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 10 Jul 2024 14:04:12 -0400 Subject: [PATCH 87/97] gh-117657: Fix TSan race in _PyDict_CheckConsistency (#121551) The only remaining race in dictobject.c was in _PyDict_CheckConsistency when the dictionary has shared keys. --- Objects/dictobject.c | 24 ++++++++++++++-------- Tools/tsan/suppressions_free_threading.txt | 8 -------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 989c7bb624f488..149e552af3a729 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -169,16 +169,15 @@ ASSERT_DICT_LOCKED(PyObject *op) #define STORE_INDEX(keys, size, idx, value) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value); #define ASSERT_OWNED_OR_SHARED(mp) \ assert(_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp)); -#define LOAD_KEYS_NENTRIES(d) #define LOCK_KEYS_IF_SPLIT(keys, kind) \ if (kind == DICT_KEYS_SPLIT) { \ - LOCK_KEYS(dk); \ + LOCK_KEYS(keys); \ } #define UNLOCK_KEYS_IF_SPLIT(keys, kind) \ if (kind == DICT_KEYS_SPLIT) { \ - UNLOCK_KEYS(dk); \ + UNLOCK_KEYS(keys); \ } static inline Py_ssize_t @@ -212,7 +211,7 @@ set_values(PyDictObject *mp, PyDictValues *values) #define INCREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, 1) // Dec refs the keys object, giving the previous value #define DECREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, -1) -#define LOAD_KEYS_NENTIRES(keys) _Py_atomic_load_ssize_relaxed(&keys->dk_nentries) +#define LOAD_KEYS_NENTRIES(keys) _Py_atomic_load_ssize_relaxed(&keys->dk_nentries) #define INCREF_KEYS_FT(dk) dictkeys_incref(dk) #define DECREF_KEYS_FT(dk, shared) dictkeys_decref(_PyInterpreterState_GET(), dk, shared) @@ -239,7 +238,7 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys) #define STORE_SHARED_KEY(key, value) key = value #define INCREF_KEYS(dk) dk->dk_refcnt++ #define DECREF_KEYS(dk) dk->dk_refcnt-- -#define LOAD_KEYS_NENTIRES(keys) keys->dk_nentries +#define LOAD_KEYS_NENTRIES(keys) keys->dk_nentries #define INCREF_KEYS_FT(dk) #define DECREF_KEYS_FT(dk, shared) #define LOCK_KEYS_IF_SPLIT(keys, kind) @@ -694,10 +693,15 @@ _PyDict_CheckConsistency(PyObject *op, int check_content) int splitted = _PyDict_HasSplitTable(mp); Py_ssize_t usable = USABLE_FRACTION(DK_SIZE(keys)); + // In the free-threaded build, shared keys may be concurrently modified, + // so use atomic loads. + Py_ssize_t dk_usable = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_usable); + Py_ssize_t dk_nentries = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_nentries); + CHECK(0 <= mp->ma_used && mp->ma_used <= usable); - CHECK(0 <= keys->dk_usable && keys->dk_usable <= usable); - CHECK(0 <= keys->dk_nentries && keys->dk_nentries <= usable); - CHECK(keys->dk_usable + keys->dk_nentries <= usable); + CHECK(0 <= dk_usable && dk_usable <= usable); + CHECK(0 <= dk_nentries && dk_nentries <= usable); + CHECK(dk_usable + dk_nentries <= usable); if (!splitted) { /* combined table */ @@ -714,6 +718,7 @@ _PyDict_CheckConsistency(PyObject *op, int check_content) } if (check_content) { + LOCK_KEYS_IF_SPLIT(keys, keys->dk_kind); for (Py_ssize_t i=0; i < DK_SIZE(keys); i++) { Py_ssize_t ix = dictkeys_get_index(keys, i); CHECK(DKIX_DUMMY <= ix && ix <= usable); @@ -769,6 +774,7 @@ _PyDict_CheckConsistency(PyObject *op, int check_content) CHECK(mp->ma_values->values[index] != NULL); } } + UNLOCK_KEYS_IF_SPLIT(keys, keys->dk_kind); } return 1; @@ -4037,7 +4043,7 @@ dict_equal_lock_held(PyDictObject *a, PyDictObject *b) /* can't be equal if # of entries differ */ return 0; /* Same # of entries -- check all of 'em. Exit early on any diff. */ - for (i = 0; i < LOAD_KEYS_NENTIRES(a->ma_keys); i++) { + for (i = 0; i < LOAD_KEYS_NENTRIES(a->ma_keys); i++) { PyObject *key, *aval; Py_hash_t hash; if (DK_IS_UNICODE(a->ma_keys)) { diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index fb97bdc128a4e1..d9d80ad6dd722b 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -27,16 +27,8 @@ race_top:_add_to_weak_set race_top:_in_weak_set race_top:_PyEval_EvalFrameDefault race_top:assign_version_tag -race_top:insertdict -race_top:lookup_tp_dict race_top:new_reference -race_top:_PyDict_CheckConsistency -race_top:_Py_dict_lookup_threadsafe race_top:_multiprocessing_SemLock_acquire_impl -race_top:dictiter_new -race_top:dictresize -race_top:insert_to_emptydict -race_top:insertdict race_top:list_get_item_ref race_top:make_pending_calls race_top:_Py_slot_tp_getattr_hook From 7641743d48b276de88a709ad40d715b6c5d7a2ea Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 10 Jul 2024 17:08:10 -0400 Subject: [PATCH 88/97] gh-117657: Remove TSAN suppressions for _abc.c (#121508) The functions look thread-safe and I haven't seen any warnings issued when running the tests locally. --- Tools/tsan/suppressions_free_threading.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index d9d80ad6dd722b..0955387dfb8370 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -23,8 +23,6 @@ race:free_threadstate # These warnings trigger directly in a CPython function. -race_top:_add_to_weak_set -race_top:_in_weak_set race_top:_PyEval_EvalFrameDefault race_top:assign_version_tag race_top:new_reference From 35a67e36aa7cb0fc915771327f58bb0c70213867 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 10 Jul 2024 15:31:09 -0600 Subject: [PATCH 89/97] gh-121596: Fix Sharing Interpreter Channels (gh-121597) This fixes a mistake in gh-113012 and adds a test that verifies the fix. --- Lib/test/test_interpreters/test_channels.py | 18 ++++++++++++++++++ Modules/_interpchannelsmodule.c | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index 68cc45d1a5e09f..6c37754142e361 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -48,6 +48,7 @@ def test_list_all(self): self.assertEqual(after, created) def test_shareable(self): + interp = interpreters.create() rch, sch = channels.create() self.assertTrue( @@ -60,8 +61,25 @@ def test_shareable(self): rch2 = rch.recv() sch2 = rch.recv() + interp.prepare_main(rch=rch, sch=sch) + sch.send_nowait(rch) + sch.send_nowait(sch) + interp.exec(dedent(""" + rch2 = rch.recv() + sch2 = rch.recv() + assert rch2 == rch + assert sch2 == sch + + sch.send_nowait(rch2) + sch.send_nowait(sch2) + """)) + rch3 = rch.recv() + sch3 = rch.recv() + self.assertEqual(rch2, rch) self.assertEqual(sch2, sch) + self.assertEqual(rch3, rch) + self.assertEqual(sch3, sch) def test_is_closed(self): rch, sch = channels.create() diff --git a/Modules/_interpchannelsmodule.c b/Modules/_interpchannelsmodule.c index 47dbdeb9a37c44..f0447475c49116 100644 --- a/Modules/_interpchannelsmodule.c +++ b/Modules/_interpchannelsmodule.c @@ -2615,10 +2615,10 @@ _get_current_channelend_type(int end) } if (cls == NULL) { // Force the module to be loaded, to register the type. - PyObject *highlevel = PyImport_ImportModule("interpreters.channel"); + PyObject *highlevel = PyImport_ImportModule("interpreters.channels"); if (highlevel == NULL) { PyErr_Clear(); - highlevel = PyImport_ImportModule("test.support.interpreters.channel"); + highlevel = PyImport_ImportModule("test.support.interpreters.channels"); if (highlevel == NULL) { return NULL; } From ef10110cd781afe8ddd3e65a54dc2b5ed2cb3187 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 10 Jul 2024 22:59:14 +0100 Subject: [PATCH 90/97] gh-119786: fix broken links in docs and comment (#121601) --- Include/internal/pycore_frame.h | 2 +- InternalDocs/compiler.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h index 1e0368faa5b510..506c20ca1950bd 100644 --- a/Include/internal/pycore_frame.h +++ b/Include/internal/pycore_frame.h @@ -13,7 +13,7 @@ extern "C" { #include "pycore_code.h" // STATS #include "pycore_stackref.h" // _PyStackRef -/* See Objects/frame_layout.md for an explanation of the frame stack +/* See InternalDocs/frames.md for an explanation of the frame stack * including explanation of the PyFrameObject and _PyInterpreterFrame * structs. */ diff --git a/InternalDocs/compiler.md b/InternalDocs/compiler.md index 17fe0df6e1db10..b3dc0a48069969 100644 --- a/InternalDocs/compiler.md +++ b/InternalDocs/compiler.md @@ -623,8 +623,8 @@ Important files Objects ======= -* [Objects/locations.md](https://github.com/python/cpython/blob/main/Objects/locations.md): Describes the location table -* [Objects/frame_layout.md](https://github.com/python/cpython/blob/main/Objects/frame_layout.md): Describes the frame stack +* [Locations](locations.md): Describes the location table +* [Frames](frames.md): Describes frames and the frame stack * [Objects/object_layout.md](https://github.com/python/cpython/blob/main/Objects/object_layout.md): Describes object layout for 3.11 and later * [Exception Handling](exception_handling.md): Describes the exception table From 6557af669899f18f8d123f8e1b6c3380d502c519 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 10 Jul 2024 23:48:37 +0100 Subject: [PATCH 91/97] gh-121554: remove unnecessary internal functions in compile.c (#121555) Co-authored-by: Erlend E. Aasland --- Include/internal/pycore_compile.h | 9 ------ Modules/Setup.bootstrap.in | 1 + Modules/Setup.stdlib.in | 1 - Modules/_opcode.c | 29 +++++++------------ Python/compile.c | 48 ------------------------------- configure | 28 ------------------ configure.ac | 1 - 7 files changed, 12 insertions(+), 105 deletions(-) diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index a1ac034e3e44af..325243e6a64e1f 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -76,15 +76,6 @@ int _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj); // Export for '_opcode' extension module -PyAPI_FUNC(int) _PyCompile_OpcodeIsValid(int opcode); -PyAPI_FUNC(int) _PyCompile_OpcodeHasArg(int opcode); -PyAPI_FUNC(int) _PyCompile_OpcodeHasConst(int opcode); -PyAPI_FUNC(int) _PyCompile_OpcodeHasName(int opcode); -PyAPI_FUNC(int) _PyCompile_OpcodeHasJump(int opcode); -PyAPI_FUNC(int) _PyCompile_OpcodeHasFree(int opcode); -PyAPI_FUNC(int) _PyCompile_OpcodeHasLocal(int opcode); -PyAPI_FUNC(int) _PyCompile_OpcodeHasExc(int opcode); - PyAPI_FUNC(PyObject*) _PyCompile_GetUnaryIntrinsicName(int index); PyAPI_FUNC(PyObject*) _PyCompile_GetBinaryIntrinsicName(int index); diff --git a/Modules/Setup.bootstrap.in b/Modules/Setup.bootstrap.in index aa4e60e272653b..4dcc0f55176d0e 100644 --- a/Modules/Setup.bootstrap.in +++ b/Modules/Setup.bootstrap.in @@ -30,6 +30,7 @@ _weakref _weakref.c _abc _abc.c _functools _functoolsmodule.c _locale _localemodule.c +_opcode _opcode.c _operator _operator.c _stat _stat.c _symtable symtablemodule.c diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 78b979698fcd75..dfc75077650df8 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -36,7 +36,6 @@ @MODULE__HEAPQ_TRUE@_heapq _heapqmodule.c @MODULE__JSON_TRUE@_json _json.c @MODULE__LSPROF_TRUE@_lsprof _lsprof.c rotatingtree.c -@MODULE__OPCODE_TRUE@_opcode _opcode.c @MODULE__PICKLE_TRUE@_pickle _pickle.c @MODULE__QUEUE_TRUE@_queue _queuemodule.c @MODULE__RANDOM_TRUE@_random _randommodule.c diff --git a/Modules/_opcode.c b/Modules/_opcode.c index 67643641bea861..dc93063aee7e54 100644 --- a/Modules/_opcode.c +++ b/Modules/_opcode.c @@ -10,6 +10,8 @@ #include "pycore_compile.h" #include "pycore_intrinsics.h" #include "pycore_optimizer.h" // _Py_GetExecutor() +#include "pycore_opcode_metadata.h" // IS_VALID_OPCODE, OPCODE_HAS_*, etc +#include "pycore_opcode_utils.h" /*[clinic input] module _opcode @@ -81,7 +83,7 @@ static int _opcode_is_valid_impl(PyObject *module, int opcode) /*[clinic end generated code: output=b0d918ea1d073f65 input=fe23e0aa194ddae0]*/ { - return _PyCompile_OpcodeIsValid(opcode); + return IS_VALID_OPCODE(opcode); } /*[clinic input] @@ -97,8 +99,7 @@ static int _opcode_has_arg_impl(PyObject *module, int opcode) /*[clinic end generated code: output=7a062d3b2dcc0815 input=93d878ba6361db5f]*/ { - return _PyCompile_OpcodeIsValid(opcode) && - _PyCompile_OpcodeHasArg(opcode); + return IS_VALID_OPCODE(opcode) && OPCODE_HAS_ARG(opcode); } /*[clinic input] @@ -114,8 +115,7 @@ static int _opcode_has_const_impl(PyObject *module, int opcode) /*[clinic end generated code: output=c646d5027c634120 input=a6999e4cf13f9410]*/ { - return _PyCompile_OpcodeIsValid(opcode) && - _PyCompile_OpcodeHasConst(opcode); + return IS_VALID_OPCODE(opcode) && OPCODE_HAS_CONST(opcode); } /*[clinic input] @@ -131,8 +131,7 @@ static int _opcode_has_name_impl(PyObject *module, int opcode) /*[clinic end generated code: output=b49a83555c2fa517 input=448aa5e4bcc947ba]*/ { - return _PyCompile_OpcodeIsValid(opcode) && - _PyCompile_OpcodeHasName(opcode); + return IS_VALID_OPCODE(opcode) && OPCODE_HAS_NAME(opcode); } /*[clinic input] @@ -148,9 +147,7 @@ static int _opcode_has_jump_impl(PyObject *module, int opcode) /*[clinic end generated code: output=e9c583c669f1c46a input=35f711274357a0c3]*/ { - return _PyCompile_OpcodeIsValid(opcode) && - _PyCompile_OpcodeHasJump(opcode); - + return IS_VALID_OPCODE(opcode) && OPCODE_HAS_JUMP(opcode); } /*[clinic input] @@ -171,9 +168,7 @@ static int _opcode_has_free_impl(PyObject *module, int opcode) /*[clinic end generated code: output=d81ae4d79af0ee26 input=117dcd5c19c1139b]*/ { - return _PyCompile_OpcodeIsValid(opcode) && - _PyCompile_OpcodeHasFree(opcode); - + return IS_VALID_OPCODE(opcode) && OPCODE_HAS_FREE(opcode); } /*[clinic input] @@ -189,8 +184,7 @@ static int _opcode_has_local_impl(PyObject *module, int opcode) /*[clinic end generated code: output=da5a8616b7a5097b input=9a798ee24aaef49d]*/ { - return _PyCompile_OpcodeIsValid(opcode) && - _PyCompile_OpcodeHasLocal(opcode); + return IS_VALID_OPCODE(opcode) && OPCODE_HAS_LOCAL(opcode); } /*[clinic input] @@ -206,8 +200,7 @@ static int _opcode_has_exc_impl(PyObject *module, int opcode) /*[clinic end generated code: output=41b68dff0ec82a52 input=db0e4bdb9bf13fa5]*/ { - return _PyCompile_OpcodeIsValid(opcode) && - _PyCompile_OpcodeHasExc(opcode); + return IS_VALID_OPCODE(opcode) && IS_BLOCK_PUSH_OPCODE(opcode); } /*[clinic input] @@ -424,7 +417,7 @@ opcode_functions[] = { {NULL, NULL, 0, NULL} }; -int +static int _opcode_exec(PyObject *m) { if (PyModule_AddIntMacro(m, ENABLE_SPECIALIZATION) < 0) { return -1; diff --git a/Python/compile.c b/Python/compile.c index 83157743ee29cd..4190b141324b38 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -665,54 +665,6 @@ compiler_set_qualname(struct compiler *c) return SUCCESS; } -int -_PyCompile_OpcodeIsValid(int opcode) -{ - return IS_VALID_OPCODE(opcode); -} - -int -_PyCompile_OpcodeHasArg(int opcode) -{ - return OPCODE_HAS_ARG(opcode); -} - -int -_PyCompile_OpcodeHasConst(int opcode) -{ - return OPCODE_HAS_CONST(opcode); -} - -int -_PyCompile_OpcodeHasName(int opcode) -{ - return OPCODE_HAS_NAME(opcode); -} - -int -_PyCompile_OpcodeHasJump(int opcode) -{ - return OPCODE_HAS_JUMP(opcode); -} - -int -_PyCompile_OpcodeHasFree(int opcode) -{ - return OPCODE_HAS_FREE(opcode); -} - -int -_PyCompile_OpcodeHasLocal(int opcode) -{ - return OPCODE_HAS_LOCAL(opcode); -} - -int -_PyCompile_OpcodeHasExc(int opcode) -{ - return IS_BLOCK_PUSH_OPCODE(opcode); -} - static int codegen_addop_noarg(instr_sequence *seq, int opcode, location loc) { diff --git a/configure b/configure index 131ca5f7f897a7..bbfa805883cac5 100755 --- a/configure +++ b/configure @@ -795,8 +795,6 @@ MODULE__POSIXSUBPROCESS_FALSE MODULE__POSIXSUBPROCESS_TRUE MODULE__PICKLE_FALSE MODULE__PICKLE_TRUE -MODULE__OPCODE_FALSE -MODULE__OPCODE_TRUE MODULE__LSPROF_FALSE MODULE__LSPROF_TRUE MODULE__JSON_FALSE @@ -29231,28 +29229,6 @@ then : -fi - - - if test "$py_cv_module__opcode" != "n/a" -then : - py_cv_module__opcode=yes -fi - if test "$py_cv_module__opcode" = yes; then - MODULE__OPCODE_TRUE= - MODULE__OPCODE_FALSE='#' -else - MODULE__OPCODE_TRUE='#' - MODULE__OPCODE_FALSE= -fi - - as_fn_append MODULE_BLOCK "MODULE__OPCODE_STATE=$py_cv_module__opcode$as_nl" - if test "x$py_cv_module__opcode" = xyes -then : - - - - fi @@ -31784,10 +31760,6 @@ if test -z "${MODULE__LSPROF_TRUE}" && test -z "${MODULE__LSPROF_FALSE}"; then as_fn_error $? "conditional \"MODULE__LSPROF\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi -if test -z "${MODULE__OPCODE_TRUE}" && test -z "${MODULE__OPCODE_FALSE}"; then - as_fn_error $? "conditional \"MODULE__OPCODE\" was never defined. -Usually this means the macro was only invoked conditionally." "$LINENO" 5 -fi if test -z "${MODULE__PICKLE_TRUE}" && test -z "${MODULE__PICKLE_FALSE}"; then as_fn_error $? "conditional \"MODULE__PICKLE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 diff --git a/configure.ac b/configure.ac index 705f8752597b96..87c4df20818808 100644 --- a/configure.ac +++ b/configure.ac @@ -7689,7 +7689,6 @@ PY_STDLIB_MOD_SIMPLE([_csv]) PY_STDLIB_MOD_SIMPLE([_heapq]) PY_STDLIB_MOD_SIMPLE([_json]) PY_STDLIB_MOD_SIMPLE([_lsprof]) -PY_STDLIB_MOD_SIMPLE([_opcode]) PY_STDLIB_MOD_SIMPLE([_pickle]) PY_STDLIB_MOD_SIMPLE([_posixsubprocess]) PY_STDLIB_MOD_SIMPLE([_queue]) From 690b9355e00d1ea52020fde3feb4c043a2b214e2 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 10 Jul 2024 19:54:27 -0700 Subject: [PATCH 92/97] gh-121450: Make inline breakpoints use the most recent pdb instance (#121451) --- Doc/whatsnew/3.14.rst | 10 +++++ Lib/bdb.py | 1 + Lib/pdb.py | 13 +++++- Lib/test/test_pdb.py | 43 +++++++++++++++++++ ...-07-06-23-39-38.gh-issue-121450.vGqb3c.rst | 4 ++ 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-06-23-39-38.gh-issue-121450.vGqb3c.rst diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index d02c10ec9cf3f3..da9b45cd8e58b3 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -114,6 +114,16 @@ pathlib another. (Contributed by Barney Gale in :gh:`73991`.) +pdb +--- + +* Hard-coded breakpoints (:func:`breakpoint` and :func:`pdb.set_trace()`) now + reuse the most recent :class:`~pdb.Pdb` instance that calls + :meth:`~pdb.Pdb.set_trace()`, instead of creating a new one each time. + As a result, all the instance specific data like :pdbcmd:`display` and + :pdbcmd:`commands` are preserved across hard-coded breakpoints. + (Contributed by Tian Gao in :gh:`121450`.) + symtable -------- diff --git a/Lib/bdb.py b/Lib/bdb.py index aa621053cfb4bc..d7543017940d4f 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -369,6 +369,7 @@ def set_trace(self, frame=None): If frame is not specified, debugging starts from caller's frame. """ + sys.settrace(None) if frame is None: frame = sys._getframe().f_back self.reset() diff --git a/Lib/pdb.py b/Lib/pdb.py index 85a3aa2e37996f..7ff973149b167b 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -306,6 +306,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): _file_mtime_table = {} + _last_pdb_instance = None + def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False, readrc=True): bdb.Bdb.__init__(self, skip=skip) @@ -359,6 +361,12 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, self._chained_exceptions = tuple() self._chained_exception_index = 0 + def set_trace(self, frame=None): + Pdb._last_pdb_instance = self + if frame is None: + frame = sys._getframe().f_back + super().set_trace(frame) + def sigint_handler(self, signum, frame): if self.allow_kbdint: raise KeyboardInterrupt @@ -2350,7 +2358,10 @@ def set_trace(*, header=None): an assertion fails). If given, *header* is printed to the console just before debugging begins. """ - pdb = Pdb() + if Pdb._last_pdb_instance is not None: + pdb = Pdb._last_pdb_instance + else: + pdb = Pdb() if header is not None: pdb.message(header) pdb.set_trace(sys._getframe().f_back) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 5c7445574f5d75..343e15a4edc14c 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2448,6 +2448,49 @@ def test_pdb_show_attribute_and_item(): (Pdb) c """ +# doctest will modify pdb.set_trace during the test, so we need to backup +# the original function to use it in the test +original_pdb_settrace = pdb.set_trace + +def test_pdb_with_inline_breakpoint(): + """Hard-coded breakpoint() calls should invoke the same debugger instance + + >>> def test_function(): + ... x = 1 + ... import pdb; pdb.Pdb().set_trace() + ... original_pdb_settrace() + ... x = 2 + + >>> with PdbTestInput(['display x', + ... 'n', + ... 'n', + ... 'n', + ... 'n', + ... 'undisplay', + ... 'c']): + ... test_function() + > (3)test_function() + -> import pdb; pdb.Pdb().set_trace() + (Pdb) display x + display x: 1 + (Pdb) n + > (4)test_function() + -> original_pdb_settrace() + (Pdb) n + > (4)test_function() + -> original_pdb_settrace() + (Pdb) n + > (5)test_function() + -> x = 2 + (Pdb) n + --Return-- + > (5)test_function()->None + -> x = 2 + display x: 2 [old: 1] + (Pdb) undisplay + (Pdb) c + """ + def test_pdb_issue_20766(): """Test for reference leaks when the SIGINT handler is set. diff --git a/Misc/NEWS.d/next/Library/2024-07-06-23-39-38.gh-issue-121450.vGqb3c.rst b/Misc/NEWS.d/next/Library/2024-07-06-23-39-38.gh-issue-121450.vGqb3c.rst new file mode 100644 index 00000000000000..4a65fb737f025b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-06-23-39-38.gh-issue-121450.vGqb3c.rst @@ -0,0 +1,4 @@ +Hard-coded breakpoints (:func:`breakpoint` and :func:`pdb.set_trace()`) now +reuse the most recent ``Pdb`` instance that calls ``Pdb.set_trace()``, +instead of creating a new one each time. As a result, all the instance specific +data like ``display`` and ``commands`` are preserved across Hard-coded breakpoints. From e6264b44dc7221c713b14dfa0f5929b33d362829 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 11 Jul 2024 11:57:22 +0300 Subject: [PATCH 93/97] gh-121615: Improve `module.rst` C-API docs with better error descriptions (#121616) --- Doc/c-api/module.rst | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst index 63e3bed6727987..ce9d5a0f758b29 100644 --- a/Doc/c-api/module.rst +++ b/Doc/c-api/module.rst @@ -43,6 +43,8 @@ Module Objects to ``None``); the caller is responsible for providing a :attr:`__file__` attribute. + Return ``NULL`` with an exception set on error. + .. versionadded:: 3.3 .. versionchanged:: 3.4 @@ -265,6 +267,8 @@ of the following two module creation functions: 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` @@ -461,6 +465,8 @@ objects dynamically. Note that both ``PyModule_FromDefAndSpec`` and 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_FromDefAndSpec` @@ -601,15 +607,16 @@ state: .. c:function:: int PyModule_AddIntConstant(PyObject *module, const char *name, long value) Add an integer constant to *module* as *name*. This convenience function can be - used from the module's initialization function. Return ``-1`` on error, ``0`` on - success. + used from the module's initialization function. + Return ``-1`` with an exception set on error, ``0`` on success. .. c:function:: int PyModule_AddStringConstant(PyObject *module, const char *name, const char *value) Add a string constant to *module* as *name*. This convenience function can be used from the module's initialization function. The string *value* must be - ``NULL``-terminated. Return ``-1`` on error, ``0`` on success. + ``NULL``-terminated. + Return ``-1`` with an exception set on error, ``0`` on success. .. c:macro:: PyModule_AddIntMacro(module, macro) @@ -617,7 +624,7 @@ state: Add an int constant to *module*. The name and the value are taken from *macro*. For example ``PyModule_AddIntMacro(module, AF_INET)`` adds the int constant *AF_INET* with the value of *AF_INET* to *module*. - Return ``-1`` on error, ``0`` on success. + Return ``-1`` with an exception set on error, ``0`` on success. .. c:macro:: PyModule_AddStringMacro(module, macro) @@ -630,7 +637,7 @@ state: The type object is finalized by calling internally :c:func:`PyType_Ready`. The name of the type object is taken from the last component of :c:member:`~PyTypeObject.tp_name` after dot. - Return ``-1`` on error, ``0`` on success. + Return ``-1`` with an exception set on error, ``0`` on success. .. versionadded:: 3.9 @@ -643,7 +650,7 @@ state: 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`. - Return ``-1`` on error, ``0`` on success. + Return ``-1`` with an exception set on error, ``0`` on success. .. versionadded:: 3.13 @@ -682,14 +689,14 @@ since multiple such modules can be created from a single definition. The caller must hold the GIL. - Return 0 on success or -1 on failure. + Return ``-1`` with an exception set on error, ``0`` on success. .. versionadded:: 3.3 .. c:function:: int PyState_RemoveModule(PyModuleDef *def) Removes the module object created from *def* from the interpreter state. - Return 0 on success or -1 on failure. + Return ``-1`` with an exception set on error, ``0`` on success. The caller must hold the GIL. From 44937d11a6a045a624918db78aa36e715ffabcd4 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 11 Jul 2024 10:21:09 -0400 Subject: [PATCH 94/97] gh-121592: Make select.poll() and related objects thread-safe (#121594) This makes select.poll() and kqueue() objects thread-safe in the free-threaded build. Note that calling close() concurrently with other functions is still not thread-safe due to races on file descriptors (gh-121544). --- Modules/clinic/selectmodule.c.h | 51 ++++++++++++++++++++++--- Modules/selectmodule.c | 67 ++++++++++++++++++++++++--------- 2 files changed, 96 insertions(+), 22 deletions(-) diff --git a/Modules/clinic/selectmodule.c.h b/Modules/clinic/selectmodule.c.h index dc7d3fb814396d..0ccbf63b688f1b 100644 --- a/Modules/clinic/selectmodule.c.h +++ b/Modules/clinic/selectmodule.c.h @@ -6,6 +6,7 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_long.h" // _PyLong_UnsignedShort_Converter() #include "pycore_modsupport.h" // _PyArg_CheckPositional() @@ -110,7 +111,9 @@ select_poll_register(pollObject *self, PyObject *const *args, Py_ssize_t nargs) goto exit; } skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_poll_register_impl(self, fd, eventmask); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -155,7 +158,9 @@ select_poll_modify(pollObject *self, PyObject *const *args, Py_ssize_t nargs) if (!_PyLong_UnsignedShort_Converter(args[1], &eventmask)) { goto exit; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_poll_modify_impl(self, fd, eventmask); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -187,7 +192,9 @@ select_poll_unregister(pollObject *self, PyObject *arg) if (fd < 0) { goto exit; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_poll_unregister_impl(self, fd); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -230,7 +237,9 @@ select_poll_poll(pollObject *self, PyObject *const *args, Py_ssize_t nargs) } timeout_obj = args[0]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_poll_poll_impl(self, timeout_obj); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -281,7 +290,9 @@ select_devpoll_register(devpollObject *self, PyObject *const *args, Py_ssize_t n goto exit; } skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_devpoll_register_impl(self, fd, eventmask); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -332,7 +343,9 @@ select_devpoll_modify(devpollObject *self, PyObject *const *args, Py_ssize_t nar goto exit; } skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_devpoll_modify_impl(self, fd, eventmask); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -364,7 +377,9 @@ select_devpoll_unregister(devpollObject *self, PyObject *arg) if (fd < 0) { goto exit; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_devpoll_unregister_impl(self, fd); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -407,7 +422,9 @@ select_devpoll_poll(devpollObject *self, PyObject *const *args, Py_ssize_t nargs } timeout_obj = args[0]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = select_devpoll_poll_impl(self, timeout_obj); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -434,7 +451,13 @@ select_devpoll_close_impl(devpollObject *self); static PyObject * select_devpoll_close(devpollObject *self, PyObject *Py_UNUSED(ignored)) { - return select_devpoll_close_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = select_devpoll_close_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } #endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */ @@ -456,7 +479,13 @@ select_devpoll_fileno_impl(devpollObject *self); static PyObject * select_devpoll_fileno(devpollObject *self, PyObject *Py_UNUSED(ignored)) { - return select_devpoll_fileno_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = select_devpoll_fileno_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } #endif /* (defined(HAVE_POLL) && !defined(HAVE_BROKEN_POLL)) && defined(HAVE_SYS_DEVPOLL_H) */ @@ -615,7 +644,13 @@ select_epoll_close_impl(pyEpoll_Object *self); static PyObject * select_epoll_close(pyEpoll_Object *self, PyObject *Py_UNUSED(ignored)) { - return select_epoll_close_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = select_epoll_close_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } #endif /* defined(HAVE_EPOLL) */ @@ -1108,7 +1143,13 @@ select_kqueue_close_impl(kqueue_queue_Object *self); static PyObject * select_kqueue_close(kqueue_queue_Object *self, PyObject *Py_UNUSED(ignored)) { - return select_kqueue_close_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = select_kqueue_close_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } #endif /* defined(HAVE_KQUEUE) */ @@ -1319,4 +1360,4 @@ select_kqueue_control(kqueue_queue_Object *self, PyObject *const *args, Py_ssize #ifndef SELECT_KQUEUE_CONTROL_METHODDEF #define SELECT_KQUEUE_CONTROL_METHODDEF #endif /* !defined(SELECT_KQUEUE_CONTROL_METHODDEF) */ -/*[clinic end generated code: output=4fc17ae9b6cfdc86 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f31e724f492225b1 input=a9049054013a1b77]*/ diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 3eaee22c652c28..0a5b5a703a5aa1 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -473,6 +473,7 @@ update_ufd_array(pollObject *self) } /*[clinic input] +@critical_section select.poll.register fd: fildes @@ -486,7 +487,7 @@ Register a file descriptor with the polling object. static PyObject * select_poll_register_impl(pollObject *self, int fd, unsigned short eventmask) -/*[clinic end generated code: output=0dc7173c800a4a65 input=34e16cfb28d3c900]*/ +/*[clinic end generated code: output=0dc7173c800a4a65 input=c475e029ce6c2830]*/ { PyObject *key, *value; int err; @@ -514,6 +515,7 @@ select_poll_register_impl(pollObject *self, int fd, unsigned short eventmask) /*[clinic input] +@critical_section select.poll.modify fd: fildes @@ -528,7 +530,7 @@ Modify an already registered file descriptor. static PyObject * select_poll_modify_impl(pollObject *self, int fd, unsigned short eventmask) -/*[clinic end generated code: output=1a7b88bf079eff17 input=a8e383df075c32cf]*/ +/*[clinic end generated code: output=1a7b88bf079eff17 input=38c9db5346711872]*/ { PyObject *key, *value; int err; @@ -566,6 +568,7 @@ select_poll_modify_impl(pollObject *self, int fd, unsigned short eventmask) /*[clinic input] +@critical_section select.poll.unregister fd: fildes @@ -576,7 +579,7 @@ Remove a file descriptor being tracked by the polling object. static PyObject * select_poll_unregister_impl(pollObject *self, int fd) -/*[clinic end generated code: output=8c9f42e75e7d291b input=4b4fccc1040e79cb]*/ +/*[clinic end generated code: output=8c9f42e75e7d291b input=ae6315d7f5243704]*/ { PyObject *key; @@ -599,6 +602,7 @@ select_poll_unregister_impl(pollObject *self, int fd) } /*[clinic input] +@critical_section select.poll.poll timeout as timeout_obj: object = None @@ -614,7 +618,7 @@ report, as a list of (fd, event) 2-tuples. static PyObject * select_poll_poll_impl(pollObject *self, PyObject *timeout_obj) -/*[clinic end generated code: output=876e837d193ed7e4 input=c2f6953ec45e5622]*/ +/*[clinic end generated code: output=876e837d193ed7e4 input=54310631457efdec]*/ { PyObject *result_list = NULL; int poll_result, i, j; @@ -857,6 +861,7 @@ internal_devpoll_register(devpollObject *self, int fd, } /*[clinic input] +@critical_section select.devpoll.register fd: fildes @@ -872,12 +877,13 @@ Register a file descriptor with the polling object. static PyObject * select_devpoll_register_impl(devpollObject *self, int fd, unsigned short eventmask) -/*[clinic end generated code: output=6e07fe8b74abba0c input=22006fabe9567522]*/ +/*[clinic end generated code: output=6e07fe8b74abba0c input=8d48bd2653a61c42]*/ { return internal_devpoll_register(self, fd, eventmask, 0); } /*[clinic input] +@critical_section select.devpoll.modify fd: fildes @@ -893,12 +899,13 @@ Modify a possible already registered file descriptor. static PyObject * select_devpoll_modify_impl(devpollObject *self, int fd, unsigned short eventmask) -/*[clinic end generated code: output=bc2e6d23aaff98b4 input=09fa335db7cdc09e]*/ +/*[clinic end generated code: output=bc2e6d23aaff98b4 input=773b37e9abca2460]*/ { return internal_devpoll_register(self, fd, eventmask, 1); } /*[clinic input] +@critical_section select.devpoll.unregister fd: fildes @@ -909,7 +916,7 @@ Remove a file descriptor being tracked by the polling object. static PyObject * select_devpoll_unregister_impl(devpollObject *self, int fd) -/*[clinic end generated code: output=95519ffa0c7d43fe input=b4ea42a4442fd467]*/ +/*[clinic end generated code: output=95519ffa0c7d43fe input=6052d368368d4d05]*/ { if (self->fd_devpoll < 0) return devpoll_err_closed(); @@ -926,6 +933,7 @@ select_devpoll_unregister_impl(devpollObject *self, int fd) } /*[clinic input] +@critical_section select.devpoll.poll timeout as timeout_obj: object = None The maximum time to wait in milliseconds, or else None (or a negative @@ -940,7 +948,7 @@ report, as a list of (fd, event) 2-tuples. static PyObject * select_devpoll_poll_impl(devpollObject *self, PyObject *timeout_obj) -/*[clinic end generated code: output=2654e5457cca0b3c input=3c3f0a355ec2bedb]*/ +/*[clinic end generated code: output=2654e5457cca0b3c input=fe7a3f6dcbc118c5]*/ { struct dvpoll dvp; PyObject *result_list = NULL; @@ -1059,6 +1067,7 @@ devpoll_internal_close(devpollObject *self) } /*[clinic input] +@critical_section select.devpoll.close Close the devpoll file descriptor. @@ -1068,7 +1077,7 @@ Further operations on the devpoll object will raise an exception. static PyObject * select_devpoll_close_impl(devpollObject *self) -/*[clinic end generated code: output=26b355bd6429f21b input=6273c30f5560a99b]*/ +/*[clinic end generated code: output=26b355bd6429f21b input=408fde21a377ccfb]*/ { errno = devpoll_internal_close(self); if (errno < 0) { @@ -1088,6 +1097,7 @@ devpoll_get_closed(devpollObject *self, void *Py_UNUSED(ignored)) } /*[clinic input] +@critical_section select.devpoll.fileno Return the file descriptor. @@ -1095,7 +1105,7 @@ Return the file descriptor. static PyObject * select_devpoll_fileno_impl(devpollObject *self) -/*[clinic end generated code: output=26920929f8d292f4 input=ef15331ebde6c368]*/ +/*[clinic end generated code: output=26920929f8d292f4 input=8c9db2efa1ade538]*/ { if (self->fd_devpoll < 0) return devpoll_err_closed(); @@ -1378,6 +1388,7 @@ pyepoll_dealloc(pyEpoll_Object *self) } /*[clinic input] +@critical_section select.epoll.close Close the epoll control file descriptor. @@ -1387,7 +1398,7 @@ Further operations on the epoll object will raise an exception. static PyObject * select_epoll_close_impl(pyEpoll_Object *self) -/*[clinic end generated code: output=ee2144c446a1a435 input=ca6c66ba5a736bfd]*/ +/*[clinic end generated code: output=ee2144c446a1a435 input=f626a769192e1dbe]*/ { errno = pyepoll_internal_close(self); if (errno < 0) { @@ -2023,10 +2034,8 @@ kqueue_tracking_init(PyObject *module) { } static int -kqueue_tracking_add(_selectstate *state, kqueue_queue_Object *self) { - if (!state->kqueue_tracking_initialized) { - kqueue_tracking_init(PyType_GetModule(Py_TYPE(self))); - } +kqueue_tracking_add_lock_held(_selectstate *state, kqueue_queue_Object *self) +{ assert(self->kqfd >= 0); _kqueue_list_item *item = PyMem_New(_kqueue_list_item, 1); if (item == NULL) { @@ -2039,8 +2048,23 @@ kqueue_tracking_add(_selectstate *state, kqueue_queue_Object *self) { return 0; } +static int +kqueue_tracking_add(_selectstate *state, kqueue_queue_Object *self) +{ + int ret; + PyObject *module = PyType_GetModule(Py_TYPE(self)); + Py_BEGIN_CRITICAL_SECTION(module); + if (!state->kqueue_tracking_initialized) { + kqueue_tracking_init(module); + } + ret = kqueue_tracking_add_lock_held(state, self); + Py_END_CRITICAL_SECTION(); + return ret; +} + static void -kqueue_tracking_remove(_selectstate *state, kqueue_queue_Object *self) { +kqueue_tracking_remove_lock_held(_selectstate *state, kqueue_queue_Object *self) +{ _kqueue_list *listptr = &state->kqueue_open_list; while (*listptr != NULL) { _kqueue_list_item *item = *listptr; @@ -2056,6 +2080,14 @@ kqueue_tracking_remove(_selectstate *state, kqueue_queue_Object *self) { assert(0); } +static void +kqueue_tracking_remove(_selectstate *state, kqueue_queue_Object *self) +{ + Py_BEGIN_CRITICAL_SECTION(PyType_GetModule(Py_TYPE(self))); + kqueue_tracking_remove_lock_held(state, self); + Py_END_CRITICAL_SECTION(); +} + static int kqueue_queue_internal_close(kqueue_queue_Object *self) { @@ -2150,6 +2182,7 @@ kqueue_queue_finalize(kqueue_queue_Object *self) } /*[clinic input] +@critical_section select.kqueue.close Close the kqueue control file descriptor. @@ -2159,7 +2192,7 @@ Further operations on the kqueue object will raise an exception. static PyObject * select_kqueue_close_impl(kqueue_queue_Object *self) -/*[clinic end generated code: output=d1c7df0b407a4bc1 input=0b12d95430e0634c]*/ +/*[clinic end generated code: output=d1c7df0b407a4bc1 input=6d763c858b17b690]*/ { errno = kqueue_queue_internal_close(self); if (errno < 0) { From 58e8cf2bb61f82df9eabd1209fe5e3d146e4c8cd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 11 Jul 2024 07:34:53 -0700 Subject: [PATCH 95/97] gh-121332: Make AST node constructor check _attributes instead of hardcoding attributes (#121334) --- Lib/test/test_ast.py | 22 ++++++----- ...-07-03-07-25-21.gh-issue-121332.Iz6FEq.rst | 4 ++ Parser/asdl_c.py | 39 ++++++++++++------- Python/Python-ast.c | 39 ++++++++++++------- 4 files changed, 67 insertions(+), 37 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-03-07-25-21.gh-issue-121332.Iz6FEq.rst diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index eb3aefd5c262f6..497c3f261a1fca 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1386,15 +1386,7 @@ class MyNode(ast.AST): self.assertEqual(node.y, 1) y = object() - # custom attributes are currently not supported and raise a warning - # because the allowed attributes are hard-coded ! - msg = ( - "MyNode.__init__ got an unexpected keyword argument 'y'. " - "Support for arbitrary keyword arguments is deprecated and " - "will be removed in Python 3.15" - ) - with self.assertWarnsRegex(DeprecationWarning, re.escape(msg)): - repl = copy.replace(node, y=y) + repl = copy.replace(node, y=y) # assert that there is no side-effect self.assertEqual(node.x, 0) self.assertEqual(node.y, 1) @@ -3250,6 +3242,18 @@ class FieldsAndTypes(ast.AST): obj = FieldsAndTypes(a=1) self.assertEqual(obj.a, 1) + def test_custom_attributes(self): + class MyAttrs(ast.AST): + _attributes = ("a", "b") + + obj = MyAttrs(a=1, b=2) + self.assertEqual(obj.a, 1) + self.assertEqual(obj.b, 2) + + with self.assertWarnsRegex(DeprecationWarning, + r"MyAttrs.__init__ got an unexpected keyword argument 'c'."): + obj = MyAttrs(c=3) + def test_fields_and_types_no_default(self): class FieldsAndTypesNoDefault(ast.AST): _fields = ('a',) diff --git a/Misc/NEWS.d/next/Library/2024-07-03-07-25-21.gh-issue-121332.Iz6FEq.rst b/Misc/NEWS.d/next/Library/2024-07-03-07-25-21.gh-issue-121332.Iz6FEq.rst new file mode 100644 index 00000000000000..480f27e05953a6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-03-07-25-21.gh-issue-121332.Iz6FEq.rst @@ -0,0 +1,4 @@ +Fix constructor of :mod:`ast` nodes with custom ``_attributes``. Previously, +passing custom attributes would raise a :py:exc:`DeprecationWarning`. Passing +arguments to the constructor that are not in ``_fields`` or ``_attributes`` +remains deprecated. Patch by Jelle Zijlstra. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index f3667801782f2b..e6867f138a5ccb 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -880,7 +880,7 @@ def visitModule(self, mod): Py_ssize_t i, numfields = 0; int res = -1; - PyObject *key, *value, *fields, *remaining_fields = NULL; + PyObject *key, *value, *fields, *attributes = NULL, *remaining_fields = NULL; if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { goto cleanup; } @@ -947,22 +947,32 @@ def visitModule(self, mod): goto cleanup; } } - else if ( - PyUnicode_CompareWithASCIIString(key, "lineno") != 0 && - PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 && - PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 && - PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0 - ) { - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "%.400s.__init__ got an unexpected keyword argument '%U'. " - "Support for arbitrary keyword arguments is deprecated " - "and will be removed in Python 3.15.", - Py_TYPE(self)->tp_name, key - ) < 0) { + else { + // Lazily initialize "attributes" + if (attributes == NULL) { + attributes = PyObject_GetAttr((PyObject*)Py_TYPE(self), state->_attributes); + if (attributes == NULL) { + res = -1; + goto cleanup; + } + } + int contains = PySequence_Contains(attributes, key); + if (contains == -1) { res = -1; goto cleanup; } + else if (contains == 0) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ got an unexpected keyword argument '%U'. " + "Support for arbitrary keyword arguments is deprecated " + "and will be removed in Python 3.15.", + Py_TYPE(self)->tp_name, key + ) < 0) { + res = -1; + goto cleanup; + } + } } res = PyObject_SetAttr(self, key, value); if (res < 0) { @@ -1045,6 +1055,7 @@ def visitModule(self, mod): Py_DECREF(field_types); } cleanup: + Py_XDECREF(attributes); Py_XDECREF(fields); Py_XDECREF(remaining_fields); return res; diff --git a/Python/Python-ast.c b/Python/Python-ast.c index cca2ee409e7978..4d0db457a8b172 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -5081,7 +5081,7 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) Py_ssize_t i, numfields = 0; int res = -1; - PyObject *key, *value, *fields, *remaining_fields = NULL; + PyObject *key, *value, *fields, *attributes = NULL, *remaining_fields = NULL; if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { goto cleanup; } @@ -5148,22 +5148,32 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) goto cleanup; } } - else if ( - PyUnicode_CompareWithASCIIString(key, "lineno") != 0 && - PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 && - PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 && - PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0 - ) { - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "%.400s.__init__ got an unexpected keyword argument '%U'. " - "Support for arbitrary keyword arguments is deprecated " - "and will be removed in Python 3.15.", - Py_TYPE(self)->tp_name, key - ) < 0) { + else { + // Lazily initialize "attributes" + if (attributes == NULL) { + attributes = PyObject_GetAttr((PyObject*)Py_TYPE(self), state->_attributes); + if (attributes == NULL) { + res = -1; + goto cleanup; + } + } + int contains = PySequence_Contains(attributes, key); + if (contains == -1) { res = -1; goto cleanup; } + else if (contains == 0) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ got an unexpected keyword argument '%U'. " + "Support for arbitrary keyword arguments is deprecated " + "and will be removed in Python 3.15.", + Py_TYPE(self)->tp_name, key + ) < 0) { + res = -1; + goto cleanup; + } + } } res = PyObject_SetAttr(self, key, value); if (res < 0) { @@ -5246,6 +5256,7 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) Py_DECREF(field_types); } cleanup: + Py_XDECREF(attributes); Py_XDECREF(fields); Py_XDECREF(remaining_fields); return res; From 5250a031332eb9499d5fc190d7287642e5a144b9 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 11 Jul 2024 14:20:14 -0600 Subject: [PATCH 96/97] gh-117482: Fix Builtin Types Slot Wrappers (gh-121602) When builtin static types are initialized for a subinterpreter, various "tp" slots have already been inherited (for the main interpreter). This was interfering with the logic in add_operators() (in Objects/typeobject.c), causing a wrapper to get created when it shouldn't. This change fixes that by preserving the original data from the static type struct and checking that. --- Include/internal/pycore_typeobject.h | 1 + Lib/test/test_types.py | 36 +++++++++++++++++ ...-07-10-15-43-54.gh-issue-117482.5WYaXR.rst | 2 + Objects/typeobject.c | 40 ++++++++++++++----- 4 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-07-10-15-43-54.gh-issue-117482.5WYaXR.rst diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 32bd19d968b917..df6bfef715dd34 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -33,6 +33,7 @@ struct _types_runtime_state { struct { struct { PyTypeObject *type; + PyTypeObject def; int64_t interp_count; } types[_Py_MAX_MANAGED_STATIC_TYPES]; } managed_static; diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index fbca198aab5180..38a98828085e2f 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -10,6 +10,7 @@ import pickle import locale import sys +import textwrap import types import unittest.mock import weakref @@ -2345,5 +2346,40 @@ def ex(a, /, b, *, c): ) +class SubinterpreterTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + global interpreters + try: + from test.support import interpreters + except ModuleNotFoundError: + raise unittest.SkipTest('subinterpreters required') + import test.support.interpreters.channels + + @cpython_only + def test_slot_wrappers(self): + rch, sch = interpreters.channels.create() + + # For now it's sufficient to check int.__str__. + # See https://github.com/python/cpython/issues/117482 + # and https://github.com/python/cpython/pull/117660. + script = textwrap.dedent(''' + text = repr(int.__str__) + sch.send_nowait(text) + ''') + + exec(script) + expected = rch.recv() + + interp = interpreters.create() + interp.exec('from test.support import interpreters') + interp.prepare_main(sch=sch) + interp.exec(script) + results = rch.recv() + + self.assertEqual(results, expected) + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-10-15-43-54.gh-issue-117482.5WYaXR.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-10-15-43-54.gh-issue-117482.5WYaXR.rst new file mode 100644 index 00000000000000..ec1e7327b77f19 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-10-15-43-54.gh-issue-117482.5WYaXR.rst @@ -0,0 +1,2 @@ +Unexpected slot wrappers are no longer created for builtin static types in +subinterpreters. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 587632cecfba9d..7d01b680605a38 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -314,6 +314,16 @@ managed_static_type_state_clear(PyInterpreterState *interp, PyTypeObject *self, } } +static PyTypeObject * +managed_static_type_get_def(PyTypeObject *self, int isbuiltin) +{ + size_t index = managed_static_type_index_get(self); + size_t full_index = isbuiltin + ? index + : index + _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES; + return &_PyRuntime.types.managed_static.types[full_index].def; +} + // Also see _PyStaticType_InitBuiltin() and _PyStaticType_FiniBuiltin(). /* end static builtin helpers */ @@ -5840,7 +5850,6 @@ fini_static_type(PyInterpreterState *interp, PyTypeObject *type, _PyStaticType_ClearWeakRefs(interp, type); managed_static_type_state_clear(interp, type, isbuiltin, final); - /* We leave _Py_TPFLAGS_STATIC_BUILTIN set on tp_flags. */ } void @@ -7850,7 +7859,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) return 0; } -static int add_operators(PyTypeObject *); +static int add_operators(PyTypeObject *, PyTypeObject *); static int add_tp_new_wrapper(PyTypeObject *type); #define COLLECTION_FLAGS (Py_TPFLAGS_SEQUENCE | Py_TPFLAGS_MAPPING) @@ -8015,10 +8024,10 @@ type_dict_set_doc(PyTypeObject *type) static int -type_ready_fill_dict(PyTypeObject *type) +type_ready_fill_dict(PyTypeObject *type, PyTypeObject *def) { /* Add type-specific descriptors to tp_dict */ - if (add_operators(type) < 0) { + if (add_operators(type, def) < 0) { return -1; } if (type_add_methods(type) < 0) { @@ -8337,7 +8346,7 @@ type_ready_post_checks(PyTypeObject *type) static int -type_ready(PyTypeObject *type, int initial) +type_ready(PyTypeObject *type, PyTypeObject *def, int initial) { ASSERT_TYPE_LOCK_HELD(); @@ -8376,7 +8385,7 @@ type_ready(PyTypeObject *type, int initial) if (type_ready_set_new(type, initial) < 0) { goto error; } - if (type_ready_fill_dict(type) < 0) { + if (type_ready_fill_dict(type, def) < 0) { goto error; } if (initial) { @@ -8433,7 +8442,7 @@ PyType_Ready(PyTypeObject *type) int res; BEGIN_TYPE_LOCK(); if (!(type->tp_flags & Py_TPFLAGS_READY)) { - res = type_ready(type, 1); + res = type_ready(type, NULL, 1); } else { res = 0; assert(_PyType_CheckConsistency(type)); @@ -8469,14 +8478,20 @@ init_static_type(PyInterpreterState *interp, PyTypeObject *self, managed_static_type_state_init(interp, self, isbuiltin, initial); + PyTypeObject *def = managed_static_type_get_def(self, isbuiltin); + if (initial) { + memcpy(def, self, sizeof(PyTypeObject)); + } + int res; BEGIN_TYPE_LOCK(); - res = type_ready(self, initial); + res = type_ready(self, def, initial); END_TYPE_LOCK(); if (res < 0) { _PyStaticType_ClearWeakRefs(interp, self); managed_static_type_state_clear(interp, self, isbuiltin, initial); } + return res; } @@ -11064,17 +11079,22 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name, infinite recursion here.) */ static int -add_operators(PyTypeObject *type) +add_operators(PyTypeObject *type, PyTypeObject *def) { PyObject *dict = lookup_tp_dict(type); pytype_slotdef *p; PyObject *descr; void **ptr; + assert(def == NULL || (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); + if (def == NULL) { + def = type; + } + for (p = slotdefs; p->name; p++) { if (p->wrapper == NULL) continue; - ptr = slotptr(type, p->offset); + ptr = slotptr(def, p->offset); if (!ptr || !*ptr) continue; int r = PyDict_Contains(dict, p->name_strobj); From e8c91d90ba8fab410a27fad4f709cc73f6ffcbf4 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 11 Jul 2024 16:21:37 -0400 Subject: [PATCH 97/97] gh-121103: Put free-threaded libraries in `lib/python3.14t` (#121293) On POSIX systems, excluding macOS framework installs, the lib directory for the free-threaded build now includes a "t" suffix to avoid conflicts with a co-located default build installation. --- Lib/site.py | 12 +++++-- Lib/sysconfig/__init__.py | 33 +++++++++++-------- Lib/test/test_embed.py | 14 ++++---- Lib/test/test_getpath.py | 1 + Lib/test/test_site.py | 4 +-- Lib/test/test_sysconfig.py | 2 +- Lib/test/test_venv.py | 5 +-- Makefile.pre.in | 3 +- ...-07-02-20-16-09.gh-issue-121103.TMef9j.rst | 3 ++ Modules/getpath.c | 5 +++ Modules/getpath.py | 8 +++-- configure | 14 +++++--- configure.ac | 13 +++++--- 13 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-07-02-20-16-09.gh-issue-121103.TMef9j.rst diff --git a/Lib/site.py b/Lib/site.py index daa56e158949db..460269433f021c 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -312,6 +312,10 @@ def joinuser(*args): # Same to sysconfig.get_path('purelib', os.name+'_user') def _get_path(userbase): version = sys.version_info + if hasattr(sys, 'abiflags') and 't' in sys.abiflags: + abi_thread = 't' + else: + abi_thread = '' implementation = _get_implementation() implementation_lower = implementation.lower() @@ -322,7 +326,7 @@ def _get_path(userbase): if sys.platform == 'darwin' and sys._framework: return f'{userbase}/lib/{implementation_lower}/site-packages' - return f'{userbase}/lib/python{version[0]}.{version[1]}/site-packages' + return f'{userbase}/lib/python{version[0]}.{version[1]}{abi_thread}/site-packages' def getuserbase(): @@ -390,6 +394,10 @@ def getsitepackages(prefixes=None): implementation = _get_implementation().lower() ver = sys.version_info + if hasattr(sys, 'abiflags') and 't' in sys.abiflags: + abi_thread = 't' + else: + abi_thread = '' if os.sep == '/': libdirs = [sys.platlibdir] if sys.platlibdir != "lib": @@ -397,7 +405,7 @@ def getsitepackages(prefixes=None): for libdir in libdirs: path = os.path.join(prefix, libdir, - f"{implementation}{ver[0]}.{ver[1]}", + f"{implementation}{ver[0]}.{ver[1]}{abi_thread}", "site-packages") sitepackages.append(path) else: diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 98a14e5d3a3187..83e057c177f8c0 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -27,10 +27,10 @@ _INSTALL_SCHEMES = { 'posix_prefix': { - 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}', - 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}', - 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', - 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', + 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}/site-packages', 'include': '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}', 'platinclude': @@ -77,10 +77,10 @@ # Downstream distributors who patch posix_prefix/nt scheme are encouraged to # leave the following schemes unchanged 'posix_venv': { - 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}', - 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}', - 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', - 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', + 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}/site-packages', 'include': '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}', 'platinclude': @@ -148,11 +148,11 @@ def joinuser(*args): 'data': '{userbase}', }, 'posix_user': { - 'stdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}', - 'platstdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}', - 'purelib': '{userbase}/lib/{implementation_lower}{py_version_short}/site-packages', - 'platlib': '{userbase}/lib/{implementation_lower}{py_version_short}/site-packages', - 'include': '{userbase}/include/{implementation_lower}{py_version_short}', + 'stdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'platstdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}', + 'purelib': '{userbase}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'platlib': '{userbase}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages', + 'include': '{userbase}/include/{implementation_lower}{py_version_short}{abi_thread}', 'scripts': '{userbase}/bin', 'data': '{userbase}', }, @@ -487,6 +487,9 @@ def _init_config_vars(): # the init-function. _CONFIG_VARS['userbase'] = _getuserbase() + # e.g., 't' for free-threaded or '' for default build + _CONFIG_VARS['abi_thread'] = 't' if _CONFIG_VARS.get('Py_GIL_DISABLED') else '' + # Always convert srcdir to an absolute path srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE) if os.name == 'posix': @@ -655,6 +658,10 @@ def get_python_version(): return _PY_VERSION_SHORT +def _get_python_version_abi(): + return _PY_VERSION_SHORT + get_config_var("abi_thread") + + def expand_makefile_vars(s, vars): """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in 'string' according to 'vars' (a dictionary mapping variable names to diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 634513ec7a5812..30dab1fbaa48b2 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -48,6 +48,8 @@ INIT_LOOPS = 4 MAX_HASH_SEED = 4294967295 +ABI_THREAD = 't' if sysconfig.get_config_var('Py_GIL_DISABLED') else '' + # If we are running from a build dir, but the stdlib has been installed, # some tests need to expect different results. @@ -1285,11 +1287,11 @@ def module_search_paths(self, prefix=None, exec_prefix=None): ver = sys.version_info return [ os.path.join(prefix, sys.platlibdir, - f'python{ver.major}{ver.minor}.zip'), + f'python{ver.major}{ver.minor}{ABI_THREAD}.zip'), os.path.join(prefix, sys.platlibdir, - f'python{ver.major}.{ver.minor}'), + f'python{ver.major}.{ver.minor}{ABI_THREAD}'), os.path.join(exec_prefix, sys.platlibdir, - f'python{ver.major}.{ver.minor}', 'lib-dynload'), + f'python{ver.major}.{ver.minor}{ABI_THREAD}', 'lib-dynload'), ] @contextlib.contextmanager @@ -1343,7 +1345,7 @@ def test_init_setpythonhome(self): expected_paths = [paths[0], os.path.join(home, 'DLLs'), stdlib] else: version = f'{sys.version_info.major}.{sys.version_info.minor}' - stdlib = os.path.join(home, sys.platlibdir, f'python{version}') + stdlib = os.path.join(home, sys.platlibdir, f'python{version}{ABI_THREAD}') expected_paths = self.module_search_paths(prefix=home, exec_prefix=home) config = { @@ -1384,7 +1386,7 @@ def test_init_is_python_build_with_home(self): expected_paths = [paths[0], os.path.join(home, 'DLLs'), stdlib] else: version = f'{sys.version_info.major}.{sys.version_info.minor}' - stdlib = os.path.join(home, sys.platlibdir, f'python{version}') + stdlib = os.path.join(home, sys.platlibdir, f'python{version}{ABI_THREAD}') expected_paths = self.module_search_paths(prefix=home, exec_prefix=home) config = { @@ -1515,7 +1517,7 @@ def test_init_pyvenv_cfg(self): if not MS_WINDOWS: lib_dynload = os.path.join(pyvenv_home, sys.platlibdir, - f'python{ver.major}.{ver.minor}', + f'python{ver.major}.{ver.minor}{ABI_THREAD}', 'lib-dynload') os.makedirs(lib_dynload) else: diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index 2f7aa69efc184a..6c86c3d1c8c57e 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -844,6 +844,7 @@ def test_explicitly_set_stdlib_dir(self): PYDEBUGEXT="", VERSION_MAJOR=9, # fixed version number for ease VERSION_MINOR=8, # of testing + ABI_THREAD="", PYWINVER=None, EXE_SUFFIX=None, diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index bcdc232c712071..035913cdd05f34 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -328,13 +328,13 @@ def test_getsitepackages(self): if sys.platlibdir != "lib": self.assertEqual(len(dirs), 2) wanted = os.path.join('xoxo', sys.platlibdir, - 'python%d.%d' % sys.version_info[:2], + f'python{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[0], wanted) else: self.assertEqual(len(dirs), 1) wanted = os.path.join('xoxo', 'lib', - 'python%d.%d' % sys.version_info[:2], + f'python{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[-1], wanted) else: diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 9233304c6a5327..37cee927686ba3 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -157,7 +157,7 @@ def test_posix_venv_scheme(self): binpath = 'bin' incpath = 'include' libpath = os.path.join('lib', - 'python%d.%d' % sys.version_info[:2], + f'python{sysconfig._get_python_version_abi()}', 'site-packages') # Resolve the paths in an imaginary venv/ directory diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 1769ed61b94075..2b7d297f011741 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -75,7 +75,7 @@ def setUp(self): self.include = 'Include' else: self.bindir = 'bin' - self.lib = ('lib', 'python%d.%d' % sys.version_info[:2]) + self.lib = ('lib', f'python{sysconfig._get_python_version_abi()}') self.include = 'include' executable = sys._base_executable self.exe = os.path.split(executable)[-1] @@ -593,7 +593,8 @@ def test_zippath_from_non_installed_posix(self): libdir = os.path.join(non_installed_dir, platlibdir, self.lib[1]) os.makedirs(libdir) landmark = os.path.join(libdir, "os.py") - stdlib_zip = "python%d%d.zip" % sys.version_info[:2] + abi_thread = "t" if sysconfig.get_config_var("Py_GIL_DISABLED") else "" + stdlib_zip = f"python{sys.version_info.major}{sys.version_info.minor}{abi_thread}" zip_landmark = os.path.join(non_installed_dir, platlibdir, stdlib_zip) diff --git a/Makefile.pre.in b/Makefile.pre.in index 0bece8717ef4c0..d380c422714a32 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -41,6 +41,7 @@ AR= @AR@ READELF= @READELF@ SOABI= @SOABI@ ABIFLAGS= @ABIFLAGS@ +ABI_THREAD= @ABI_THREAD@ LDVERSION= @LDVERSION@ MODULE_LDFLAGS=@MODULE_LDFLAGS@ GITVERSION= @GITVERSION@ @@ -158,7 +159,7 @@ WHEEL_PKG_DIR= @WHEEL_PKG_DIR@ # Detailed destination directories BINLIBDEST= @BINLIBDEST@ -LIBDEST= $(SCRIPTDIR)/python$(VERSION) +LIBDEST= $(SCRIPTDIR)/python$(VERSION)$(ABI_THREAD) INCLUDEPY= $(INCLUDEDIR)/python$(LDVERSION) CONFINCLUDEPY= $(CONFINCLUDEDIR)/python$(LDVERSION) diff --git a/Misc/NEWS.d/next/Build/2024-07-02-20-16-09.gh-issue-121103.TMef9j.rst b/Misc/NEWS.d/next/Build/2024-07-02-20-16-09.gh-issue-121103.TMef9j.rst new file mode 100644 index 00000000000000..4bc8c6de0b7733 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-07-02-20-16-09.gh-issue-121103.TMef9j.rst @@ -0,0 +1,3 @@ +On POSIX systems, excluding macOS framework installs, the lib directory +for the free-threaded build now includes a "t" suffix to avoid conflicts +with a co-located default build installation. diff --git a/Modules/getpath.c b/Modules/getpath.c index abed139028244a..d0128b20faeeae 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -951,6 +951,11 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) !wchar_to_dict(dict, "executable_dir", NULL) || !wchar_to_dict(dict, "py_setpath", _PyPathConfig_GetGlobalModuleSearchPath()) || !funcs_to_dict(dict, config->pathconfig_warnings) || +#ifdef Py_GIL_DISABLED + !decode_to_dict(dict, "ABI_THREAD", "t") || +#else + !decode_to_dict(dict, "ABI_THREAD", "") || +#endif #ifndef MS_WINDOWS PyDict_SetItemString(dict, "winreg", Py_None) < 0 || #endif diff --git a/Modules/getpath.py b/Modules/getpath.py index bc7053224aaf16..1f1bfcb4f64dd4 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -40,6 +40,7 @@ # EXE_SUFFIX -- [in, opt] '.exe' on Windows/Cygwin/similar # VERSION_MAJOR -- [in] sys.version_info.major # VERSION_MINOR -- [in] sys.version_info.minor +# ABI_THREAD -- [in] either 't' for free-threaded builds or '' # PYWINVER -- [in] the Windows platform-specific version (e.g. 3.8-32) # ** Values read from the environment ** @@ -172,17 +173,18 @@ # ****************************************************************************** platlibdir = config.get('platlibdir') or PLATLIBDIR +ABI_THREAD = ABI_THREAD or '' if os_name == 'posix' or os_name == 'darwin': BUILDDIR_TXT = 'pybuilddir.txt' BUILD_LANDMARK = 'Modules/Setup.local' DEFAULT_PROGRAM_NAME = f'python{VERSION_MAJOR}' - STDLIB_SUBDIR = f'{platlibdir}/python{VERSION_MAJOR}.{VERSION_MINOR}' + STDLIB_SUBDIR = f'{platlibdir}/python{VERSION_MAJOR}.{VERSION_MINOR}{ABI_THREAD}' STDLIB_LANDMARKS = [f'{STDLIB_SUBDIR}/os.py', f'{STDLIB_SUBDIR}/os.pyc'] - PLATSTDLIB_LANDMARK = f'{platlibdir}/python{VERSION_MAJOR}.{VERSION_MINOR}/lib-dynload' + PLATSTDLIB_LANDMARK = f'{platlibdir}/python{VERSION_MAJOR}.{VERSION_MINOR}{ABI_THREAD}/lib-dynload' BUILDSTDLIB_LANDMARKS = ['Lib/os.py'] VENV_LANDMARK = 'pyvenv.cfg' - ZIP_LANDMARK = f'{platlibdir}/python{VERSION_MAJOR}{VERSION_MINOR}.zip' + ZIP_LANDMARK = f'{platlibdir}/python{VERSION_MAJOR}{VERSION_MINOR}{ABI_THREAD}.zip' DELIM = ':' SEP = '/' diff --git a/configure b/configure index bbfa805883cac5..19786f18e61726 100755 --- a/configure +++ b/configure @@ -928,6 +928,7 @@ DEF_MAKE_RULE DEF_MAKE_ALL_RULE JIT_STENCILS_H REGEN_JIT_COMMAND +ABI_THREAD ABIFLAGS LN MKDIR_P @@ -8160,7 +8161,9 @@ fi # For calculating the .so ABI tag. + ABIFLAGS="" +ABI_THREAD="" # Check for --disable-gil # --disable-gil @@ -8190,6 +8193,7 @@ printf "%s\n" "#define Py_GIL_DISABLED 1" >>confdefs.h # Add "t" for "threaded" ABIFLAGS="${ABIFLAGS}t" + ABI_THREAD="t" fi # Check for --with-pydebug @@ -24738,11 +24742,11 @@ fi -BINLIBDEST='$(LIBDIR)/python$(VERSION)' +BINLIBDEST='$(LIBDIR)/python$(VERSION)$(ABI_THREAD)' # Check for --with-platlibdir -# /usr/$LIDIRNAME/python$VERSION +# /usr/$PLATLIBDIR/python$(VERSION)$(ABI_THREAD) PLATLIBDIR="lib" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-platlibdir" >&5 @@ -24761,7 +24765,7 @@ then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } PLATLIBDIR="$withval" - BINLIBDEST='${exec_prefix}/${PLATLIBDIR}/python$(VERSION)' + BINLIBDEST='${exec_prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } @@ -24775,9 +24779,9 @@ fi if test x$PLATFORM_TRIPLET = x; then - LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}/config-${LDVERSION}" + LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}${ABI_THREAD}/config-${LDVERSION}" else - LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}" + LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}${ABI_THREAD}/config-${LDVERSION}-${PLATFORM_TRIPLET}" fi diff --git a/configure.ac b/configure.ac index 87c4df20818808..df146cc9cf0b75 100644 --- a/configure.ac +++ b/configure.ac @@ -1739,7 +1739,9 @@ fi # For calculating the .so ABI tag. AC_SUBST([ABIFLAGS]) +AC_SUBST([ABI_THREAD]) ABIFLAGS="" +ABI_THREAD="" # Check for --disable-gil # --disable-gil @@ -1756,6 +1758,7 @@ then [Define if you want to disable the GIL]) # Add "t" for "threaded" ABIFLAGS="${ABIFLAGS}t" + ABI_THREAD="t" fi # Check for --with-pydebug @@ -6234,11 +6237,11 @@ fi AC_SUBST([BINLIBDEST]) -BINLIBDEST='$(LIBDIR)/python$(VERSION)' +BINLIBDEST='$(LIBDIR)/python$(VERSION)$(ABI_THREAD)' # Check for --with-platlibdir -# /usr/$LIDIRNAME/python$VERSION +# /usr/$PLATLIBDIR/python$(VERSION)$(ABI_THREAD) AC_SUBST([PLATLIBDIR]) PLATLIBDIR="lib" AC_MSG_CHECKING([for --with-platlibdir]) @@ -6257,7 +6260,7 @@ if test -n "$withval" -a "$withval" != yes -a "$withval" != no then AC_MSG_RESULT([yes]) PLATLIBDIR="$withval" - BINLIBDEST='${exec_prefix}/${PLATLIBDIR}/python$(VERSION)' + BINLIBDEST='${exec_prefix}/${PLATLIBDIR}/python$(VERSION)$(ABI_THREAD)' else AC_MSG_RESULT([no]) fi], @@ -6267,9 +6270,9 @@ fi], dnl define LIBPL after ABIFLAGS and LDVERSION is defined. AC_SUBST([PY_ENABLE_SHARED]) if test x$PLATFORM_TRIPLET = x; then - LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}/config-${LDVERSION}" + LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}${ABI_THREAD}/config-${LDVERSION}" else - LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}" + LIBPL='$(prefix)'"/${PLATLIBDIR}/python${VERSION}${ABI_THREAD}/config-${LDVERSION}-${PLATFORM_TRIPLET}" fi AC_SUBST([LIBPL])