From fac7153d17f0671b404c303c1be5a9630f0c36f1 Mon Sep 17 00:00:00 2001 From: Anuj Bharambe Date: Sun, 3 May 2026 15:27:52 +0530 Subject: [PATCH 1/3] gh-149180: Avoid double checking tp_as_number, tp_as_sequence, tp_as_mapping During PyType_Ready, assign NULL tp_as_number, tp_as_sequence, and tp_as_mapping pointers to shared static empty (all-zero) structs. After initialization these three fields are guaranteed non-NULL for all ready types. Also fix inherit_slots() basebase NULL dereference and PyNumber_InPlaceMultiply else-if fallthrough exposed by the change. --- Objects/abstract.c | 2 +- Objects/typeobject.c | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index 0bbf60840a3..a52d6fd347d 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1343,7 +1343,7 @@ PyNumber_InPlaceMultiply(PyObject *v, PyObject *w) if (f != NULL) return sequence_repeat(f, v, w); } - else if (mw != NULL) { + if (mw != NULL) { /* Note that the right hand operand should not be * mutated in this case so sq_inplace_repeat is not * used. */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fb3c7101410..6387fab9b18 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -896,6 +896,10 @@ _PyType_CheckConsistency(PyTypeObject *type) CHECK(PyDict_Contains(lookup_tp_dict(type), &_Py_ID(__new__)) == 0); } + CHECK(type->tp_as_number != NULL); + CHECK(type->tp_as_sequence != NULL); + CHECK(type->tp_as_mapping != NULL); + return 1; #undef CHECK } @@ -8718,7 +8722,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) if (type->tp_as_number != NULL && base->tp_as_number != NULL) { basebase = base->tp_base; - if (basebase->tp_as_number == NULL) + if (basebase == NULL || basebase->tp_as_number == NULL) basebase = NULL; COPYNUM(nb_add); COPYNUM(nb_subtract); @@ -8768,7 +8772,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) if (type->tp_as_sequence != NULL && base->tp_as_sequence != NULL) { basebase = base->tp_base; - if (basebase->tp_as_sequence == NULL) + if (basebase == NULL || basebase->tp_as_sequence == NULL) basebase = NULL; COPYSEQ(sq_length); COPYSEQ(sq_concat); @@ -8782,7 +8786,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) if (type->tp_as_mapping != NULL && base->tp_as_mapping != NULL) { basebase = base->tp_base; - if (basebase->tp_as_mapping == NULL) + if (basebase == NULL || basebase->tp_as_mapping == NULL) basebase = NULL; COPYMAP(mp_length); COPYMAP(mp_subscript); @@ -9155,6 +9159,10 @@ type_ready_mro(PyTypeObject *type, int initial) } +static PyNumberMethods _Py_empty_number_methods = {0}; +static PySequenceMethods _Py_empty_sequence_methods = {0}; +static PyMappingMethods _Py_empty_mapping_methods = {0}; + // For static types, inherit tp_as_xxx structures from the base class // if it's NULL. // @@ -9216,6 +9224,19 @@ type_ready_inherit(PyTypeObject *type) type_ready_inherit_as_structs(type, base); } + // Ensure tp_as_number, tp_as_sequence, and tp_as_mapping are never + // NULL after PyType_Ready. Types that don't provide their own struct + // share a common empty one. + if (type->tp_as_number == NULL) { + type->tp_as_number = &_Py_empty_number_methods; + } + if (type->tp_as_sequence == NULL) { + type->tp_as_sequence = &_Py_empty_sequence_methods; + } + if (type->tp_as_mapping == NULL) { + type->tp_as_mapping = &_Py_empty_mapping_methods; + } + /* Sanity check for tp_free. */ if (_PyType_IS_GC(type) && (type->tp_flags & Py_TPFLAGS_BASETYPE) && (type->tp_free == NULL || type->tp_free == PyObject_Free)) From d903177225ec4eef429d58d4ecb65b922442c256 Mon Sep 17 00:00:00 2001 From: Anuj Bharambe Date: Sun, 3 May 2026 15:44:40 +0530 Subject: [PATCH 2/3] Add NEWS blurb for gh-149180 --- .../2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst new file mode 100644 index 00000000000..cb485d3edc0 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst @@ -0,0 +1,3 @@ +During :func:`PyType_Ready`, ``tp_as_number``, ``tp_as_sequence``, and +``tp_as_mapping`` are now guaranteed to be non-NULL for all ready types. +Types that don't define their own struct share a common empty one. From 3f51f2dbb4b6e0f4c5ea85dd4db95516cb8a0c76 Mon Sep 17 00:00:00 2001 From: Anuj Bharambe Date: Sun, 3 May 2026 15:53:48 +0530 Subject: [PATCH 3/3] Fix NEWS blurb: use :c:func: for C API function --- .../2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst index cb485d3edc0..cdb2ec13025 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst @@ -1,3 +1,3 @@ -During :func:`PyType_Ready`, ``tp_as_number``, ``tp_as_sequence``, and +During :c:func:`PyType_Ready`, ``tp_as_number``, ``tp_as_sequence``, and ``tp_as_mapping`` are now guaranteed to be non-NULL for all ready types. Types that don't define their own struct share a common empty one.