blob: 1f36d752a49260101677ef9d979df4c73718a195 [file] [log] [blame]
#![recursion_limit = "256"]
//! Procedural macro for defining global constructor/destructor functions.
//!
//! This provides module initialization/teardown functions for Rust (like
//! `__attribute__((constructor))` in C/C++) for Linux, OSX, and Windows via
//! the `#[ctor]` and `#[dtor]` macros.
//!
//! This library works and has been tested for Linux, OSX and Windows. This
//! library will also work as expected in both `bin` and `cdylib` outputs,
//! ie: the `ctor` and `dtor` will run at executable or library
//! startup/shutdown respectively.
//!
//! This library currently requires Rust > `1.31.0` at a minimum for the
//! procedural macro support.
// Code note:
// You might wonder why we don't use `__attribute__((destructor))`/etc for
// dtor. Unfortunately mingw doesn't appear to properly support section-based
// hooks for shutdown, ie:
// https://github.com/Alexpux/mingw-w64/blob/d0d7f784833bbb0b2d279310ddc6afb52fe47a46/mingw-w64-crt/crt/crtdll.c
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
/// Marks a function or static variable as a library/executable constructor.
/// This uses OS-specific linker sections to call a specific function at
/// load time.
///
/// Multiple startup functions/statics are supported, but the invocation order is not
/// guaranteed.
///
/// # Examples
///
/// Print a startup message:
///
/// ```rust
/// # extern crate ctor;
/// # use ctor::*;
/// #[ctor]
/// fn foo() {
/// println!("Hello, world!");
/// }
///
/// # fn main() {
/// println!("main()");
/// # }
/// ```
///
/// Make changes to `static` variables:
///
/// ```rust
/// # extern crate ctor;
/// # use ctor::*;
/// # use std::sync::atomic::{AtomicBool, Ordering};
/// static INITED: AtomicBool = AtomicBool::new(false);
///
/// #[ctor]
/// fn foo() {
/// INITED.store(true, Ordering::SeqCst);
/// }
/// ```
///
/// Initialize a `HashMap` at startup time:
///
/// ```rust
/// # extern crate ctor;
/// # use std::collections::HashMap;
/// # use ctor::*;
/// #[ctor]
/// static STATIC_CTOR: HashMap<u32, String> = {
/// let mut m = HashMap::new();
/// for i in 0..100 {
/// m.insert(i, format!("x*100={}", i*100));
/// }
/// m
/// };
///
/// # pub fn main() {
/// # assert_eq!(STATIC_CTOR.len(), 100);
/// # assert_eq!(STATIC_CTOR[&20], "x*100=2000");
/// # }
/// ```
///
/// # Details
///
/// The `#[ctor]` macro makes use of linker sections to ensure that a
/// function is run at startup time.
///
/// The above example translates into the following Rust code (approximately):
///
///```rust
/// #[used]
/// #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
/// #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
/// #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
/// #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
/// #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
/// #[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_init_func")]
/// #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
/// static FOO: extern fn() = {
/// #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
/// extern fn foo() { /* ... */ };
/// foo
/// };
/// ```
#[proc_macro_attribute]
pub fn ctor(_attribute: TokenStream, function: TokenStream) -> TokenStream {
let item: syn::Item = syn::parse_macro_input!(function);
if let syn::Item::Fn(function) = item {
validate_item("ctor", &function);
let syn::ItemFn {
attrs,
block,
vis,
sig:
syn::Signature {
ident,
unsafety,
constness,
abi,
..
},
..
} = function;
// Linux/ELF: https://www.exploit-db.com/papers/13234
// Mac details: https://blog.timac.org/2016/0716-constructor-and-destructor-attributes/
// Why .CRT$XCU on Windows? https://www.cnblogs.com/sunkang/archive/2011/05/24/2055635.html
// 'I'=C init, 'C'=C++ init, 'P'=Pre-terminators and 'T'=Terminators
let ctor_ident =
syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___ctor", ident).as_ref())
.expect("Unable to create identifier");
let output = quote!(
#[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 = "haiku", target_os = "macos", target_os = "ios", windows)))]
compile_error!("#[ctor] is not supported on the current target");
#(#attrs)*
#vis #unsafety extern #abi #constness fn #ident() #block
#[used]
#[allow(non_upper_case_globals)]
#[doc(hidden)]
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
#[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
#[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
#[cfg_attr(target_os = "illumos", link_section = ".init_array")]
#[cfg_attr(target_os = "haiku", link_section = ".init_array")]
#[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_init_func")]
#[cfg_attr(windows, link_section = ".CRT$XCU")]
static #ctor_ident
:
unsafe extern "C" fn() =
{
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
unsafe extern "C" fn #ctor_ident() { #ident() };
#ctor_ident
}
;
);
// eprintln!("{}", output);
output.into()
} else if let syn::Item::Static(var) = item {
let syn::ItemStatic {
ident,
mutability,
expr,
attrs,
ty,
vis,
..
} = var;
if let Some(_) = mutability {
panic!("#[ctor]-annotated static objects must not be mutable");
}
if attrs.iter().any(|attr| {
attr.path
.segments
.iter()
.any(|segment| segment.ident == "no_mangle")
}) {
panic!("#[ctor]-annotated static objects do not support #[no_mangle]");
}
let ctor_ident =
syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___ctor", ident).as_ref())
.expect("Unable to create identifier");
let storage_ident =
syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___storage", ident).as_ref())
.expect("Unable to create identifier");
let output = quote!(
#[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 = "haiku", target_os = "macos", target_os = "ios", windows)))]
compile_error!("#[ctor] is not supported on the current target");
// This is mutable, but only by this macro code!
static mut #storage_ident: Option<#ty> = None;
#[doc(hidden)]
#[allow(non_camel_case_types)]
#vis struct #ident<T> {
_data: core::marker::PhantomData<T>
}
#(#attrs)*
#vis static #ident: #ident<#ty> = #ident {
_data: core::marker::PhantomData::<#ty>
};
impl core::ops::Deref for #ident<#ty> {
type Target = #ty;
fn deref(&self) -> &'static #ty {
unsafe {
#storage_ident.as_ref().unwrap()
}
}
}
#[used]
#[allow(non_upper_case_globals)]
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
#[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
#[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
#[cfg_attr(target_os = "illumos", link_section = ".init_array")]
#[cfg_attr(target_os = "haiku", link_section = ".init_array")]
#[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_init_func")]
#[cfg_attr(windows, link_section = ".CRT$XCU")]
static #ctor_ident
:
unsafe extern "C" fn() = {
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
unsafe extern "C" fn initer() {
#storage_ident = Some(#expr);
}; initer }
;
);
// eprintln!("{}", output);
output.into()
} else {
panic!("#[ctor] items must be functions or static globals");
}
}
/// Marks a function as a library/executable destructor. This uses OS-specific
/// linker sections to call a specific function at termination time.
///
/// Multiple shutdown functions are supported, but the invocation order is not
/// guaranteed.
///
/// `sys_common::at_exit` is usually a better solution for shutdown handling, as
/// it allows you to use `stdout` in your handlers.
///
/// ```rust
/// # extern crate ctor;
/// # use ctor::*;
/// # fn main() {}
///
/// #[dtor]
/// fn shutdown() {
/// /* ... */
/// }
/// ```
#[proc_macro_attribute]
pub fn dtor(_attribute: TokenStream, function: TokenStream) -> TokenStream {
let function: syn::ItemFn = syn::parse_macro_input!(function);
validate_item("dtor", &function);
let syn::ItemFn {
attrs,
block,
vis,
sig:
syn::Signature {
ident,
unsafety,
constness,
abi,
..
},
..
} = function;
let mod_ident = syn::parse_str::<syn::Ident>(format!("{}___rust_dtor___mod", ident).as_ref())
.expect("Unable to create identifier");
let dtor_ident = syn::parse_str::<syn::Ident>(format!("{}___rust_dtor___dtor", ident).as_ref())
.expect("Unable to create identifier");
let output = quote!(
#[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 = "haiku", target_os = "macos", target_os = "ios", windows)))]
compile_error!("#[dtor] is not supported on the current target");
#(#attrs)*
#vis #unsafety extern #abi #constness fn #ident() #block
// Targets that use `atexit`.
#[cfg(not(any(
target_os = "macos",
target_os = "ios",
)))]
mod #mod_ident {
use super::#ident;
// Avoid a dep on libc by linking directly
extern "C" {
fn atexit(cb: unsafe extern fn());
}
#[used]
#[allow(non_upper_case_globals)]
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
#[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
#[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
#[cfg_attr(target_os = "illumos", link_section = ".init_array")]
#[cfg_attr(target_os = "haiku", link_section = ".init_array")]
#[cfg_attr(windows, link_section = ".CRT$XCU")]
static __dtor_export
:
unsafe extern "C" fn() =
{
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.exit")]
unsafe extern "C" fn #dtor_ident() { #ident() };
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
unsafe extern fn __dtor_atexit() {
atexit(#dtor_ident);
};
__dtor_atexit
};
}
// Targets that don't rely on `atexit`.
#[cfg(any(
target_os = "macos",
target_os = "ios",
))]
mod #mod_ident {
use super::#ident;
#[used]
#[allow(non_upper_case_globals)]
#[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_term_func")]
static __dtor_export
:
unsafe extern "C" fn() =
{
unsafe extern fn __dtor() { #ident() };
__dtor
};
}
);
// eprintln!("{}", output);
output.into()
}
fn validate_item(typ: &str, item: &syn::ItemFn) {
let syn::ItemFn { vis, sig, .. } = item;
// Ensure that visibility modifier is not present
match vis {
syn::Visibility::Inherited => {}
_ => panic!("#[{}] methods must not have visibility modifiers", typ),
}
// No parameters allowed
if sig.inputs.len() > 0 {
panic!("#[{}] methods may not have parameters", typ);
}
// No return type allowed
match sig.output {
syn::ReturnType::Default => {}
_ => panic!("#[{}] methods must not have return types", typ),
}
}