[go: up one dir, main page]

ctor/
lib.rs

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 is regularly tested on Linux, OSX and Windows, with both `+crt-static` and `-crt-static`.
10//! Other platforms are supported but not tested as part of the automatic builds. This library will also work as expected in both
11//! `bin` and `cdylib` outputs, 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// In addition, OSX has removed support for section-based shutdown hooks after
26// warning about it for a number of years:
27
28// https://reviews.llvm.org/D45578
29
30extern crate proc_macro;
31
32use std::iter::FromIterator;
33
34use proc_macro::*;
35
36#[rustfmt::skip]
37mod gen;
38
39/// Marks a function or static variable as a library/executable constructor.
40/// This uses OS-specific linker sections to call a specific function at
41/// load time.
42///
43/// Multiple startup functions/statics are supported, but the invocation order is not
44/// guaranteed.
45///
46/// # Examples
47///
48/// Print a startup message (using `libc_print` for safety):
49///
50/// ```rust
51/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
52/// # extern crate ctor;
53/// # use ctor::*;
54/// use libc_print::std_name::println;
55///
56/// #[ctor]
57/// fn foo() {
58///   println!("Hello, world!");
59/// }
60///
61/// # fn main() {
62/// println!("main()");
63/// # }
64/// ```
65///
66/// Make changes to `static` variables:
67///
68/// ```rust
69/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
70/// # extern crate ctor;
71/// # mod test {
72/// # use ctor::*;
73/// # use std::sync::atomic::{AtomicBool, Ordering};
74/// static INITED: AtomicBool = AtomicBool::new(false);
75///
76/// #[ctor]
77/// fn foo() {
78///   INITED.store(true, Ordering::SeqCst);
79/// }
80/// # }
81/// ```
82///
83/// Initialize a `HashMap` at startup time:
84///
85/// ```rust
86/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
87/// # extern crate ctor;
88/// # mod test {
89/// # use std::collections::HashMap;
90/// # use ctor::*;
91/// #[ctor]
92/// pub static STATIC_CTOR: HashMap<u32, String> = {
93///   let mut m = HashMap::new();
94///   for i in 0..100 {
95///     m.insert(i, format!("x*100={}", i*100));
96///   }
97///   m
98/// };
99/// # }
100/// # pub fn main() {
101/// #   assert_eq!(test::STATIC_CTOR.len(), 100);
102/// #   assert_eq!(test::STATIC_CTOR[&20], "x*100=2000");
103/// # }
104/// ```
105///
106/// # Details
107///
108/// The `#[ctor]` macro makes use of linker sections to ensure that a
109/// function is run at startup time.
110///
111/// The above example translates into the following Rust code (approximately):
112///
113///```rust
114/// #[used]
115/// #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
116/// #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
117/// #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
118/// #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
119/// #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
120/// #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")]
121/// #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
122/// static FOO: extern fn() = {
123///   #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
124///   extern fn foo() { /* ... */ };
125///   foo
126/// };
127/// ```
128#[proc_macro_attribute]
129pub fn ctor(_attribute: TokenStream, item: TokenStream) -> TokenStream {
130    fn identify_item(item: TokenStream) -> String {
131        let mut next_is_name = false;
132        for token in item {
133            if let TokenTree::Ident(ident) = token {
134                let ident = ident.to_string();
135                if next_is_name {
136                    return ident;
137                }
138                if ident == "fn" || ident == "static" {
139                    next_is_name = true;
140                }
141            }
142        }
143
144        panic!("#[ctor] may only be applied to `fn` or `static` items");
145    }
146
147    let name = identify_item(item.clone());
148    generate(&name, "ctor", item)
149}
150
151/// Marks a function as a library/executable destructor. This uses OS-specific
152/// linker sections to call a specific function at termination time.
153///
154/// Multiple shutdown functions are supported, but the invocation order is not
155/// guaranteed.
156///
157/// `sys_common::at_exit` is usually a better solution for shutdown handling, as
158/// it allows you to use `stdout` in your handlers.
159///
160/// ```rust
161/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
162/// # extern crate ctor;
163/// # use ctor::*;
164/// # fn main() {}
165///
166/// #[dtor]
167/// fn shutdown() {
168///   /* ... */
169/// }
170/// ```
171#[proc_macro_attribute]
172pub fn dtor(_attribute: TokenStream, item: TokenStream) -> TokenStream {
173    fn identify_item(item: TokenStream) -> String {
174        let mut next_is_name = false;
175        for token in item {
176            if let TokenTree::Ident(ident) = token {
177                let ident = ident.to_string();
178                if next_is_name {
179                    return ident;
180                }
181                if ident == "fn" || ident == "static" {
182                    next_is_name = true;
183                }
184            }
185        }
186
187        panic!("#[dtor] may only be applied to `fn` items");
188    }
189
190    let name = identify_item(item.clone());
191    generate(&name, "dtor", item)
192}
193
194fn generate(name: &str, ctor_type: &str, item: TokenStream) -> TokenStream {
195    use proc_macro::TokenTree as T;
196
197    let macros_name = format!("__rust_ctor_macros_{name}");
198    let mut macro_inner = TokenStream::from_iter([
199        T::Punct(Punct::new('#', Spacing::Alone)),
200        T::Group(Group::new(
201            Delimiter::Bracket,
202            TokenStream::from_iter([T::Ident(Ident::new(ctor_type, Span::call_site()))]),
203        )),
204        #[cfg(feature = "used_linker")]
205        T::Punct(Punct::new('#', Spacing::Alone)),
206        #[cfg(feature = "used_linker")]
207        T::Group(Group::new(
208            Delimiter::Bracket,
209            TokenStream::from_iter([
210                T::Ident(Ident::new("feature", Span::call_site())),
211                T::Group(Group::new(
212                    Delimiter::Parenthesis,
213                    TokenStream::from_iter([T::Ident(Ident::new(
214                        "used_linker",
215                        Span::call_site(),
216                    ))]),
217                )),
218            ]),
219        )),
220        #[cfg(feature = "__warn_on_missing_unsafe")]
221        T::Punct(Punct::new('#', Spacing::Alone)),
222        #[cfg(feature = "__warn_on_missing_unsafe")]
223        T::Group(Group::new(
224            Delimiter::Bracket,
225            TokenStream::from_iter([
226                T::Ident(Ident::new("feature", Span::call_site())),
227                T::Group(Group::new(
228                    Delimiter::Parenthesis,
229                    TokenStream::from_iter([T::Ident(Ident::new(
230                        "__warn_on_missing_unsafe",
231                        Span::call_site(),
232                    ))]),
233                )),
234            ]),
235        )),
236        T::Punct(Punct::new('#', Spacing::Alone)),
237        T::Group(Group::new(
238            Delimiter::Bracket,
239            TokenStream::from_iter([
240                T::Ident(Ident::new("macro_path", Span::call_site())),
241                T::Punct(Punct::new('=', Spacing::Alone)),
242                T::Ident(Ident::new(&macros_name, Span::call_site())),
243            ]),
244        )),
245    ]);
246    macro_inner.extend(item);
247
248    TokenStream::from_iter([
249        T::Ident(Ident::new(&macros_name, Span::call_site())),
250        T::Punct(Punct::new(':', Spacing::Joint)),
251        T::Punct(Punct::new(':', Spacing::Alone)),
252        T::Ident(Ident::new("ctor_parse", Span::call_site())),
253        T::Punct(Punct::new('!', Spacing::Alone)),
254        T::Group(Group::new(Delimiter::Parenthesis, macro_inner)),
255        T::Punct(Punct::new(';', Spacing::Alone)),
256        T::Ident(Ident::new("mod", Span::call_site())),
257        T::Ident(Ident::new(&macros_name, Span::call_site())),
258        T::Group(Group::new(Delimiter::Brace, gen::ctor())),
259    ])
260}