Skip to content
Draft
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
7 changes: 3 additions & 4 deletions crates/stdlib/src/pystruct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ pub(crate) mod _struct {
s
}
b @ PyBytes => {
let ascii_str = ascii::AsciiStr::from_ascii(&b).map_err(|_| {
new_struct_error(vm, "bad char in struct format".to_owned())
})?;
let ascii_str = ascii::AsciiStr::from_ascii(&b)
.map_err(|_| new_struct_error(vm, "bad char in struct format"))?;
vm.ctx.new_str(ascii_str)
}
other =>
Expand Down Expand Up @@ -192,7 +191,7 @@ pub(crate) mod _struct {
if format_spec.size == 0 {
Err(new_struct_error(
vm,
"cannot iteratively unpack with a struct of length 0".to_owned(),
"cannot iteratively unpack with a struct of length 0",
))
} else if !buffer.len().is_multiple_of(format_spec.size) {
Err(new_struct_error(
Expand Down
58 changes: 21 additions & 37 deletions crates/vm/src/anystr.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use core::ops::Range;

use icu_properties::props::{
BinaryProperty, EnumeratedProperty, GeneralCategory, GeneralCategoryGroup,
};
use num_traits::{cast::ToPrimitive, sign::Signed};

use crate::{
Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyIntRef, PyTuple},
convert::TryFromBorrowedObject,
function::OptionalOption,
};
use icu_properties::props::{
BinaryProperty, EnumeratedProperty, GeneralCategory, GeneralCategoryGroup,
};
use num_traits::{cast::ToPrimitive, sign::Signed};

use core::ops::Range;

#[derive(FromArgs)]
pub struct SplitArgs<T: TryFromObject> {
Expand Down Expand Up @@ -410,36 +411,20 @@ pub(crate) trait AnyStr {

// _Py_bytes_islower
fn py_islower(&self) -> bool {
let mut lower = false;
for byte in self
.as_bytes()
self.as_bytes()
.iter()
.copied()
.filter(u8::is_ascii_alphabetic)
{
if byte.is_ascii_uppercase() {
return false;
}
lower = true;
}
lower
.all(|byte| byte.is_ascii_lowercase())
}
Comment on lines +414 to 419

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the “at least one ASCII cased letter” requirement in py_islower/py_isupper.

Line 414 and Line 423 now use filter(...).all(...); on inputs with no ASCII alphabetic bytes, all() is vacuously true, so these functions return true for empty/digit-only content.

💡 Suggested fix
 fn py_islower(&self) -> bool {
-    self.as_bytes()
-        .iter()
-        .copied()
-        .filter(u8::is_ascii_alphabetic)
-        .all(|byte| byte.is_ascii_lowercase())
+    let mut seen_alpha = false;
+    for byte in self
+        .as_bytes()
+        .iter()
+        .copied()
+        .filter(u8::is_ascii_alphabetic)
+    {
+        seen_alpha = true;
+        if !byte.is_ascii_lowercase() {
+            return false;
+        }
+    }
+    seen_alpha
 }
 
 // Py_bytes_isupper
 fn py_isupper(&self) -> bool {
-    self.as_bytes()
-        .iter()
-        .copied()
-        .filter(u8::is_ascii_alphabetic)
-        .all(|byte| byte.is_ascii_uppercase())
+    let mut seen_alpha = false;
+    for byte in self
+        .as_bytes()
+        .iter()
+        .copied()
+        .filter(u8::is_ascii_alphabetic)
+    {
+        seen_alpha = true;
+        if !byte.is_ascii_uppercase() {
+            return false;
+        }
+    }
+    seen_alpha
 }

Also applies to: 423-428

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/anystr.rs` around lines 414 - 419, The py_islower and
py_isupper methods are using filter().all() which returns vacuously true when no
ASCII alphabetic characters are present, causing empty strings and digit-only
strings to incorrectly return true. In both functions (py_islower at lines
414-419 and py_isupper at lines 423-428), you need to ensure at least one ASCII
alphabetic character exists before checking the case condition. Modify the logic
to use both an any() check to verify at least one ASCII alphabetic byte is
present AND the all() check to ensure all such bytes match the required case,
combining them with AND logic so both conditions must be true for the function
to return true.


// Py_bytes_isupper
fn py_isupper(&self) -> bool {
let mut upper = false;
for byte in self
.as_bytes()
self.as_bytes()
.iter()
.copied()
.filter(u8::is_ascii_alphabetic)
{
if byte.is_ascii_lowercase() {
return false;
}
upper = true;
}
upper
.all(|byte| byte.is_ascii_uppercase())
}

// Unified form of CPython functions:
Expand Down Expand Up @@ -484,18 +469,17 @@ where
F: Fn(T) -> PyResult<bool>,
M: Fn(&PyObject) -> String,
{
match obj.try_to_value::<T>(vm) {
Ok(single) => (predicate)(single),
Err(_) => {
let tuple: &Py<PyTuple> = obj
.try_to_value(vm)
.map_err(|_| vm.new_type_error((message)(obj)))?;
for obj in tuple {
if single_or_tuple_any(obj, predicate, message, vm)? {
return Ok(true);
}
if let Ok(single) = obj.try_to_value::<T>(vm) {
(predicate)(single)
} else {
let tuple: &Py<PyTuple> = obj
.try_to_value(vm)
.map_err(|_| vm.new_type_error((message)(obj)))?;
for obj in tuple {
if single_or_tuple_any(obj, predicate, message, vm)? {
return Ok(true);
}
Ok(false)
}
Ok(false)
}
}
9 changes: 5 additions & 4 deletions crates/vm/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ use crate::{
convert::ToPyObject,
function::{ArgBytesLike, ArgIntoBool, ArgIntoFloat},
};
use alloc::fmt;
use core::{iter::Peekable, mem};

use rustpython_common::wtf8::Wtf8Buf;

use core::{fmt, iter::Peekable, mem};
use half::f16;
use itertools::Itertools;
use malachite_bigint::BigInt;
Expand Down Expand Up @@ -737,9 +739,8 @@ pub fn struct_error_type(vm: &VirtualMachine) -> &'static PyTypeRef {
INSTANCE.get_or_init(|| vm.ctx.new_exception_type("struct", "error", None))
}

pub fn new_struct_error(vm: &VirtualMachine, msg: impl Into<String>) -> PyBaseExceptionRef {
pub fn new_struct_error<T: Into<Wtf8Buf>>(vm: &VirtualMachine, msg: T) -> PyBaseExceptionRef {
// can't just STRUCT_ERROR.get().unwrap() cause this could be called before from buffer
// machinery, independent of whether _struct was ever imported
let msg: String = msg.into();
vm.new_exception_msg(struct_error_type(vm).clone(), msg.into())
}
4 changes: 2 additions & 2 deletions crates/vm/src/builtins/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ impl ToPyObject for f64 {
vm.ctx.new_float(self).into()
}
}

impl ToPyObject for f32 {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_float(f64::from(self)).into()
Expand Down Expand Up @@ -156,8 +157,7 @@ fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> {

pub(crate) fn float_pow(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult {
if v1.is_zero() && v2.is_sign_negative() {
let msg = "zero to a negative power";
Err(vm.new_zero_division_error(msg.to_owned()))
Err(vm.new_zero_division_error("zero to a negative power"))
} else if v1.is_sign_negative() && (v2.floor() - v2).abs() > f64::EPSILON {
let v1 = Complex64::new(v1, 0.);
let v2 = Complex64::new(v2, 0.);
Expand Down
1 change: 1 addition & 0 deletions crates/vm/src/builtins/staticmethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl PyStaticMethod {
callable: PyMutex::new(callable),
}
}

#[deprecated(note = "use PyStaticMethod::new(...).into_ref() instead")]
pub fn new_ref(callable: PyObjectRef, ctx: &Context) -> PyRef<Self> {
Self::new(callable).into_ref(ctx)
Expand Down
5 changes: 3 additions & 2 deletions crates/vm/src/byte.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! byte operation APIs
use crate::object::AsObject;
use crate::{PyObject, PyResult, VirtualMachine};

use num_traits::ToPrimitive;

use crate::{AsObject, PyObject, PyResult, VirtualMachine};

pub fn bytes_from_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Vec<u8>> {
if let Ok(elements) = obj.try_bytes_like(vm, |bytes| bytes.to_vec()) {
return Ok(elements);
Expand Down
Loading