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 | |