mirror of https://github.com/torvalds/linux.git
rust: support formatting of foreign types
Introduce a `fmt!` macro which wraps all arguments in `kernel::fmt::Adapter` and a `kernel::fmt::Display` trait. This enables formatting of foreign types (like `core::ffi::CStr`) that do not implement `core::fmt::Display` due to concerns around lossy conversions which do not apply in the kernel. Suggested-by: Alice Ryhl <aliceryhl@google.com> Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Custom.20formatting/with/516476467 Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Acked-by: Danilo Krummrich <dakr@kernel.org> Reviewed-by: Benno Lossin <lossin@kernel.org> Signed-off-by: Tamir Duberstein <tamird@gmail.com> Link: https://patch.msgid.link/20251018-cstr-core-v18-15-9378a54385f8@gmail.com Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
parent
1dcd763ba1
commit
c5cf01ba8d
|
|
@ -4,4 +4,89 @@
|
|||
//!
|
||||
//! This module is intended to be used in place of `core::fmt` in kernel code.
|
||||
|
||||
pub use core::fmt::{Arguments, Debug, Display, Error, Formatter, Result, Write};
|
||||
pub use core::fmt::{Arguments, Debug, Error, Formatter, Result, Write};
|
||||
|
||||
/// Internal adapter used to route allow implementations of formatting traits for foreign types.
|
||||
///
|
||||
/// It is inserted automatically by the [`fmt!`] macro and is not meant to be used directly.
|
||||
///
|
||||
/// [`fmt!`]: crate::prelude::fmt!
|
||||
#[doc(hidden)]
|
||||
pub struct Adapter<T>(pub T);
|
||||
|
||||
macro_rules! impl_fmt_adapter_forward {
|
||||
($($trait:ident),* $(,)?) => {
|
||||
$(
|
||||
impl<T: $trait> $trait for Adapter<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
let Self(t) = self;
|
||||
$trait::fmt(t, f)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
use core::fmt::{Binary, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex};
|
||||
impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, Pointer, LowerExp, UpperExp);
|
||||
|
||||
/// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types.
|
||||
///
|
||||
/// Types should implement this trait rather than [`core::fmt::Display`]. Together with the
|
||||
/// [`Adapter`] type and [`fmt!`] macro, it allows for formatting foreign types (e.g. types from
|
||||
/// core) which do not implement [`core::fmt::Display`] directly.
|
||||
///
|
||||
/// [`fmt!`]: crate::prelude::fmt!
|
||||
pub trait Display {
|
||||
/// Same as [`core::fmt::Display::fmt`].
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Display> Display for &T {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
Display::fmt(*self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + Display> core::fmt::Display for Adapter<&T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
let Self(t) = self;
|
||||
Display::fmt(t, f)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_display_forward {
|
||||
($(
|
||||
$( { $($generics:tt)* } )? $ty:ty $( { where $($where:tt)* } )?
|
||||
),* $(,)?) => {
|
||||
$(
|
||||
impl$($($generics)*)? Display for $ty $(where $($where)*)? {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
core::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_display_forward!(
|
||||
bool,
|
||||
char,
|
||||
core::panic::PanicInfo<'_>,
|
||||
Arguments<'_>,
|
||||
i128,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i8,
|
||||
isize,
|
||||
str,
|
||||
u128,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u8,
|
||||
usize,
|
||||
{<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display},
|
||||
{<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
pub use crate::alloc::{flags::*, Box, KBox, KVBox, KVVec, KVec, VBox, VVec, Vec};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use macros::{export, kunit_tests, module, vtable};
|
||||
pub use macros::{export, fmt, kunit_tests, module, vtable};
|
||||
|
||||
pub use pin_init::{init, pin_data, pin_init, pinned_drop, InPlaceWrite, Init, PinInit, Zeroable};
|
||||
|
||||
|
|
@ -36,7 +36,6 @@
|
|||
pub use super::dbg;
|
||||
pub use super::{dev_alert, dev_crit, dev_dbg, dev_emerg, dev_err, dev_info, dev_notice, dev_warn};
|
||||
pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
|
||||
pub use core::format_args as fmt;
|
||||
|
||||
pub use super::{try_init, try_pin_init};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use proc_macro::{Ident, TokenStream, TokenTree};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// Please see [`crate::fmt`] for documentation.
|
||||
pub(crate) fn fmt(input: TokenStream) -> TokenStream {
|
||||
let mut input = input.into_iter();
|
||||
|
||||
let first_opt = input.next();
|
||||
let first_owned_str;
|
||||
let mut names = BTreeSet::new();
|
||||
let first_span = {
|
||||
let Some((mut first_str, first_span)) = (match first_opt.as_ref() {
|
||||
Some(TokenTree::Literal(first_lit)) => {
|
||||
first_owned_str = first_lit.to_string();
|
||||
Some(first_owned_str.as_str()).and_then(|first| {
|
||||
let first = first.strip_prefix('"')?;
|
||||
let first = first.strip_suffix('"')?;
|
||||
Some((first, first_lit.span()))
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}) else {
|
||||
return first_opt.into_iter().chain(input).collect();
|
||||
};
|
||||
|
||||
// Parse `identifier`s from the format string.
|
||||
//
|
||||
// See https://doc.rust-lang.org/std/fmt/index.html#syntax.
|
||||
while let Some((_, rest)) = first_str.split_once('{') {
|
||||
first_str = rest;
|
||||
if let Some(rest) = first_str.strip_prefix('{') {
|
||||
first_str = rest;
|
||||
continue;
|
||||
}
|
||||
if let Some((name, rest)) = first_str.split_once('}') {
|
||||
first_str = rest;
|
||||
let name = name.split_once(':').map_or(name, |(name, _)| name);
|
||||
if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) {
|
||||
names.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
first_span
|
||||
};
|
||||
|
||||
let adapter = quote_spanned!(first_span => ::kernel::fmt::Adapter);
|
||||
|
||||
let mut args = TokenStream::from_iter(first_opt);
|
||||
{
|
||||
let mut flush = |args: &mut TokenStream, current: &mut TokenStream| {
|
||||
let current = std::mem::take(current);
|
||||
if !current.is_empty() {
|
||||
let (lhs, rhs) = (|| {
|
||||
let mut current = current.into_iter();
|
||||
let mut acc = TokenStream::new();
|
||||
while let Some(tt) = current.next() {
|
||||
// Split on `=` only once to handle cases like `a = b = c`.
|
||||
if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '=') {
|
||||
names.remove(acc.to_string().as_str());
|
||||
// Include the `=` itself to keep the handling below uniform.
|
||||
acc.extend([tt]);
|
||||
return (Some(acc), current.collect::<TokenStream>());
|
||||
}
|
||||
acc.extend([tt]);
|
||||
}
|
||||
(None, acc)
|
||||
})();
|
||||
args.extend(quote_spanned!(first_span => #lhs #adapter(&#rhs)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut current = TokenStream::new();
|
||||
for tt in input {
|
||||
match &tt {
|
||||
TokenTree::Punct(p) if p.as_char() == ',' => {
|
||||
flush(&mut args, &mut current);
|
||||
&mut args
|
||||
}
|
||||
_ => &mut current,
|
||||
}
|
||||
.extend([tt]);
|
||||
}
|
||||
flush(&mut args, &mut current);
|
||||
}
|
||||
|
||||
for name in names {
|
||||
let name = Ident::new(name, first_span);
|
||||
args.extend(quote_spanned!(first_span => , #name = #adapter(&#name)));
|
||||
}
|
||||
|
||||
quote_spanned!(first_span => ::core::format_args!(#args))
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
mod quote;
|
||||
mod concat_idents;
|
||||
mod export;
|
||||
mod fmt;
|
||||
mod helpers;
|
||||
mod kunit;
|
||||
mod module;
|
||||
|
|
@ -201,6 +202,24 @@ pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream {
|
|||
export::export(attr, ts)
|
||||
}
|
||||
|
||||
/// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
|
||||
///
|
||||
/// This macro allows generating `fmt::Arguments` while ensuring that each argument is wrapped with
|
||||
/// `::kernel::fmt::Adapter`, which customizes formatting behavior for kernel logging.
|
||||
///
|
||||
/// Named arguments used in the format string (e.g. `{foo}`) are detected and resolved from local
|
||||
/// bindings. All positional and named arguments are automatically wrapped.
|
||||
///
|
||||
/// This macro is an implementation detail of other kernel logging macros like [`pr_info!`] and
|
||||
/// should not typically be used directly.
|
||||
///
|
||||
/// [`kernel::fmt::Adapter`]: ../kernel/fmt/struct.Adapter.html
|
||||
/// [`pr_info!`]: ../kernel/macro.pr_info.html
|
||||
#[proc_macro]
|
||||
pub fn fmt(input: TokenStream) -> TokenStream {
|
||||
fmt::fmt(input)
|
||||
}
|
||||
|
||||
/// Concatenate two identifiers.
|
||||
///
|
||||
/// This is useful in macros that need to declare or reference items with names
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ macro_rules! quote_spanned {
|
|||
($span:expr => $($tt:tt)*) => {{
|
||||
let mut tokens = ::proc_macro::TokenStream::new();
|
||||
{
|
||||
#[allow(unused_variables)]
|
||||
let span = $span;
|
||||
quote_spanned!(@proc tokens span $($tt)*);
|
||||
}
|
||||
|
|
@ -146,6 +147,12 @@ macro_rules! quote_spanned {
|
|||
)]);
|
||||
quote_spanned!(@proc $v $span $($tt)*);
|
||||
};
|
||||
(@proc $v:ident $span:ident & $($tt:tt)*) => {
|
||||
$v.extend([::proc_macro::TokenTree::Punct(
|
||||
::proc_macro::Punct::new('&', ::proc_macro::Spacing::Alone),
|
||||
)]);
|
||||
quote_spanned!(@proc $v $span $($tt)*);
|
||||
};
|
||||
(@proc $v:ident $span:ident _ $($tt:tt)*) => {
|
||||
$v.extend([::proc_macro::TokenTree::Ident(
|
||||
::proc_macro::Ident::new("_", $span),
|
||||
|
|
|
|||
Loading…
Reference in New Issue