|
1 | 1 | use crate::object::define_py_check; |
2 | 2 | use crate::util::FfiPtrExt; |
3 | 3 | 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}; |
5 | 5 | use rustpython_vm::builtins::PyBytes; |
6 | 6 |
|
7 | 7 | 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 { |
38 | 38 | }) |
39 | 39 | } |
40 | 40 |
|
| 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 | + |
41 | 171 | #[unsafe(no_mangle)] |
42 | 172 | pub unsafe extern "C" fn PyBytes_FromObject(obj: *mut PyObject) -> *mut PyObject { |
43 | 173 | with_vm(|vm| { |
|
0 commit comments