@@ -1748,7 +1748,7 @@ type_get_mro(PyObject *tp, void *Py_UNUSED(closure))
17481748static PyTypeObject * find_best_base (PyObject * );
17491749static int mro_internal (PyTypeObject * , int , PyObject * * );
17501750static int type_is_subtype_base_chain (PyTypeObject * , PyTypeObject * );
1751- static int compatible_for_assignment (PyTypeObject * , PyTypeObject * , const char * );
1751+ static int compatible_for_assignment (PyTypeObject * , PyTypeObject * , const char * , int );
17521752static int add_subclass (PyTypeObject * , PyTypeObject * );
17531753static int add_all_subclasses (PyTypeObject * type , PyObject * bases );
17541754static void remove_subclass (PyTypeObject * , PyTypeObject * );
@@ -1886,7 +1886,7 @@ type_check_new_bases(PyTypeObject *type, PyObject *new_bases, PyTypeObject **bes
18861886 if (* best_base == NULL )
18871887 return -1 ;
18881888
1889- if (!compatible_for_assignment (type -> tp_base , * best_base , "__bases__" )) {
1889+ if (!compatible_for_assignment (type , * best_base , "__bases__" , 0 )) {
18901890 return -1 ;
18911891 }
18921892
@@ -7230,8 +7230,6 @@ compatible_with_tp_base(PyTypeObject *child)
72307230 child -> tp_itemsize == parent -> tp_itemsize &&
72317231 child -> tp_dictoffset == parent -> tp_dictoffset &&
72327232 child -> tp_weaklistoffset == parent -> tp_weaklistoffset &&
7233- ((child -> tp_flags & Py_TPFLAGS_HAVE_GC ) ==
7234- (parent -> tp_flags & Py_TPFLAGS_HAVE_GC )) &&
72357233 (child -> tp_dealloc == subtype_dealloc ||
72367234 child -> tp_dealloc == parent -> tp_dealloc ));
72377235}
@@ -7266,11 +7264,24 @@ same_slots_added(PyTypeObject *a, PyTypeObject *b)
72667264}
72677265
72687266static int
7269- compatible_for_assignment (PyTypeObject * oldto , PyTypeObject * newto , const char * attr )
7267+ compatible_flags (int setclass , PyTypeObject * origto , PyTypeObject * newto , unsigned long flags )
7268+ {
7269+ /* For __class__ assignment, the flags should be the same.
7270+ For __bases__ assignment, the new base flags can only be set
7271+ if the original class flags are set.
7272+ */
7273+ return setclass ? (origto -> tp_flags & flags ) == (newto -> tp_flags & flags )
7274+ : !(~(origto -> tp_flags & flags ) & (newto -> tp_flags & flags ));
7275+ }
7276+
7277+ static int
7278+ compatible_for_assignment (PyTypeObject * origto , PyTypeObject * newto ,
7279+ const char * attr , int setclass )
72707280{
72717281 PyTypeObject * newbase , * oldbase ;
7282+ PyTypeObject * oldto = setclass ? origto : origto -> tp_base ;
72727283
7273- if (newto -> tp_free != oldto -> tp_free ) {
7284+ if (setclass && newto -> tp_free != oldto -> tp_free ) {
72747285 PyErr_Format (PyExc_TypeError ,
72757286 "%s assignment: "
72767287 "'%s' deallocator differs from '%s'" ,
@@ -7279,6 +7290,28 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char*
72797290 oldto -> tp_name );
72807291 return 0 ;
72817292 }
7293+ if (!compatible_flags (setclass , origto , newto ,
7294+ Py_TPFLAGS_HAVE_GC |
7295+ Py_TPFLAGS_INLINE_VALUES |
7296+ Py_TPFLAGS_PREHEADER ))
7297+ {
7298+ goto differs ;
7299+ }
7300+ /* For __class__ assignment, tp_dictoffset and tp_weaklistoffset should
7301+ be the same for old and new types.
7302+ For __bases__ assignment, they can only be set in the new base
7303+ if they are set in the original class with the same value.
7304+ */
7305+ if ((setclass || newto -> tp_dictoffset )
7306+ && origto -> tp_dictoffset != newto -> tp_dictoffset )
7307+ {
7308+ goto differs ;
7309+ }
7310+ if ((setclass || newto -> tp_weaklistoffset )
7311+ && origto -> tp_weaklistoffset != newto -> tp_weaklistoffset )
7312+ {
7313+ goto differs ;
7314+ }
72827315 /*
72837316 It's tricky to tell if two arbitrary types are sufficiently compatible as
72847317 to be interchangeable; e.g., even if they have the same tp_basicsize, they
@@ -7300,17 +7333,7 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char*
73007333 !same_slots_added (newbase , oldbase ))) {
73017334 goto differs ;
73027335 }
7303- if ((oldto -> tp_flags & Py_TPFLAGS_INLINE_VALUES ) !=
7304- ((newto -> tp_flags & Py_TPFLAGS_INLINE_VALUES )))
7305- {
7306- goto differs ;
7307- }
7308- /* The above does not check for the preheader */
7309- if ((oldto -> tp_flags & Py_TPFLAGS_PREHEADER ) ==
7310- ((newto -> tp_flags & Py_TPFLAGS_PREHEADER )))
7311- {
7312- return 1 ;
7313- }
7336+ return 1 ;
73147337differs :
73157338 PyErr_Format (PyExc_TypeError ,
73167339 "%s assignment: "
@@ -7387,7 +7410,7 @@ object_set_class_world_stopped(PyObject *self, PyTypeObject *newto)
73877410 return -1 ;
73887411 }
73897412
7390- if (compatible_for_assignment (oldto , newto , "__class__" )) {
7413+ if (compatible_for_assignment (oldto , newto , "__class__" , 1 )) {
73917414 /* Changing the class will change the implicit dict keys,
73927415 * so we must materialize the dictionary first. */
73937416 if (oldto -> tp_flags & Py_TPFLAGS_INLINE_VALUES ) {
0 commit comments