@@ -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+
49995223def 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
50055230if __name__ == "__main__" :
50065231 test_main ()
0 commit comments