From 6c9d5983642fb703b27a201d4bab74142e9c7fe1 Mon Sep 17 00:00:00 2001 From: changjoon-park Date: Mon, 27 Apr 2026 22:04:28 +0900 Subject: [PATCH] Reject __abc_tpflags__ with both SEQUENCE and MAPPING bits CPython's _abc._abc_init validates that __abc_tpflags__ doesn't combine Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING (Modules/_abc.c::_abc_init). RustPython's check_abc_tpflags copied the bits onto slots.flags without that validation, so a class declaring both produced an inconsistent type silently. Validate the masked __abc_tpflags__ value against COLLECTION_FLAGS and return a String error (which the existing new_heap_inner caller turns into a TypeError) when both bits are present. Move the inheritance-skip check after the self-attrs branch so a child overriding __abc_tpflags__ is still validated even when the parent already set one of the bits. --- Lib/test/test_collections.py | 1 - crates/vm/src/builtins/type.rs | 48 +++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 7a57ba722e8..b5d3411c71a 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -2028,7 +2028,6 @@ def insert(self, index, value): self.assertEqual(len(mss), len(mss2)) self.assertEqual(list(mss), list(mss2)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised def test_illegal_patma_flags(self): with self.assertRaises(TypeError): class Both(Collection): diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 1cf8119e9c9..9aa0936adde 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -604,38 +604,60 @@ impl PyType { attrs: &PyAttributes, bases: &[PyRef], ctx: &Context, - ) { + ) -> Result<(), String> { const COLLECTION_FLAGS: PyTypeFlags = PyTypeFlags::from_bits_truncate( PyTypeFlags::SEQUENCE.bits() | PyTypeFlags::MAPPING.bits(), ); - // Don't override if flags are already set - if slots.flags.intersects(COLLECTION_FLAGS) { - return; - } - - // First check in our own attributes + // Always validate this class's own __abc_tpflags__ even when slot + // flags were already inherited, otherwise a child setting both + // Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING would slip through. let abc_tpflags_name = ctx.intern_str("__abc_tpflags__"); if let Some(abc_tpflags_obj) = attrs.get(abc_tpflags_name) && let Some(int_obj) = abc_tpflags_obj.downcast_ref::() { let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0); let abc_flags = PyTypeFlags::from_bits_truncate(flags_val as u64); - slots.flags |= abc_flags & COLLECTION_FLAGS; - return; + let masked = abc_flags & COLLECTION_FLAGS; + if masked == COLLECTION_FLAGS { + return Err( + "__abc_tpflags__ cannot be both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING" + .to_owned(), + ); + } + // Don't override flags already inherited from a base class. + if !slots.flags.intersects(COLLECTION_FLAGS) { + slots.flags |= masked; + } + return Ok(()); + } + + // No __abc_tpflags__ on this class — inheritance already happened + // in inherit_patma_flags, so nothing more to do if those bits are set. + if slots.flags.intersects(COLLECTION_FLAGS) { + return Ok(()); } - // Then check in base classes + // Then check in base classes (legacy path for cases that bypass + // inherit_patma_flags). for base in bases { if let Some(abc_tpflags_obj) = base.find_name_in_mro(abc_tpflags_name) && let Some(int_obj) = abc_tpflags_obj.downcast_ref::() { let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0); let abc_flags = PyTypeFlags::from_bits_truncate(flags_val as u64); - slots.flags |= abc_flags & COLLECTION_FLAGS; - return; + let masked = abc_flags & COLLECTION_FLAGS; + if masked == COLLECTION_FLAGS { + return Err( + "__abc_tpflags__ cannot be both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING" + .to_owned(), + ); + } + slots.flags |= masked; + return Ok(()); } } + Ok(()) } #[allow(clippy::too_many_arguments)] @@ -671,7 +693,7 @@ impl PyType { Self::inherit_patma_flags(&mut slots, &bases); // Check for __abc_tpflags__ from ABCMeta (for collections.abc.Sequence, Mapping, etc.) - Self::check_abc_tpflags(&mut slots, &attrs, &bases, ctx); + Self::check_abc_tpflags(&mut slots, &attrs, &bases, ctx)?; if slots.basicsize == 0 { slots.basicsize = base.slots.basicsize;