1 #![recursion_limit = "256"]
2 
3 //! Procedural macro for defining global constructor/destructor functions.
4 //!
5 //! This provides module initialization/teardown functions for Rust (like
6 //! `__attribute__((constructor))` in C/C++) for Linux, OSX, and Windows via
7 //! the `#[ctor]` and `#[dtor]` macros.
8 //!
9 //! This library works and has been tested for Linux, OSX and Windows. This
10 //! library will also work as expected in both `bin` and `cdylib` outputs,
11 //! ie: the `ctor` and `dtor` will run at executable or library
12 //! startup/shutdown respectively.
13 //!
14 //! This library currently requires Rust > `1.31.0` at a minimum for the
15 //! procedural macro support.
16 
17 // Code note:
18 
19 // You might wonder why we don't use `__attribute__((destructor))`/etc for
20 // dtor. Unfortunately mingw doesn't appear to properly support section-based
21 // hooks for shutdown, ie:
22 
23 // https://github.com/Alexpux/mingw-w64/blob/d0d7f784833bbb0b2d279310ddc6afb52fe47a46/mingw-w64-crt/crt/crtdll.c
24 
25 extern crate proc_macro;
26 extern crate syn;
27 #[macro_use]
28 extern crate quote;
29 
30 use proc_macro::TokenStream;
31 
32 /// Marks a function or static variable as a library/executable constructor.
33 /// This uses OS-specific linker sections to call a specific function at
34 /// load time.
35 ///
36 /// Multiple startup functions/statics are supported, but the invocation order is not
37 /// guaranteed.
38 ///
39 /// # Examples
40 ///
41 /// Print a startup message:
42 ///
43 /// ```rust
44 /// # extern crate ctor;
45 /// # use ctor::*;
46 /// #[ctor]
47 /// fn foo() {
48 ///   println!("Hello, world!");
49 /// }
50 ///
51 /// # fn main() {
52 /// println!("main()");
53 /// # }
54 /// ```
55 ///
56 /// Make changes to `static` variables:
57 ///
58 /// ```rust
59 /// # extern crate ctor;
60 /// # use ctor::*;
61 /// # use std::sync::atomic::{AtomicBool, Ordering};
62 /// static INITED: AtomicBool = AtomicBool::new(false);
63 ///
64 /// #[ctor]
65 /// fn foo() {
66 ///   INITED.store(true, Ordering::SeqCst);
67 /// }
68 /// ```
69 ///
70 /// Initialize a `HashMap` at startup time:
71 ///
72 /// ```rust
73 /// # extern crate ctor;
74 /// # use std::collections::HashMap;
75 /// # use ctor::*;
76 /// #[ctor]
77 /// static STATIC_CTOR: HashMap<u32, String> = {
78 ///   let mut m = HashMap::new();
79 ///   for i in 0..100 {
80 ///     m.insert(i, format!("x*100={}", i*100));
81 ///   }
82 ///   m
83 /// };
84 ///
85 /// # pub fn main() {
86 /// #   assert_eq!(STATIC_CTOR.len(), 100);
87 /// #   assert_eq!(STATIC_CTOR[&20], "x*100=2000");
88 /// # }
89 /// ```
90 ///
91 /// # Details
92 ///
93 /// The `#[ctor]` macro makes use of linker sections to ensure that a
94 /// function is run at startup time.
95 ///
96 /// The above example translates into the following Rust code (approximately):
97 ///
98 ///```rust
99 /// #[used]
100 /// #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
101 /// #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
102 /// #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
103 /// #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
104 /// #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
105 /// #[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_init_func")]
106 /// #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
107 /// static FOO: extern fn() = {
108 ///   #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
109 ///   extern fn foo() { /* ... */ };
110 ///   foo
111 /// };
112 /// ```
113 #[proc_macro_attribute]
ctor(_attribute: TokenStream, function: TokenStream) -> TokenStream114 pub fn ctor(_attribute: TokenStream, function: TokenStream) -> TokenStream {
115     let item: syn::Item = syn::parse_macro_input!(function);
116     if let syn::Item::Fn(function) = item {
117         validate_item("ctor", &function);
118 
119         let syn::ItemFn {
120             attrs,
121             block,
122             vis,
123             sig:
124                 syn::Signature {
125                     ident,
126                     unsafety,
127                     constness,
128                     abi,
129                     ..
130                 },
131             ..
132         } = function;
133 
134         // Linux/ELF: https://www.exploit-db.com/papers/13234
135 
136         // Mac details: https://blog.timac.org/2016/0716-constructor-and-destructor-attributes/
137 
138         // Why .CRT$XCU on Windows? https://www.cnblogs.com/sunkang/archive/2011/05/24/2055635.html
139         // 'I'=C init, 'C'=C++ init, 'P'=Pre-terminators and 'T'=Terminators
140 
141         let ctor_ident =
142             syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___ctor", ident).as_ref())
143                 .expect("Unable to create identifier");
144 
145         let output = quote!(
146             #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "illumos", target_os = "macos", target_os = "ios", windows)))]
147             compile_error!("#[ctor] is not supported on the current target");
148 
149             #(#attrs)*
150             #vis #unsafety extern #abi #constness fn #ident() #block
151 
152             #[used]
153             #[allow(non_upper_case_globals)]
154             #[doc(hidden)]
155             #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
156             #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
157             #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
158             #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
159             #[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
160             #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
161             #[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_init_func")]
162             #[cfg_attr(windows, link_section = ".CRT$XCU")]
163             static #ctor_ident
164             :
165             unsafe extern "C" fn() =
166             {
167                 #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
168                 unsafe extern "C" fn #ctor_ident() { #ident() };
169                 #ctor_ident
170             }
171             ;
172         );
173 
174         // eprintln!("{}", output);
175 
176         output.into()
177     } else if let syn::Item::Static(var) = item {
178         let syn::ItemStatic {
179             ident,
180             mutability,
181             expr,
182             attrs,
183             ty,
184             vis,
185             ..
186         } = var;
187 
188         if let Some(_) = mutability {
189             panic!("#[ctor]-annotated static objects must not be mutable");
190         }
191 
192         if attrs.iter().any(|attr| {
193             attr.path
194                 .segments
195                 .iter()
196                 .any(|segment| segment.ident == "no_mangle")
197         }) {
198             panic!("#[ctor]-annotated static objects do not support #[no_mangle]");
199         }
200 
201         let ctor_ident =
202             syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___ctor", ident).as_ref())
203                 .expect("Unable to create identifier");
204         let storage_ident =
205             syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___storage", ident).as_ref())
206                 .expect("Unable to create identifier");
207 
208         let output = quote!(
209             #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "illumos", target_os = "macos", target_os = "ios", windows)))]
210             compile_error!("#[ctor] is not supported on the current target");
211 
212             // This is mutable, but only by this macro code!
213             static mut #storage_ident: Option<#ty> = None;
214 
215             #[doc(hidden)]
216             #[allow(non_camel_case_types)]
217             #vis struct #ident<T> {
218                 _data: core::marker::PhantomData<T>
219             }
220 
221             #(#attrs)*
222             #vis static #ident: #ident<#ty> = #ident {
223                 _data: core::marker::PhantomData::<#ty>
224             };
225 
226             impl core::ops::Deref for #ident<#ty> {
227                 type Target = #ty;
228                 fn deref(&self) -> &'static #ty {
229                     unsafe {
230                         #storage_ident.as_ref().unwrap()
231                     }
232                 }
233             }
234 
235             #[used]
236             #[allow(non_upper_case_globals)]
237             #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
238             #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
239             #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
240             #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
241             #[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
242             #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
243             #[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_init_func")]
244             #[cfg_attr(windows, link_section = ".CRT$XCU")]
245             static #ctor_ident
246             :
247             unsafe extern "C" fn() = {
248                 #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
249                 unsafe extern "C" fn initer() {
250                     #storage_ident = Some(#expr);
251                 }; initer }
252             ;
253         );
254 
255         // eprintln!("{}", output);
256 
257         output.into()
258     } else {
259         panic!("#[ctor] items must be functions or static globals");
260     }
261 }
262 
263 /// Marks a function as a library/executable destructor. This uses OS-specific
264 /// linker sections to call a specific function at termination time.
265 ///
266 /// Multiple shutdown functions are supported, but the invocation order is not
267 /// guaranteed.
268 ///
269 /// `sys_common::at_exit` is usually a better solution for shutdown handling, as
270 /// it allows you to use `stdout` in your handlers.
271 ///
272 /// ```rust
273 /// # extern crate ctor;
274 /// # use ctor::*;
275 /// # fn main() {}
276 ///
277 /// #[dtor]
278 /// fn shutdown() {
279 ///   /* ... */
280 /// }
281 /// ```
282 #[proc_macro_attribute]
dtor(_attribute: TokenStream, function: TokenStream) -> TokenStream283 pub fn dtor(_attribute: TokenStream, function: TokenStream) -> TokenStream {
284     let function: syn::ItemFn = syn::parse_macro_input!(function);
285     validate_item("dtor", &function);
286 
287     let syn::ItemFn {
288         attrs,
289         block,
290         vis,
291         sig:
292             syn::Signature {
293                 ident,
294                 unsafety,
295                 constness,
296                 abi,
297                 ..
298             },
299         ..
300     } = function;
301 
302     let mod_ident = syn::parse_str::<syn::Ident>(format!("{}___rust_dtor___mod", ident).as_ref())
303         .expect("Unable to create identifier");
304 
305     let dtor_ident = syn::parse_str::<syn::Ident>(format!("{}___rust_dtor___dtor", ident).as_ref())
306         .expect("Unable to create identifier");
307 
308     let output = quote!(
309         #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "illumos", target_os = "macos", target_os = "ios", windows)))]
310         compile_error!("#[dtor] is not supported on the current target");
311 
312         #(#attrs)*
313         #vis #unsafety extern #abi #constness fn #ident() #block
314 
315         // Targets that use `atexit`.
316         #[cfg(not(any(
317             target_os = "macos",
318             target_os = "ios",
319         )))]
320         mod #mod_ident {
321             use super::#ident;
322 
323             // Avoid a dep on libc by linking directly
324             extern "C" {
325                 fn atexit(cb: unsafe extern fn());
326             }
327 
328             #[used]
329             #[allow(non_upper_case_globals)]
330             #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
331             #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
332             #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
333             #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
334             #[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
335             #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
336             #[cfg_attr(windows, link_section = ".CRT$XCU")]
337             static __dtor_export
338             :
339             unsafe extern "C" fn() =
340             {
341                 #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.exit")]
342                 unsafe extern "C" fn #dtor_ident() { #ident() };
343                 #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
344                 unsafe extern fn __dtor_atexit() {
345                     atexit(#dtor_ident);
346                 };
347                 __dtor_atexit
348             };
349         }
350 
351         // Targets that don't rely on `atexit`.
352         #[cfg(any(
353             target_os = "macos",
354             target_os = "ios",
355         ))]
356         mod #mod_ident {
357             use super::#ident;
358 
359             #[used]
360             #[allow(non_upper_case_globals)]
361             #[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_term_func")]
362             static __dtor_export
363             :
364             unsafe extern "C" fn() =
365             {
366                 unsafe extern fn __dtor() { #ident() };
367                 __dtor
368             };
369         }
370     );
371 
372     // eprintln!("{}", output);
373 
374     output.into()
375 }
376 
validate_item(typ: &str, item: &syn::ItemFn)377 fn validate_item(typ: &str, item: &syn::ItemFn) {
378     let syn::ItemFn { vis, sig, .. } = item;
379 
380     // Ensure that visibility modifier is not present
381     match vis {
382         syn::Visibility::Inherited => {}
383         _ => panic!("#[{}] methods must not have visibility modifiers", typ),
384     }
385 
386     // No parameters allowed
387     if sig.inputs.len() > 0 {
388         panic!("#[{}] methods may not have parameters", typ);
389     }
390 
391     // No return type allowed
392     match sig.output {
393         syn::ReturnType::Default => {}
394         _ => panic!("#[{}] methods must not have return types", typ),
395     }
396 }
397