Skip to main content

kstring/
interned_string.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// A binary-stable, transparent representation of `fxt::InternedString`.
6///
7/// This structure has the exact same memory layout as the C++ `fxt::InternedString`
8/// (which is a single `const char*` pointer pointing to a null-terminated string).
9///
10/// Placing instances of this structure in the special linker section `__fxt_interned_string_table`
11/// allows them to sit contiguous alongside C++'s own interned strings in the global
12/// string table segment, meaning their trace-ID calculations (`this - section_begin`)
13/// are safe, exact, and valid.
14#[repr(transparent)]
15pub struct InternedString {
16    ptr: *const u8,
17}
18
19// SAFETY: InternedString points to a static, read-only null-terminated string byte array.
20// It is completely thread-safe to share and access across threads.
21unsafe impl Sync for InternedString {}
22unsafe impl Send for InternedString {}
23
24impl InternedString {
25    /// Creates a new `InternedString` from a raw pointer.
26    ///
27    /// # Safety
28    ///
29    /// The caller must ensure that the pointer points to a null-terminated string
30    /// with static storage duration ('static).
31    #[inline]
32    pub const unsafe fn new_raw(ptr: *const u8) -> Self {
33        Self { ptr }
34    }
35
36    /// Returns the raw pointer to the null-terminated string.
37    #[inline]
38    pub const fn as_ptr(&self) -> *const u8 {
39        self.ptr
40    }
41
42    /// Returns the static name as a safe, null-terminated C string reference.
43    #[inline]
44    pub fn as_c_str(&self) -> &'static core::ffi::CStr {
45        // SAFETY: The safety invariants of the raw constructor guarantee that the pointer
46        // points to a valid, null-terminated static byte string in read-only memory.
47        unsafe { core::ffi::CStr::from_ptr(self.ptr as *const core::ffi::c_char) }
48    }
49
50    /// Returns the numeric trace ID for this interned string.
51    #[inline]
52    pub fn id(&self) -> u16 {
53        unsafe extern "C" {
54            #[link_name = "__start___fxt_interned_string_table"]
55            static START: InternedString;
56        }
57        let self_ptr = self as *const InternedString;
58        let start_ptr = unsafe { &START as *const InternedString };
59        // SAFETY: Both pointers reside within the contiguous `__fxt_interned_string_table` linker
60        // section.
61        let diff = unsafe { self_ptr.offset_from(start_ptr) };
62        (diff + 1) as u16
63    }
64}
65
66/// Statically declares a new `InternedString`.
67///
68/// By default, this macro allocates the string inside the special
69/// `__fxt_interned_string_table` linker section.
70///
71/// If the `extern` parameter is provided, the macro instead references an external
72/// symbol (e.g. C++ template-allocated) with the C++ mangled name for the string,
73/// preventing duplicate physical allocation in the linker section.
74///
75/// # Examples
76///
77/// Local allocation:
78/// ```rust
79/// declare_interned_string!(MY_STRING, "hello.world");
80/// ```
81///
82/// External reference (references C++ symbol, avoids physical duplicates):
83/// ```rust
84/// declare_interned_string!(MY_STRING, "drop_stats", extern);
85/// ```
86#[macro_export]
87macro_rules! declare_interned_string {
88    ($var_name:ident, $str_lit:literal) => {
89        #[allow(non_snake_case)]
90        mod $var_name {
91            #[$crate::interned_string_export_name($str_lit)]
92            #[unsafe(link_section = "__fxt_interned_string_table")]
93            #[used]
94            pub static STRING: $crate::interned_string::InternedString = unsafe {
95                // Append a null byte to the string literal at compile time to satisfy
96                // the C++ const char* expectations.
97                $crate::interned_string::InternedString::new_raw(concat!($str_lit, "\0").as_ptr())
98            };
99        }
100
101        pub static $var_name: &$crate::interned_string::InternedString = &$var_name::STRING;
102    };
103
104    ($var_name:ident, $str_lit:literal, extern) => {
105        #[allow(non_snake_case)]
106        mod $var_name {
107            $crate::import_string!(STRING, $str_lit);
108        }
109
110        pub static $var_name: &$crate::interned_string::InternedString =
111            unsafe { &$var_name::STRING };
112    };
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    declare_interned_string!(TEST_STR_1, "hello");
120    declare_interned_string!(TEST_STR_2, "world");
121
122    #[test]
123    fn test_as_ptr() {
124        assert!(!TEST_STR_1.as_ptr().is_null());
125        assert!(!TEST_STR_2.as_ptr().is_null());
126
127        // Verify null termination!
128        unsafe {
129            let mut ptr = TEST_STR_1.as_ptr();
130            while *ptr != 0 {
131                ptr = ptr.add(1);
132            }
133            assert_eq!(*ptr, 0);
134        }
135    }
136
137    #[test]
138    fn test_as_c_str() {
139        let c_str1 = TEST_STR_1.as_c_str();
140        let c_str2 = TEST_STR_2.as_c_str();
141
142        assert_eq!(c_str1, c"hello");
143        assert_eq!(c_str2, c"world");
144
145        // Verify conversion to standard Rust &str
146        assert_eq!(c_str1.to_str().unwrap(), "hello");
147        assert_eq!(c_str2.to_str().unwrap(), "world");
148    }
149
150    // This linker section bound test is only valid on ELF targets (Fuchsia/Linux)
151    // where the linker generates __start and __stop symbols for orphan sections.
152    #[cfg(any(target_os = "fuchsia", target_os = "linux"))]
153    #[test]
154    fn test_linker_section_allocation() {
155        unsafe extern "C" {
156            #[link_name = "__start___fxt_interned_string_table"]
157            static START: InternedString;
158            #[link_name = "__stop___fxt_interned_string_table"]
159            static STOP: InternedString;
160        }
161
162        let start_ptr = unsafe { &START as *const InternedString };
163        let stop_ptr = unsafe { &STOP as *const InternedString };
164
165        // Ensure the boundary is valid and holds our entries
166        assert!(start_ptr <= stop_ptr);
167        let diff =
168            (stop_ptr as usize - start_ptr as usize) / core::mem::size_of::<InternedString>();
169        assert!(diff >= 2, "Expected at least 2 entries in the table, found {diff}");
170
171        // Verify our static variables reside strictly within the linker bounds
172        let p1 = TEST_STR_1 as *const InternedString;
173        let p2 = TEST_STR_2 as *const InternedString;
174
175        assert!(
176            p1 >= start_ptr && p1 < stop_ptr,
177            "TEST_STR_1 pointer {p1:p} is outside bounds [{start_ptr:p}, {stop_ptr:p})"
178        );
179        assert!(
180            p2 >= start_ptr && p2 < stop_ptr,
181            "TEST_STR_2 pointer {p2:p} is outside bounds [{start_ptr:p}, {stop_ptr:p})"
182        );
183    }
184
185    #[test]
186    fn test_id() {
187        assert!(TEST_STR_1.id() > 0);
188        assert!(TEST_STR_2.id() > 0);
189
190        let p1 = TEST_STR_1 as *const InternedString;
191        let p2 = TEST_STR_2 as *const InternedString;
192        let expected_diff = unsafe { p2.offset_from(p1) };
193        let actual_diff = (TEST_STR_2.id() as isize) - (TEST_STR_1.id() as isize);
194        assert_eq!(actual_diff, expected_diff);
195    }
196
197    #[test]
198    fn test_symbol_name_matching() {
199        // "hello" should mangle to the exact C++ symbol name:
200        // _ZN3fxt8internal21InternedStringStorageIJLc104ELc101ELc108ELc108ELc111EEE15interned_stringE
201        unsafe extern "C" {
202            #[link_name = "_ZN3fxt8internal21InternedStringStorageIJLc104ELc101ELc108ELc108ELc111EEE15interned_stringE"]
203            static EXPECTED_SYMBOL: InternedString;
204        }
205
206        let p_expected = unsafe { &EXPECTED_SYMBOL as *const InternedString };
207        let p_actual = TEST_STR_1 as *const InternedString;
208        assert_eq!(p_actual, p_expected);
209    }
210}