diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 8f3ca59c9ef5ed..464a8a52cbe926 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1575,6 +1575,37 @@ class EvilZoneInfo(self.klass): class CZoneInfoCacheTest(ZoneInfoCacheTest): module = c_zoneinfo + def test_inconsistent_weak_cache_get(self): + class Cache: + def get(self, key, default=None): + return 1337 + + class ZI(self.klass): + pass + # Must set AFTER class creation to override __init_subclass__ + ZI._weak_cache = Cache() + + with self.assertRaises(TypeError) as te: + ZI("America/Los_Angeles") + # Heap type objects' tp_name should just be the type name, i.e. ZI + self.assertEqual(str(te.exception), "expected ZI, got int") + + def test_inconsistent_weak_cache_setdefault(self): + class Cache: + def get(self, key, default=None): + return default + def setdefault(self, key, value): + return 1337 + + class ZI(self.klass): + pass + # Must set AFTER class creation to override __init_subclass__ + ZI._weak_cache = Cache() + + with self.assertRaises(TypeError) as te: + ZI("America/Los_Angeles") + # Heap type objects' tp_name should just be the type name, i.e. ZI + self.assertEqual(str(te.exception), "expected ZI, got int") class ZoneInfoPickleTest(TzPathUserMixin, ZoneInfoTestBase): module = py_zoneinfo diff --git a/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst new file mode 100644 index 00000000000000..772e05766c5c69 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-18-00-14-16.gh-issue-142781.gcOeYF.rst @@ -0,0 +1,2 @@ +:mod:`zoneinfo`: fix a crash when instantiating :class:`~zoneinfo.ZoneInfo` +objects for which the internal class-level cache is inconsistent. diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index e07dfd19efa06d..6387e533f5c8d0 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -327,6 +327,14 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) return NULL; } + if (instance != Py_None && !PyObject_TypeCheck(instance, type)) { + PyErr_Format(PyExc_TypeError, "expected %s, got %s", + type->tp_name, Py_TYPE(instance)->tp_name); + Py_DECREF(instance); + Py_DECREF(weak_cache); + return NULL; + } + if (instance == Py_None) { Py_DECREF(instance); PyObject *tmp = zoneinfo_new_instance(state, type, key); @@ -342,6 +350,14 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) Py_DECREF(weak_cache); return NULL; } + + if (!PyObject_TypeCheck(instance, type)) { + PyErr_Format(PyExc_TypeError, "expected %s, got %s", + type->tp_name, Py_TYPE(instance)->tp_name); + Py_DECREF(instance); + Py_DECREF(weak_cache); + return NULL; + } ((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE; }