From eda8316af9f9cb32a9d1290017abc0bed725bb4e Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sun, 7 Dec 2025 18:25:48 -0500 Subject: [PATCH 1/4] Add unsafe blocks around unsafe operations in unsafe functions This is in preparation for switching to the 2024 edition. --- src/output.rs | 8 ++----- src/parser.rs | 64 ++++++++++++++++++++++++++------------------------ tests/tests.rs | 17 +++++++------- 3 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/output.rs b/src/output.rs index 59b4b16..2d46d25 100644 --- a/src/output.rs +++ b/src/output.rs @@ -325,7 +325,7 @@ pub unsafe fn display<'a>(format: *const c_char, va_list: VaList<'a>) -> VaListD /// /// #[no_mangle] /// unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int { -/// let format = printf_compat::output::display(str, args); +/// let format = unsafe { printf_compat::output::display(str, args) }; /// println!("{}", format); /// format.bytes_written() /// } @@ -348,11 +348,7 @@ impl<'a> fmt::Display for VaListDisplay<'a> { unsafe { let bytes = crate::format(self.format, self.va_list.clone(), fmt_write(f)); self.written.set(bytes); - if bytes < 0 { - Err(fmt::Error) - } else { - Ok(()) - } + if bytes < 0 { Err(fmt::Error) } else { Ok(()) } } } } diff --git a/src/parser.rs b/src/parser.rs index ef14622..beb2b3e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -29,7 +29,7 @@ fn parse_flags(mut sub: &[u8]) -> (Flags, &[u8]) { unsafe fn parse_width<'a>(mut sub: &'a [u8], args: &mut VaList) -> (c_int, &'a [u8]) { let mut width: c_int = 0; if sub.first() == Some(&b'*') { - return (args.arg(), next_char(sub)); + return (unsafe { args.arg() }, next_char(sub)); } while let Some(&ch) = sub.first() { match ch { @@ -46,7 +46,7 @@ unsafe fn parse_width<'a>(mut sub: &'a [u8], args: &mut VaList) -> (c_int, &'a [ unsafe fn parse_precision<'a>(sub: &'a [u8], args: &mut VaList) -> (Option, &'a [u8]) { match sub.first() { Some(&b'.') => { - let (prec, sub) = parse_width(next_char(sub), args); + let (prec, sub) = unsafe { parse_width(next_char(sub), args) }; (Some(prec), sub) } _ => (None, sub), @@ -73,24 +73,24 @@ enum Length { impl Length { unsafe fn parse_signed(self, args: &mut VaList) -> SignedInt { match self { - Length::Int => SignedInt::Int(args.arg()), - Length::Char => SignedInt::Char(args.arg::() as c_schar), - Length::Short => SignedInt::Short(args.arg::() as c_short), - Length::Long => SignedInt::Long(args.arg()), - Length::LongLong => SignedInt::LongLong(args.arg()), + Length::Int => SignedInt::Int(unsafe { args.arg() }), + Length::Char => SignedInt::Char(unsafe { args.arg::() } as c_schar), + Length::Short => SignedInt::Short(unsafe { args.arg::() } as c_short), + Length::Long => SignedInt::Long(unsafe { args.arg() }), + Length::LongLong => SignedInt::LongLong(unsafe { args.arg() }), // for some reason, these exist as different options, yet produce the same output - Length::Usize | Length::Isize => SignedInt::Isize(args.arg()), + Length::Usize | Length::Isize => SignedInt::Isize(unsafe { args.arg() }), } } unsafe fn parse_unsigned(self, args: &mut VaList) -> UnsignedInt { match self { - Length::Int => UnsignedInt::Int(args.arg()), - Length::Char => UnsignedInt::Char(args.arg::() as c_uchar), - Length::Short => UnsignedInt::Short(args.arg::() as c_ushort), - Length::Long => UnsignedInt::Long(args.arg()), - Length::LongLong => UnsignedInt::LongLong(args.arg()), + Length::Int => UnsignedInt::Int(unsafe { args.arg() }), + Length::Char => UnsignedInt::Char(unsafe { args.arg::() } as c_uchar), + Length::Short => UnsignedInt::Short(unsafe { args.arg::() } as c_ushort), + Length::Long => UnsignedInt::Long(unsafe { args.arg() }), + Length::LongLong => UnsignedInt::LongLong(unsafe { args.arg() }), // for some reason, these exist as different options, yet produce the same output - Length::Usize | Length::Isize => UnsignedInt::Isize(args.arg()), + Length::Usize | Length::Isize => UnsignedInt::Isize(unsafe { args.arg() }), } } } @@ -122,7 +122,7 @@ pub unsafe fn format( mut args: VaList, mut handler: impl FnMut(Argument) -> c_int, ) -> c_int { - let str = CStr::from_ptr(format).to_bytes(); + let str = unsafe { CStr::from_ptr(format).to_bytes() }; let mut iter = str.split(|&c| c == b'%'); let mut written = 0; @@ -151,8 +151,8 @@ pub unsafe fn format( continue; } let (flags, sub) = parse_flags(sub); - let (width, sub) = parse_width(sub, &mut args); - let (precision, sub) = parse_precision(sub, &mut args); + let (width, sub) = unsafe { parse_width(sub, &mut args) }; + let (precision, sub) = unsafe { parse_precision(sub, &mut args) }; let (length, sub) = parse_length(sub); let ch = sub .first() @@ -166,35 +166,35 @@ pub unsafe fn format( last_was_percent = true; Specifier::Percent } - b'd' | b'i' => Specifier::Int(length.parse_signed(&mut args)), - b'x' => Specifier::Hex(length.parse_unsigned(&mut args)), - b'X' => Specifier::UpperHex(length.parse_unsigned(&mut args)), - b'u' => Specifier::Uint(length.parse_unsigned(&mut args)), - b'o' => Specifier::Octal(length.parse_unsigned(&mut args)), + b'd' | b'i' => Specifier::Int(unsafe { length.parse_signed(&mut args) }), + b'x' => Specifier::Hex(unsafe { length.parse_unsigned(&mut args) }), + b'X' => Specifier::UpperHex(unsafe { length.parse_unsigned(&mut args) }), + b'u' => Specifier::Uint(unsafe { length.parse_unsigned(&mut args) }), + b'o' => Specifier::Octal(unsafe { length.parse_unsigned(&mut args) }), b'f' | b'F' => Specifier::Double { - value: args.arg(), + value: unsafe { args.arg() }, format: DoubleFormat::Normal.set_upper(ch.is_ascii_uppercase()), }, b'e' | b'E' => Specifier::Double { - value: args.arg(), + value: unsafe { args.arg() }, format: DoubleFormat::Scientific.set_upper(ch.is_ascii_uppercase()), }, b'g' | b'G' => Specifier::Double { - value: args.arg(), + value: unsafe { args.arg() }, format: DoubleFormat::Auto.set_upper(ch.is_ascii_uppercase()), }, b'a' | b'A' => Specifier::Double { - value: args.arg(), + value: unsafe { args.arg() }, format: DoubleFormat::Hex.set_upper(ch.is_ascii_uppercase()), }, b's' => { - let arg: *mut c_char = args.arg(); + let arg: *mut c_char = unsafe { args.arg() }; // As a common extension supported by glibc, musl, and // others, format a NULL pointer as "(null)". if arg.is_null() { Specifier::Bytes(b"(null)") } else { - Specifier::String(CStr::from_ptr(arg)) + Specifier::String(unsafe { CStr::from_ptr(arg) }) } } b'c' => { @@ -210,10 +210,12 @@ pub unsafe fn format( type IntType = c_uint; } - Specifier::Char(args.arg::<::IntType>() as c_char) + Specifier::Char( + unsafe { args.arg::<::IntType>() } as c_char + ) } - b'p' => Specifier::Pointer(args.arg()), - b'n' => Specifier::WriteBytesWritten(written, args.arg()), + b'p' => Specifier::Pointer(unsafe { args.arg() }), + b'n' => Specifier::WriteBytesWritten(written, unsafe { args.arg() }), _ => return -1, }, })); diff --git a/tests/tests.rs b/tests/tests.rs index 48ab90f..f63c95e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -9,14 +9,14 @@ extern "C" { unsafe extern "C" fn rust_fmt(str: *const c_char, args: ...) -> Box<(c_int, String)> { let mut s = String::new(); - let bytes_written = - printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s)); + let bytes_written = unsafe { + printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s)) + }; assert!(bytes_written >= 0); let mut s2 = std::io::Cursor::new(vec![]); - assert_eq!( - bytes_written, - printf_compat::format(str, args, printf_compat::output::io_write(&mut s2),) - ); + assert_eq!(bytes_written, unsafe { + printf_compat::format(str, args, printf_compat::output::io_write(&mut s2)) + }); assert_eq!(s.as_bytes(), s2.get_ref()); Box::new((bytes_written, s)) } @@ -61,8 +61,9 @@ fn assert_fmt_err(fmt: &CStr) { unsafe extern "C" fn format(str: *const c_char, args: ...) -> c_int { let mut s = String::new(); - let bytes_written = - printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s)); + let bytes_written = unsafe { + printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s)) + }; bytes_written } let bytes_written = unsafe { format(fmt.as_ptr()) }; From 3abc63bdf7f251995b63454575d47a90b7a604ff Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sun, 7 Dec 2025 18:27:00 -0500 Subject: [PATCH 2/4] Mark extern "C" block as unsafe This is in preparation for switching to the 2024 edition. --- tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index f63c95e..af74bc5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,7 +2,7 @@ use core::{ffi::*, ptr::null_mut}; -extern "C" { +unsafe extern "C" { fn asprintf(s: *mut *mut c_char, format: *const c_char, ...) -> c_int; fn free(p: *mut c_void); } From c4f80ea68016a762e21a26df40916de169e9de8b Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sun, 7 Dec 2025 18:29:48 -0500 Subject: [PATCH 3/4] Switch from `#[no_mangle]` to `#[unsafe(no_mangle)]` This is in preparation for switching to the 2024 edition. --- README.md | 8 ++++---- src/lib.rs | 10 +++++----- src/output.rs | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d64f149..13f2ed3 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Now, add your function signature: ```rust use core::ffi::{c_char, c_int}; -#[no_mangle] +#[unsafe(no_mangle)] unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int { todo!() } @@ -78,13 +78,13 @@ unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int { Think about what you're doing: - If you're implenting `printf` *because you don't have one*, you'll want to - call it `printf` and add `#[no_mangle]`. + call it `printf` and add `#[unsafe(no_mangle)]`. - Likewise, if you're creating a custom log function for a C library and it - expects to call a globally-defined function, keep `#[no_mangle]` and + expects to call a globally-defined function, keep `#[unsafe(no_mangle)]` and rename the function to what it expects. - On the other hand, if your C library expects you to call a function to register a callback ([example 1][sigrok-log], [example 2][libusb-log]), - remove `#[no_mangle]`. + remove `#[unsafe(no_mangle)]`. Now, add your logic: diff --git a/src/lib.rs b/src/lib.rs index 485a3c7..43078c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,7 +65,7 @@ //! # #![feature(c_variadic)] //! use core::ffi::{c_char, c_int}; //! -//! #[no_mangle] +//! #[unsafe(no_mangle)] //! unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int { //! todo!() //! } @@ -74,20 +74,20 @@ //! Think about what you're doing: //! //! - If you're implenting `printf` *because you don't have one*, you'll want to -//! call it `printf` and add `#[no_mangle]`. +//! call it `printf` and add `#[unsafe(no_mangle)]`. //! - Likewise, if you're creating a custom log function for a C library and it -//! expects to call a globally-defined function, keep `#[no_mangle]` and +//! expects to call a globally-defined function, keep `#[unsafe(no_mangle)]` and //! rename the function to what it expects. //! - On the other hand, if your C library expects you to call a function to //! register a callback ([example 1][sigrok-log], [example 2][libusb-log]), -//! remove `#[no_mangle]`. +//! remove `#[unsafe(no_mangle)]`. //! //! Now, add your logic: //! //! ```rust //! # #![feature(c_variadic)] //! # use core::ffi::{c_char, c_int}; -//! # #[no_mangle] +//! # #[unsafe(no_mangle)] //! # unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int { //! use printf_compat::{format, output}; //! let mut s = String::new(); diff --git a/src/output.rs b/src/output.rs index 2d46d25..2cd419b 100644 --- a/src/output.rs +++ b/src/output.rs @@ -323,7 +323,7 @@ pub unsafe fn display<'a>(format: *const c_char, va_list: VaList<'a>) -> VaListD /// /// use core::ffi::{c_char, c_int}; /// -/// #[no_mangle] +/// #[unsafe(no_mangle)] /// unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int { /// let format = unsafe { printf_compat::output::display(str, args) }; /// println!("{}", format); From 056b5f5a06abcf5b3150dde52d9098f0a5875b65 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sun, 7 Dec 2025 18:33:18 -0500 Subject: [PATCH 4/4] Switch to 2024 edition Since the `VaList` implementation recently changed [1], making the current incompatible with older Rust versions anyway, might as well switch to the current edition. [1]: https://github.com/lights0123/printf-compat/pull/45 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 56f90cb..a3504bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = "printf reimplemented in Rust" version = "0.2.1" repository = "https://github.com/lights0123/printf-compat" authors = ["lights0123 "] -edition = "2021" +edition = "2024" license = "MIT OR Apache-2.0" readme = "README.md"