FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Fix object.__getstate__ deadlock and panic on __slotnames__ by luantaraschi · Pull Request #8981 · RustPython/RustPython · GitHub

Repository navigation

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (2) .rs  (1) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
1 change: 0 additions & 1 deletion Lib/test/test_descr.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5742,7 +5742,6 @@ def __repr__(self):
objcopy2 = deepcopy(objcopy)
self._assert_is_copy(obj, objcopy2)

@unittest.skip("TODO: RUSTPYTHON")
def test_issue24097(self):
# Slot name is freed inside __getattr__ and is later used.
class S(str): # Not interned
Expand Down
23 changes: 15 additions & 8 deletions crates/vm/src/builtins/object.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,23 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine)
if slot_names_len > 0 {
let slots = vm.ctx.new_dict();
for i in 0..slot_names_len {
let borrowed_names = slot_names.borrow_vec();
// Check if slotnames changed during iteration
if borrowed_names.len() != slot_names_len {
// Release the list lock before the attribute lookup: a getter
// or `__getattr__` may mutate `__slotnames__`.
let name = slot_names.borrow_vec()[i].clone();
let name = name.downcast::<PyStr>().map_err(|name| {
vm.new_type_error(format!(
"attribute name must be string, not '{}'",
name.class().name()
))
})?;
if let Some(value) = vm.get_attribute_opt(obj, &name)? {
slots.set_item(name.as_wtf8(), value, vm)?;
}
// The list is stored on the class, so it may change while we
// iterate over it.
if slot_names.borrow_vec().len() != slot_names_len {
return Err(vm.new_runtime_error("__slotnames__ changed size during iteration"));
}
let name = borrowed_names[i].downcast_ref::<PyStr>().unwrap();
let Ok(value) = obj.get_attr(name, vm) else {
continue;
};
slots.set_item(name.as_wtf8(), value, vm).unwrap();
}

if !slots.is_empty() {
Expand Down
43 changes: 43 additions & 0 deletions extra_tests/snippets/builtin_object.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,46 @@ def __getattr__(self, name):
with assert_raises(ValueError) as caught:
RaisingAttributeHook().absent
assert caught.exception.__context__ is outer


# object.__getstate__ reads names from a cached __slotnames__ list
class NonStrSlotName:
__slotnames__ = [1]


with assert_raises(TypeError) as caught:
NonStrSlotName().__getstate__()
assert str(caught.exception) == "attribute name must be string, not 'int'"


class GrowingSlotNames:
__slotnames__ = ["a", "b"]

def __getattr__(self, name):
if name == "a":
GrowingSlotNames.__slotnames__.append("c")
return 1
raise AttributeError(name)


with assert_raises(RuntimeError):
GrowingSlotNames().__getstate__()


class RaisingSlot:
__slotnames__ = ["x"]

def __getattr__(self, name):
raise ValueError("boom")


with assert_raises(ValueError):
RaisingSlot().__getstate__()


class MissingSlot:
__slotnames__ = ["x", "y"]
y = 2


assert MissingSlot().__getstate__() == (None, {"y": 2})
Loading

Back | FazBrowse Home | New Git URL