From accae924430a42cdb105edb90a2f041752ee350a Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:45:26 +0800 Subject: [PATCH 1/4] Fix error message in method_get for invalid type argument --- Objects/descrobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 5ac4fbd812924c..517d9e9fa9d45d 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -150,7 +150,7 @@ method_get(PyObject *self, PyObject *obj, PyObject *type) } else { PyErr_Format(PyExc_TypeError, "descriptor '%V' needs a type, not '%s', as arg 2", - descr_name((PyDescrObject *)descr), + descr_name((PyDescrObject *)descr), "?", Py_TYPE(type)->tp_name); return NULL; } From 9efb16bdab11d95294743a8f1154cec6af5d62ac Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 31 Mar 2026 02:00:28 +0800 Subject: [PATCH 2/4] Fix crash in method_get for METH_METHOD descriptors with invalid type argument --- Lib/test/test_descr.py | 22 +++++++++++++++++++ ...1-06-35.gh-issue-146615.fix-method-get.rst | 3 +++ 2 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-01-06-35.gh-issue-146615.fix-method-get.rst diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index d6e3719479a214..8fd2b7ac70a0fb 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1803,6 +1803,28 @@ class SubSpam(spam.spamlist): pass spam_cm.__get__(None, list) self.assertEqual(str(cm.exception), expected_errmsg) + def test_method_get_meth_method_invalid_type(self): + # gh-146615: method_get() for METH_METHOD descriptors used to pass + # Py_TYPE(type)->tp_name as the %V fallback instead of the separate + # %s argument, causing a missing argument for %s and a crash. + # Verify the error message is correct when __get__() is called with a + # non-type as the second argument. + # + # METH_METHOD|METH_FASTCALL|METH_KEYWORDS is the only flag combination + # that enters the affected branch in method_get(). + + import xxlimited + + xxo = xxlimited.Xxo() + descr = xxlimited.Xxo.demo + + with self.assertRaises(TypeError) as cm: + descr.__get__(xxo, "not_a_type") + self.assertEqual( + str(cm.exception), + "descriptor 'demo' needs a type, not 'str', as arg 2", + ) + def test_staticmethods(self): # Testing static methods... class C(object): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-01-06-35.gh-issue-146615.fix-method-get.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-01-06-35.gh-issue-146615.fix-method-get.rst new file mode 100644 index 00000000000000..7a205f1d6dda61 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-31-01-06-35.gh-issue-146615.fix-method-get.rst @@ -0,0 +1,3 @@ +Fix a crash in :meth:`~object.__get__` for :c:expr:`METH_METHOD` descriptors +when an invalid (non-type) object is passed as the second argument. +Patch by Steven Sun. From 6799dd847340d5ed0978e1b2c2a3c0b2992897cb Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 31 Mar 2026 02:14:32 +0800 Subject: [PATCH 3/4] Add xxlimited import and skip test if unavailable for method_get METH_METHOD --- Lib/test/test_descr.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 176cd54d75916b..22f0c96c3d1fb9 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -28,6 +28,11 @@ except ImportError: xxsubtype = None +try: + import xxlimited +except ImportError: + xxlimited = None + class OperatorsTest(unittest.TestCase): @@ -1803,6 +1808,8 @@ class SubSpam(spam.spamlist): pass spam_cm.__get__(None, list) self.assertEqual(str(cm.exception), expected_errmsg) + @support.impl_detail("the module 'xxlimited' is internal") + @unittest.skipIf(xxlimited is None, "requires xxlimited module") def test_method_get_meth_method_invalid_type(self): # gh-146615: method_get() for METH_METHOD descriptors used to pass # Py_TYPE(type)->tp_name as the %V fallback instead of the separate @@ -1812,9 +1819,6 @@ def test_method_get_meth_method_invalid_type(self): # # METH_METHOD|METH_FASTCALL|METH_KEYWORDS is the only flag combination # that enters the affected branch in method_get(). - - import xxlimited - xxo = xxlimited.Xxo() descr = xxlimited.Xxo.demo From 52fe847763648742189fb421da8191a36c754f92 Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:54:55 +0800 Subject: [PATCH 4/4] Remove xxlimited import and update test for method_get with io.StringIO --- Lib/test/test_descr.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 22f0c96c3d1fb9..8a8e70214e27ae 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -28,11 +28,6 @@ except ImportError: xxsubtype = None -try: - import xxlimited -except ImportError: - xxlimited = None - class OperatorsTest(unittest.TestCase): @@ -1808,8 +1803,7 @@ class SubSpam(spam.spamlist): pass spam_cm.__get__(None, list) self.assertEqual(str(cm.exception), expected_errmsg) - @support.impl_detail("the module 'xxlimited' is internal") - @unittest.skipIf(xxlimited is None, "requires xxlimited module") + @support.cpython_only def test_method_get_meth_method_invalid_type(self): # gh-146615: method_get() for METH_METHOD descriptors used to pass # Py_TYPE(type)->tp_name as the %V fallback instead of the separate @@ -1819,14 +1813,16 @@ def test_method_get_meth_method_invalid_type(self): # # METH_METHOD|METH_FASTCALL|METH_KEYWORDS is the only flag combination # that enters the affected branch in method_get(). - xxo = xxlimited.Xxo() - descr = xxlimited.Xxo.demo + import io + + obj = io.StringIO() + descr = io.TextIOBase.read with self.assertRaises(TypeError) as cm: - descr.__get__(xxo, "not_a_type") + descr.__get__(obj, "not_a_type") self.assertEqual( str(cm.exception), - "descriptor 'demo' needs a type, not 'str', as arg 2", + "descriptor 'read' needs a type, not 'str', as arg 2", ) def test_staticmethods(self):