From d533faffe0329d8e2ae672394069be6f0aa44a1c 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, 10 Jan 2026 13:11:42 +0100 Subject: [PATCH 1/2] gh-143636: fix a crash when calling ``__replace__`` on invalid `SimpleNamespace` instances --- Lib/test/test_types.py | 13 +++++++++++++ .../2026-01-10-12-59-58.gh-issue-143636.dzr26e.rst | 2 ++ Objects/namespaceobject.c | 9 +++++++++ 3 files changed, 24 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-01-10-12-59-58.gh-issue-143636.dzr26e.rst diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 4595e7e5d3edc1..0c550571a006d3 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -2167,6 +2167,19 @@ class Spam(types.SimpleNamespace): self.assertIs(type(spam2), Spam) self.assertEqual(vars(spam2), {'ham': 5, 'eggs': 9}) + def test_replace_invalid_subtype(self): + # See https://github.com/python/cpython/issues/143636. + class NS(types.SimpleNamespace): + called = False + def __new__(cls, *args, **kwargs): + if cls.called: + return object() + cls.called = True + return super().__new__(cls) + + err = re.escape("expecting a types.SimpleNamespace object, got object") + self.assertRaisesRegex(TypeError, err, NS().__replace__) + def test_fake_namespace_compare(self): # Issue #24257: Incorrect use of PyObject_IsInstance() caused # SystemError. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-10-12-59-58.gh-issue-143636.dzr26e.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-10-12-59-58.gh-issue-143636.dzr26e.rst new file mode 100644 index 00000000000000..4d5249ffe3a206 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-10-12-59-58.gh-issue-143636.dzr26e.rst @@ -0,0 +1,2 @@ +Fix a crash when calling :class:`SimpleNamespace.__replace__() +` on non-namespace instances. Patch by Bénédikt Tran. diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 201cb8a7df8da1..e55cc5f81b49da 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -13,6 +13,7 @@ typedef struct { } _PyNamespaceObject; #define _PyNamespace_CAST(op) _Py_CAST(_PyNamespaceObject*, (op)) +#define _PyNamespace_Check(op) PyObject_TypeCheck((op), &_PyNamespace_Type) static PyMemberDef namespace_members[] = { @@ -234,6 +235,14 @@ namespace_replace(PyObject *self, PyObject *args, PyObject *kwargs) if (!result) { return NULL; } + if (!_PyNamespace_Check(result)) { + PyErr_Format(PyExc_TypeError, + "expecting a %s object, got %T", + _PyNamespace_Type.tp_name, result); + Py_DECREF(result); + return NULL; + } + if (PyDict_Update(((_PyNamespaceObject*)result)->ns_dict, ((_PyNamespaceObject*)self)->ns_dict) < 0) { From 9027d9e1aa656d588289d380df814e4da09b237a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:41:17 +0100 Subject: [PATCH 2/2] address review feedback --- Lib/test/test_types.py | 12 +++++++----- Objects/namespaceobject.c | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 0c550571a006d3..25135aaf2fe9fb 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -2170,15 +2170,17 @@ class Spam(types.SimpleNamespace): def test_replace_invalid_subtype(self): # See https://github.com/python/cpython/issues/143636. class NS(types.SimpleNamespace): - called = False def __new__(cls, *args, **kwargs): - if cls.called: + if created: return object() - cls.called = True return super().__new__(cls) - err = re.escape("expecting a types.SimpleNamespace object, got object") - self.assertRaisesRegex(TypeError, err, NS().__replace__) + created = False + ns = NS() + created = True + err = re.escape("NS.__new__() must return an instance " + "of a subclass of types.SimpleNamespace") + self.assertRaisesRegex(TypeError, err, copy.replace, ns) def test_fake_namespace_compare(self): # Issue #24257: Incorrect use of PyObject_IsInstance() caused diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index e55cc5f81b49da..42d524edd09424 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -237,8 +237,8 @@ namespace_replace(PyObject *self, PyObject *args, PyObject *kwargs) } if (!_PyNamespace_Check(result)) { PyErr_Format(PyExc_TypeError, - "expecting a %s object, got %T", - _PyNamespace_Type.tp_name, result); + "%T.__new__() must return an instance of a subclass of %s", + self, _PyNamespace_Type.tp_name); Py_DECREF(result); return NULL; }