Skip to content

Commit fd77ce9

Browse files
committed
Implement the stable ABI bytes formatting functions
Assisted-by: OpenAI Codex:model identifier unavailable
1 parent c516ca9 commit fd77ce9

1 file changed

Lines changed: 131 additions & 1 deletion

File tree

‎crates/capi/src/bytesobject.rs‎

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::object::define_py_check;
22
use crate::util::FfiPtrExt;
33
use crate::{PyObject, pystate::with_vm};
4-
use core::ffi::{CStr, c_char, c_int};
4+
use core::ffi::{CStr, VaList, c_char, c_int, c_long, c_uint, c_ulong, c_void};
55
use rustpython_vm::builtins::PyBytes;
66

77
define_py_check!(fn PyBytes_Check, types.bytes_type);
@@ -38,6 +38,136 @@ pub unsafe extern "C" fn PyBytes_FromString(s: *const c_char) -> *mut PyObject {
3838
})
3939
}
4040

41+
#[unsafe(no_mangle)]
42+
pub unsafe extern "C" fn PyBytes_FromFormat(format: *const c_char, args: ...) -> *mut PyObject {
43+
unsafe { PyBytes_FromFormatV(format, args) }
44+
}
45+
46+
#[unsafe(no_mangle)]
47+
pub unsafe extern "C" fn PyBytes_FromFormatV(
48+
format: *const c_char,
49+
mut args: VaList<'_>,
50+
) -> *mut PyObject {
51+
with_vm(|vm| {
52+
let format = unsafe { CStr::from_ptr(format) }.to_bytes();
53+
let mut result = Vec::with_capacity(format.len());
54+
let mut pos = 0;
55+
while pos < format.len() {
56+
if format[pos] != b'%' {
57+
result.push(format[pos]);
58+
pos += 1;
59+
continue;
60+
}
61+
let start = pos;
62+
pos += 1;
63+
// The C API ignores field width and applies precision only to %s.
64+
while format.get(pos).is_some_and(u8::is_ascii_digit) {
65+
pos += 1;
66+
}
67+
let mut precision = 0usize;
68+
if format.get(pos) == Some(&b'.') {
69+
pos += 1;
70+
while let Some(&digit) = format.get(pos).filter(|b| b.is_ascii_digit()) {
71+
precision = precision
72+
.saturating_mul(10)
73+
.saturating_add((digit - b'0').into());
74+
pos += 1;
75+
}
76+
}
77+
while format
78+
.get(pos)
79+
.is_some_and(|b| *b != b'%' && !b.is_ascii_alphabetic())
80+
{
81+
pos += 1;
82+
}
83+
let modifier = if matches!(format.get(pos), Some(b'l' | b'z'))
84+
&& matches!(format.get(pos + 1), Some(b'd' | b'u'))
85+
{
86+
let modifier = format[pos];
87+
pos += 1;
88+
Some(modifier)
89+
} else {
90+
None
91+
};
92+
match format.get(pos) {
93+
Some(b'c') => {
94+
let value = unsafe { args.next_arg::<c_int>() };
95+
let value = u8::try_from(value).map_err(|_| {
96+
vm.new_overflow_error(
97+
"PyBytes_FromFormatV(): %c format expects an integer in range [0; 255]",
98+
)
99+
})?;
100+
result.push(value);
101+
}
102+
Some(b'd') => {
103+
let value = match modifier {
104+
Some(b'l') => unsafe { args.next_arg::<c_long>() }.to_string(),
105+
Some(b'z') => unsafe { args.next_arg::<isize>() }.to_string(),
106+
_ => unsafe { args.next_arg::<c_int>() }.to_string(),
107+
};
108+
result.extend_from_slice(value.as_bytes());
109+
}
110+
Some(b'u') => {
111+
let value = match modifier {
112+
Some(b'l') => unsafe { args.next_arg::<c_ulong>() }.to_string(),
113+
Some(b'z') => unsafe { args.next_arg::<usize>() }.to_string(),
114+
_ => unsafe { args.next_arg::<c_uint>() }.to_string(),
115+
};
116+
result.extend_from_slice(value.as_bytes());
117+
}
118+
Some(b'i') => result
119+
.extend_from_slice(unsafe { args.next_arg::<c_int>() }.to_string().as_bytes()),
120+
Some(b'x') => result.extend_from_slice(
121+
format!("{:x}", unsafe { args.next_arg::<c_int>() } as c_uint).as_bytes(),
122+
),
123+
Some(b's') => {
124+
let string = unsafe { args.next_arg::<*const c_char>() };
125+
if precision == 0 {
126+
result.extend_from_slice(unsafe { CStr::from_ptr(string) }.to_bytes());
127+
} else {
128+
// A precision-limited string need not have a terminator
129+
// beyond the specified readable region.
130+
for offset in 0..precision {
131+
let byte = unsafe { *string.add(offset) } as u8;
132+
if byte == 0 {
133+
break;
134+
}
135+
result.push(byte);
136+
}
137+
}
138+
}
139+
Some(b'p') => {
140+
let pointer = unsafe { args.next_arg::<*const c_void>() };
141+
// Preserve the platform's %p spelling, including Windows
142+
// uppercase digits, but always supply the C API's 0x prefix.
143+
let mut buffer = [0 as c_char; 2 * size_of::<usize>() + 16];
144+
unsafe {
145+
libc::snprintf(buffer.as_mut_ptr(), buffer.len(), c"%p".as_ptr(), pointer);
146+
}
147+
let pointer = unsafe { CStr::from_ptr(buffer.as_ptr()) }.to_bytes();
148+
result.extend_from_slice(b"0x");
149+
result.extend_from_slice(
150+
if pointer.starts_with(b"0x") || pointer.starts_with(b"0X") {
151+
&pointer[2..]
152+
} else {
153+
pointer
154+
},
155+
);
156+
}
157+
Some(b'%') => result.push(b'%'),
158+
_ => {
159+
// The first unrecognized format copies the remaining
160+
// format verbatim, without consuming any more arguments.
161+
result.extend_from_slice(&format[start..]);
162+
break;
163+
}
164+
}
165+
pos += 1;
166+
}
167+
Ok(vm.ctx.new_bytes(result))
168+
})
169+
}
170+
41171
#[unsafe(no_mangle)]
42172
pub unsafe extern "C" fn PyBytes_FromObject(obj: *mut PyObject) -> *mut PyObject {
43173
with_vm(|vm| {

0 commit comments

Comments
 (0)