Skip to content

Commit 07ce394

Browse files
committed
Refactor init to avoid link bugs on macOS Monterey
Compilation currently fails for curl on macOS Monterey due to upstream rustc issue rust-lang/rust#90342. To make this problem hurt users less, we can work around this by avoiding the specific issue that this bug causes. To avoid the rustc issue, we cannot directly reference any symbol that is configured to be in a constructor linker section, which we were previously doing intentionally to work around a different rustc issue rust-lang/rust#47384. We should be able to avoid both bugs by defining our constructor symbol as a public item in the root module, though not directly referencing it in other code. Since the root module is always used (`init` is called on-demand in key places in the code) it should not be removed by optimization. Also add a quick unit test to make sure the constructor is still working for the platforms we have CI for. Fixes #417.
1 parent 44f5cfa commit 07ce394

File tree

1 file changed

+68
-34
lines changed

1 file changed

+68
-34
lines changed

src/lib.rs

+68-34
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ pub mod easy;
7070
pub mod multi;
7171
mod panic;
7272

73+
#[cfg(test)]
74+
static INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
75+
7376
/// Initializes the underlying libcurl library.
7477
///
7578
/// The underlying libcurl library must be initialized before use, and must be
@@ -90,48 +93,62 @@ pub fn init() {
9093
/// Used to prevent concurrent or duplicate initialization.
9194
static INIT: Once = Once::new();
9295

93-
/// An exported constructor function. On supported platforms, this will be
94-
/// invoked automatically before the program's `main` is called.
95-
#[cfg_attr(
96-
any(target_os = "linux", target_os = "freebsd", target_os = "android"),
97-
link_section = ".init_array"
98-
)]
99-
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
100-
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
101-
static INIT_CTOR: extern "C" fn() = init_inner;
96+
INIT.call_once(|| {
97+
#[cfg(need_openssl_init)]
98+
openssl_probe::init_ssl_cert_env_vars();
99+
#[cfg(need_openssl_init)]
100+
openssl_sys::init();
101+
102+
unsafe {
103+
assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0);
104+
}
105+
106+
#[cfg(test)]
107+
{
108+
INITIALIZED.store(true, std::sync::atomic::Ordering::SeqCst);
109+
}
110+
111+
// Note that we explicitly don't schedule a call to
112+
// `curl_global_cleanup`. The documentation for that function says
113+
//
114+
// > You must not call it when any other thread in the program (i.e. a
115+
// > thread sharing the same memory) is running. This doesn't just mean
116+
// > no other thread that is using libcurl.
117+
//
118+
// We can't ever be sure of that, so unfortunately we can't call the
119+
// function.
120+
});
121+
}
102122

123+
/// An exported constructor function. On supported platforms, this will be
124+
/// invoked automatically before the program's `main` is called. This is done
125+
/// for the convenience of library users since otherwise the thread-safety rules
126+
/// around initialization can be difficult to fulfill.
127+
///
128+
/// This is a hidden public item to ensure the symbol isn't optimized away by a
129+
/// rustc/LLVM bug: https://github.com/rust-lang/rust/issues/47384. As long as
130+
/// any item in this module is used by the final binary (which `init` will be)
131+
/// then this symbol should be preserved.
132+
#[used]
133+
#[doc(hidden)]
134+
#[cfg_attr(
135+
any(target_os = "linux", target_os = "freebsd", target_os = "android"),
136+
link_section = ".init_array"
137+
)]
138+
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
139+
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
140+
pub static INIT_CTOR: extern "C" fn() = {
103141
/// This is the body of our constructor function.
104142
#[cfg_attr(
105143
any(target_os = "linux", target_os = "android"),
106144
link_section = ".text.startup"
107145
)]
108-
extern "C" fn init_inner() {
109-
INIT.call_once(|| {
110-
#[cfg(need_openssl_init)]
111-
openssl_probe::init_ssl_cert_env_vars();
112-
#[cfg(need_openssl_init)]
113-
openssl_sys::init();
114-
115-
unsafe {
116-
assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0);
117-
}
118-
119-
// Note that we explicitly don't schedule a call to
120-
// `curl_global_cleanup`. The documentation for that function says
121-
//
122-
// > You must not call it when any other thread in the program (i.e.
123-
// > a thread sharing the same memory) is running. This doesn't just
124-
// > mean no other thread that is using libcurl.
125-
//
126-
// We can't ever be sure of that, so unfortunately we can't call the
127-
// function.
128-
});
146+
extern "C" fn init_ctor() {
147+
init();
129148
}
130149

131-
// We invoke our init function through our static to ensure the symbol isn't
132-
// optimized away by a bug: https://github.com/rust-lang/rust/issues/47384
133-
INIT_CTOR();
134-
}
150+
init_ctor
151+
};
135152

136153
unsafe fn opt_str<'a>(ptr: *const libc::c_char) -> Option<&'a str> {
137154
if ptr.is_null() {
@@ -148,3 +165,20 @@ fn cvt(r: curl_sys::CURLcode) -> Result<(), Error> {
148165
Err(Error::new(r))
149166
}
150167
}
168+
169+
#[cfg(test)]
170+
mod tests {
171+
use super::*;
172+
173+
#[test]
174+
#[cfg(any(
175+
target_os = "linux",
176+
target_os = "macos",
177+
target_os = "windows",
178+
target_os = "freebsd",
179+
target_os = "android"
180+
))]
181+
fn is_initialized_before_main() {
182+
assert!(INITIALIZED.load(std::sync::atomic::Ordering::SeqCst));
183+
}
184+
}

0 commit comments

Comments
 (0)