Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 104b9e0

Browse files
committed
fix many custom mro() edge cases and improve code quality (#22735)
Patch by Eldar Abusalimov.
1 parent 9125fe2 commit 104b9e0

4 files changed

Lines changed: 622 additions & 254 deletions

File tree

Lib/test/test_descr.py

Lines changed: 226 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4996,11 +4996,236 @@ class B(A):
49964996
self.assertLess(sys.getsizeof(vars(b)), sys.getsizeof({}))
49974997

49984998

4999+
class DebugHelperMeta(type):
5000+
"""
5001+
Sets default __doc__ and simplifies repr() output.
5002+
"""
5003+
def __new__(mcls, name, bases, attrs):
5004+
if attrs.get('__doc__') is None:
5005+
attrs['__doc__'] = name # helps when debugging with gdb
5006+
return type.__new__(mcls, name, bases, attrs)
5007+
def __repr__(cls):
5008+
return repr(cls.__name__)
5009+
5010+
5011+
class MroTest(unittest.TestCase):
5012+
"""
5013+
Regressions for some bugs revealed through
5014+
mcsl.mro() customization (typeobject.c: mro_internal()) and
5015+
cls.__bases__ assignment (typeobject.c: type_set_bases()).
5016+
"""
5017+
5018+
def setUp(self):
5019+
self.step = 0
5020+
self.ready = False
5021+
5022+
def step_until(self, limit):
5023+
ret = (self.step < limit)
5024+
if ret:
5025+
self.step += 1
5026+
return ret
5027+
5028+
def test_incomplete_set_bases_on_self(self):
5029+
"""
5030+
type_set_bases must be aware that type->tp_mro can be NULL.
5031+
"""
5032+
class M(DebugHelperMeta):
5033+
def mro(cls):
5034+
if self.step_until(1):
5035+
assert cls.__mro__ is None
5036+
cls.__bases__ += ()
5037+
5038+
return type.mro(cls)
5039+
5040+
class A(metaclass=M):
5041+
pass
5042+
5043+
def test_reent_set_bases_on_base(self):
5044+
"""
5045+
Deep reentrancy must not over-decref old_mro.
5046+
"""
5047+
class M(DebugHelperMeta):
5048+
def mro(cls):
5049+
if cls.__mro__ is not None and cls.__name__ == 'B':
5050+
# 4-5 steps are usually enough to make it crash somewhere
5051+
if self.step_until(10):
5052+
A.__bases__ += ()
5053+
5054+
return type.mro(cls)
5055+
5056+
class A(metaclass=M):
5057+
pass
5058+
class B(A):
5059+
pass
5060+
B.__bases__ += ()
5061+
5062+
def test_reent_set_bases_on_direct_base(self):
5063+
"""
5064+
Similar to test_reent_set_bases_on_base, but may crash differently.
5065+
"""
5066+
class M(DebugHelperMeta):
5067+
def mro(cls):
5068+
base = cls.__bases__[0]
5069+
if base is not object:
5070+
if self.step_until(5):
5071+
base.__bases__ += ()
5072+
5073+
return type.mro(cls)
5074+
5075+
class A(metaclass=M):
5076+
pass
5077+
class B(A):
5078+
pass
5079+
class C(B):
5080+
pass
5081+
5082+
def test_reent_set_bases_tp_base_cycle(self):
5083+
"""
5084+
type_set_bases must check for an inheritance cycle not only through
5085+
MRO of the type, which may be not yet updated in case of reentrance,
5086+
but also through tp_base chain, which is assigned before diving into
5087+
inner calls to mro().
5088+
5089+
Otherwise, the following snippet can loop forever:
5090+
do {
5091+
// ...
5092+
type = type->tp_base;
5093+
} while (type != NULL);
5094+
5095+
Functions that rely on tp_base (like solid_base and PyType_IsSubtype)
5096+
would not be happy in that case, causing a stack overflow.
5097+
"""
5098+
class M(DebugHelperMeta):
5099+
def mro(cls):
5100+
if self.ready:
5101+
if cls.__name__ == 'B1':
5102+
B2.__bases__ = (B1,)
5103+
if cls.__name__ == 'B2':
5104+
B1.__bases__ = (B2,)
5105+
return type.mro(cls)
5106+
5107+
class A(metaclass=M):
5108+
pass
5109+
class B1(A):
5110+
pass
5111+
class B2(A):
5112+
pass
5113+
5114+
self.ready = True
5115+
with self.assertRaises(TypeError):
5116+
B1.__bases__ += ()
5117+
5118+
def test_tp_subclasses_cycle_in_update_slots(self):
5119+
"""
5120+
type_set_bases must check for reentrancy upon finishing its job
5121+
by updating tp_subclasses of old/new bases of the type.
5122+
Otherwise, an implicit inheritance cycle through tp_subclasses
5123+
can break functions that recurse on elements of that field
5124+
(like recurse_down_subclasses and mro_hierarchy) eventually
5125+
leading to a stack overflow.
5126+
"""
5127+
class M(DebugHelperMeta):
5128+
def mro(cls):
5129+
if self.ready and cls.__name__ == 'C':
5130+
self.ready = False
5131+
C.__bases__ = (B2,)
5132+
return type.mro(cls)
5133+
5134+
class A(metaclass=M):
5135+
pass
5136+
class B1(A):
5137+
pass
5138+
class B2(A):
5139+
pass
5140+
class C(A):
5141+
pass
5142+
5143+
self.ready = True
5144+
C.__bases__ = (B1,)
5145+
B1.__bases__ = (C,)
5146+
5147+
self.assertEqual(C.__bases__, (B2,))
5148+
self.assertEqual(B2.__subclasses__(), [C])
5149+
self.assertEqual(B1.__subclasses__(), [])
5150+
5151+
self.assertEqual(B1.__bases__, (C,))
5152+
self.assertEqual(C.__subclasses__(), [B1])
5153+
5154+
def test_tp_subclasses_cycle_error_return_path(self):
5155+
"""
5156+
The same as test_tp_subclasses_cycle_in_update_slots, but tests
5157+
a code path executed on error (goto bail).
5158+
"""
5159+
class E(Exception):
5160+
pass
5161+
class M(DebugHelperMeta):
5162+
def mro(cls):
5163+
if self.ready and cls.__name__ == 'C':
5164+
if C.__bases__ == (B2,):
5165+
self.ready = False
5166+
else:
5167+
C.__bases__ = (B2,)
5168+
raise E
5169+
return type.mro(cls)
5170+
5171+
class A(metaclass=M):
5172+
pass
5173+
class B1(A):
5174+
pass
5175+
class B2(A):
5176+
pass
5177+
class C(A):
5178+
pass
5179+
5180+
self.ready = True
5181+
with self.assertRaises(E):
5182+
C.__bases__ = (B1,)
5183+
B1.__bases__ = (C,)
5184+
5185+
self.assertEqual(C.__bases__, (B2,))
5186+
self.assertEqual(C.__mro__, tuple(type.mro(C)))
5187+
5188+
def test_incomplete_extend(self):
5189+
"""
5190+
Extending an unitialized type with type->tp_mro == NULL must
5191+
throw a reasonable TypeError exception, instead of failing
5192+
with PyErr_BadInternalCall.
5193+
"""
5194+
class M(DebugHelperMeta):
5195+
def mro(cls):
5196+
if cls.__mro__ is None and cls.__name__ != 'X':
5197+
with self.assertRaises(TypeError):
5198+
class X(cls):
5199+
pass
5200+
5201+
return type.mro(cls)
5202+
5203+
class A(metaclass=M):
5204+
pass
5205+
5206+
def test_incomplete_super(self):
5207+
"""
5208+
Attrubute lookup on a super object must be aware that
5209+
its target type can be uninitialized (type->tp_mro == NULL).
5210+
"""
5211+
class M(DebugHelperMeta):
5212+
def mro(cls):
5213+
if cls.__mro__ is None:
5214+
with self.assertRaises(AttributeError):
5215+
super(cls, cls).xxx
5216+
5217+
return type.mro(cls)
5218+
5219+
class A(metaclass=M):
5220+
pass
5221+
5222+
49995223
def test_main():
50005224
# Run all local test cases, with PTypesLongInitTest first.
50015225
support.run_unittest(PTypesLongInitTest, OperatorsTest,
50025226
ClassPropertiesAndMethods, DictProxyTests,
5003-
MiscTests, PicklingTests, SharedKeyTests)
5227+
MiscTests, PicklingTests, SharedKeyTests,
5228+
MroTest)
50045229

50055230
if __name__ == "__main__":
50065231
test_main()

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Michael Abbott
1616
Rajiv Abraham
1717
David Abrahams
1818
Marc Abramowitz
19+
Eldar Abusalimov
1920
Ron Adam
2021
Anton Afanasyev
2122
Ali Afshar

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ Release date: TBA
1111
Core and Builtins
1212
-----------------
1313

14+
- Issue #22735: Fix many edge cases (including crashes) involving custom mro()
15+
implementations.
16+
1417
- Issue #22896: Avoid using PyObject_AsCharBuffer(), PyObject_AsReadBuffer()
1518
and PyObject_AsWriteBuffer().
1619

0 commit comments

Comments
 (0)