]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-146075: Prevent crash in `functools.partial()` from malformed `str` subclass ...
authorbkap123 <97006829+bkap123@users.noreply.github.com>
Wed, 18 Mar 2026 12:46:01 +0000 (08:46 -0400)
committerGitHub <noreply@github.com>
Wed, 18 Mar 2026 12:46:01 +0000 (13:46 +0100)
In `partial_vectorcall`, an error returned by `PyDict_Contains` was
considered to be a truthy value. Now, the error is handled
appropriately.

Lib/test/test_functools.py
Misc/NEWS.d/next/Library/2026-03-17-19-30-45.gh-issue-146075.85sCSh.rst [new file with mode: 0644]
Modules/_functoolsmodule.c

index dda42cb33072c3d5a834316a716a2072b73a3913..efa85b564f7cdf806a1b5fccb29290881dd9f6ed 100644 (file)
@@ -565,7 +565,19 @@ class TestPartial:
         g_partial = functools.partial(func, trigger, None, None, None, None, arg=None)
         self.assertEqual(repr(g_partial),"functools.partial(Function(old_function), EvilObject, None, None, None, None, arg=None)")
 
+    def test_str_subclass_error(self):
+        class BadStr(str):
+            def __eq__(self, other):
+                raise RuntimeError
+            def __hash__(self):
+                return str.__hash__(self)
+
+        def f(**kwargs):
+            return kwargs
 
+        p = functools.partial(f, poison="")
+        with self.assertRaises(RuntimeError):
+            result = p(**{BadStr("poison"): "new_value"})
 
 @unittest.skipUnless(c_functools, 'requires the C _functools module')
 class TestPartialC(TestPartial, unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Library/2026-03-17-19-30-45.gh-issue-146075.85sCSh.rst b/Misc/NEWS.d/next/Library/2026-03-17-19-30-45.gh-issue-146075.85sCSh.rst
new file mode 100644 (file)
index 0000000..792ea3a
--- /dev/null
@@ -0,0 +1 @@
+Errors when calling :func:`functools.partial` with a malformed keyword will no longer crash the interpreter.
index 723080ede1d9aed667e0bd351bbe6c3aa873ae9e..576494e846ca0c2179e49e3ae3c86096cd046865 100644 (file)
@@ -457,7 +457,11 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
         for (Py_ssize_t i = 0; i < nkwds; ++i) {
             key = PyTuple_GET_ITEM(kwnames, i);
             val = args[nargs + i];
-            if (PyDict_Contains(pto->kw, key)) {
+            int contains = PyDict_Contains(pto->kw, key);
+            if (contains < 0) {
+                goto error;
+            }
+            else if (contains == 1) {
                 if (pto_kw_merged == NULL) {
                     pto_kw_merged = PyDict_Copy(pto->kw);
                     if (pto_kw_merged == NULL) {