Skip to content

Commit 55f99ee

Browse files
committed
badslot
1 parent 028c05f commit 55f99ee

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

Lib/test/test_builtin.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,8 +2481,6 @@ def test_bad_args(self):
24812481
with self.assertRaises(TypeError):
24822482
type('A', (int, str), {})
24832483

2484-
# TODO: RUSTPYTHON
2485-
@unittest.expectedFailure
24862484
def test_bad_slots(self):
24872485
with self.assertRaises(TypeError):
24882486
type('A', (), {'__slots__': b'x'})

crates/vm/src/builtins/type.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1383,11 +1383,89 @@ impl Constructor for PyType {
13831383
tuple.try_into_typed(vm)?
13841384
};
13851385

1386-
// Validate that all slots are valid identifiers
1386+
// Check if base has itemsize > 0 - can't add arbitrary slots to variable-size types
1387+
// Types like int, bytes, tuple have itemsize > 0 and don't allow custom slots
1388+
// But types like weakref.ref have itemsize = 0 and DO allow slots
1389+
let has_custom_slots = slots
1390+
.iter()
1391+
.any(|s| s.as_str() != "__dict__" && s.as_str() != "__weakref__");
1392+
if has_custom_slots && base.slots.itemsize > 0 {
1393+
return Err(vm.new_type_error(format!(
1394+
"nonempty __slots__ not supported for subtype of '{}'",
1395+
base.name()
1396+
)));
1397+
}
1398+
1399+
// Validate slot names and track duplicates
1400+
let mut seen_dict = false;
1401+
let mut seen_weakref = false;
13871402
for slot in slots.iter() {
1403+
// Use isidentifier for validation (handles Unicode properly)
13881404
if !slot.isidentifier() {
13891405
return Err(vm.new_type_error("__slots__ must be identifiers".to_owned()));
13901406
}
1407+
1408+
let slot_name = slot.as_str();
1409+
1410+
// Check for duplicate __dict__
1411+
if slot_name == "__dict__" {
1412+
if seen_dict {
1413+
return Err(vm.new_type_error(
1414+
"__dict__ slot disallowed: we already got one".to_owned(),
1415+
));
1416+
}
1417+
seen_dict = true;
1418+
}
1419+
1420+
// Check for duplicate __weakref__
1421+
if slot_name == "__weakref__" {
1422+
if seen_weakref {
1423+
return Err(vm.new_type_error(
1424+
"__weakref__ slot disallowed: we already got one".to_owned(),
1425+
));
1426+
}
1427+
seen_weakref = true;
1428+
}
1429+
1430+
// Check if slot name conflicts with class attributes
1431+
if attributes.contains_key(vm.ctx.intern_str(slot_name)) {
1432+
return Err(vm.new_value_error(format!(
1433+
"'{}' in __slots__ conflicts with a class variable",
1434+
slot_name
1435+
)));
1436+
}
1437+
}
1438+
1439+
// Check if base class already has __dict__ - can't redefine it
1440+
if seen_dict && base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
1441+
return Err(
1442+
vm.new_type_error("__dict__ slot disallowed: we already got one".to_owned())
1443+
);
1444+
}
1445+
1446+
// Check if base class already has __weakref__ - can't redefine it
1447+
// A base has weakref support if:
1448+
// 1. It's a heap type without explicit __slots__ (automatic weakref), OR
1449+
// 2. It's a heap type with __weakref__ in its __slots__
1450+
if seen_weakref {
1451+
let base_has_weakref = if let Some(ref ext) = base.heaptype_ext {
1452+
match &ext.slots {
1453+
// Heap type without __slots__ - has automatic weakref
1454+
None => true,
1455+
// Heap type with __slots__ - check if __weakref__ is in slots
1456+
Some(base_slots) => base_slots.iter().any(|s| s.as_str() == "__weakref__"),
1457+
}
1458+
} else {
1459+
// Builtin type - check if it has __weakref__ descriptor
1460+
let weakref_name = vm.ctx.intern_str("__weakref__");
1461+
base.attributes.read().contains_key(weakref_name)
1462+
};
1463+
1464+
if base_has_weakref {
1465+
return Err(vm.new_type_error(
1466+
"__weakref__ slot disallowed: we already got one".to_owned(),
1467+
));
1468+
}
13911469
}
13921470

13931471
// Check if __dict__ is in slots

0 commit comments

Comments
 (0)