mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Use memchr consistently for NUL checks (#8372)
`memchr` is used throughout RustPython for searching through bytes expediently. However, for NUL checks, it's scantily used. Instead, our NUL checks either use `contains` or `memchr`. I switched all of the `contains(b'\0')` I could find to using memchr instead. I marked the failure paths as cold to hint to LLVM that interior NULs are truly exceptional. This should help branch prediction a bit which is nice for string/bytes functions since they are likely called a lot.
This commit is contained in:
1 parent
9648f30605
commit
71f2ffc289
21 files changed
+134
-115
No files matched your search
Generated
+1
@@ -3431,6 +3431,7 @@ dependencies = [
|
||||
"libffi",
|
||||
"libloading 0.9.0",
|
||||
"mac_address",
|
||||
"memchr",
|
||||
"memmap2 0.9.11",
|
||||
"nix 0.31.3",
|
||||
"num-traits",
|
||||
|
||||
@@ -51,6 +51,7 @@ libffi = { workspace = true, features = ["system"] }
|
||||
system-configuration = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
memchr.workspace = true
|
||||
junction = { workspace = true }
|
||||
schannel = { workspace = true }
|
||||
widestring = { workspace = true }
|
||||
|
||||
@@ -32,12 +32,13 @@ pub enum SemError {
|
||||
AlreadyExists,
|
||||
NotFound,
|
||||
InvalidInput,
|
||||
InteriorNul,
|
||||
Other(i32),
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl SemError {
|
||||
fn from_errno(err: Errno) -> Self {
|
||||
const fn from_errno(err: Errno) -> Self {
|
||||
match err {
|
||||
Errno::EAGAIN => Self::WouldBlock,
|
||||
Errno::ETIMEDOUT => Self::TimedOut,
|
||||
@@ -49,14 +50,14 @@ impl SemError {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw_os_error(self) -> i32 {
|
||||
pub const fn raw_os_error(self) -> i32 {
|
||||
match self {
|
||||
Self::WouldBlock => Errno::EAGAIN as i32,
|
||||
Self::TimedOut => Errno::ETIMEDOUT as i32,
|
||||
Self::Interrupted => Errno::EINTR as i32,
|
||||
Self::AlreadyExists => Errno::EEXIST as i32,
|
||||
Self::NotFound => Errno::ENOENT as i32,
|
||||
Self::InvalidInput => Errno::EINVAL as i32,
|
||||
Self::InvalidInput | Self::InteriorNul => Errno::EINVAL as i32,
|
||||
Self::Other(code) => code,
|
||||
}
|
||||
}
|
||||
@@ -119,7 +120,7 @@ impl SemHandle {
|
||||
value: u32,
|
||||
unlink: bool,
|
||||
) -> Result<(Self, Option<String>), SemError> {
|
||||
let cname = semaphore_name(name).map_err(|_| SemError::InvalidInput)?;
|
||||
let cname = semaphore_name(name)?;
|
||||
let raw =
|
||||
unsafe { libc::sem_open(cname.as_ptr(), libc::O_CREAT | libc::O_EXCL, 0o600, value) };
|
||||
if raw == libc::SEM_FAILED {
|
||||
@@ -141,7 +142,7 @@ impl SemHandle {
|
||||
}
|
||||
|
||||
pub fn open_existing(name: &str) -> Result<Self, SemError> {
|
||||
let cname = semaphore_name(name).map_err(|_| SemError::InvalidInput)?;
|
||||
let cname = semaphore_name(name)?;
|
||||
let raw = unsafe { libc::sem_open(cname.as_ptr(), 0) };
|
||||
if raw == libc::SEM_FAILED {
|
||||
Err(SemError::from_errno(Errno::last()))
|
||||
@@ -305,18 +306,18 @@ pub fn is_too_many_posts(err: u32) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn semaphore_name(name: &str) -> Result<CString, alloc::ffi::NulError> {
|
||||
let mut full = String::with_capacity(name.len() + 1);
|
||||
pub fn semaphore_name(name: &str) -> Result<CString, SemError> {
|
||||
let mut full = String::with_capacity(name.len() + 2);
|
||||
if !name.starts_with('/') {
|
||||
full.push('/');
|
||||
}
|
||||
full.push_str(name);
|
||||
CString::new(full)
|
||||
CString::new(full).map_err(|_| SemError::InteriorNul)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn sem_unlink(name: &str) -> Result<(), SemError> {
|
||||
let cname = semaphore_name(name).map_err(|_| SemError::InvalidInput)?;
|
||||
let cname = semaphore_name(name)?;
|
||||
let res = unsafe { libc::sem_unlink(cname.as_ptr()) };
|
||||
if res < 0 {
|
||||
Err(SemError::from_errno(Errno::last()))
|
||||
|
||||
@@ -638,10 +638,9 @@ unsafe extern "C" {
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn strftime_ascii(fmt: &str, tm: &libc::tm) -> Result<String, CheckedTmError> {
|
||||
if fmt.contains('\0') {
|
||||
return Err(CheckedTmError::EmbeddedNul);
|
||||
}
|
||||
let fmt_wide: Vec<u16> = fmt.encode_utf16().chain(core::iter::once(0)).collect();
|
||||
let fmt_wide = widestring::WideCString::from_str(fmt)
|
||||
.map_err(|_| CheckedTmError::EmbeddedNul)?
|
||||
.into_vec_with_nul();
|
||||
let mut size = 1024usize;
|
||||
let max_scale = 256usize.saturating_mul(fmt.len().max(1));
|
||||
loop {
|
||||
|
||||
@@ -3,22 +3,20 @@
|
||||
reason = "This module mirrors Win32 APIs with raw handle and pointer parameters."
|
||||
)]
|
||||
|
||||
use core::hint::cold_path;
|
||||
use std::{io, path::Path};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{HANDLE, HMODULE, WAIT_FAILED},
|
||||
System::Threading::PROCESS_INFORMATION,
|
||||
};
|
||||
|
||||
use crate::windows::{CheckWin32Bool, CheckWin32Handle};
|
||||
|
||||
use memchr::memchr;
|
||||
pub use windows_sys::Win32::{
|
||||
Foundation::{
|
||||
DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS,
|
||||
ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_NETNAME_DELETED, ERROR_NO_DATA,
|
||||
ERROR_NO_SYSTEM_RESOURCES, ERROR_NOT_FOUND, ERROR_OPERATION_ABORTED, ERROR_PIPE_BUSY,
|
||||
ERROR_PIPE_CONNECTED, ERROR_PORT_UNREACHABLE, ERROR_PRIVILEGE_NOT_HELD, ERROR_SEM_TIMEOUT,
|
||||
ERROR_SUCCESS, GENERIC_READ, GENERIC_WRITE, STILL_ACTIVE, WAIT_ABANDONED_0, WAIT_OBJECT_0,
|
||||
WAIT_TIMEOUT,
|
||||
ERROR_SUCCESS, GENERIC_READ, GENERIC_WRITE, HANDLE, HMODULE, STILL_ACTIVE,
|
||||
WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT,
|
||||
},
|
||||
Globalization::{
|
||||
LCMAP_FULLWIDTH, LCMAP_HALFWIDTH, LCMAP_HIRAGANA, LCMAP_KATAKANA, LCMAP_LINGUISTIC_CASING,
|
||||
@@ -57,12 +55,12 @@ pub use windows_sys::Win32::{
|
||||
ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, CREATE_BREAKAWAY_FROM_JOB,
|
||||
CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP,
|
||||
CREATE_NO_WINDOW, DETACHED_PROCESS, HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS,
|
||||
NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_DUP_HANDLE, REALTIME_PRIORITY_CLASS,
|
||||
STARTF_FORCEOFFFEEDBACK, STARTF_FORCEONFEEDBACK, STARTF_PREVENTPINNING,
|
||||
STARTF_RUNFULLSCREEN, STARTF_TITLEISAPPID, STARTF_TITLEISLINKNAME,
|
||||
STARTF_UNTRUSTEDSOURCE, STARTF_USECOUNTCHARS, STARTF_USEFILLATTRIBUTE,
|
||||
STARTF_USEHOTKEY, STARTF_USEPOSITION, STARTF_USESHOWWINDOW, STARTF_USESIZE,
|
||||
STARTF_USESTDHANDLES,
|
||||
NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_DUP_HANDLE, PROCESS_INFORMATION,
|
||||
REALTIME_PRIORITY_CLASS, STARTF_FORCEOFFFEEDBACK, STARTF_FORCEONFEEDBACK,
|
||||
STARTF_PREVENTPINNING, STARTF_RUNFULLSCREEN, STARTF_TITLEISAPPID,
|
||||
STARTF_TITLEISLINKNAME, STARTF_UNTRUSTEDSOURCE, STARTF_USECOUNTCHARS,
|
||||
STARTF_USEFILLATTRIBUTE, STARTF_USEHOTKEY, STARTF_USEPOSITION, STARTF_USESHOWWINDOW,
|
||||
STARTF_USESIZE, STARTF_USESTDHANDLES,
|
||||
},
|
||||
},
|
||||
UI::WindowsAndMessaging::SW_HIDE,
|
||||
@@ -312,7 +310,8 @@ pub fn build_environment_block(
|
||||
|
||||
let mut last_entry: HashMap<String, Vec<u16>> = HashMap::new();
|
||||
for (key, value) in entries {
|
||||
if key.contains('\0') || value.contains('\0') {
|
||||
if memchr(b'\0', key.as_bytes()).is_some() || memchr(b'\0', value.as_bytes()).is_some() {
|
||||
cold_path();
|
||||
return Err(BuildEnvironmentBlockError::ContainsNul);
|
||||
}
|
||||
if key.is_empty() || key[1..].contains('=') {
|
||||
|
||||
@@ -10,6 +10,7 @@ mod grp {
|
||||
exceptions,
|
||||
types::PyStructSequence,
|
||||
};
|
||||
use core::hint::cold_path;
|
||||
use rustpython_host_env::grp as host_grp;
|
||||
|
||||
#[pystruct_sequence_data]
|
||||
@@ -61,10 +62,11 @@ mod grp {
|
||||
|
||||
#[pyfunction]
|
||||
fn getgrnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<GroupData> {
|
||||
let gr_name = name.as_str();
|
||||
if gr_name.contains('\0') {
|
||||
if name.as_pystr().contains_nuls() {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_char_error(vm));
|
||||
}
|
||||
let gr_name = name.as_str();
|
||||
let group = host_grp::getgrnam(gr_name).map_err(|err| err.into_pyexception(vm))?;
|
||||
let group = group.ok_or_else(|| {
|
||||
vm.new_key_error(
|
||||
|
||||
@@ -24,9 +24,9 @@ mod mmap {
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use num_traits::Signed;
|
||||
#[cfg(windows)]
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
#[cfg(windows)]
|
||||
use {core::hint::cold_path, memchr::memchr, rustpython_vm::exceptions, std::io};
|
||||
|
||||
#[cfg(unix)]
|
||||
use rustpython_host_env::crt_fd;
|
||||
@@ -460,8 +460,9 @@ mod mmap {
|
||||
let s = obj
|
||||
.try_to_value::<String>(vm)
|
||||
.map_err(|_| vm.new_type_error("tagname must be a string or None"))?;
|
||||
if s.contains('\0') {
|
||||
return Err(vm.new_value_error("tagname must not contain null characters"));
|
||||
if memchr(b'\0', s.as_bytes()).is_some() {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_char_error(vm));
|
||||
}
|
||||
Some(s)
|
||||
}
|
||||
|
||||
@@ -811,7 +811,7 @@ mod _multiprocessing {
|
||||
let value = args.value as u32;
|
||||
let (handle, name) =
|
||||
SemHandle::create(&args.name, value, args.unlink).map_err(|err| {
|
||||
if err == SemError::InvalidInput && args.name.contains('\0') {
|
||||
if err == SemError::InteriorNul {
|
||||
exceptions::nul_char_error(vm)
|
||||
} else {
|
||||
os_error(vm, err)
|
||||
@@ -835,7 +835,7 @@ mod _multiprocessing {
|
||||
#[pyfunction]
|
||||
fn sem_unlink(name: String, vm: &VirtualMachine) -> PyResult<()> {
|
||||
host_multiprocessing::sem_unlink(&name).map_err(|err| {
|
||||
if err == SemError::InvalidInput && name.contains('\0') {
|
||||
if err == SemError::InteriorNul {
|
||||
exceptions::nul_char_error(vm)
|
||||
} else {
|
||||
os_error(vm, err)
|
||||
|
||||
@@ -53,6 +53,8 @@ fn probe() -> &'static ProbeResult {
|
||||
#[cfg(ossl111)] ossl111,
|
||||
#[cfg(windows)] windows))]
|
||||
mod _ssl {
|
||||
use core::hint::cold_path;
|
||||
|
||||
use super::{bio, probe};
|
||||
|
||||
// Import error types and helpers used in this module (others are exposed via pymodule(with(...)))
|
||||
@@ -85,6 +87,7 @@ mod _ssl {
|
||||
};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use foreign_types_shared::{ForeignType, ForeignTypeRef};
|
||||
use memchr::memchr;
|
||||
use openssl::{
|
||||
asn1::{Asn1Object, Asn1ObjectRef},
|
||||
error::ErrorStack,
|
||||
@@ -1039,12 +1042,13 @@ mod _ssl {
|
||||
|
||||
#[pymethod]
|
||||
fn set_ciphers(&self, cipherlist: PyStrRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let ciphers: &str = cipherlist.as_ref();
|
||||
if ciphers.contains('\0') {
|
||||
if cipherlist.contains_nuls() {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_char_error(vm));
|
||||
}
|
||||
|
||||
self.builder()
|
||||
.set_cipher_list(ciphers)
|
||||
.set_cipher_list(cipherlist.as_ref())
|
||||
.map_err(|_| new_ssl_error(vm, "No cipher can be selected."))
|
||||
}
|
||||
|
||||
@@ -1096,9 +1100,6 @@ mod _ssl {
|
||||
let name_cstr = match name {
|
||||
Either::A(s) => {
|
||||
let s: &str = s.as_ref();
|
||||
if s.contains('\0') {
|
||||
return Err(exceptions::nul_char_error(vm));
|
||||
}
|
||||
s.to_cstring(vm)?
|
||||
}
|
||||
Either::B(b) => std::ffi::CString::new(b.borrow_buf().to_vec())
|
||||
@@ -2031,15 +2032,16 @@ mod _ssl {
|
||||
|
||||
// Configure server hostname
|
||||
if let Some(hostname) = &server_hostname {
|
||||
if hostname.contains_nuls() {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_char_type_error(vm));
|
||||
}
|
||||
let hostname_str: &str = hostname.as_ref();
|
||||
if hostname_str.is_empty() || hostname_str.starts_with('.') {
|
||||
return Err(vm.new_value_error(
|
||||
"server_hostname cannot be an empty string or start with a leading dot.",
|
||||
));
|
||||
}
|
||||
if hostname_str.contains('\0') {
|
||||
return Err(exceptions::nul_char_type_error(vm));
|
||||
}
|
||||
let ip = hostname_str.parse::<core::net::IpAddr>();
|
||||
if ip.is_err() {
|
||||
ssl.set_hostname(hostname_str)
|
||||
|
||||
@@ -67,9 +67,11 @@ mod _ssl {
|
||||
use alloc::sync::Arc;
|
||||
use core::{
|
||||
hash::{Hash, Hasher},
|
||||
hint::cold_path,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
use memchr::memchr;
|
||||
use rustpython_vm::exceptions;
|
||||
use std::{
|
||||
collections::{HashMap, hash_map::DefaultHasher},
|
||||
@@ -392,7 +394,8 @@ mod _ssl {
|
||||
// IP addresses are allowed as server_hostname
|
||||
// SNI will not be sent for IP addresses
|
||||
|
||||
if hostname.contains('\0') {
|
||||
if memchr(b'\0', hostname.as_bytes()).is_some() {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_char_type_error(vm));
|
||||
}
|
||||
|
||||
@@ -1854,25 +1857,7 @@ mod _ssl {
|
||||
let hostname = match args.server_hostname.into_option().flatten() {
|
||||
Some(hostname_str) => {
|
||||
let hostname = hostname_str.as_str();
|
||||
|
||||
// Validate hostname
|
||||
if hostname.is_empty() {
|
||||
return Err(vm.new_value_error("server_hostname cannot be an empty string"));
|
||||
}
|
||||
|
||||
// Check if it starts with a dot
|
||||
if hostname.starts_with('.') {
|
||||
return Err(vm.new_value_error("server_hostname cannot start with a dot"));
|
||||
}
|
||||
|
||||
// IP addresses are allowed
|
||||
// SNI will not be sent for IP addresses
|
||||
|
||||
// Check for NULL bytes
|
||||
if hostname.contains('\0') {
|
||||
return Err(exceptions::nul_char_error(vm));
|
||||
}
|
||||
|
||||
validate_hostname(hostname, vm)?;
|
||||
Some(hostname.to_string())
|
||||
}
|
||||
None => None,
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::{
|
||||
};
|
||||
use bstr::ByteSlice;
|
||||
use core::{mem::size_of, ops::Deref};
|
||||
use memchr::memchr;
|
||||
|
||||
#[pyclass(module = false, name = "bytes")]
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -169,6 +170,13 @@ impl PyBytes {
|
||||
.map(|x| vm.ctx.new_bytes(x).into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check bytes for interior NULs.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains_nuls(&self) -> bool {
|
||||
memchr(b'\0', self.as_bytes()).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl PyRef<PyBytes> {
|
||||
@@ -218,7 +226,7 @@ impl PyBytes {
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
pub const fn as_bytes(&self) -> &[u8] {
|
||||
self.inner.as_bytes()
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ use bstr::ByteSlice;
|
||||
use core::ffi::CStr;
|
||||
use core::{char, mem, ops::Range};
|
||||
use itertools::Itertools;
|
||||
use memchr::memchr;
|
||||
use num_traits::ToPrimitive;
|
||||
use rustpython_common::{
|
||||
ascii,
|
||||
@@ -545,6 +546,13 @@ impl PyStr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check string bytes for interior NULs.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains_nuls(&self) -> bool {
|
||||
memchr(b'\0', self.as_bytes()).is_some()
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(&self) -> Cow<'_, str> {
|
||||
self.to_str()
|
||||
.map_or_else(|| self.as_wtf8().to_string_lossy(), Cow::Borrowed)
|
||||
@@ -2150,7 +2158,7 @@ impl PyUtf8Str {
|
||||
|
||||
impl Py<PyUtf8Str> {
|
||||
/// Upcast to PyStr.
|
||||
pub fn as_pystr(&self) -> &Py<PyStr> {
|
||||
pub const fn as_pystr(&self) -> &Py<PyStr> {
|
||||
unsafe {
|
||||
// Safety: PyUtf8Str is a wrapper around PyStr, so this cast is safe.
|
||||
&*(self as *const Self as *const Py<PyStr>)
|
||||
|
||||
@@ -255,8 +255,8 @@ fn write_bytearray_repr_char(ch: u8, buf: &mut String) {
|
||||
|
||||
impl PyBytesInner {
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.elements
|
||||
pub const fn as_bytes(&self) -> &[u8] {
|
||||
self.elements.as_slice()
|
||||
}
|
||||
|
||||
fn new_repr_overflow_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
protocol::PyBuffer,
|
||||
};
|
||||
use alloc::borrow::Cow;
|
||||
use core::hint::cold_path;
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
|
||||
/// Helper to implement os.fspath()
|
||||
@@ -36,21 +37,20 @@ impl FsPath {
|
||||
msg: &'static str,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Self> {
|
||||
let check_nul = |b: &[u8]| {
|
||||
if !check_for_nul || memchr::memchr(b'\0', b).is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::exceptions::nul_char_error(vm))
|
||||
}
|
||||
};
|
||||
let match1 = |obj: PyObjectRef| {
|
||||
let pathlike = match_class!(match obj {
|
||||
s @ PyStr => {
|
||||
check_nul(s.as_bytes())?;
|
||||
if check_for_nul && s.contains_nuls() {
|
||||
cold_path();
|
||||
return Err(crate::exceptions::nul_char_error(vm));
|
||||
}
|
||||
Self::Str(s)
|
||||
}
|
||||
b @ PyBytes => {
|
||||
check_nul(&b)?;
|
||||
if check_for_nul && b.contains_nuls() {
|
||||
cold_path();
|
||||
return Err(crate::exceptions::nul_char_error(vm));
|
||||
}
|
||||
Self::Bytes(b)
|
||||
}
|
||||
obj => return Ok(Err(obj)),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject},
|
||||
function::FsPath,
|
||||
};
|
||||
use core::hint::cold_path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// path_converter
|
||||
@@ -149,6 +150,7 @@ impl PathConverter {
|
||||
if self.non_strict || memchr::memchr(b'\0', b).is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
cold_path();
|
||||
Err(vm.new_value_error(format!(
|
||||
"{}embedded null character in {}",
|
||||
self.error_prefix(),
|
||||
|
||||
@@ -6,6 +6,8 @@ use crate::common::static_cell::StaticCell;
|
||||
|
||||
#[pymodule(with(#[cfg(windows)] _codecs_windows))]
|
||||
mod _codecs {
|
||||
use core::hint::cold_path;
|
||||
|
||||
use crate::codecs::{ErrorsHandler, PyDecodeContext, PyEncodeContext};
|
||||
use crate::common::encodings;
|
||||
use crate::common::wtf8::Wtf8Buf;
|
||||
@@ -29,7 +31,8 @@ mod _codecs {
|
||||
|
||||
#[pyfunction]
|
||||
fn lookup(encoding: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult {
|
||||
if encoding.as_str().contains('\0') {
|
||||
if encoding.as_pystr().contains_nuls() {
|
||||
cold_path();
|
||||
return Err(nul_char_error(vm));
|
||||
}
|
||||
vm.state
|
||||
@@ -105,7 +108,8 @@ mod _codecs {
|
||||
|
||||
#[pyfunction]
|
||||
fn lookup_error(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult {
|
||||
if name.as_str().contains('\0') {
|
||||
if name.as_pystr().contains_nuls() {
|
||||
cold_path();
|
||||
return Err(nul_char_error(vm));
|
||||
}
|
||||
vm.state.codec_registry.lookup_error(name.as_str(), vm)
|
||||
@@ -113,7 +117,8 @@ mod _codecs {
|
||||
|
||||
#[pyfunction]
|
||||
fn _unregister_error(errors: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
if errors.as_str().contains('\0') {
|
||||
if errors.as_pystr().contains_nuls() {
|
||||
cold_path();
|
||||
return Err(nul_char_error(vm));
|
||||
}
|
||||
vm.state
|
||||
|
||||
@@ -150,6 +150,7 @@ mod _io {
|
||||
use alloc::borrow::Cow;
|
||||
use bstr::ByteSlice;
|
||||
use core::{
|
||||
hint::cold_path,
|
||||
ops::Range,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
@@ -2854,7 +2855,8 @@ mod _io {
|
||||
}
|
||||
|
||||
fn validate_errors(errors: &PyRef<PyUtf8Str>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if errors.as_str().contains('\0') {
|
||||
if errors.as_pystr().contains_nuls() {
|
||||
cold_path();
|
||||
return Err(nul_char_error(vm));
|
||||
}
|
||||
vm.state
|
||||
@@ -2894,12 +2896,7 @@ mod _io {
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
Some(enc) => {
|
||||
if enc.as_str().contains('\0') {
|
||||
return Err(nul_char_error(vm));
|
||||
}
|
||||
enc
|
||||
}
|
||||
Some(enc) => enc,
|
||||
_ => match vm.import("locale", 0) {
|
||||
Ok(locale) => locale
|
||||
.get_attr("getencoding", vm)?
|
||||
@@ -2914,7 +2911,8 @@ mod _io {
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
};
|
||||
if encoding.as_str().contains('\0') {
|
||||
if encoding.as_pystr().contains_nuls() {
|
||||
cold_path();
|
||||
return Err(nul_char_error(vm));
|
||||
}
|
||||
Ok(encoding)
|
||||
@@ -3067,7 +3065,8 @@ mod _io {
|
||||
let mut write_through = None;
|
||||
|
||||
if let Some(enc) = args.encoding {
|
||||
if enc.as_str().contains('\0') && enc.as_str().starts_with("locale") {
|
||||
if enc.as_pystr().contains_nuls() && enc.as_str().starts_with("locale") {
|
||||
cold_path();
|
||||
return Err(vm.new_lookup_error(format!("unknown encoding: {enc}")));
|
||||
}
|
||||
let resolved = Self::resolve_encoding(Some(enc), vm)?;
|
||||
|
||||
@@ -15,6 +15,7 @@ pub(crate) mod module {
|
||||
ospath::{OsPath, OsPathOrFd},
|
||||
stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory},
|
||||
};
|
||||
use core::hint::cold_path;
|
||||
use libc::intptr_t;
|
||||
use rustpython_common::wtf8::Wtf8Buf;
|
||||
use rustpython_host_env::nt as host_nt;
|
||||
@@ -551,7 +552,8 @@ pub(crate) mod module {
|
||||
let value_str = value.expect_str();
|
||||
|
||||
// Validate: no null characters in key or value
|
||||
if key_str.contains('\0') || value_str.contains('\0') {
|
||||
if key.contains_nuls() || value.contains_nuls() {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_char_error(vm));
|
||||
}
|
||||
// Validate: empty key or '=' in key after position 0
|
||||
|
||||
+24
-14
@@ -211,7 +211,7 @@ pub(super) mod _os {
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
use core::marker::PhantomData;
|
||||
use core::time::Duration;
|
||||
use core::{hint::cold_path, time::Duration};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use rustpython_common::wtf8::Wtf8Buf;
|
||||
#[cfg(windows)]
|
||||
@@ -487,10 +487,16 @@ pub(super) mod _os {
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn env_bytes_as_bytes(obj: &crate::function::Either<PyStrRef, PyBytesRef>) -> &[u8] {
|
||||
fn env_bytes_as_bytes_checked(
|
||||
obj: &crate::function::Either<PyStrRef, PyBytesRef>,
|
||||
) -> Option<&[u8]> {
|
||||
match obj {
|
||||
crate::function::Either::A(s) => s.as_bytes(),
|
||||
crate::function::Either::B(b) => b.as_bytes(),
|
||||
crate::function::Either::A(s) if !s.contains_nuls() => Some(s.as_bytes()),
|
||||
crate::function::Either::B(b) if !b.contains_nuls() => Some(b.as_bytes()),
|
||||
_ => {
|
||||
cold_path();
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,9 +521,10 @@ pub(super) mod _os {
|
||||
// defining hidden environment variables.
|
||||
if key_str.is_empty()
|
||||
|| key_str.get(1..).is_some_and(|s| s.contains('='))
|
||||
|| key_str.contains('\0')
|
||||
|| value_str.contains('\0')
|
||||
|| key.contains_nuls()
|
||||
|| value.contains_nuls()
|
||||
{
|
||||
cold_path();
|
||||
return Err(vm.new_value_error("illegal environment variable name"));
|
||||
}
|
||||
let env_str = format!("{key_str}={value_str}");
|
||||
@@ -537,11 +544,13 @@ pub(super) mod _os {
|
||||
value: crate::function::Either<PyStrRef, PyBytesRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let key = env_bytes_as_bytes(&key);
|
||||
let value = env_bytes_as_bytes(&value);
|
||||
if key.contains(&b'\0') || value.contains(&b'\0') {
|
||||
let (Some(key), Some(value)) = (
|
||||
env_bytes_as_bytes_checked(&key),
|
||||
env_bytes_as_bytes_checked(&value),
|
||||
) else {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_byte_error(vm));
|
||||
}
|
||||
};
|
||||
if key.is_empty() || key.contains(&b'=') {
|
||||
return Err(vm.new_value_error("illegal environment variable name"));
|
||||
}
|
||||
@@ -560,8 +569,9 @@ pub(super) mod _os {
|
||||
// defining hidden environment variables.
|
||||
if key_str.is_empty()
|
||||
|| key_str.get(1..).is_some_and(|s| s.contains('='))
|
||||
|| key_str.contains('\0')
|
||||
|| key.contains_nuls()
|
||||
{
|
||||
cold_path();
|
||||
return Err(vm.new_value_error("illegal environment variable name"));
|
||||
}
|
||||
// "key=" to unset (empty value removes the variable)
|
||||
@@ -581,10 +591,10 @@ pub(super) mod _os {
|
||||
key: crate::function::Either<PyStrRef, PyBytesRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let key = env_bytes_as_bytes(&key);
|
||||
if key.contains(&b'\0') {
|
||||
let Some(key) = env_bytes_as_bytes_checked(&key) else {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_byte_error(vm));
|
||||
}
|
||||
};
|
||||
if key.is_empty() || key.contains(&b'=') {
|
||||
let x = vm.new_errno_error(
|
||||
22,
|
||||
|
||||
@@ -11,6 +11,7 @@ mod pwd {
|
||||
exceptions,
|
||||
types::PyStructSequence,
|
||||
};
|
||||
use core::hint::cold_path;
|
||||
use rustpython_host_env::pwd as host_pwd;
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
@@ -50,15 +51,16 @@ mod pwd {
|
||||
|
||||
#[pyfunction]
|
||||
fn getpwnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PasswdData> {
|
||||
let pw_name = name.as_str();
|
||||
if pw_name.contains('\0') {
|
||||
if name.as_pystr().contains_nuls() {
|
||||
cold_path();
|
||||
return Err(exceptions::nul_char_error(vm));
|
||||
}
|
||||
let user = host_pwd::getpwnam(name.as_str());
|
||||
let name = name.as_str();
|
||||
let user = host_pwd::getpwnam(name);
|
||||
let user = user.ok_or_else(|| {
|
||||
vm.new_key_error(
|
||||
vm.ctx
|
||||
.new_str(format!("getpwnam(): name not found: {pw_name}"))
|
||||
.new_str(format!("getpwnam(): name not found: {name}"))
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::{
|
||||
PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyStr, PyUtf8Str},
|
||||
convert::{ToPyException, ToPyObject},
|
||||
exceptions::nul_char_error,
|
||||
};
|
||||
|
||||
pub fn hash_iter<'a, I: IntoIterator<Item = &'a PyObjectRef>>(
|
||||
@@ -24,13 +23,6 @@ pub trait ToCString: AsRef<Wtf8> {
|
||||
fn to_cstring(&self, vm: &VirtualMachine) -> PyResult<alloc::ffi::CString> {
|
||||
alloc::ffi::CString::new(self.as_ref().as_bytes()).map_err(|err| err.to_pyexception(vm))
|
||||
}
|
||||
fn ensure_no_nul(&self, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if self.as_ref().as_bytes().contains(&b'\0') {
|
||||
Err(nul_char_error(vm))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCString for &str {}
|
||||
|
||||
Reference in new issue
Block a user