gh-113190: Reenable non-debug interned string cleanup by eduardo-elizondo · Pull Request #113601 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions Doc/c-api/init.rst
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ New Features
which has an ambiguous return value.
(Contributed by Irit Katriel and Erlend Aasland in :gh:`105201`.)

* :c:func:`Py_Finalize` now deletes all interned strings. This
is backwards incompatible to any C-Extension that holds onto an interned
string after a call to :c:func:`Py_Finalize` and is then reused after a
call to :c:func:`Py_Initialize`. Any issues arising from this behavior will
normally result in crashes during the exectuion of the subsequent call to
:c:func:`Py_Initialize` from accessing uninitialized memory. To fix, use
an address sanitizer to identify any use-after-free coming from
an interned string and deallocate it during module shutdown.
(Contribued by Eddie Elizondo in :gh:`113601`.)

Porting to Python 3.14
----------------------

Expand Down
40 changes: 18 additions & 22 deletions Lib/test/_test_embed_structseq.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import sys
import types
import unittest

# Note: This test file can't import `unittest` since the runtime can't
# currently guarantee that it will not leak memory. Doing so will mark
# the test as passing but with reference leaks. This can safely import
# the `unittest` library once there's a strict guarantee of no leaks
# during runtime shutdown.

# bpo-46417: Test that structseq types used by the sys module are still
# valid when Py_Finalize()/Py_Initialize() are called multiple times.
class TestStructSeq:
class TestStructSeq(unittest.TestCase):
# test PyTypeObject members
def _check_structseq(self, obj_type):
def check_structseq(self, obj_type):
# ob_refcnt
assert sys.getrefcount(obj_type) > 1
self.assertGreaterEqual(sys.getrefcount(obj_type), 1)
# tp_base
assert issubclass(obj_type, tuple)
self.assertTrue(issubclass(obj_type, tuple))
# tp_bases
assert obj_type.__bases__ == (tuple,)
self.assertEqual(obj_type.__bases__, (tuple,))
# tp_dict
assert isinstance(obj_type.__dict__, types.MappingProxyType)
self.assertIsInstance(obj_type.__dict__, types.MappingProxyType)
# tp_mro
assert obj_type.__mro__ == (obj_type, tuple, object)
self.assertEqual(obj_type.__mro__, (obj_type, tuple, object))
# tp_name
assert isinstance(type.__name__, str)
self.assertIsInstance(type.__name__, str)
# tp_subclasses
assert obj_type.__subclasses__() == []
self.assertEqual(obj_type.__subclasses__(), [])

def test_sys_attrs(self):
for attr_name in (
Expand All @@ -36,23 +32,23 @@ def test_sys_attrs(self):
'thread_info', # ThreadInfoType
'version_info', # VersionInfoType
):
attr = getattr(sys, attr_name)
self._check_structseq(type(attr))
with self.subTest(attr=attr_name):
attr = getattr(sys, attr_name)
self.check_structseq(type(attr))

def test_sys_funcs(self):
func_names = ['get_asyncgen_hooks'] # AsyncGenHooksType
if hasattr(sys, 'getwindowsversion'):
func_names.append('getwindowsversion') # WindowsVersionType
for func_name in func_names:
func = getattr(sys, func_name)
obj = func()
self._check_structseq(type(obj))
with self.subTest(func=func_name):
func = getattr(sys, func_name)
obj = func()
self.check_structseq(type(obj))


try:
tests = TestStructSeq()
tests.test_sys_attrs()
tests.test_sys_funcs()
unittest.main()
except SystemExit as exc:
if exc.args[0] != 0:
raise
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:c:func:`Py_Finalize` now deletes all interned strings.
15 changes: 1 addition & 14 deletions Objects/unicodeobject.c