diff --git a/Doc/library/xmlrpc.client.rst b/Doc/library/xmlrpc.client.rst index 547cb50be78a78..a21c7d3e4e3ad5 100644 --- a/Doc/library/xmlrpc.client.rst +++ b/Doc/library/xmlrpc.client.rst @@ -524,7 +524,7 @@ Convenience Functions .. function:: dumps(params, methodname=None, methodresponse=None, encoding=None, allow_none=False) - Convert *params* into an XML-RPC request. or into a response if *methodresponse* + Convert *params* into an XML-RPC request, or into a response if *methodresponse* is true. *params* can be either a tuple of arguments or an instance of the :exc:`Fault` exception class. If *methodresponse* is true, only a single value can be returned, meaning that *params* must be of length 1. *encoding*, if diff --git a/Lib/test/libregrtest/save_env.py b/Lib/test/libregrtest/save_env.py index 4cf1a075b30013..138465012a252c 100644 --- a/Lib/test/libregrtest/save_env.py +++ b/Lib/test/libregrtest/save_env.py @@ -9,6 +9,13 @@ from .utils import print_warning +# Import termios to save and restore terminal echo. This is only available on +# Unix, and it's fine if the module can't be found. +try: + import termios # noqa: F401 +except ModuleNotFoundError: + pass + class SkipTestEnvironment(Exception): pass @@ -65,6 +72,7 @@ def __init__(self, test_name, verbose, quiet, *, pgo): 'shutil_archive_formats', 'shutil_unpack_formats', 'asyncio.events._event_loop_policy', 'urllib.requests._url_tempfiles', 'urllib.requests._opener', + 'stty_echo', ) def get_module(self, name): @@ -292,6 +300,24 @@ def restore_warnings_showwarning(self, fxn): warnings = self.get_module('warnings') warnings.showwarning = fxn + def get_stty_echo(self): + termios = self.try_get_module('termios') + if not os.isatty(fd := sys.__stdin__.fileno()): + return None + attrs = termios.tcgetattr(fd) + lflags = attrs[3] + return bool(lflags & termios.ECHO) + def restore_stty_echo(self, echo): + termios = self.get_module('termios') + attrs = termios.tcgetattr(fd := sys.__stdin__.fileno()) + if echo: + # Turn echo on. + attrs[3] |= termios.ECHO + else: + # Turn echo off. + attrs[3] &= ~termios.ECHO + termios.tcsetattr(fd, termios.TCSADRAIN, attrs) + def resource_info(self): for name in self.resources: method_suffix = name.replace('.', '_') diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 1a7658b13fa5e3..e3663e44546ded 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -1882,7 +1882,7 @@ def test_bad_newobj_ex_args(self): with self.assertRaises(TypeError) as cm: self.dumps(obj, proto) self.assertEqual(str(cm.exception), - 'functools.partial() argument after ** must be a mapping, not list') + 'Value after ** must be a mapping, not list') self.assertEqual(cm.exception.__notes__, [ 'when serializing test.pickletester.REX object']) else: diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 034cd5f7f9e89f..fe3e391a7f5ba1 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1385,6 +1385,22 @@ def test_map_strict(self): self.assertRaises(ValueError, tuple, map(pack, (1, 2), (1, 2), 'abc', strict=True)) + # gh-140517: Testing refleaks with mortal objects. + t1 = (None, object()) + t2 = (object(), object()) + t3 = (object(),) + + self.assertRaises(ValueError, tuple, + map(pack, t1, 'a', strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, t1, t2, 'a', strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, t1, t2, t3, strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, 'a', t1, strict=True)) + self.assertRaises(ValueError, tuple, + map(pack, 'a', t2, t3, strict=True)) + def test_map_strict_iterators(self): x = iter(range(5)) y = [0] diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py index d9d85fe79af883..f003a5837ae0eb 100644 --- a/Lib/test/test_extcall.py +++ b/Lib/test/test_extcall.py @@ -137,7 +137,7 @@ >>> g(*Nothing()) Traceback (most recent call last): ... - TypeError: test.test_extcall.g() argument after * must be an iterable, not Nothing + TypeError: Value after * must be an iterable, not Nothing >>> class Nothing: ... def __len__(self): return 5 @@ -146,7 +146,7 @@ >>> g(*Nothing()) Traceback (most recent call last): ... - TypeError: test.test_extcall.g() argument after * must be an iterable, not Nothing + TypeError: Value after * must be an iterable, not Nothing >>> class Nothing(): ... def __len__(self): return 5 @@ -266,7 +266,7 @@ >>> h(*h) Traceback (most recent call last): ... - TypeError: test.test_extcall.h() argument after * must be an iterable, not function + TypeError: Value after * must be an iterable, not function >>> h(1, *h) Traceback (most recent call last): @@ -281,55 +281,53 @@ >>> dir(*h) Traceback (most recent call last): ... - TypeError: dir() argument after * must be an iterable, not function + TypeError: Value after * must be an iterable, not function >>> nothing = None >>> nothing(*h) Traceback (most recent call last): ... - TypeError: None argument after * must be an iterable, \ -not function + TypeError: Value after * must be an iterable, not function >>> h(**h) Traceback (most recent call last): ... - TypeError: test.test_extcall.h() argument after ** must be a mapping, not function + TypeError: Value after ** must be a mapping, not function >>> h(**[]) Traceback (most recent call last): ... - TypeError: test.test_extcall.h() argument after ** must be a mapping, not list + TypeError: Value after ** must be a mapping, not list >>> h(a=1, **h) Traceback (most recent call last): ... - TypeError: test.test_extcall.h() argument after ** must be a mapping, not function + TypeError: Value after ** must be a mapping, not function >>> h(a=1, **[]) Traceback (most recent call last): ... - TypeError: test.test_extcall.h() argument after ** must be a mapping, not list + TypeError: Value after ** must be a mapping, not list >>> h(**{'a': 1}, **h) Traceback (most recent call last): ... - TypeError: test.test_extcall.h() argument after ** must be a mapping, not function + TypeError: Value after ** must be a mapping, not function >>> h(**{'a': 1}, **[]) Traceback (most recent call last): ... - TypeError: test.test_extcall.h() argument after ** must be a mapping, not list + TypeError: Value after ** must be a mapping, not list >>> dir(**h) Traceback (most recent call last): ... - TypeError: dir() argument after ** must be a mapping, not function + TypeError: Value after ** must be a mapping, not function >>> nothing(**h) Traceback (most recent call last): ... - TypeError: None argument after ** must be a mapping, \ -not function + TypeError: Value after ** must be a mapping, not function >>> dir(b=1, **{'b': 1}) Traceback (most recent call last): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-08-00-41-46.gh-issue-136327.7AiTb_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-08-00-41-46.gh-issue-136327.7AiTb_.rst new file mode 100644 index 00000000000000..3798e956c95ab6 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-08-00-41-46.gh-issue-136327.7AiTb_.rst @@ -0,0 +1,2 @@ +Errors when calling functions with invalid values after ``*`` and ``**`` now do not +include the function name. Patch by Ilia Solin. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-20-16-42.gh-issue-140517.cqun-K.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-20-16-42.gh-issue-140517.cqun-K.rst new file mode 100644 index 00000000000000..15aaea8ab027e3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-20-16-42.gh-issue-140517.cqun-K.rst @@ -0,0 +1,3 @@ +Fixed a reference leak when iterating over the result of :func:`map` +with ``strict=True`` when the input iterables have different lengths. +Patch by Mikhail Efimov. diff --git a/Misc/NEWS.d/next/Tests/2025-10-23-16-39-49.gh-issue-140482.ZMtyeD.rst b/Misc/NEWS.d/next/Tests/2025-10-23-16-39-49.gh-issue-140482.ZMtyeD.rst new file mode 100644 index 00000000000000..20747ad7f113ec --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2025-10-23-16-39-49.gh-issue-140482.ZMtyeD.rst @@ -0,0 +1 @@ +Preserve and restore the state of ``stty echo`` as part of the test environment. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 64249177eec5f2..f6fadd936bb8ff 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1501,34 +1501,27 @@ map_next(PyObject *self) } Py_ssize_t nargs = 0; - for (i=0; i < niters; i++) { + for (i = 0; i < niters; i++) { PyObject *it = PyTuple_GET_ITEM(lz->iters, i); PyObject *val = Py_TYPE(it)->tp_iternext(it); if (val == NULL) { if (lz->strict) { goto check; } - goto exit; + goto exit_no_result; } stack[i] = val; nargs++; } result = _PyObject_VectorcallTstate(tstate, lz->func, stack, nargs, NULL); + goto exit; -exit: - for (i=0; i < nargs; i++) { - Py_DECREF(stack[i]); - } - if (stack != small_stack) { - PyMem_Free(stack); - } - return result; check: if (PyErr_Occurred()) { if (!PyErr_ExceptionMatches(PyExc_StopIteration)) { // next() on argument i raised an exception (not StopIteration) - return NULL; + goto exit_no_result; } PyErr_Clear(); } @@ -1536,9 +1529,10 @@ map_next(PyObject *self) // ValueError: map() argument 2 is shorter than argument 1 // ValueError: map() argument 3 is shorter than arguments 1-2 const char* plural = i == 1 ? " " : "s 1-"; - return PyErr_Format(PyExc_ValueError, - "map() argument %d is shorter than argument%s%d", - i + 1, plural, i); + PyErr_Format(PyExc_ValueError, + "map() argument %d is shorter than argument%s%d", + i + 1, plural, i); + goto exit_no_result; } for (i = 1; i < niters; i++) { PyObject *it = PyTuple_GET_ITEM(lz->iters, i); @@ -1546,21 +1540,33 @@ map_next(PyObject *self) if (val) { Py_DECREF(val); const char* plural = i == 1 ? " " : "s 1-"; - return PyErr_Format(PyExc_ValueError, - "map() argument %d is longer than argument%s%d", - i + 1, plural, i); + PyErr_Format(PyExc_ValueError, + "map() argument %d is longer than argument%s%d", + i + 1, plural, i); + goto exit_no_result; } if (PyErr_Occurred()) { if (!PyErr_ExceptionMatches(PyExc_StopIteration)) { // next() on argument i raised an exception (not StopIteration) - return NULL; + goto exit_no_result; } PyErr_Clear(); } // Argument i is exhausted. So far so good... } // All arguments are exhausted. Success! - goto exit; + +exit_no_result: + assert(result == NULL); + +exit: + for (i = 0; i < nargs; i++) { + Py_DECREF(stack[i]); + } + if (stack != small_stack) { + PyMem_Free(stack); + } + return result; } static PyObject * diff --git a/Python/ceval.c b/Python/ceval.c index defd084db9a4bb..7ec5abf5a76bd9 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3272,17 +3272,9 @@ int _Py_Check_ArgsIterable(PyThreadState *tstate, PyObject *func, PyObject *args) { if (Py_TYPE(args)->tp_iter == NULL && !PySequence_Check(args)) { - /* _Py_Check_ArgsIterable() may be called with a live exception: - * clear it to prevent calling _PyObject_FunctionStr() with an - * exception set. */ - _PyErr_Clear(tstate); - PyObject *funcstr = _PyObject_FunctionStr(func); - if (funcstr != NULL) { - _PyErr_Format(tstate, PyExc_TypeError, - "%U argument after * must be an iterable, not %.200s", - funcstr, Py_TYPE(args)->tp_name); - Py_DECREF(funcstr); - } + _PyErr_Format(tstate, PyExc_TypeError, + "Value after * must be an iterable, not %.200s", + Py_TYPE(args)->tp_name); return -1; } return 0; @@ -3298,15 +3290,10 @@ _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwarg * is not a mapping. */ if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) { - _PyErr_Clear(tstate); - PyObject *funcstr = _PyObject_FunctionStr(func); - if (funcstr != NULL) { - _PyErr_Format( - tstate, PyExc_TypeError, - "%U argument after ** must be a mapping, not %.200s", - funcstr, Py_TYPE(kwargs)->tp_name); - Py_DECREF(funcstr); - } + _PyErr_Format( + tstate, PyExc_TypeError, + "Value after ** must be a mapping, not %.200s", + Py_TYPE(kwargs)->tp_name); } else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { PyObject *exc = _PyErr_GetRaisedException(tstate);