Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 13 additions & 22 deletions crates/capi/src/descrobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::util::CStrExt;
use core::ffi::{c_char, c_int, c_void};
use core::ptr::NonNull;
use rustpython_vm::builtins::{
DescriptorMemberDef, MemberGetter, MemberKind, MemberSetter, PyDescriptorOwned, PyGetSet,
PyMappingProxy, PyMemberDescriptor, PyType,
DescriptorMemberDef, MemberAccess, MemberKind, PY_READONLY, PY_RELATIVE_OFFSET,
PyDescriptorOwned, PyGetSet, PyMappingProxy, PyMemberDescriptor, PyType,
};
use rustpython_vm::common::lock::PyRwLock;
use rustpython_vm::function::PySetterValue;
Expand Down Expand Up @@ -133,36 +133,29 @@ pub struct PyMemberDef {
}

impl PyMemberDef {
const PY_READONLY: c_int = 1;
const PY_RELATIVE_OFFSET: c_int = 8;

pub(crate) fn build(
&self,
ty: &Py<PyType>,
vm: &VirtualMachine,
) -> PyResult<PyRef<PyMemberDescriptor>> {
let name = unsafe { self.name.try_as_str(vm) }?;
let kind = match self.type_code {
6 => MemberKind::Object,
16 => MemberKind::ObjectEx,
14 => MemberKind::Bool,
_ => {
return Err(vm.new_system_error(format!(
"PyDescr_NewMember does not support member type code {}",
self.type_code
)));
}
let Some(kind) = MemberKind::from_i32(self.type_code) else {
return Err(vm.new_system_error(format!(
"PyDescr_NewMember does not support member type code {}",
self.type_code
)));
};
if self.offset < 0 {
return Err(vm.new_system_error("PyDescr_NewMember does not support negative offsets"));
}
if self.flags & Self::PY_RELATIVE_OFFSET != 0 {
if self.flags & PY_RELATIVE_OFFSET != 0 {
return Err(
vm.new_system_error("PyDescr_NewMember does not support Py_RELATIVE_OFFSET")
);
}

let doc = unsafe { self.doc.try_as_str_opt(vm) }?.map(str::to_owned);
let readonly = self.flags & PY_READONLY != 0;

let descriptor = PyMemberDescriptor {
common: PyDescriptorOwned {
Expand All @@ -173,14 +166,12 @@ impl PyMemberDef {
member: DescriptorMemberDef {
name: name.to_owned(),
kind,
getter: MemberGetter::Offset(self.offset as usize),
setter: if self.flags & Self::PY_READONLY != 0 {
MemberSetter::Setter(None)
} else {
MemberSetter::Offset(self.offset as usize)
},
offset: self.offset,
flags: if readonly { PY_READONLY } else { 0 },
doc,
},
// Instance members live in the slot array. `offset` is that index.
access: MemberAccess::Slot,
};

Ok(descriptor.into_ref(&vm.ctx))
Expand Down
55 changes: 55 additions & 0 deletions crates/common/src/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ pub fn bytes_to_int(
}
}

// A pure digit string longer than the limit can be rejected from its
// length. The check is a wide digit test so it stays cheaper than parsing.
if digit_limit > 0
&& !base.is_power_of_two()
&& buf.len() > digit_limit
&& let Some(digits) = plain_ascii_digit_len(buf)
{
return Err(BytesToIntError::DigitLimit {
got: digits,
limit: digit_limit,
});
}

// Reject empty strings
let mut prev = *buf.first().ok_or(BytesToIntError::InvalidLiteral {
base: requested_base,
Expand Down Expand Up @@ -168,6 +181,27 @@ pub fn bytes_to_int(
Ok(BigInt::from_biguint(sign.unwrap_or(Sign::Plus), uint))
}

/// `Some(buf.len())` when every byte is an ASCII digit.
fn plain_ascii_digit_len(buf: &[u8]) -> Option<usize> {
let mut rest = buf;
while rest.len() >= 8 {
let (head, tail) = rest.split_at(8);
let word = u64::from_le_bytes(head.try_into().unwrap());
if !word_all_ascii_digits(word) {
return None;
}
rest = tail;
}
rest.iter().all(u8::is_ascii_digit).then_some(buf.len())
}

/// True when each byte of `word` is in `b'0'..=b'9'`.
fn word_all_ascii_digits(word: u64) -> bool {
let below = word.wrapping_sub(0x3030_3030_3030_3030);
let above = 0x3939_3939_3939_3939u64.wrapping_sub(word);
(below | above) & 0x8080_8080_8080_8080 == 0
}

// num-bigint now returns Some(inf) for to_f64() in some cases, so just keep that the same for now
#[inline(always)]
#[must_use]
Expand Down Expand Up @@ -231,5 +265,26 @@ mod tests {
bytes_to_int("012345".as_bytes(), 10, 5),
Err(BytesToIntError::DigitLimit { got: 6, limit: 5 })
);
let long = "8".repeat(20_000);
assert_eq!(
bytes_to_int(long.as_bytes(), 10, 100),
Err(BytesToIntError::DigitLimit {
got: 20_000,
limit: 100
})
);
// Underscores are not digits, so the plain-digit fast path must not count them.
assert_eq!(
bytes_to_int(b"1_1_1", 10, 2),
Err(BytesToIntError::DigitLimit { got: 3, limit: 2 })
);
let over = format!("{}{}", "1_".repeat(80), "2".repeat(40));
assert_eq!(
bytes_to_int(over.as_bytes(), 10, 100),
Err(BytesToIntError::DigitLimit {
got: 120,
limit: 100
})
);
}
}
4 changes: 2 additions & 2 deletions crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1568,8 +1568,8 @@ impl ToTokens for GetSetNursery {
}
}

/// Member kind as string, matching `rustpython_vm::builtins::descriptor::MemberKind` variants.
/// None means ObjectEx (default). Valid values: "bool", "object".
/// Member type as string, matching `Py_T_*` codes.
/// None means `MemberKind::ObjectEx`. Valid values: "bool", "object".
type MemberKindStr = Option<String>;

#[derive(Default)]
Expand Down
Loading
Loading