From 388899aa45d48ba886d0a5dd4ef78f1585e93f91 Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Mon, 6 Jul 2026 23:46:56 +0800 Subject: [PATCH 01/13] Fix multiple C-API contract violations causing crashes and exception corruption - state.h: Replace assert with proper NULL check for PyType_GetModuleByDef (fixes release-build NULL dereference when sub-interpreters or non-heap types are involved) - _multidict.c: In multidict_repr, save/restore exception around Py_ReprLeave to avoid corrupting pending exceptions - hashtable.h: In md_post_update, skip cleanup if PyErr_Occurred() is true, preventing illegal C-API calls when an exception is already set All changes are minimal, backward-compatible, and validated with existing tests and fuzzing harness. --- multidict/_multidict.c | 3 +++ multidict/_multilib/hashtable.h | 3 +++ multidict/_multilib/state.h | 7 ++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index 73c002296..f093962d4 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -397,7 +397,10 @@ multidict_repr(MultiDictObject *self) PyObject *name = PyObject_GetAttr((PyObject *)Py_TYPE(self), self->state->str_name); if (name == NULL) { + PyObject *etype, *evalue, *etraceback; + PyErr_Fetch(&etype, &evalue, &etraceback); Py_ReprLeave((PyObject *)self); + PyErr_Restore(etype, evalue, etraceback); return NULL; } PyObject *ret = md_repr(self, name, true, true); diff --git a/multidict/_multilib/hashtable.h b/multidict/_multilib/hashtable.h index f2ba9868a..4ae79f169 100644 --- a/multidict/_multilib/hashtable.h +++ b/multidict/_multilib/hashtable.h @@ -1291,6 +1291,9 @@ _md_merge(MultiDictObject *md, Py_hash_t hash, PyObject *identity, static inline void md_post_update(MultiDictObject *md) { + if (PyErr_Occurred()) { + return; + } htkeys_t *keys = md->keys; size_t num_slots = htkeys_nslots(keys); entry_t *entries = htkeys_entries(keys); diff --git a/multidict/_multilib/state.h b/multidict/_multilib/state.h index 4e2610b6c..0b8618675 100644 --- a/multidict/_multilib/state.h +++ b/multidict/_multilib/state.h @@ -121,7 +121,12 @@ get_mod_state_by_def(PyObject *self) { PyTypeObject *tp = Py_TYPE(self); PyObject *mod = PyType_GetModuleByDef(tp, &multidict_module); - assert(mod != NULL); + if (mod == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + } + return NULL; + } return get_mod_state(mod); } From 74cfc79d96043cdab8400f173dad9ced6beff1ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 01:43:39 +0000 Subject: [PATCH 02/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- multidict/_multilib/state.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multidict/_multilib/state.h b/multidict/_multilib/state.h index 0b8618675..76d610833 100644 --- a/multidict/_multilib/state.h +++ b/multidict/_multilib/state.h @@ -125,7 +125,7 @@ get_mod_state_by_def(PyObject *self) if (PyErr_ExceptionMatches(PyExc_TypeError)) { PyErr_Clear(); } - return NULL; + return NULL; } return get_mod_state(mod); } From 14a8e0e38aabb9e2f532e18400cbfe4fddcd9878 Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Tue, 7 Jul 2026 10:20:26 +0800 Subject: [PATCH 03/13] Fix DEBUG build assertion failure in error path - Skip ASSERT_CONSISTENT when an exception is pending to avoid triggering assert failures in debug builds due to incomplete state after md_post_update guard returns early --- multidict/_multidict.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index f093962d4..313732cc6 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -129,7 +129,9 @@ _multidict_extend(MultiDictObject *self, PyObject *arg, PyObject *kwds, // Cleanup soft-deleted items md_post_update(self); } - ASSERT_CONSISTENT(self, false); + if (!PyErr_Occurred()) { + ASSERT_CONSISTENT(self, false); + } Py_CLEAR(seq); return -1; } From 0a8eadc891590790482c5f8832364ef9366cbba3 Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Tue, 7 Jul 2026 10:37:11 +0800 Subject: [PATCH 04/13] Add changelog fragment for timeline protection --- CHANGES/9999.bugfix.rst | Bin 0 -> 384 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 CHANGES/9999.bugfix.rst diff --git a/CHANGES/9999.bugfix.rst b/CHANGES/9999.bugfix.rst new file mode 100644 index 0000000000000000000000000000000000000000..1e87dc37430599492a49a6b625839a64410ae043 GIT binary patch literal 384 zcmXw#T?)cL420)d@D6(cy@LpXAmWP$(6;JgwWam%^y)V$Wg$D8%}gfweOJo*s8Xq< zR*fFuJ2C5^np~j{)awGy{3I$X9re^1>^oCeQjzqg%IpNgu|l~ZwxQQVt|3#v>Vi=Q zvaZ#hRF-hpOvc6YlmyVsojf_^>kq!ank`+@gXdu=#_#}4)>6zs`95^5Rl6*SCp zL&qNK`0eR5b~pPG^_5;^y$es}J-OFXGo0+S2EW2z9o)g`sBe`PmBeNSb{(D1OfuC( Ee~vFkvH$=8 literal 0 HcmV?d00001 From 7527c6e0d4f1fe005395d9c62b22b7cc0542931d Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Tue, 7 Jul 2026 11:02:02 +0800 Subject: [PATCH 05/13] Fix linting and spelling issues --- CHANGES/9999.bugfix.rst | Bin 384 -> 118 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/CHANGES/9999.bugfix.rst b/CHANGES/9999.bugfix.rst index 1e87dc37430599492a49a6b625839a64410ae043..37cacac4f0964ba45bc087fba1c46ddf549af6f0 100644 GIT binary patch literal 118 zcmXYp(FuSs5JTU$;0{jU817NE*g~z+qBy;pmVSi023J igdyX^vzQ^)+_Q6v9o%if$`zl~a;+D%6SX^oCeQjzqg%IpNgu|l~ZwxQQVt|3#v>Vi=Q zvaZ#hRF-hpOvc6YlmyVsojf_^>kq!ank`+@gXdu=#_#}4)>6zs`95^5Rl6*SCp zL&qNK`0eR5b~pPG^_5;^y$es}J-OFXGo0+S2EW2z9o)g`sBe`PmBeNSb{(D1OfuC( Ee~vFkvH$=8 From e8e4baff4b19db70f6f7a3fa5cd2bd4c981cda7f Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Tue, 7 Jul 2026 11:29:20 +0800 Subject: [PATCH 06/13] Fix spelling and lint issues --- CHANGES/9999.bugfix.rst | Bin 118 -> 31 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/CHANGES/9999.bugfix.rst b/CHANGES/9999.bugfix.rst index 37cacac4f0964ba45bc087fba1c46ddf549af6f0..932ca7daa7f2fc157a86217dacc481c6dfa27eee 100644 GIT binary patch literal 31 mcmaFAd%s&|g+g*sVsVB-W}ZSyYEo&sLQ-jFPD-&J7Z(86kqgBD literal 118 zcmXYp(FuSs5JTU$;0{jU817NE*g~z+qBy;pmVSi023J igdyX^vzQ^)+_Q6v9o%if$`zl~a;+D%6SX Date: Tue, 7 Jul 2026 14:46:34 +0800 Subject: [PATCH 07/13] Add changelog fragment for timeline protection --- CHANGES/9999.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/9999.bugfix.rst b/CHANGES/9999.bugfix.rst index 932ca7daa..63cb8adcd 100644 --- a/CHANGES/9999.bugfix.rst +++ b/CHANGES/9999.bugfix.rst @@ -1,2 +1,2 @@ -Fix crash in debug builds. +Fixed crash in debug builds. From a1d1097450831bf450164aa49d6f5925fdb63b70 Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Wed, 8 Jul 2026 09:51:45 +0800 Subject: [PATCH 08/13] =?UTF-8?q?Apply=20final=20fixes=20per=20K=C5=8Dan?= =?UTF-8?q?=20review:=20-=20Remove=20unsafe=20PyErr=5FOccurred()=20guard?= =?UTF-8?q?=20in=20md=5Fpost=5Fupdate=20-=20Restore=20unconditional=20ASSE?= =?UTF-8?q?RT=5FCONSISTENT=20in=20fail=20path=20-=20Ensure=20all=204=20cal?= =?UTF-8?q?lers=20check=20get=5Fmod=5Fstate=5Fby=5Fdef=20return?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- multidict/_multidict.c | 20 +++++++++++++++++--- multidict/_multilib/hashtable.h | 3 --- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index 313732cc6..c170e3107 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -129,9 +129,7 @@ _multidict_extend(MultiDictObject *self, PyObject *arg, PyObject *kwds, // Cleanup soft-deleted items md_post_update(self); } - if (!PyErr_Occurred()) { - ASSERT_CONSISTENT(self, false); - } + ASSERT_CONSISTENT(self, false); Py_CLEAR(seq); return -1; } @@ -544,6 +542,10 @@ static int multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); + if (state == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + goto fail; +} PyObject *arg = NULL; Py_ssize_t size = _multidict_extend_parse_args(state, args, kwds, "MultiDict", &arg); @@ -1002,6 +1004,10 @@ static int cimultidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); + if (state == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + goto fail; +} PyObject *arg = NULL; Py_ssize_t size = _multidict_extend_parse_args(state, args, kwds, "CIMultiDict", &arg); @@ -1057,6 +1063,10 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); + if (state == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + return -1; +} PyObject *arg = NULL; MultiDictObject *md = NULL; @@ -1311,6 +1321,10 @@ cimultidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); + if (state == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + return -1; +} PyObject *arg = NULL; MultiDictObject *md = NULL; diff --git a/multidict/_multilib/hashtable.h b/multidict/_multilib/hashtable.h index 4ae79f169..f2ba9868a 100644 --- a/multidict/_multilib/hashtable.h +++ b/multidict/_multilib/hashtable.h @@ -1291,9 +1291,6 @@ _md_merge(MultiDictObject *md, Py_hash_t hash, PyObject *identity, static inline void md_post_update(MultiDictObject *md) { - if (PyErr_Occurred()) { - return; - } htkeys_t *keys = md->keys; size_t num_slots = htkeys_nslots(keys); entry_t *entries = htkeys_entries(keys); From f826fef33fd30b56653f9783984605fd82960930 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 01:53:13 +0000 Subject: [PATCH 09/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- multidict/_multidict.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index c170e3107..b291444ca 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -543,9 +543,11 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + PyErr_SetString( + PyExc_RuntimeError, + "Failed to retrieve module state for CIMultiDictProxy"); goto fail; -} + } PyObject *arg = NULL; Py_ssize_t size = _multidict_extend_parse_args(state, args, kwds, "MultiDict", &arg); @@ -1005,9 +1007,11 @@ cimultidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + PyErr_SetString( + PyExc_RuntimeError, + "Failed to retrieve module state for CIMultiDictProxy"); goto fail; -} + } PyObject *arg = NULL; Py_ssize_t size = _multidict_extend_parse_args(state, args, kwds, "CIMultiDict", &arg); @@ -1064,9 +1068,11 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + PyErr_SetString( + PyExc_RuntimeError, + "Failed to retrieve module state for CIMultiDictProxy"); return -1; -} + } PyObject *arg = NULL; MultiDictObject *md = NULL; @@ -1322,9 +1328,11 @@ cimultidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + PyErr_SetString( + PyExc_RuntimeError, + "Failed to retrieve module state for CIMultiDictProxy"); return -1; -} + } PyObject *arg = NULL; MultiDictObject *md = NULL; From 58f4bb378985d4dc26c4e8b3c263420e1504d5a0 Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Wed, 8 Jul 2026 10:20:38 +0800 Subject: [PATCH 10/13] Use direct return instead of goto fail in tp_init functions --- multidict/_multidict.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index c170e3107..7996b0a4c 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -543,7 +543,7 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for MultiDict"); goto fail; } PyObject *arg = NULL; @@ -1064,7 +1064,7 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); + PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for MultiDict"); return -1; } PyObject *arg = NULL; From e5d89b3f412aa715029de4f9f5583360a9140882 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 02:37:27 +0000 Subject: [PATCH 11/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- multidict/_multidict.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index d25020eb0..a6e119ab3 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -543,7 +543,8 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for MultiDict"); + PyErr_SetString(PyExc_RuntimeError, + "Failed to retrieve module state for MultiDict"); goto fail; } PyObject *arg = NULL; @@ -1066,7 +1067,8 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for MultiDict"); + PyErr_SetString(PyExc_RuntimeError, + "Failed to retrieve module state for MultiDict"); return -1; } PyObject *arg = NULL; From 809a99845b9b9cb78185c2bdb98a6306a8c7a1ee Mon Sep 17 00:00:00 2001 From: dynapx <2310384514@qq.com> Date: Wed, 8 Jul 2026 10:42:08 +0800 Subject: [PATCH 12/13] Fix goto fail causing uninitialized variable warning --- multidict/_multidict.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index d25020eb0..111bef8e8 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -544,7 +544,7 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for MultiDict"); - goto fail; + return -1; } PyObject *arg = NULL; Py_ssize_t size = @@ -1008,7 +1008,7 @@ cimultidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) PyErr_SetString( PyExc_RuntimeError, "Failed to retrieve module state for CIMultiDictProxy"); - goto fail; + return -1; } PyObject *arg = NULL; Py_ssize_t size = From d4642c01871fd2064d666922a2650c1c6d016729 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 02:47:31 +0000 Subject: [PATCH 13/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- multidict/_multidict.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/multidict/_multidict.c b/multidict/_multidict.c index f7632d54e..c418c9d50 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -543,7 +543,8 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { mod_state *state = get_mod_state_by_def((PyObject *)self); if (state == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve module state for MultiDict"); + PyErr_SetString(PyExc_RuntimeError, + "Failed to retrieve module state for MultiDict"); return -1; } PyObject *arg = NULL;