Reject __abc_tpflags__ with both SEQUENCE and MAPPING bits#7700
Conversation
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.
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/vm/src/builtins/type.rs (1)
612-660: Optional: factor out the duplicated__abc_tpflags__parse/validate/apply logic.The own-class branch (lines 615–633) and the legacy base-class branch (lines 643–659) repeat the same five steps: downcast to
PyInt, convert viato_i64, buildPyTypeFlags, mask withCOLLECTION_FLAGS, reject the both-bits case, and OR intoslots.flags. The error string is also duplicated verbatim, so any future tweak (wording, behavior on non-int, BigInt overflow handling) has to be kept in sync in two places.A small helper would tighten this up without changing semantics:
♻️ Proposed extraction
+ /// Parse `__abc_tpflags__` (if it is an int), reject the + /// `SEQUENCE | MAPPING` combination, and return the masked + /// collection bits to be ORed into `slots.flags`. Returns + /// `Ok(None)` if the value is missing or not a `PyInt`. + fn parse_abc_tpflags( + value: Option<&PyObjectRef>, + collection_flags: PyTypeFlags, + ) -> Result<Option<PyTypeFlags>, String> { + let Some(obj) = value else { return Ok(None) }; + let Some(int_obj) = obj.downcast_ref::<crate::builtins::int::PyInt>() else { + return Ok(None); + }; + let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0); + let masked = PyTypeFlags::from_bits_truncate(flags_val as u64) & collection_flags; + if masked == collection_flags { + return Err( + "__abc_tpflags__ cannot be both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING" + .to_owned(), + ); + } + Ok(Some(masked)) + }Then
check_abc_tpflagsbecomes a straight orchestration overattrs.get(...)and the base loop, each callingparse_abc_tpflagsand applyingslots.flags |= maskedunder the existing "don't override inherited" rule.Pre-existing nit on the same surface:
COLLECTION_FLAGSis also defined identically ininherit_patma_flags(lines 581–583) — pulling it up to a module-levelconstwould let both functions share it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/vm/src/builtins/type.rs` around lines 612 - 660, Extract the duplicated parse/validate/apply logic into a small helper (e.g. parse_and_validate_abc_tpflags) that takes a PyObjectRef (or Option<&PyObject>) and returns Result<Option<PyTypeFlags>, String> or similar: it should downcast to crate::builtins::int::PyInt, convert via to_i64().unwrap_or(0), build PyTypeFlags::from_bits_truncate(...), mask with COLLECTION_FLAGS, and return Err(...) if both sequence and mapping bits are set; then replace the two duplicated blocks in the current function with calls to that helper and apply slots.flags |= masked only under the existing inherited-bit check; also promote COLLECTION_FLAGS to a module-level const so inherit_patma_flags and this new helper share the same constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/vm/src/builtins/type.rs`:
- Around line 612-660: Extract the duplicated parse/validate/apply logic into a
small helper (e.g. parse_and_validate_abc_tpflags) that takes a PyObjectRef (or
Option<&PyObject>) and returns Result<Option<PyTypeFlags>, String> or similar:
it should downcast to crate::builtins::int::PyInt, convert via
to_i64().unwrap_or(0), build PyTypeFlags::from_bits_truncate(...), mask with
COLLECTION_FLAGS, and return Err(...) if both sequence and mapping bits are set;
then replace the two duplicated blocks in the current function with calls to
that helper and apply slots.flags |= masked only under the existing
inherited-bit check; also promote COLLECTION_FLAGS to a module-level const so
inherit_patma_flags and this new helper share the same constant.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: cae320f1-dd99-497e-b271-a266059af8a0
⛔ Files ignored due to path filters (1)
Lib/test/test_collections.pyis excluded by!Lib/**
📒 Files selected for processing (1)
crates/vm/src/builtins/type.rs
📦 Library DependenciesThe following Lib/ modules were modified. Here are their dependencies: (module 'ast test_collections test_deque test_descr test_int test_reprlib' not found) Legend:
|
Background
__abc_tpflags__is the integer ABC pattern-matching flag set on a class somatchstatements can short-circuitSequencevsMappingdispatch. Setting both bits is meaningless for pattern matching and CPython rejects it inModules/_abc.c::_abc_init:RustPython's
check_abc_tpflags(crates/vm/src/builtins/type.rs) copied the bits ontoslots.flagswithout validating the conflict, silently producing an inconsistent type.Repro
Fix
Two changes inside
check_abc_tpflags:abc_flagswithCOLLECTION_FLAGS(theSEQUENCE | MAPPINGbits). If the masked value equalsCOLLECTION_FLAGS, return aStringerror —new_heap_inneralready converts that toTypeErrorfor the user.__abc_tpflags__slip through whenever the parent had already set one of the bits.Tests unmasked
test_collections.TestCollectionABCs.test_illegal_patma_flagsVerification
test_collections test_descr test_class test_typing test_abc test_dataclasses test_super test_call test_module test_patma(1,951 tests)__abc_tpflags__value (silently ignored, matches CPython)Summary by CodeRabbit
Refactor
Bug Fixes