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(¯os_name, Span::call_site())),
243 ]),
244 )),
245 ]);
246 macro_inner.extend(item);
247
248 TokenStream::from_iter([
249 T::Ident(Ident::new(¯os_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(¯os_name, Span::call_site())),
258 T::Group(Group::new(Delimiter::Brace, gen::ctor())),
259 ])
260}