Merge branch 'nulstr' into 'master'

Generalize CStr into NulStr, adding WStr

See merge request redox-os/relibc!739
This commit is contained in:
Jeremy Soller
2025-10-07 10:50:08 -06:00
6 changed files with 475 additions and 1249 deletions
+248 -57
View File
@@ -7,114 +7,305 @@ use alloc::{
string::String,
};
use crate::{
header::string::{strchr, strlen},
platform::types::c_char,
};
use crate::platform::types::{c_char, c_int, wchar_t};
/// C string wrapper, guaranteed to be
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct CStr<'a> {
ptr: NonNull<c_char>,
_marker: PhantomData<&'a [u8]>,
mod private {
pub trait Sealed {}
}
#[derive(Clone, Copy, Debug)]
pub enum Thin {}
#[derive(Clone, Copy, Debug)]
pub enum Wide {}
impl private::Sealed for Thin {}
impl private::Sealed for Wide {}
pub trait Kind: private::Sealed + Copy + 'static {
/// c_char or wchar_t
type C: Copy + 'static;
// u8 or u32
type Char: Copy + From<u8> + Into<u32> + PartialEq + 'static;
const NUL: Self::Char;
const IS_THIN_NOT_WIDE: bool;
fn r2c(c: Self::Char) -> Self::C;
fn c2r(c: Self::C) -> Self::Char;
fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]>;
fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]>;
unsafe fn strlen(s: *const Self::C) -> usize;
unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C;
unsafe fn strchrnul(s: *const Self::C, c: Self::C) -> *const Self::C;
}
impl Kind for Thin {
type C = c_char;
type Char = u8;
const NUL: Self::Char = 0;
const IS_THIN_NOT_WIDE: bool = true;
unsafe fn strlen(s: *const c_char) -> usize {
crate::header::string::strlen(s)
}
unsafe fn strchr(s: *const c_char, c: c_char) -> *const c_char {
crate::header::string::strchr(s, c.into())
}
unsafe fn strchrnul(s: *const c_char, c: c_char) -> *const c_char {
crate::header::string::strchrnul(s, c.into())
}
fn r2c(c: u8) -> c_char {
c as _
}
fn c2r(c: c_char) -> u8 {
c as _
}
fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> {
Some(b)
}
fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> {
Some(c)
}
}
impl Kind for Wide {
type C = wchar_t;
type Char = u32;
const NUL: Self::Char = 0;
const IS_THIN_NOT_WIDE: bool = false;
unsafe fn strlen(s: *const Self::C) -> usize {
crate::header::wchar::wcslen(s)
}
unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C {
crate::header::wchar::wcschr(s, c)
}
unsafe fn strchrnul(mut s: *const Self::C, c: Self::C) -> *const Self::C {
// TODO: optimized function
while s.read() != c && s.read() != 0 {
s = s.add(1);
}
s
}
fn r2c(c: Self::Char) -> Self::C {
c as _
}
fn c2r(c: Self::C) -> Self::Char {
c as _
}
fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> {
None
}
fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> {
None
}
}
impl<'a> CStr<'a> {
/// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct NulStr<'a, T: Kind> {
ptr: NonNull<T::C>,
_marker: PhantomData<&'a [u8]>,
}
pub type CStr<'a> = NulStr<'a, Thin>;
pub type WStr<'a> = NulStr<'a, Wide>;
impl<'a, T: Kind> NulStr<'a, T> {
/// Safety
///
/// The ptr must be valid up to and including the first NUL byte from the base ptr.
pub const unsafe fn from_ptr(ptr: *const c_char) -> Self {
pub const unsafe fn from_ptr(ptr: *const T::C) -> Self {
Self {
ptr: NonNull::new_unchecked(ptr as *mut c_char),
ptr: NonNull::new_unchecked(ptr as *mut T::C),
_marker: PhantomData,
}
}
pub unsafe fn from_nullable_ptr(ptr: *const c_char) -> Option<Self> {
pub unsafe fn from_nullable_ptr(ptr: *const T::C) -> Option<Self> {
if ptr.is_null() {
None
} else {
Some(Self::from_ptr(ptr))
}
}
#[doc(alias = "strchr")]
/// Look for the closest occurence of `c`, and if found, split the string into a slice up to
/// that byte and a `CStr` starting at that byte.
#[inline]
pub fn find(self, c: u8) -> Option<Self> {
#[doc(alias = "strchrnul")]
pub fn find_get_subslice_or_all(
self,
c: impl Into<T::Char>,
) -> Result<(&'a [T::Char], Self), (&'a [T::Char], Self)> {
let c = c.into();
// SAFETY: strchrnul expects self.as_ptr() to be valid up to and including its last NUL
// byte
let found = unsafe { T::strchrnul(self.as_ptr(), T::r2c(c)) };
// SAFETY: the pointer returned from strchrnul is always a substring of this string, and
// hence always valid as a CStr.
let found = unsafe { Self::from_ptr(found) };
let until = unsafe { self.slice_until_substr(found) };
if found.first() == T::NUL {
// The character was not found, and we got the end of the string instead.
Err((until, found))
} else {
Ok((until, found))
}
}
/// # Safety
///
/// `substr` must be contained within `self`
#[inline]
pub unsafe fn slice_until_substr(self, substr: NulStr<'_, T>) -> &'a [T::Char] {
let index = unsafe {
// SAFETY: the sub-pointer as returned by strchr must be derived from the same
// allocation
substr.as_ptr().offset_from(self.as_ptr()) as usize
};
unsafe { core::slice::from_raw_parts(self.as_ptr().cast::<T::Char>(), index) }
}
/// Look for the closest occurence of `c`, and if found, split the string into a slice up to
/// that byte and a `CStr` starting at that byte.
#[inline]
pub fn find_get_subslice(self, c: T::Char) -> Option<(&'a [T::Char], Self)> {
let rest = self.find(c)?;
// SAFETY: the output of strchr is obviously a substring if it doesn't return NULL
Some((unsafe { self.slice_until_substr(rest) }, rest))
}
/// Look for the closest occurence of `c`, and return a new string starting at that byte if
/// found.
#[doc(alias = "strchr")]
#[doc(alias = "wcschr")]
#[inline]
pub fn find(self, c: T::Char) -> Option<Self> {
unsafe {
// SAFETY: the only requirement is for self.as_ptr() to be valid up to and including
// the nearest NUL byte, which this type requires
let ret = strchr(self.as_ptr(), c.into());
let ret = T::strchr(self.as_ptr(), T::r2c(c));
// SAFETY: strchr must either return NULL (not found) or a substring of self, which can
// never exceed the nearest NUL byte of self
Self::from_nullable_ptr(ret)
}
}
// TODO: strrchr, strchrnul wrappers
#[inline]
pub fn contains(self, c: u8) -> bool {
pub fn contains(self, c: T::Char) -> bool {
self.find(c).is_some()
}
#[inline]
pub fn first(self) -> u8 {
pub fn first(self) -> T::Char {
unsafe {
// SAFETY: Self must be valid up to and including its nearest NUL byte, which certainly
// implies its readable length is nonzero (string is empty if this first byte is 0).
self.ptr.read() as u8
T::c2r(self.ptr.read())
}
}
pub fn to_bytes_with_nul(self) -> &'a [u8] {
#[inline]
pub fn first_char(self) -> Option<char> {
char::from_u32(self.first().into())
}
/// Same as `split_first` except also requires that the first char be convertible into `char`
#[inline]
pub fn split_first_char(self) -> Option<(char, Self)> {
self.split_first()
.and_then(|(c, r)| Some((char::from_u32(c.into())?, r)))
}
/// Split this string into `Some((first_byte, string_after_that))` or `None` if empty.
#[inline]
pub fn split_first(self) -> Option<(T::Char, Self)> {
if self.first() == T::NUL {
return None;
}
Some((self.first(), unsafe {
Self::from_ptr(self.as_ptr().add(1))
}))
}
pub fn to_chars_with_nul(self) -> &'a [T::Char] {
unsafe {
// SAFETY: The string must be valid at least until (and including) the NUL byte.
let len = strlen(self.ptr.as_ptr());
let len = T::strlen(self.ptr.as_ptr());
core::slice::from_raw_parts(self.ptr.as_ptr().cast(), len + 1)
}
}
pub fn to_bytes(self) -> &'a [u8] {
let s = self.to_bytes_with_nul();
pub fn to_chars(self) -> &'a [T::Char] {
let s = self.to_chars_with_nul();
&s[..s.len() - 1]
}
pub const fn as_ptr(self) -> *const T::C {
self.ptr.as_ptr()
}
pub const unsafe fn from_chars_with_nul_unchecked(chars: &'a [T::Char]) -> Self {
Self::from_ptr(chars.as_ptr().cast())
}
pub fn from_chars_with_nul(chars: &'a [T::Char]) -> Result<Self, FromCharsWithNulError> {
if chars.last() != Some(&T::NUL) || chars[..chars.len() - 1].contains(&T::NUL) {
return Err(FromCharsWithNulError);
}
Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) })
}
pub fn from_chars_until_nul(chars: &'a [T::Char]) -> Result<Self, FromCharsUntilNulError> {
if !chars.contains(&T::NUL) {
return Err(FromCharsUntilNulError);
}
Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) })
}
/// Scan the string to get its length.
#[doc(alias = "strlen")]
#[doc(alias = "wcslen")]
pub fn len(self) -> usize {
self.to_chars().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.first() == T::NUL
}
}
impl<'a> CStr<'a> {
pub fn to_owned_cstring(self) -> CString {
CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) })
}
pub fn borrow(string: &'a CString) -> Self {
unsafe { Self::from_ptr(string.as_ptr()) }
}
#[inline]
pub fn to_bytes(self) -> &'a [u8] {
self.to_chars()
}
#[inline]
pub fn to_bytes_with_nul(self) -> &'a [u8] {
self.to_chars_with_nul()
}
pub fn to_str(self) -> Result<&'a str, Utf8Error> {
core::str::from_utf8(self.to_bytes())
}
pub fn to_string_lossy(self) -> Cow<'a, str> {
String::from_utf8_lossy(self.to_bytes())
}
pub const fn as_ptr(self) -> *const c_char {
self.ptr.as_ptr()
}
#[inline]
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self {
Self::from_ptr(bytes.as_ptr().cast())
Self::from_chars_with_nul_unchecked(bytes)
}
pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result<Self, FromBytesWithNulError> {
if bytes.last() != Some(&b'\0') || bytes[..bytes.len() - 1].contains(&b'\0') {
return Err(FromBytesWithNulError);
}
Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) })
#[inline]
pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result<Self, FromCharsWithNulError> {
Self::from_chars_with_nul(bytes)
}
pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result<Self, FromBytesUntilNulError> {
if !bytes.contains(&b'\0') {
return Err(FromBytesUntilNulError);
}
Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) })
}
pub fn to_owned_cstring(self) -> CString {
CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) })
}
pub fn borrow(string: &'a CString) -> Self {
unsafe { Self::from_ptr(string.as_ptr()) }
}
#[doc(alias = "strlen")]
pub fn count_bytes(&self) -> usize {
self.to_bytes().len()
}
pub fn is_empty(&self) -> bool {
self.count_bytes() == 0
#[inline]
pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result<Self, FromCharsUntilNulError> {
Self::from_chars_until_nul(bytes)
}
}
unsafe impl Send for CStr<'_> {}
unsafe impl Sync for CStr<'_> {}
unsafe impl<T: Kind> Send for NulStr<'_, T> {}
unsafe impl<T: Kind> Sync for NulStr<'_, T> {}
impl From<&core::ffi::CStr> for CStr<'_> {
fn from(s: &core::ffi::CStr) -> Self {
@@ -126,9 +317,9 @@ impl From<&core::ffi::CStr> for CStr<'_> {
}
#[derive(Debug)]
pub struct FromBytesWithNulError;
pub struct FromCharsWithNulError;
#[derive(Debug)]
pub struct FromBytesUntilNulError;
pub struct FromCharsUntilNulError;
pub use alloc::ffi::CString;
+11 -7
View File
@@ -1194,7 +1194,7 @@ pub unsafe extern "C" fn vfprintf(file: *mut FILE, format: *const c_char, ap: va
return -1;
}
printf::printf(&mut *file, format, ap)
printf::printf(&mut *file, CStr::from_ptr(format), ap)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fprintf(
@@ -1213,7 +1213,7 @@ pub unsafe extern "C" fn vdprintf(fd: c_int, format: *const c_char, ap: va_list)
// borrowing the file descriptor here
f.reference = true;
printf::printf(f, format, ap)
printf::printf(f, CStr::from_ptr(format), ap)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dprintf(fd: c_int, format: *const c_char, mut __valist: ...) -> c_int {
@@ -1236,7 +1236,7 @@ pub unsafe extern "C" fn vasprintf(
ap: va_list,
) -> c_int {
let mut alloc_writer = CVec::new();
let ret = printf::printf(&mut alloc_writer, format, ap);
let ret = printf::printf(&mut alloc_writer, CStr::from_ptr(format), ap);
alloc_writer.push(0).unwrap();
alloc_writer.shrink_to_fit().unwrap();
*strp = alloc_writer.leak() as *mut c_char;
@@ -1260,7 +1260,7 @@ pub unsafe extern "C" fn vsnprintf(
) -> c_int {
printf::printf(
&mut platform::StringWriter(s as *mut u8, n as usize),
format,
CStr::from_ptr(format),
ap,
)
}
@@ -1273,14 +1273,18 @@ pub unsafe extern "C" fn snprintf(
) -> c_int {
printf::printf(
&mut platform::StringWriter(s as *mut u8, n as usize),
format,
CStr::from_ptr(format),
__valist.as_va_list(),
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn vsprintf(s: *mut c_char, format: *const c_char, ap: va_list) -> c_int {
printf::printf(&mut platform::UnsafeStringWriter(s as *mut u8), format, ap)
printf::printf(
&mut platform::UnsafeStringWriter(s as *mut u8),
CStr::from_ptr(format),
ap,
)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sprintf(
@@ -1290,7 +1294,7 @@ pub unsafe extern "C" fn sprintf(
) -> c_int {
printf::printf(
&mut platform::UnsafeStringWriter(s as *mut u8),
format,
CStr::from_ptr(format),
__valist.as_va_list(),
)
}
+205 -186
View File
@@ -1,4 +1,8 @@
use crate::io::{self, Write};
// TODO: reuse more code with the wide printf impl
use crate::{
c_str::{self, CStr, NulStr},
io::{self, Write},
};
use alloc::{
collections::BTreeMap,
string::{String, ToString},
@@ -19,7 +23,7 @@ use crate::{
// |_|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum IntKind {
pub(crate) enum IntKind {
Byte,
Short,
Int,
@@ -30,7 +34,7 @@ enum IntKind {
Size,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum FmtKind {
pub(crate) enum FmtKind {
Percent,
Signed,
@@ -46,13 +50,13 @@ enum FmtKind {
GetWritten,
}
#[derive(Clone, Copy, Debug)]
enum Number {
pub(crate) enum Number {
Static(usize),
Index(usize),
Next,
}
impl Number {
unsafe fn resolve(self, varargs: &mut VaListCache, ap: &mut VaList) -> usize {
pub(crate) unsafe fn resolve(self, varargs: &mut VaListCache, ap: &mut VaList) -> usize {
let arg = match self {
Number::Static(num) => return num,
Number::Index(i) => varargs.get(i - 1, ap, None),
@@ -78,7 +82,7 @@ impl Number {
}
}
#[derive(Clone, Copy, Debug)]
enum VaArg {
pub(crate) enum VaArg {
c_char(c_char),
c_double(c_double),
c_int(c_int),
@@ -92,7 +96,7 @@ enum VaArg {
wint_t(wint_t),
}
impl VaArg {
unsafe fn arg_from(fmtkind: FmtKind, intkind: IntKind, ap: &mut VaList) -> VaArg {
pub(crate) unsafe fn arg_from(fmtkind: FmtKind, intkind: IntKind, ap: &mut VaList) -> VaArg {
// Per the C standard using va_arg with a type with a size
// less than that of an int for integers and double for floats
// is invalid. As a result any arguments smaller than an int or
@@ -223,12 +227,12 @@ impl VaArg {
}
}
#[derive(Default)]
struct VaListCache {
args: Vec<VaArg>,
i: usize,
pub(crate) struct VaListCache {
pub(crate) args: Vec<VaArg>,
pub(crate) i: usize,
}
impl VaListCache {
unsafe fn get(
pub(crate) unsafe fn get(
&mut self,
i: usize,
ap: &mut VaList,
@@ -283,10 +287,13 @@ static INF_STR_UPPER: &str = "INF";
static NAN_STR_LOWER: &str = "nan";
static NAN_STR_UPPER: &str = "NAN";
unsafe fn pop_int_raw(format: &mut *const u8) -> Option<usize> {
fn pop_int_raw<T: c_str::Kind>(format: &mut NulStr<T>) -> Option<usize> {
let mut int = None;
while let Some(digit) = (**format as char).to_digit(10) {
*format = format.add(1);
while let Some((digit, rest)) = format
.split_first_char()
.and_then(|(d, r)| Some((d.to_digit(10)?, r)))
{
*format = rest;
if int.is_none() {
int = Some(0);
}
@@ -295,39 +302,39 @@ unsafe fn pop_int_raw(format: &mut *const u8) -> Option<usize> {
}
int
}
unsafe fn pop_index(format: &mut *const u8) -> Option<usize> {
fn pop_index<T: c_str::Kind>(format: &mut NulStr<T>) -> Option<usize> {
// Peek ahead for a positional argument:
let mut format2 = *format;
if let Some(i) = pop_int_raw(&mut format2) {
if *format2 == b'$' {
*format = format2.add(1);
if let Some(('$', format2)) = format2.split_first_char() {
*format = format2;
return Some(i);
}
}
None
}
unsafe fn pop_int(format: &mut *const u8) -> Option<Number> {
if **format == b'*' {
*format = format.add(1);
fn pop_int<T: c_str::Kind>(format: &mut NulStr<T>) -> Option<Number> {
if let Some(('*', rest)) = format.split_first_char() {
*format = rest;
Some(pop_index(format).map(Number::Index).unwrap_or(Number::Next))
} else {
pop_int_raw(format).map(Number::Static)
}
}
unsafe fn fmt_int<I>(fmt: u8, i: I) -> String
fn fmt_int<I, T: c_str::Kind>(fmt: char, i: I) -> String
where
I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex + fmt::Binary,
{
match fmt {
b'o' => format!("{:o}", i),
b'u' => i.to_string(),
b'x' => format!("{:x}", i),
b'X' => format!("{:X}", i),
b'b' | b'B' => format!("{:b}", i),
'o' => format!("{:o}", i),
'u' => i.to_string(),
'x' => format!("{:x}", i),
'X' => format!("{:X}", i),
'b' | 'B' if T::IS_THIN_NOT_WIDE => format!("{:b}", i),
_ => panic!(
"fmt_int should never be called with the fmt {:?}",
fmt as char
fmt as char,
),
}
}
@@ -377,7 +384,7 @@ fn float_exp(mut float: c_double) -> (c_double, isize) {
fn fmt_float_exp<W: Write>(
w: &mut W,
exp_fmt: u8,
exp_fmt: char,
trim: bool,
precision: usize,
float: c_double,
@@ -405,7 +412,7 @@ fn fmt_float_exp<W: Write>(
};
pad(w, !left, b'0', len..pad_zero)?;
w.write_all(bytes)?;
write!(w, "{}{:+03}", exp_fmt as char, exp)?;
write!(w, "{}{:+03}", exp_fmt, exp)?;
pad(w, left, b' ', len..pad_space)?;
Ok(())
@@ -463,165 +470,166 @@ fn fmt_float_nonfinite<W: Write>(w: &mut W, float: c_double, case: FmtCase) -> i
}
#[derive(Clone, Copy)]
struct PrintfIter {
format: *const u8,
pub(crate) struct PrintfIter<'a, T: c_str::Kind> {
pub(crate) format: NulStr<'a, T>,
}
#[derive(Clone, Copy, Debug)]
struct PrintfArg {
index: Option<usize>,
alternate: bool,
zero: bool,
left: bool,
sign_reserve: bool,
sign_always: bool,
min_width: Number,
precision: Option<Number>,
intkind: IntKind,
fmt: u8,
fmtkind: FmtKind,
pub(crate) struct PrintfArg {
pub(crate) index: Option<usize>,
pub(crate) alternate: bool,
pub(crate) zero: bool,
pub(crate) left: bool,
pub(crate) sign_reserve: bool,
pub(crate) sign_always: bool,
pub(crate) min_width: Number,
pub(crate) precision: Option<Number>,
pub(crate) intkind: IntKind,
pub(crate) fmt: char,
pub(crate) fmtkind: FmtKind,
}
#[derive(Debug)]
enum PrintfFmt {
Plain(&'static [u8]),
pub(crate) enum PrintfFmt<'a, U> {
Plain(&'a [U]),
Arg(PrintfArg),
}
impl Iterator for PrintfIter {
type Item = Result<PrintfFmt, ()>;
impl<'a, T: c_str::Kind> Iterator for PrintfIter<'a, T> {
type Item = Result<PrintfFmt<'a, T::Char>, ()>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
// Send PrintfFmt::Plain until the next %
let mut len = 0;
while *self.format.add(len) != 0 && *self.format.add(len) != b'%' {
len += 1;
// Send PrintfFmt::Plain until the next %
let first_percent = match self.format.find_get_subslice_or_all(b'%') {
Err(([], _)) => return None,
Ok((chunk @ [_, ..], rest)) | Err((chunk @ [_, ..], rest)) => {
self.format = rest;
return Some(Ok(PrintfFmt::Plain(chunk)));
}
if len > 0 {
let slice = slice::from_raw_parts(self.format as *const u8, len);
self.format = self.format.add(len);
return Some(Ok(PrintfFmt::Plain(slice)));
Ok(([], rest)) => rest,
};
// at this point the next char must be %
self.format = first_percent.split_first().expect("must be %").1;
let mut peekahead = self.format;
let index = pop_index(&mut peekahead).map(|i| {
self.format = peekahead;
i
});
// Flags:
let mut alternate = false;
let mut zero = false;
let mut left = false;
let mut sign_reserve = false;
let mut sign_always = false;
while let Some((c, rest)) = self.format.split_first_char() {
match c {
'#' => alternate = true,
'0' => zero = true,
'-' => left = true,
' ' => sign_reserve = true,
'+' => sign_always = true,
_ => break,
}
self.format = self.format.add(len);
if *self.format == 0 {
return None;
self.format = rest;
}
// Width and precision:
let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0));
let precision = if let Some(('.', rest)) = self.format.split_first_char() {
self.format = rest;
match pop_int(&mut self.format) {
int @ Some(_) => int,
None => return Some(Err(())),
}
} else {
None
};
// *self.format is guaranteed to be '%' at this point
self.format = self.format.add(1);
let mut peekahead = self.format;
let index = pop_index(&mut peekahead).map(|i| {
self.format = peekahead;
i
});
// Flags:
let mut alternate = false;
let mut zero = false;
let mut left = false;
let mut sign_reserve = false;
let mut sign_always = false;
loop {
match *self.format {
b'#' => alternate = true,
b'0' => zero = true,
b'-' => left = true,
b' ' => sign_reserve = true,
b'+' => sign_always = true,
_ => break,
// Integer size:
let mut intkind = IntKind::Int;
while let Some((byte, rest)) = self.format.split_first_char() {
intkind = match byte {
'h' => {
if intkind == IntKind::Short || intkind == IntKind::Byte {
IntKind::Byte
} else {
IntKind::Short
}
}
self.format = self.format.add(1);
}
// Width and precision:
let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0));
let precision = if *self.format == b'.' {
self.format = self.format.add(1);
match pop_int(&mut self.format) {
int @ Some(_) => int,
None => return Some(Err(())),
'j' => IntKind::IntMax,
'l' => {
if intkind == IntKind::Long || intkind == IntKind::LongLong {
IntKind::LongLong
} else {
IntKind::Long
}
}
} else {
None
'q' | 'L' => IntKind::LongLong,
't' => IntKind::PtrDiff,
'z' => IntKind::Size,
_ => break,
};
// Integer size:
let mut intkind = IntKind::Int;
loop {
intkind = match *self.format {
b'h' => {
if intkind == IntKind::Short || intkind == IntKind::Byte {
IntKind::Byte
} else {
IntKind::Short
}
}
b'j' => IntKind::IntMax,
b'l' => {
if intkind == IntKind::Long || intkind == IntKind::LongLong {
IntKind::LongLong
} else {
IntKind::Long
}
}
b'q' | b'L' => IntKind::LongLong,
b't' => IntKind::PtrDiff,
b'z' => IntKind::Size,
_ => break,
};
self.format = self.format.add(1);
}
let fmt = *self.format;
let fmtkind = match fmt {
b'%' => FmtKind::Percent,
b'd' | b'i' => FmtKind::Signed,
b'o' | b'u' | b'x' | b'X' | b'b' | b'B' => FmtKind::Unsigned,
b'e' | b'E' => FmtKind::Scientific,
b'f' | b'F' => FmtKind::Decimal,
b'g' | b'G' => FmtKind::AnyNotation,
b's' => FmtKind::String,
b'c' => FmtKind::Char,
b'p' => FmtKind::Pointer,
b'n' => FmtKind::GetWritten,
b'm' => {
// %m is technically for syslog only, but musl and glibc implement it for
// printf because it is difficult and error prone to implement a format
// specifier for just *one* function.
self.format = self.format.add(1);
return Some(Ok(PrintfFmt::Plain(
self.format = rest;
}
let Some((fmt, rest)) = self.format.split_first_char() else {
return Some(Err(()));
};
self.format = rest;
let fmtkind = match fmt {
'%' => FmtKind::Percent,
'd' | 'i' => FmtKind::Signed,
'o' | 'u' | 'x' | 'X' => FmtKind::Unsigned,
'b' | 'B' if T::IS_THIN_NOT_WIDE => FmtKind::Unsigned,
'e' | 'E' => FmtKind::Scientific,
'f' | 'F' => FmtKind::Decimal,
'g' | 'G' => FmtKind::AnyNotation,
's' => FmtKind::String,
'c' => FmtKind::Char,
'p' => FmtKind::Pointer,
'n' => FmtKind::GetWritten,
'm' if T::IS_THIN_NOT_WIDE => {
// %m is technically for syslog only, but musl and glibc implement it for
// printf because it is difficult and error prone to implement a format
// specifier for just *one* function.
return Some(Ok(PrintfFmt::Plain(
T::chars_from_bytes(
errno::STR_ERROR
.get(platform::ERRNO.get() as usize)
.map(|e| e.as_bytes())
.unwrap_or(b"unknown error"),
)));
}
_ => return Some(Err(())),
};
self.format = self.format.add(1);
)
.expect("string must be thin"),
)));
}
_ => return Some(Err(())),
};
Some(Ok(PrintfFmt::Arg(PrintfArg {
index,
alternate,
zero,
left,
sign_reserve,
sign_always,
min_width,
precision,
intkind,
fmt,
fmtkind,
})))
}
Some(Ok(PrintfFmt::Arg(PrintfArg {
index,
alternate,
zero,
left,
sign_reserve,
sign_always,
min_width,
precision,
intkind,
fmt,
fmtkind,
})))
}
}
unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) -> io::Result<c_int> {
pub(crate) unsafe fn inner_printf<T: c_str::Kind>(
w: impl Write,
format: NulStr<T>,
mut ap: VaList,
) -> io::Result<c_int> {
let w = &mut platform::CountingWriter::new(w);
let iterator = PrintfIter {
format: format as *const u8,
};
let iterator = PrintfIter { format };
// Pre-fetch vararg types
let mut varargs = VaListCache::default();
@@ -665,7 +673,15 @@ unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) ->
for section in iterator {
let arg = match section {
Ok(PrintfFmt::Plain(text)) => {
w.write_all(text)?;
if T::IS_THIN_NOT_WIDE {
let bytes = T::chars_to_bytes(text).expect("is thin");
w.write_all(bytes)?;
} else {
// TODO: wcsrtombs wrapper
for c in text.iter().filter_map(|u| char::from_u32((*u).into())) {
write!(w, "{}", c);
}
}
continue;
}
Ok(PrintfFmt::Arg(arg)) => arg,
@@ -693,8 +709,10 @@ unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) ->
let fmt = arg.fmt;
let fmtkind = arg.fmtkind;
let fmtcase = match fmt {
b'x' | b'b' | b'f' | b'e' | b'g' => Some(FmtCase::Lower),
b'X' | b'B' | b'F' | b'E' | b'G' => Some(FmtCase::Upper),
'b' if T::IS_THIN_NOT_WIDE => Some(FmtCase::Lower),
'B' if T::IS_THIN_NOT_WIDE => Some(FmtCase::Upper),
'x' | 'f' | 'e' | 'g' => Some(FmtCase::Lower),
'X' | 'F' | 'E' | 'G' => Some(FmtCase::Upper),
_ => None,
};
@@ -760,16 +778,16 @@ unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) ->
}
FmtKind::Unsigned => {
let string = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) {
VaArg::c_char(i) => fmt_int(fmt, i as c_uchar),
VaArg::c_char(i) => fmt_int::<_, T>(fmt, i as c_uchar),
VaArg::c_double(i) => panic!("this should not be possible"),
VaArg::c_int(i) => fmt_int(fmt, i as c_uint),
VaArg::c_long(i) => fmt_int(fmt, i as c_ulong),
VaArg::c_longlong(i) => fmt_int(fmt, i as c_ulonglong),
VaArg::c_short(i) => fmt_int(fmt, i as c_ushort),
VaArg::intmax_t(i) => fmt_int(fmt, i as uintmax_t),
VaArg::pointer(i) => fmt_int(fmt, i as usize),
VaArg::ptrdiff_t(i) => fmt_int(fmt, i as size_t),
VaArg::ssize_t(i) => fmt_int(fmt, i as size_t),
VaArg::c_int(i) => fmt_int::<_, T>(fmt, i as c_uint),
VaArg::c_long(i) => fmt_int::<_, T>(fmt, i as c_ulong),
VaArg::c_longlong(i) => fmt_int::<_, T>(fmt, i as c_ulonglong),
VaArg::c_short(i) => fmt_int::<_, T>(fmt, i as c_ushort),
VaArg::intmax_t(i) => fmt_int::<_, T>(fmt, i as uintmax_t),
VaArg::pointer(i) => fmt_int::<_, T>(fmt, i as usize),
VaArg::ptrdiff_t(i) => fmt_int::<_, T>(fmt, i as size_t),
VaArg::ssize_t(i) => fmt_int::<_, T>(fmt, i as size_t),
VaArg::wint_t(_) => unreachable!("this should not be possible"),
};
let zero = precision == Some(0) && string == "0";
@@ -787,8 +805,9 @@ unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) ->
len.max(precision.unwrap_or(0))
+ if alternate && string != "0" {
match fmt {
b'o' if no_precision => 1,
b'x' | b'X' | b'b' | b'B' => 2,
'o' if no_precision => 1,
'x' | 'X' => 2,
'b' | 'B' if T::IS_THIN_NOT_WIDE => 2,
_ => 0,
}
} else {
@@ -800,11 +819,11 @@ unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) ->
if alternate && string != "0" {
match fmt {
b'o' if no_precision => w.write_all(b"0")?,
b'x' => w.write_all(b"0x")?,
b'X' => w.write_all(b"0X")?,
b'b' => w.write_all(b"0b")?,
b'B' => w.write_all(b"0B")?,
'o' if no_precision => w.write_all(b"0")?,
'x' => w.write_all(b"0x")?,
'X' => w.write_all(b"0X")?,
'b' if T::IS_THIN_NOT_WIDE => w.write_all(b"0b")?,
'B' if T::IS_THIN_NOT_WIDE => w.write_all(b"0B")?,
_ => (),
}
}
@@ -852,7 +871,8 @@ unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) ->
};
if float.is_finite() {
let (log, exp) = float_exp(float);
let exp_fmt = b'E' | (fmt & 32);
// TODO: .is_uppercase()?
let exp_fmt = if fmt as u32 & 32 == 32 { 'e' } else { 'E' };
let precision = precision.unwrap_or(6);
let use_exp_format = exp < -4 || exp >= precision as isize;
@@ -1258,8 +1278,7 @@ unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) ->
///
/// # Safety
/// Behavior is undefined if any of the following conditions are violated:
/// - `format` must point to valid null-terminated string.
/// - `ap` must follow the safety contract of variable arguments of C.
pub unsafe fn printf<W: Write>(w: W, format: *const c_char, ap: VaList) -> c_int {
inner_printf(w, format, ap).unwrap_or(-1)
pub unsafe fn printf(w: impl Write, format: CStr, ap: VaList) -> c_int {
inner_printf::<c_str::Thin>(w, format, ap).unwrap_or(-1)
}
+1 -1
View File
@@ -82,7 +82,7 @@ impl<L: LogSink> LogParams<L> {
// SAFETY:
// * Assumes caller passed in a valid C string; printf should handle that invariant.
// * `buffer` grows to fit the formatted string.
unsafe { printf(&mut buffer, message.as_ptr(), ap) };
unsafe { printf(&mut buffer, message, ap) };
buffer.extend(b"\n\0");
if self.maybe_open_logger().is_ok() {
+2 -1
View File
@@ -3,6 +3,7 @@
use core::{char, ffi::VaList as va_list, mem, ptr, slice, usize};
use crate::{
c_str::WStr,
header::{
ctype::isspace,
errno::{EILSEQ, ENOMEM, ERANGE},
@@ -328,7 +329,7 @@ pub unsafe extern "C" fn vfwprintf(
return -1;
}
wprintf::wprintf(&mut *stream, format, arg)
wprintf::wprintf(&mut *stream, WStr::from_ptr(format), arg)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn fwprintf(
File diff suppressed because it is too large Load Diff