| 1 | //! The [`ctor`] crate reimplemented using procedural macros. |
| 2 | //! |
| 3 | //! [`ctor`]: https://crates.io/crates/ctor |
| 4 | //! |
| 5 | //! In some cases it is necessary to run code at the very start or the very end |
| 6 | //! of the program. This crate provides a macro that can be used to run code at |
| 7 | //! the very beginning of program execution, along with some extra features. |
| 8 | //! |
| 9 | //! ## Advantages over [`ctor`] |
| 10 | //! |
| 11 | //! - Completely dependency free, thanks to relying on procedural macros instead |
| 12 | //! of proc macros. |
| 13 | //! - Supports all of the same use cases as the [`ctor`] crate. |
| 14 | //! - Supports all of the same platforms as the [`ctor`] crate. |
| 15 | //! - Fixes a couple of warts in [`ctor`]'s API, such as: |
| 16 | //! - `unsafe` is required when it is used, see the "Safety" section below. |
| 17 | //! - Global variables are required to be `Sync`. |
| 18 | //! - Global variables use `MaybeUninit` instead of `Option`. |
| 19 | //! - Functions set up with the `ctor` or `dtor` macros cannot be called in |
| 20 | //! other Rust code. |
| 21 | //! |
| 22 | //! ## Disadvantages |
| 23 | //! |
| 24 | //! - The API has a slightly different form factor that can be inconvenient in |
| 25 | //! some cases. |
| 26 | //! - The MSRV has been raised to 1.36.0. |
| 27 | //! |
| 28 | //! ## Functional Usage |
| 29 | //! |
| 30 | //! The `ctor` macro can be used to run a function at program startup time. |
| 31 | //! |
| 32 | //! ``` |
| 33 | //! use std::sync::atomic::{AtomicUsize, Ordering}; |
| 34 | //! |
| 35 | //! static INITIALIZED: AtomicUsize = AtomicUsize::new(0); |
| 36 | //! |
| 37 | //! ctor_lite::ctor! { |
| 38 | //! unsafe fn set_value() { |
| 39 | //! INITIALIZED.store(1, Ordering::Relaxed); |
| 40 | //! } |
| 41 | //! } |
| 42 | //! |
| 43 | //! assert_eq!(INITIALIZED.load(Ordering::Relaxed), 1); |
| 44 | //! ``` |
| 45 | //! |
| 46 | //! Note that this macro is a procedural block rather than an attribute macro. |
| 47 | //! If you prefer the old way of using the macro you can use the |
| 48 | //! [`macro-rules-attribute`] crate. |
| 49 | //! |
| 50 | //! [`macro-rules-attribute`]: https://crates.io/crates/macro-rules-attribute |
| 51 | //! |
| 52 | //! ``` |
| 53 | //! use macro_rules_attribute::apply; |
| 54 | //! use std::sync::atomic::{AtomicUsize, Ordering}; |
| 55 | //! |
| 56 | //! static INITIALIZED: AtomicUsize = AtomicUsize::new(0); |
| 57 | //! |
| 58 | //! #[apply(ctor_lite::ctor!)] |
| 59 | //! unsafe fn set_value() { |
| 60 | //! INITIALIZED.store(1, Ordering::Relaxed); |
| 61 | //! } |
| 62 | //! |
| 63 | //! assert_eq!(INITIALIZED.load(Ordering::Relaxed), 1); |
| 64 | //! ``` |
| 65 | //! |
| 66 | //! ## Static Usage |
| 67 | //! |
| 68 | //! The `ctor` macro can be used to create a static variable initialized to a |
| 69 | //! default value. At startup time, the function is used to initialize the |
| 70 | //! static variable. |
| 71 | //! |
| 72 | //! ``` |
| 73 | //! fn value() -> i32 { |
| 74 | //! 6 |
| 75 | //! } |
| 76 | //! |
| 77 | //! ctor_lite::ctor! { |
| 78 | //! unsafe static VALUE: i32 = value(); |
| 79 | //! } |
| 80 | //! |
| 81 | //! assert_eq!(*VALUE, 6); |
| 82 | //! ``` |
| 83 | //! |
| 84 | //! ## Destructor |
| 85 | //! |
| 86 | //! This crate can also be used to run a function at program exit as well. The |
| 87 | //! `dtor` macro can be used to run a function when the program ends. |
| 88 | //! |
| 89 | //! ``` |
| 90 | //! use macro_rules_attribute::apply; |
| 91 | //! |
| 92 | //! #[apply(ctor_lite::dtor!)] |
| 93 | //! unsafe fn run_at_exit() { |
| 94 | //! do_some_cleanup(); |
| 95 | //! } |
| 96 | //! |
| 97 | //! # fn do_some_cleanup() {} |
| 98 | //! ``` |
| 99 | //! |
| 100 | //! ## Safety |
| 101 | //! |
| 102 | //! Macros from this crate must be used with care. In general Rust code is run |
| 103 | //! with the assumption that no other code is run before program startup, and |
| 104 | //! no other code is run after program shutdown. Specifically, `libstd` sets up |
| 105 | //! some global variables before the `main` function and then assumes these |
| 106 | //! variables are set throughout its runtime. Therefore, calling `libstd` |
| 107 | //! functions that use these variables will lead to undefined behavior. |
| 108 | //! |
| 109 | //! Generally, functions from `core` or `alloc` are safe to call in these |
| 110 | //! functions. In addition, functions from [`libc`] should be able to be called |
| 111 | //! freely, as well as most of the functions contained in [`rustix`]. Other |
| 112 | //! crates should be used only when it is understood what other calls they |
| 113 | //! contain. |
| 114 | //! |
| 115 | //! [`libc`]: https://crates.io/crates/libc |
| 116 | //! [`rustix`]: https://crates.io/crates/rustix |
| 117 | //! |
| 118 | //! In addition, no ordering is guaranteed for functions ran in the `ctor` or |
| 119 | //! `dtor` macros. |
| 120 | //! |
| 121 | //! ## Implementation |
| 122 | //! |
| 123 | //! The `ctor` macro works by creating a function with linker attributes that |
| 124 | //! place it into a special section in the file. When the C runtime starts the |
| 125 | //! program, it reads function pointers from this section and runs them. |
| 126 | //! |
| 127 | //! This function call... |
| 128 | //! |
| 129 | //! ``` |
| 130 | //! ctor_lite::ctor! { |
| 131 | //! unsafe fn foo() { /* ... */ } |
| 132 | //! } |
| 133 | //! ``` |
| 134 | //! |
| 135 | //! ...is translated to code that looks like this: |
| 136 | //! |
| 137 | //! ``` |
| 138 | //! #[used] |
| 139 | //! #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".init_array" )] |
| 140 | //! #[cfg_attr(target_os = "freebsd" , link_section = ".init_array" )] |
| 141 | //! #[cfg_attr(target_os = "netbsd" , link_section = ".init_array" )] |
| 142 | //! #[cfg_attr(target_os = "openbsd" , link_section = ".init_array" )] |
| 143 | //! #[cfg_attr(target_os = "illumos" , link_section = ".init_array" )] |
| 144 | //! #[cfg_attr(any(target_os = "macos" , target_os = "ios" , target_os = "tvos" ), link_section = "__DATA_CONST,__mod_init_func" )] |
| 145 | //! #[cfg_attr(target_os = "windows" , link_section = ".CRT$XCU" )] |
| 146 | //! static FOO: extern fn() = { |
| 147 | //! #[cfg_attr (any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
| 148 | //! extern fn foo() { /* ... */ }; |
| 149 | //! foo |
| 150 | //! }; |
| 151 | //! ``` |
| 152 | //! |
| 153 | //! When creating a global constant with the `ctor` macro it writes code that |
| 154 | //! runs the function then writes the value into a global constant. |
| 155 | //! |
| 156 | //! This code... |
| 157 | //! |
| 158 | //! ``` |
| 159 | //! ctor_lite::ctor! { |
| 160 | //! unsafe static FOO: i32 = foo(); |
| 161 | //! } |
| 162 | //! # fn foo() -> i32 { 1 } |
| 163 | //! ``` |
| 164 | //! |
| 165 | //! ...is translated to code that looks like this, with modifications that allow |
| 166 | //! for `FOO` to be used from safe code: |
| 167 | //! |
| 168 | //! ```no_compile |
| 169 | //! static mut FOO: i32 = core::mem::uninitialized(); |
| 170 | //! ctor_lite::ctor! { |
| 171 | //! unsafe fn init_storage() { |
| 172 | //! FOO = foo(); |
| 173 | //! } |
| 174 | //! } |
| 175 | //! # fn foo() -> i32 { 1 } |
| 176 | //! ``` |
| 177 | //! |
| 178 | //! When functions are put into `dtor`, it runs `ctor` with the `libc::atexit` |
| 179 | //! function to ensure that the function is run at program exit. |
| 180 | //! |
| 181 | //! This code... |
| 182 | //! |
| 183 | //! ``` |
| 184 | //! ctor_lite::dtor! { |
| 185 | //! unsafe fn foo() { |
| 186 | //! /* ... */ |
| 187 | //! } |
| 188 | //! } |
| 189 | //! ``` |
| 190 | //! |
| 191 | //! ...is translated to code that looks like this, with modifications that let |
| 192 | //! us avoid a dependency on the [`libc`] crate: |
| 193 | //! |
| 194 | //! ```no_compile |
| 195 | //! unsafe fn foo() { |
| 196 | //! /* ... */ |
| 197 | //! } |
| 198 | //! |
| 199 | //! ctor_lite::ctor! { |
| 200 | //! unsafe fn run_dtor() { |
| 201 | //! libc::atexit(foo); |
| 202 | //! } |
| 203 | //! } |
| 204 | //! ``` |
| 205 | |
| 206 | #![no_std ] |
| 207 | |
| 208 | /// Run a function on program startup or initialize a constant. |
| 209 | /// |
| 210 | /// See the crate level documentation for more info. |
| 211 | #[macro_export ] |
| 212 | macro_rules! ctor { |
| 213 | // Case 1: Run a function at startup time. |
| 214 | ( |
| 215 | $(#[$meta:meta])* |
| 216 | $vis:vis unsafe fn $name:ident () $bl:block |
| 217 | ) => { |
| 218 | const _: () = { |
| 219 | $(#[$meta])* |
| 220 | $vis unsafe fn $name () { |
| 221 | unsafe fn __this_thing_is_always_unsafe() {} |
| 222 | __this_thing_is_always_unsafe(); |
| 223 | $bl |
| 224 | } |
| 225 | |
| 226 | #[cfg(not(any( |
| 227 | target_os = "linux" , |
| 228 | target_os = "android" , |
| 229 | target_os = "freebsd" , |
| 230 | target_os = "netbsd" , |
| 231 | target_os = "openbsd" , |
| 232 | target_os = "dragonfly" , |
| 233 | target_os = "illumos" , |
| 234 | target_os = "haiku" , |
| 235 | target_os = "macos" , |
| 236 | target_os = "ios" , |
| 237 | target_os = "visionos" , |
| 238 | target_os = "tvos" , |
| 239 | windows |
| 240 | )))] |
| 241 | compile_error!("ctor! is not supported on the current target" ); |
| 242 | |
| 243 | #[used] |
| 244 | #[allow(non_upper_case_globals, non_snake_case)] |
| 245 | #[doc(hidden)] |
| 246 | #[cfg_attr( |
| 247 | any(target_os = "linux" , target_os = "android" ), |
| 248 | link_section = ".init_array" |
| 249 | )] |
| 250 | #[cfg_attr(target_os = "freebsd" , link_section = ".init_array" )] |
| 251 | #[cfg_attr(target_os = "netbsd" , link_section = ".init_array" )] |
| 252 | #[cfg_attr(target_os = "openbsd" , link_section = ".init_array" )] |
| 253 | #[cfg_attr(target_os = "dragonfly" , link_section = ".init_array" )] |
| 254 | #[cfg_attr(target_os = "illumos" , link_section = ".init_array" )] |
| 255 | #[cfg_attr(target_os = "haiku" , link_section = ".init_array" )] |
| 256 | #[cfg_attr( |
| 257 | any( |
| 258 | target_os = "macos" , |
| 259 | target_os = "ios" , |
| 260 | target_os = "visionos" , |
| 261 | target_os = "tvos" |
| 262 | ), |
| 263 | link_section = "__DATA,__mod_init_func" |
| 264 | )] |
| 265 | #[cfg_attr(windows, link_section = ".CRT$XCU" )] |
| 266 | static __rust_ctor_lite__ctor: unsafe extern "C" fn() -> usize = { |
| 267 | #[cfg_attr( |
| 268 | any(target_os = "linux" , target_os = "android" ), |
| 269 | link_section = ".text.startup" |
| 270 | )] |
| 271 | unsafe extern "C" fn ctor() -> usize { |
| 272 | $name (); |
| 273 | 0 |
| 274 | } |
| 275 | |
| 276 | ctor |
| 277 | }; |
| 278 | }; |
| 279 | }; |
| 280 | |
| 281 | // Case 2: Initialize a constant at bootup time. |
| 282 | ( |
| 283 | $(#[$meta:meta])* |
| 284 | $vis:vis unsafe static $(mut)? $name:ident:$ty:ty = $e:expr; |
| 285 | ) => { |
| 286 | #[doc(hidden)] |
| 287 | #[allow(non_camel_case_types)] |
| 288 | $vis struct $name<T> { |
| 289 | _data: ::core::marker::PhantomData<T> |
| 290 | } |
| 291 | |
| 292 | $(#[$meta:meta])* |
| 293 | $vis static $name: $name<$ty> = $name { |
| 294 | _data: ::core::marker::PhantomData::<$ty> |
| 295 | }; |
| 296 | |
| 297 | const _: () = { |
| 298 | use ::core::cell::UnsafeCell; |
| 299 | use ::core::mem::MaybeUninit; |
| 300 | use ::core::ops::Deref; |
| 301 | |
| 302 | struct SyncSlot(UnsafeCell<MaybeUninit<$ty>>); |
| 303 | unsafe impl Sync for SyncSlot {} |
| 304 | |
| 305 | static STORAGE: SyncSlot = { |
| 306 | SyncSlot(UnsafeCell::new(MaybeUninit::uninit())) |
| 307 | }; |
| 308 | |
| 309 | impl Deref for $name<$ty> { |
| 310 | type Target = $ty; |
| 311 | |
| 312 | fn deref(&self) -> &$ty { |
| 313 | // SAFETY: This will always be initialized. |
| 314 | unsafe { |
| 315 | &*(&*STORAGE.0.get()).as_ptr() |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | $crate::ctor! { |
| 321 | unsafe fn init_storage() { |
| 322 | let val = $e; |
| 323 | |
| 324 | // SAFETY: We are the only ones who can write into STORAGE. |
| 325 | unsafe { |
| 326 | *STORAGE.0.get() = MaybeUninit::new(val); |
| 327 | } |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | fn __assert_type_is_sync() { |
| 332 | fn __must_be_sync<T: Sync>() {} |
| 333 | __must_be_sync::<$ty>(); |
| 334 | } |
| 335 | }; |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | /// Run a function on program shutdown. |
| 340 | /// |
| 341 | /// See the crate level documentation for more information. |
| 342 | #[macro_export ] |
| 343 | macro_rules! dtor { |
| 344 | ( |
| 345 | $(#[$meta:meta])* |
| 346 | $vis:vis unsafe fn $name:ident () $bl:block |
| 347 | ) => { |
| 348 | const _: () = { |
| 349 | $(#[$meta])* |
| 350 | $vis unsafe fn $name () { |
| 351 | unsafe fn __this_thing_is_always_unsafe() {} |
| 352 | __this_thing_is_always_unsafe(); |
| 353 | $bl |
| 354 | } |
| 355 | |
| 356 | // Link directly to atexit in order to avoid a libc dependency. |
| 357 | #[cfg(not(any( |
| 358 | target_os = "macos" , |
| 359 | target_os = "ios" , |
| 360 | target_os = "visionos" , |
| 361 | target_os = "tvos" |
| 362 | )))] |
| 363 | #[inline(always)] |
| 364 | unsafe fn __do_atexit(cb: unsafe extern fn()) { |
| 365 | extern "C" { |
| 366 | fn atexit(cb: unsafe extern fn()); |
| 367 | } |
| 368 | atexit(cb); |
| 369 | } |
| 370 | |
| 371 | // For platforms that have __cxa_atexit, we register the dtor as scoped to dso_handle |
| 372 | #[cfg(any( |
| 373 | target_os = "macos" , |
| 374 | target_os = "ios" , |
| 375 | target_os = "visionos" , |
| 376 | target_os = "tvos" |
| 377 | ))] |
| 378 | #[inline(always)] |
| 379 | unsafe fn __do_atexit(cb: unsafe extern fn(_: *const u8)) { |
| 380 | extern "C" { |
| 381 | static __dso_handle: *const u8; |
| 382 | fn __cxa_atexit( |
| 383 | cb: unsafe extern fn(_: *const u8), |
| 384 | arg: *const u8, |
| 385 | dso_handle: *const u8 |
| 386 | ); |
| 387 | } |
| 388 | __cxa_atexit(cb, ::core::ptr::null(), __dso_handle); |
| 389 | } |
| 390 | |
| 391 | #[cfg(not(any( |
| 392 | target_os = "macos" , |
| 393 | target_os = "ios" , |
| 394 | target_os = "visionos" , |
| 395 | target_os = "tvos" |
| 396 | )))] |
| 397 | #[cfg_attr( |
| 398 | any( |
| 399 | target_os = "linux" , |
| 400 | target_os = "android" |
| 401 | ), |
| 402 | link_section = ".text.exit" |
| 403 | )] |
| 404 | unsafe extern "C" fn __run_destructor() { $name() }; |
| 405 | #[cfg(any( |
| 406 | target_os = "macos" , |
| 407 | target_os = "ios" , |
| 408 | target_os = "visionos" , |
| 409 | target_os = "tvos" |
| 410 | ))] |
| 411 | unsafe extern "C" fn __run_destructor(_: *const u8) { $name() }; |
| 412 | |
| 413 | $crate::ctor! { |
| 414 | unsafe fn register_dtor() { |
| 415 | __do_atexit(__run_destructor); |
| 416 | } |
| 417 | } |
| 418 | }; |
| 419 | }; |
| 420 | } |
| 421 | |