1 //! A dependency free library that declares functions to be automatically
2 //! executed before `main` is invoked.
3 //!
4 //! This crate implements the exact same behavior as the
5 //! [`ctor`](https://crates.io/crates/ctor) crate with the following
6 //! differences:
7 //!
8 //! * It has no dependencies other than `proc_macro` itself.
9 //! * It requires that functions are marked with `unsafe`.
10 //! * It can only be used with functions, not static items.
11 //! * It only supports `#[ctor]`
12 //!
13 //! ## Example
14 //!
15 //! This is a motivating example that registers a struct as a plugin in a
16 //! hypothetical global plugins registry:
17 //!
18 //! ```
19 //! struct MyPlugin;
20 //! # impl MyPlugin { fn register(&self, _: MyPlugin) {} }
21 //! # static PLUGINS: MyPlugin = MyPlugin;
22 //!
23 //! #[small_ctor::ctor]
24 //! unsafe fn register_plugin() {
25 //! PLUGINS.register(MyPlugin);
26 //! }
27 //! ```
28 //!
29 //! ## Safety
30 //!
31 //! This library involves "life before main" which is explicitly not permitted
32 //! in Rust which is why this library is anything but safe. In fact, it's a
33 //! really bad idea to do what this crate is promoting. For instance at
34 //! present code that runs in `#[ctor]` will run before `lang_start` managed to
35 //! execute. Some of the effects of this are that the main thread does not have
36 //! a name yet, the stack protection is not enabled and code must not panic.
37 //!
38 //! It's recommended that the only thing you do in a `#[ctor]` function is to
39 //! append to a vector, insert into a hashmap or similar.
40 //!
41 //! ## Recommended Usage
42 //!
43 //! It's recommended to perform basic operations which are unlikely to panic
44 //! and to defer most work to when the actual `main` happens. So for
45 //! instead instead of initializing plugins in the `ctor`, just push them
46 //! to a plugin registry and then trigger callbacks registered that way
47 //! regularly in `main`.
48 //!
49 //! ## Compiler and Linker Bugs
50 //!
51 //! Currently this library is prone to break due to compiler bugs in subtle
52 //! ways. The core issue is [rust #47384](https://github.com/rust-lang/rust/issues/47384).
53 //! You can reduze the likelihood of it happening by disabling incremental
54 //! compilation or setting `codegen-units` to `1` in your profile in the
55 //! `Cargo.toml`.
56 //!
57 //! ## Destructors
58 //!
59 //! This crate intentionally does not support an at-exit mechanism. The reason
60 //! for this is that those are running so late that even more Rust code is
61 //! unable to properly run. Not only does panicking not work, the entire standard
62 //! IO system is already unusable. More importantly on many platforms these
63 //! do not run properly. For instance on macOS destructors do not run when
64 //! thread local storage is in use. If you do want to use something like this
65 //! you can do something like invoke `libc::at_exit` from within a `#[ctor]`
66 //! function.
67 use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
68
get_function_name(stream: TokenStream) -> String69 fn get_function_name(stream: TokenStream) -> String {
70 let mut iter = stream.into_iter();
71
72 macro_rules! unexpected {
73 () => {
74 panic!("#[ctor] can only be applied to unsafe functions")
75 };
76 }
77
78 macro_rules! expect_ident {
79 () => {
80 match iter.next() {
81 Some(TokenTree::Ident(ident)) => ident,
82 _ => unexpected!(),
83 }
84 };
85 }
86
87 while let Some(token) = iter.next() {
88 if let TokenTree::Ident(ident) = token {
89 if ident.to_string() != "unsafe" || expect_ident!().to_string() != "fn" {
90 unexpected!()
91 }
92 return expect_ident!().to_string();
93 }
94 }
95
96 unexpected!();
97 }
98
99 macro_rules! tokens {
100 ($($expr:expr),* $(,)?) => {
101 vec![$($expr,)*].into_iter().collect::<TokenStream>()
102 }
103 }
104
105 /// Marks a function or static variable as a library/executable constructor.
106 /// This uses OS-specific linker sections to call a specific function at load
107 /// time.
108 ///
109 /// Multiple startup functions/statics are supported, but the invocation order
110 /// is not guaranteed. For information about what is safe or not safe to do
111 /// in such functions refer to the module documention.
112 ///
113 /// # Example
114 ///
115 /// ```
116 /// # struct MyPlugin;
117 /// # impl MyPlugin { fn insert(&self, _: MyPlugin) {} }
118 /// #[small_ctor::ctor]
119 /// unsafe fn register_plugin() {
120 /// # let PLUGINS = MyPlugin;
121 /// PLUGINS.insert(MyPlugin);
122 /// }
123 /// ```
124 #[proc_macro_attribute]
ctor(args: TokenStream, input: TokenStream) -> TokenStream125 pub fn ctor(args: TokenStream, input: TokenStream) -> TokenStream {
126 if args.into_iter().next().is_some() {
127 panic!("#[ctor] takes no arguments");
128 }
129 let name = get_function_name(input.clone());
130 let ctor_ident = TokenTree::Ident(Ident::new(
131 &format!("___{}___ctor", name),
132 Span::call_site(),
133 ));
134 vec![
135 TokenTree::Punct(Punct::new('#', Spacing::Alone)),
136 TokenTree::Group(Group::new(
137 Delimiter::Bracket,
138 tokens![TokenTree::Ident(Ident::new("used", Span::call_site()))],
139 )),
140 TokenTree::Punct(Punct::new('#', Spacing::Alone)),
141 TokenTree::Group(Group::new(
142 Delimiter::Bracket,
143 tokens![
144 TokenTree::Ident(Ident::new("doc", Span::call_site())),
145 TokenTree::Group(Group::new(
146 Delimiter::Parenthesis,
147 vec![TokenTree::Ident(Ident::new("hidden", Span::call_site()))]
148 .into_iter()
149 .collect(),
150 )),
151 ],
152 )),
153 TokenTree::Punct(Punct::new('#', Spacing::Alone)),
154 TokenTree::Group(Group::new(
155 Delimiter::Bracket,
156 tokens![
157 TokenTree::Ident(Ident::new("allow", Span::call_site())),
158 TokenTree::Group(Group::new(
159 Delimiter::Parenthesis,
160 tokens![TokenTree::Ident(Ident::new(
161 "non_upper_case_globals",
162 Span::call_site(),
163 ))]
164 )),
165 ],
166 )),
167 TokenTree::Punct(Punct::new('#', Spacing::Alone)),
168 TokenTree::Group(Group::new(
169 Delimiter::Bracket,
170 tokens![
171 TokenTree::Ident(Ident::new("cfg_attr", Span::call_site())),
172 TokenTree::Group(Group::new(
173 Delimiter::Parenthesis,
174 tokens![
175 TokenTree::Ident(Ident::new("any", Span::call_site())),
176 TokenTree::Group(Group::new(
177 Delimiter::Parenthesis,
178 tokens![
179 TokenTree::Ident(Ident::new("target_os", Span::call_site())),
180 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
181 TokenTree::Literal(Literal::string("linux")),
182 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
183 TokenTree::Ident(Ident::new("target_os", Span::call_site())),
184 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
185 TokenTree::Literal(Literal::string("freebsd")),
186 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
187 TokenTree::Ident(Ident::new("target_os", Span::call_site())),
188 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
189 TokenTree::Literal(Literal::string("netbsd")),
190 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
191 TokenTree::Ident(Ident::new("target_os", Span::call_site())),
192 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
193 TokenTree::Literal(Literal::string("android")),
194 ],
195 )),
196 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
197 TokenTree::Ident(Ident::new("link_section", Span::call_site())),
198 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
199 TokenTree::Literal(Literal::string(".init_array")),
200 ],
201 ))
202 ],
203 )),
204 TokenTree::Punct(Punct::new('#', Spacing::Alone)),
205 TokenTree::Group(Group::new(
206 Delimiter::Bracket,
207 tokens![
208 TokenTree::Ident(Ident::new("cfg_attr", Span::call_site())),
209 TokenTree::Group(Group::new(
210 Delimiter::Parenthesis,
211 tokens![
212 TokenTree::Ident(Ident::new("target_os", Span::call_site())),
213 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
214 TokenTree::Literal(Literal::string("macos")),
215 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
216 TokenTree::Ident(Ident::new("link_section", Span::call_site())),
217 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
218 TokenTree::Literal(Literal::string("__DATA_CONST,__mod_init_func")),
219 ],
220 ))
221 ],
222 )),
223 TokenTree::Punct(Punct::new('#', Spacing::Alone)),
224 TokenTree::Group(Group::new(
225 Delimiter::Bracket,
226 tokens![
227 TokenTree::Ident(Ident::new("cfg_attr", Span::call_site())),
228 TokenTree::Group(Group::new(
229 Delimiter::Parenthesis,
230 tokens![
231 TokenTree::Ident(Ident::new("target_os", Span::call_site())),
232 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
233 TokenTree::Literal(Literal::string("windows")),
234 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
235 TokenTree::Ident(Ident::new("link_section", Span::call_site())),
236 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
237 TokenTree::Literal(Literal::string(".CRT$XCU")),
238 ],
239 ))
240 ],
241 )),
242 TokenTree::Ident(Ident::new("static", Span::call_site())),
243 ctor_ident.clone(),
244 TokenTree::Punct(Punct::new(':', Spacing::Alone)),
245 TokenTree::Ident(Ident::new("unsafe", Span::call_site())),
246 TokenTree::Ident(Ident::new("extern", Span::call_site())),
247 TokenTree::Literal(Literal::string("C")),
248 TokenTree::Ident(Ident::new("fn", Span::call_site())),
249 TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::default())),
250 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
251 TokenTree::Group(Group::new(
252 Delimiter::Brace,
253 tokens![
254 TokenTree::Punct(Punct::new('#', Spacing::Alone)),
255 TokenTree::Group(Group::new(
256 Delimiter::Bracket,
257 tokens![
258 TokenTree::Ident(Ident::new("cfg_attr", Span::call_site())),
259 TokenTree::Group(Group::new(
260 Delimiter::Parenthesis,
261 tokens![
262 TokenTree::Ident(Ident::new("any", Span::call_site())),
263 TokenTree::Group(Group::new(
264 Delimiter::Parenthesis,
265 tokens![
266 TokenTree::Ident(Ident::new(
267 "target_os",
268 Span::call_site()
269 )),
270 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
271 TokenTree::Literal(Literal::string("linux")),
272 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
273 TokenTree::Ident(Ident::new(
274 "target_os",
275 Span::call_site()
276 )),
277 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
278 TokenTree::Literal(Literal::string("android")),
279 ],
280 )),
281 TokenTree::Punct(Punct::new(',', Spacing::Alone)),
282 TokenTree::Ident(Ident::new("link_section", Span::call_site())),
283 TokenTree::Punct(Punct::new('=', Spacing::Alone)),
284 TokenTree::Literal(Literal::string(".text.startup")),
285 ],
286 ))
287 ],
288 )),
289 TokenTree::Ident(Ident::new("unsafe", Span::call_site())),
290 TokenTree::Ident(Ident::new("extern", Span::call_site())),
291 TokenTree::Literal(Literal::string("C")),
292 TokenTree::Ident(Ident::new("fn", Span::call_site())),
293 ctor_ident.clone(),
294 TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::default())),
295 TokenTree::Group(Group::new(
296 Delimiter::Brace,
297 vec![
298 TokenTree::Ident(Ident::new(&name, Span::call_site())),
299 TokenTree::Group(Group::new(
300 Delimiter::Parenthesis,
301 TokenStream::default(),
302 )),
303 ]
304 .into_iter()
305 .collect(),
306 )),
307 ctor_ident.clone(),
308 ],
309 )),
310 TokenTree::Punct(Punct::new(';', Spacing::Alone)),
311 ]
312 .into_iter()
313 .chain(input.into_iter())
314 .collect()
315 }
316