[PATCH v2 04/14] rust: introduce a c_str macro

Paolo Bonzini posted 14 patches 13 hours ago
[PATCH v2 04/14] rust: introduce a c_str macro
Posted by Paolo Bonzini 13 hours ago
This allows CStr constants to be defined easily on Rust 1.63.0, while
checking that there are no embedded NULs.  c"" literals were only
stabilized in Rust 1.77.0.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 rust/hw/char/pl011/src/device.rs       |  5 ++-
 rust/hw/char/pl011/src/device_class.rs |  5 ++-
 rust/hw/char/pl011/src/lib.rs          |  4 +-
 rust/qemu-api/meson.build              |  4 ++
 rust/qemu-api/src/c_str.rs             | 53 ++++++++++++++++++++++++++
 rust/qemu-api/src/lib.rs               |  1 +
 rust/qemu-api/tests/tests.rs           |  8 ++--
 7 files changed, 71 insertions(+), 9 deletions(-)
 create mode 100644 rust/qemu-api/src/c_str.rs

diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs
index 7be21769ae4..5ffe1911376 100644
--- a/rust/hw/char/pl011/src/device.rs
+++ b/rust/hw/char/pl011/src/device.rs
@@ -11,6 +11,7 @@
 
 use qemu_api::{
     bindings::{self, *},
+    c_str,
     definitions::ObjectImpl,
 };
 
@@ -99,7 +100,7 @@ impl qemu_api::definitions::Class for PL011Class {
 }
 
 #[used]
-pub static CLK_NAME: &CStr = c"clk";
+pub static CLK_NAME: &CStr = c_str!("clk");
 
 impl PL011State {
     /// Initializes a pre-allocated, unitialized instance of `PL011State`.
@@ -575,7 +576,7 @@ pub fn update(&self) {
         let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
         let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
 
-        qdev_prop_set_chr(dev, c"chardev".as_ptr(), chr);
+        qdev_prop_set_chr(dev, c_str!("chardev").as_ptr(), chr);
         sysbus_realize_and_unref(sysbus, addr_of!(error_fatal) as *mut *mut Error);
         sysbus_mmio_map(sysbus, 0, addr);
         sysbus_connect_irq(sysbus, 0, irq);
diff --git a/rust/hw/char/pl011/src/device_class.rs b/rust/hw/char/pl011/src/device_class.rs
index 78fa1cdd5b6..3b9b0267b4c 100644
--- a/rust/hw/char/pl011/src/device_class.rs
+++ b/rust/hw/char/pl011/src/device_class.rs
@@ -6,6 +6,7 @@
 
 use qemu_api::{
     bindings::*,
+    c_str,
     definitions::ObjectImpl,
     zeroable::Zeroable,
 };
@@ -22,14 +23,14 @@
 qemu_api::declare_properties! {
     PL011_PROPERTIES,
     qemu_api::define_property!(
-        c"chardev",
+        c_str!("chardev"),
         PL011State,
         char_backend,
         unsafe { &qdev_prop_chr },
         CharBackend
     ),
     qemu_api::define_property!(
-        c"migrate-clk",
+        c_str!("migrate-clk"),
         PL011State,
         migrate_clock,
         unsafe { &qdev_prop_bool },
diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs
index 2b157868b0f..0a598e5629d 100644
--- a/rust/hw/char/pl011/src/lib.rs
+++ b/rust/hw/char/pl011/src/lib.rs
@@ -41,11 +41,13 @@
 extern crate bilge_impl;
 extern crate qemu_api;
 
+use qemu_api::c_str;
+
 pub mod device;
 pub mod device_class;
 pub mod memory_ops;
 
-pub const TYPE_PL011: &::std::ffi::CStr = c"pl011";
+pub const TYPE_PL011: &::std::ffi::CStr = c_str!("pl011");
 
 /// Offset of each register from the base memory address of the device.
 ///
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index 1b0fd406378..2b3dea59fb6 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -3,6 +3,7 @@ _qemu_api_rs = static_library(
   structured_sources(
     [
       'src/lib.rs',
+      'src/c_str.rs',
       'src/definitions.rs',
       'src/device_class.rs',
       'src/zeroable.rs',
@@ -17,6 +18,9 @@ _qemu_api_rs = static_library(
   ],
 )
 
+rust.test('rust-qemu-api-tests', _qemu_api_rs,
+          suite: ['unit', 'rust'])
+
 qemu_api = declare_dependency(
   link_with: _qemu_api_rs,
   dependencies: qemu_api_macros,
diff --git a/rust/qemu-api/src/c_str.rs b/rust/qemu-api/src/c_str.rs
new file mode 100644
index 00000000000..5ef46d12bef
--- /dev/null
+++ b/rust/qemu-api/src/c_str.rs
@@ -0,0 +1,53 @@
+// Copyright 2024 Red Hat, Inc.
+// Author(s): Paolo Bonzini <pbonzini@redhat.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#[macro_export]
+/// Given a string constant _without_ embedded or trailing NULs, return
+/// a CStr.
+///
+/// Needed for compatibility with Rust <1.77.
+macro_rules! c_str {
+    ($str:expr) => {{
+        const STRING: &str = concat!($str, "\0");
+        const BYTES: &[u8] = STRING.as_bytes();
+
+        // "for" is not allowed in const context... oh well,
+        // everybody loves some lisp.  This could be turned into
+        // a procedural macro if this is a problem; alternatively
+        // Rust 1.72 makes CStr::from_bytes_with_nul a const function.
+        const fn f(b: &[u8], i: usize) {
+            if i == BYTES.len() - 1 {
+            } else if BYTES[i] == 0 {
+                panic!("c_str argument contains NUL")
+            } else {
+                f(b, i + 1)
+            }
+        }
+        f(BYTES, 0);
+
+        // SAFETY: absence of NULs apart from the final byte was checked above
+        unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(BYTES) }
+    }};
+}
+
+#[cfg(test)]
+mod tests {
+    use std::ffi::CStr;
+
+    use crate::c_str;
+
+    #[test]
+    fn test_cstr_macro() {
+        let good = c_str!("🦀");
+        let good_bytes = b"\xf0\x9f\xa6\x80\0";
+        assert_eq!(good.to_bytes_with_nul(), good_bytes);
+    }
+
+    #[test]
+    fn test_cstr_macro_const() {
+        const GOOD: &CStr = c_str!("🦀");
+        const GOOD_BYTES: &[u8] = b"\xf0\x9f\xa6\x80\0";
+        assert_eq!(GOOD.to_bytes_with_nul(), GOOD_BYTES);
+    }
+}
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index 321c33d2c7b..9c3be4f6489 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -27,6 +27,7 @@ unsafe impl Sync for bindings::Property {}
 unsafe impl Sync for bindings::TypeInfo {}
 unsafe impl Sync for bindings::VMStateDescription {}
 
+pub mod c_str;
 pub mod definitions;
 pub mod device_class;
 pub mod zeroable;
diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs
index 3396c33fe1e..605bc99ed4c 100644
--- a/rust/qemu-api/tests/tests.rs
+++ b/rust/qemu-api/tests/tests.rs
@@ -6,7 +6,7 @@
 
 use qemu_api::{
     bindings::*,
-    declare_properties, define_property,
+    c_str, declare_properties, define_property,
     definitions::{Class, ObjectImpl},
     device_class_init, vm_state_description,
 };
@@ -16,7 +16,7 @@ fn test_device_decl_macros() {
     // Test that macros can compile.
     vm_state_description! {
         VMSTATE,
-        name: c"name",
+        name: c_str!("name"),
         unmigratable: true,
     }
 
@@ -35,7 +35,7 @@ pub struct DummyClass {
     declare_properties! {
         DUMMY_PROPERTIES,
             define_property!(
-                c"migrate-clk",
+                c_str!("migrate-clk"),
                 DummyState,
                 migrate_clock,
                 unsafe { &qdev_prop_bool },
@@ -54,7 +54,7 @@ pub struct DummyClass {
     impl ObjectImpl for DummyState {
         type Class = DummyClass;
         const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
-        const TYPE_NAME: &'static CStr = c"dummy";
+        const TYPE_NAME: &'static CStr = c_str!("dummy");
         const PARENT_TYPE_NAME: Option<&'static CStr> = Some(TYPE_DEVICE);
         const ABSTRACT: bool = false;
         const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
-- 
2.46.2