@@ -643,6 +643,32 @@ def test_delattr(self):
643643 msg = r"^attribute name must be string, not 'int'$"
644644 self .assertRaisesRegex (TypeError , msg , delattr , sys , 1 )
645645
646+ def test_delattr_reentrant_name_hash_does_not_crash (self ):
647+ code = dedent ("""
648+ import gc
649+
650+ class Victim:
651+ pass
652+
653+ class Evil(str):
654+ def __hash__(self):
655+ old = target.__dict__
656+ target.__dict__ = {}
657+ del old
658+ for _ in range(32):
659+ dict.fromkeys(range(4), 1)
660+ gc.collect()
661+ return hash(str(self))
662+
663+ for _ in range(2000):
664+ target = Victim()
665+ try:
666+ delattr(target, Evil("missing"))
667+ except AttributeError:
668+ pass
669+ """ )
670+ assert_python_ok ("-c" , code )
671+
646672 def test_dir (self ):
647673 # dir(wrong number of arguments)
648674 self .assertRaises (TypeError , dir , 42 , 42 )
@@ -1994,6 +2020,29 @@ def test_setattr(self):
19942020 msg = r"^attribute name must be string, not 'int'$"
19952021 self .assertRaisesRegex (TypeError , msg , setattr , sys , 1 , 'spam' )
19962022
2023+ def test_setattr_reentrant_name_hash_does_not_crash (self ):
2024+ code = dedent ("""
2025+ import gc
2026+
2027+ class Victim:
2028+ pass
2029+
2030+ class Evil(str):
2031+ def __hash__(self):
2032+ old = target.__dict__
2033+ target.__dict__ = {}
2034+ del old
2035+ for _ in range(32):
2036+ dict.fromkeys(range(4), 1)
2037+ gc.collect()
2038+ return hash(str(self))
2039+
2040+ for _ in range(2000):
2041+ target = Victim()
2042+ setattr(target, Evil("name"), 1)
2043+ """ )
2044+ assert_python_ok ("-c" , code )
2045+
19972046 # test_str(): see test_str.py and test_bytes.py for str() tests.
19982047
19992048 def test_sum (self ):
0 commit comments