From 56fdcc08dbfc3052f27d581e770bebc4434a9034 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 14:47:07 +0800 Subject: [PATCH 01/14] gh-142783: Fix use-after-free vulnerability in zoneinfo module --- Lib/test/test_zoneinfo/test_zoneinfo.py | 39 +++++++++++++++++++ ...-12-16-14-49-19.gh-issue-142783.VPV1ig.rst | 3 ++ Modules/_zoneinfo.c | 16 ++++---- 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index fad741e477b84a..d4e7e1bc8d3a31 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1551,6 +1551,45 @@ def __eq__(self, other): except CustomError: pass + def test_weak_cache_descriptor_use_after_free(self): + from zoneinfo import ZoneInfo + + class Cache: + def __init__(self): + self.data = {} + + def get(self, key, default=None): + return self.data.get(key, default) + + def setdefault(self, key, default): + return self.data.setdefault(key, default) + + def clear(self, *args, **kwargs): + self.data.clear() + + class BombDescriptor: + def __get__(self, obj, owner): + return Cache() + + class EvilZoneInfo(ZoneInfo): + pass + + EvilZoneInfo._weak_cache = BombDescriptor() + + zone1 = EvilZoneInfo("UTC") + zone2 = EvilZoneInfo("UTC") + + self.assertIsNotNone(zone1) + self.assertIsNotNone(zone2) + self.assertEqual(str(zone1), "UTC") + self.assertEqual(str(zone2), "UTC") + + EvilZoneInfo.clear_cache() + + zone3 = EvilZoneInfo("UTC") + self.assertIsNotNone(zone3) + self.assertEqual(str(zone3), "UTC") + class CZoneInfoCacheTest(ZoneInfoCacheTest): module = c_zoneinfo diff --git a/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst b/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst new file mode 100644 index 00000000000000..1da6662178e8bb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst @@ -0,0 +1,3 @@ +Fix zoneinfo use-after-free with descriptor _weak_cache. Return new +reference from get_weak_cache() instead of borrowed reference to prevent +premature object free. diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index b99be073db5460..e07dfd19efa06d 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -292,16 +292,11 @@ static PyObject * get_weak_cache(zoneinfo_state *state, PyTypeObject *type) { if (type == state->ZoneInfoType) { + Py_INCREF(state->ZONEINFO_WEAK_CACHE); return state->ZONEINFO_WEAK_CACHE; } else { - PyObject *cache = - PyObject_GetAttrString((PyObject *)type, "_weak_cache"); - // We are assuming that the type lives at least as long as the function - // that calls get_weak_cache, and that it holds a reference to the - // cache, so we'll return a "borrowed reference". - Py_XDECREF(cache); - return cache; + return PyObject_GetAttrString((PyObject *)type, "_weak_cache"); } } @@ -328,6 +323,7 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) PyObject *weak_cache = get_weak_cache(state, type); instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None); if (instance == NULL) { + Py_DECREF(weak_cache); return NULL; } @@ -335,6 +331,7 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) Py_DECREF(instance); PyObject *tmp = zoneinfo_new_instance(state, type, key); if (tmp == NULL) { + Py_DECREF(weak_cache); return NULL; } @@ -342,12 +339,14 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key) PyObject_CallMethod(weak_cache, "setdefault", "OO", key, tmp); Py_DECREF(tmp); if (instance == NULL) { + Py_DECREF(weak_cache); return NULL; } ((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE; } update_strong_cache(state, type, key, instance); + Py_DECREF(weak_cache); return instance; } @@ -510,12 +509,14 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, PyObject *item = NULL; PyObject *pop = PyUnicode_FromString("pop"); if (pop == NULL) { + Py_DECREF(weak_cache); return NULL; } PyObject *iter = PyObject_GetIter(only_keys); if (iter == NULL) { Py_DECREF(pop); + Py_DECREF(weak_cache); return NULL; } @@ -540,6 +541,7 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls, Py_DECREF(pop); } + Py_DECREF(weak_cache); if (PyErr_Occurred()) { return NULL; } From 9445e0a6809ccb7651b0e0e7713237c6501939f2 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 15:31:44 +0800 Subject: [PATCH 02/14] fix: fix windows missing ModuleNotFoundError: No module named 'tzdata.zoneinfo'; 'tzdata' is not a package --- Lib/test/test_zoneinfo/test_zoneinfo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index d4e7e1bc8d3a31..2cc3a85d9e3dbf 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1551,6 +1551,10 @@ def __eq__(self, other): except CustomError: pass + @unittest.skipUnless( + lambda: any(os.path.exists(os.path.join(p, 'UTC')) for p in ZoneInfo.tzpath), + "timezone data not available" + ) def test_weak_cache_descriptor_use_after_free(self): from zoneinfo import ZoneInfo From 25ec6c19bc560e109d4c3a426d2565f4b52f6aa3 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 16:13:05 +0800 Subject: [PATCH 03/14] fix: fix windows ci failed --- Lib/test/test_zoneinfo/test_zoneinfo.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 2cc3a85d9e3dbf..8f48c195140de5 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1580,19 +1580,22 @@ class EvilZoneInfo(ZoneInfo): EvilZoneInfo._weak_cache = BombDescriptor() - zone1 = EvilZoneInfo("UTC") - zone2 = EvilZoneInfo("UTC") + try: + zone1 = EvilZoneInfo("UTC") - self.assertIsNotNone(zone1) - self.assertIsNotNone(zone2) - self.assertEqual(str(zone1), "UTC") - self.assertEqual(str(zone2), "UTC") + self.assertIsNotNone(zone1) + self.assertEqual(str(zone1), "UTC") + except ModuleNotFoundError: + pass EvilZoneInfo.clear_cache() - zone3 = EvilZoneInfo("UTC") - self.assertIsNotNone(zone3) - self.assertEqual(str(zone3), "UTC") + try: + zone2 = EvilZoneInfo("UTC") + self.assertIsNotNone(zone2) + self.assertEqual(str(zone2), "UTC") + except ModuleNotFoundError: + pass class CZoneInfoCacheTest(ZoneInfoCacheTest): From 857fcd99dfd500e5a962e09db821bc748de11cd2 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 16:32:32 +0800 Subject: [PATCH 04/14] chore: update catch exception --- Lib/test/test_zoneinfo/test_zoneinfo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 8f48c195140de5..54ede2c3a01c49 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1585,7 +1585,7 @@ class EvilZoneInfo(ZoneInfo): self.assertIsNotNone(zone1) self.assertEqual(str(zone1), "UTC") - except ModuleNotFoundError: + except self.module.ZoneInfoNotFoundError: pass EvilZoneInfo.clear_cache() @@ -1594,7 +1594,7 @@ class EvilZoneInfo(ZoneInfo): zone2 = EvilZoneInfo("UTC") self.assertIsNotNone(zone2) self.assertEqual(str(zone2), "UTC") - except ModuleNotFoundError: + except self.module.ZoneInfoNotFoundError: pass From daec74f4276290af926099ef9dd085f9272c349f Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 16:47:34 +0800 Subject: [PATCH 05/14] fix: in windows ci skip test test_weak_cache_descriptor_use_after_free --- Lib/test/test_zoneinfo/test_zoneinfo.py | 28 +++++++++++-------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 54ede2c3a01c49..a58fe97430fecf 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -14,6 +14,7 @@ import struct import tempfile import unittest +import zoneinfo from datetime import date, datetime, time, timedelta, timezone from functools import cached_property @@ -21,6 +22,7 @@ from test.support.os_helper import EnvironmentVarGuard, FakePath from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import TZPATH_TEST_LOCK, ZoneInfoTestBase +from test.support.hypothesis_helper import hypothesis from test.support.import_helper import import_module, CleanImport from test.support.script_helper import assert_python_ok @@ -1551,13 +1553,13 @@ def __eq__(self, other): except CustomError: pass - @unittest.skipUnless( - lambda: any(os.path.exists(os.path.join(p, 'UTC')) for p in ZoneInfo.tzpath), - "timezone data not available" - ) def test_weak_cache_descriptor_use_after_free(self): from zoneinfo import ZoneInfo + available_zones = sorted(zoneinfo.available_timezones()) + if "UTC" not in available_zones: + raise unittest.SkipTest("No time zone data available") + class Cache: def __init__(self): self.data = {} @@ -1580,22 +1582,16 @@ class EvilZoneInfo(ZoneInfo): EvilZoneInfo._weak_cache = BombDescriptor() - try: - zone1 = EvilZoneInfo("UTC") + zone1 = EvilZoneInfo("UTC") - self.assertIsNotNone(zone1) - self.assertEqual(str(zone1), "UTC") - except self.module.ZoneInfoNotFoundError: - pass + self.assertIsNotNone(zone1) + self.assertEqual(str(zone1), "UTC") EvilZoneInfo.clear_cache() - try: - zone2 = EvilZoneInfo("UTC") - self.assertIsNotNone(zone2) - self.assertEqual(str(zone2), "UTC") - except self.module.ZoneInfoNotFoundError: - pass + zone2 = EvilZoneInfo("UTC") + self.assertIsNotNone(zone2) + self.assertEqual(str(zone2), "UTC") class CZoneInfoCacheTest(ZoneInfoCacheTest): From 716b0c524da1612bdd0d9bf39ffa7efaf909ca85 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 16:49:54 +0800 Subject: [PATCH 06/14] chore: remove unused import --- Lib/test/test_zoneinfo/test_zoneinfo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index a58fe97430fecf..adac539d171225 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -22,7 +22,6 @@ from test.support.os_helper import EnvironmentVarGuard, FakePath from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import TZPATH_TEST_LOCK, ZoneInfoTestBase -from test.support.hypothesis_helper import hypothesis from test.support.import_helper import import_module, CleanImport from test.support.script_helper import assert_python_ok From abcd1c4cab5364f173c8d41d0cc68ccac414ac30 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 20:04:54 +0800 Subject: [PATCH 07/14] chore: resolve comment --- Lib/test/test_zoneinfo/test_zoneinfo.py | 15 +-------------- ...2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst | 4 +--- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index adac539d171225..96aa66ee811601 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1559,22 +1559,9 @@ def test_weak_cache_descriptor_use_after_free(self): if "UTC" not in available_zones: raise unittest.SkipTest("No time zone data available") - class Cache: - def __init__(self): - self.data = {} - - def get(self, key, default=None): - return self.data.get(key, default) - - def setdefault(self, key, default): - return self.data.setdefault(key, default) - - def clear(self, *args, **kwargs): - self.data.clear() - class BombDescriptor: def __get__(self, obj, owner): - return Cache() + return dict() class EvilZoneInfo(ZoneInfo): pass diff --git a/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst b/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst index 1da6662178e8bb..0d4ac7a80a0209 100644 --- a/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst +++ b/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst @@ -1,3 +1 @@ -Fix zoneinfo use-after-free with descriptor _weak_cache. Return new -reference from get_weak_cache() instead of borrowed reference to prevent -premature object free. +Fix zoneinfo use-after-free with descriptor _weak_cache. a descriptor as _weak_cache could cause crashes during object creation. The fix ensures proper reference counting for descriptor-provided objects. \ No newline at end of file From b91766e11ca4fcb08de35bed41cf5c6dbd00121e Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 20:09:10 +0800 Subject: [PATCH 08/14] fix: fix lint error --- .../next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst b/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst index 0d4ac7a80a0209..f014771ae9a146 100644 --- a/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst +++ b/Misc/NEWS.d/next/Library/2025-12-16-14-49-19.gh-issue-142783.VPV1ig.rst @@ -1 +1 @@ -Fix zoneinfo use-after-free with descriptor _weak_cache. a descriptor as _weak_cache could cause crashes during object creation. The fix ensures proper reference counting for descriptor-provided objects. \ No newline at end of file +Fix zoneinfo use-after-free with descriptor _weak_cache. a descriptor as _weak_cache could cause crashes during object creation. The fix ensures proper reference counting for descriptor-provided objects. From 765d11e27672de0d690dfbe5b5d3eb08d3787c84 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 22:27:07 +0800 Subject: [PATCH 09/14] chore: change dict to {} --- Lib/test/test_zoneinfo/test_zoneinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 96aa66ee811601..424221967185c7 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1561,7 +1561,7 @@ def test_weak_cache_descriptor_use_after_free(self): class BombDescriptor: def __get__(self, obj, owner): - return dict() + return {} class EvilZoneInfo(ZoneInfo): pass From ca1e690da1c83b6f8f8c0f399f69fc95a65fa355 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 23:01:30 +0800 Subject: [PATCH 10/14] chore: using America/Los_Angeles --- Lib/test/test_zoneinfo/test_zoneinfo.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 424221967185c7..41fb8112280cfa 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1554,11 +1554,6 @@ def __eq__(self, other): def test_weak_cache_descriptor_use_after_free(self): from zoneinfo import ZoneInfo - - available_zones = sorted(zoneinfo.available_timezones()) - if "UTC" not in available_zones: - raise unittest.SkipTest("No time zone data available") - class BombDescriptor: def __get__(self, obj, owner): return {} @@ -1568,16 +1563,16 @@ class EvilZoneInfo(ZoneInfo): EvilZoneInfo._weak_cache = BombDescriptor() - zone1 = EvilZoneInfo("UTC") + zone1 = EvilZoneInfo("America/Los_Angeles") self.assertIsNotNone(zone1) - self.assertEqual(str(zone1), "UTC") + self.assertEqual(str(zone1), "America/Los_Angeles") EvilZoneInfo.clear_cache() - zone2 = EvilZoneInfo("UTC") + zone2 = EvilZoneInfo("America/Los_Angeles") self.assertIsNotNone(zone2) - self.assertEqual(str(zone2), "UTC") + self.assertEqual(str(zone2), "America/Los_Angeles") class CZoneInfoCacheTest(ZoneInfoCacheTest): From 911958d72f0e9060aab789d088b50f05d80dbd5d Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 16 Dec 2025 23:03:42 +0800 Subject: [PATCH 11/14] fix: fix pre commit --- Lib/test/test_zoneinfo/test_zoneinfo.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 41fb8112280cfa..c45fad2067a8a6 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -14,9 +14,9 @@ import struct import tempfile import unittest -import zoneinfo from datetime import date, datetime, time, timedelta, timezone from functools import cached_property +from zoneinfo import ZoneInfo from test.support import MISSING_C_DOCSTRINGS from test.support.os_helper import EnvironmentVarGuard, FakePath @@ -1553,7 +1553,6 @@ def __eq__(self, other): pass def test_weak_cache_descriptor_use_after_free(self): - from zoneinfo import ZoneInfo class BombDescriptor: def __get__(self, obj, owner): return {} From e39953b4393b43fa70d136cffffe5dc2e1906d47 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 16 Dec 2025 17:37:51 +0200 Subject: [PATCH 12/14] Update Lib/test/test_zoneinfo/test_zoneinfo.py --- Lib/test/test_zoneinfo/test_zoneinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index c45fad2067a8a6..320461b2698982 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1557,7 +1557,7 @@ class BombDescriptor: def __get__(self, obj, owner): return {} - class EvilZoneInfo(ZoneInfo): + class EvilZoneInfo(self.klass): pass EvilZoneInfo._weak_cache = BombDescriptor() From ed98b5156a965b7ae21a577d8f6327b324d2640e Mon Sep 17 00:00:00 2001 From: fatelei Date: Wed, 17 Dec 2025 09:07:40 +0800 Subject: [PATCH 13/14] fix: fix lint error --- Lib/test/test_zoneinfo/test_zoneinfo.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 320461b2698982..e3904908249e09 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -16,7 +16,6 @@ import unittest from datetime import date, datetime, time, timedelta, timezone from functools import cached_property -from zoneinfo import ZoneInfo from test.support import MISSING_C_DOCSTRINGS from test.support.os_helper import EnvironmentVarGuard, FakePath @@ -1564,13 +1563,11 @@ class EvilZoneInfo(self.klass): zone1 = EvilZoneInfo("America/Los_Angeles") - self.assertIsNotNone(zone1) self.assertEqual(str(zone1), "America/Los_Angeles") EvilZoneInfo.clear_cache() zone2 = EvilZoneInfo("America/Los_Angeles") - self.assertIsNotNone(zone2) self.assertEqual(str(zone2), "America/Los_Angeles") From d264becc34e13618bb065b541c1ff7c221ca50a3 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 17 Dec 2025 10:07:17 +0200 Subject: [PATCH 14/14] Polishing. --- Lib/test/test_zoneinfo/test_zoneinfo.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index e3904908249e09..8f3ca59c9ef5ed 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1559,16 +1559,17 @@ def __get__(self, obj, owner): class EvilZoneInfo(self.klass): pass + # Must be set after the class creation. EvilZoneInfo._weak_cache = BombDescriptor() - zone1 = EvilZoneInfo("America/Los_Angeles") - - self.assertEqual(str(zone1), "America/Los_Angeles") + key = "America/Los_Angeles" + zone1 = EvilZoneInfo(key) + self.assertEqual(str(zone1), key) EvilZoneInfo.clear_cache() - - zone2 = EvilZoneInfo("America/Los_Angeles") - self.assertEqual(str(zone2), "America/Los_Angeles") + zone2 = EvilZoneInfo(key) + self.assertEqual(str(zone2), key) + self.assertIsNot(zone2, zone1) class CZoneInfoCacheTest(ZoneInfoCacheTest):