Skip to content

Commit ebcd558

Browse files
authored
Unrolled build for rust-lang#125225
Rollup merge of rust-lang#125225 - madsmtm:ios-crt_externs.h, r=workingjubilee Use functions from `crt_externs.h` on iOS/tvOS/watchOS/visionOS Use `_NSGetEnviron`, `_NSGetArgc` and `_NSGetArgv` on iOS/tvOS/watchOS/visionOS, see each commit and the code comments for details. This allows us to unify more code with the macOS implementation, as well as avoiding linking to the `Foundation` framework (which is good for startup performance). The biggest problem with doing this would be if it lead to App Store rejections. After doing a bunch of research on this, while [it did happen once in 2009](https://blog.unity.com/engine-platform/unity-app-store-submissions-problem-solved), I find it fairly unlikely to happen nowadays, especially considering that Apple has later _added_ `crt_externs.h` to the iOS/tvOS/watchOS/visionOS SDKs, strongly signifying the functions therein is indeed supported on those platforms (even though they lack an availability attribute). That we've been overly cautious here has also been noted by `@thomcc` in rust-lang#117910 (comment). r? `@workingjubilee` `@rustbot` label O-apple
2 parents e8fbd99 + 38ad851 commit ebcd558

File tree

3 files changed

+127
-150
lines changed

3 files changed

+127
-150
lines changed

library/std/src/sys/pal/unix/args.rs

+96-142
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
66
#![allow(dead_code)] // runtime init functions not used during testing
77

8-
use crate::ffi::OsString;
8+
use crate::ffi::{CStr, OsString};
99
use crate::fmt;
10+
use crate::os::unix::ffi::OsStringExt;
1011
use crate::vec;
1112

1213
/// One-time global initialization.
@@ -16,7 +17,46 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) {
1617

1718
/// Returns the command line arguments
1819
pub fn args() -> Args {
19-
imp::args()
20+
let (argc, argv) = imp::argc_argv();
21+
22+
let mut vec = Vec::with_capacity(argc as usize);
23+
24+
for i in 0..argc {
25+
// SAFETY: `argv` is non-null if `argc` is positive, and it is
26+
// guaranteed to be at least as long as `argc`, so reading from it
27+
// should be safe.
28+
let ptr = unsafe { argv.offset(i).read() };
29+
30+
// Some C commandline parsers (e.g. GLib and Qt) are replacing already
31+
// handled arguments in `argv` with `NULL` and move them to the end.
32+
//
33+
// Since they can't directly ensure updates to `argc` as well, this
34+
// means that `argc` might be bigger than the actual number of
35+
// non-`NULL` pointers in `argv` at this point.
36+
//
37+
// To handle this we simply stop iterating at the first `NULL`
38+
// argument. `argv` is also guaranteed to be `NULL`-terminated so any
39+
// non-`NULL` arguments after the first `NULL` can safely be ignored.
40+
if ptr.is_null() {
41+
// NOTE: On Apple platforms, `-[NSProcessInfo arguments]` does not
42+
// stop iterating here, but instead `continue`, always iterating
43+
// up until it reached `argc`.
44+
//
45+
// This difference will only matter in very specific circumstances
46+
// where `argc`/`argv` have been modified, but in unexpected ways,
47+
// so it likely doesn't really matter which option we choose.
48+
// See the following PR for further discussion:
49+
// <https://github.com/rust-lang/rust/pull/125225>
50+
break;
51+
}
52+
53+
// SAFETY: Just checked that the pointer is not NULL, and arguments
54+
// are otherwise guaranteed to be valid C strings.
55+
let cstr = unsafe { CStr::from_ptr(ptr) };
56+
vec.push(OsStringExt::from_vec(cstr.to_bytes().to_vec()));
57+
}
58+
59+
Args { iter: vec.into_iter() }
2060
}
2161

2262
pub struct Args {
@@ -75,9 +115,7 @@ impl DoubleEndedIterator for Args {
75115
target_os = "hurd",
76116
))]
77117
mod imp {
78-
use super::Args;
79-
use crate::ffi::{CStr, OsString};
80-
use crate::os::unix::prelude::*;
118+
use crate::ffi::c_char;
81119
use crate::ptr;
82120
use crate::sync::atomic::{AtomicIsize, AtomicPtr, Ordering};
83121

@@ -126,162 +164,78 @@ mod imp {
126164
init_wrapper
127165
};
128166

129-
pub fn args() -> Args {
130-
Args { iter: clone().into_iter() }
131-
}
132-
133-
fn clone() -> Vec<OsString> {
134-
unsafe {
135-
// Load ARGC and ARGV, which hold the unmodified system-provided
136-
// argc/argv, so we can read the pointed-to memory without atomics
137-
// or synchronization.
138-
//
139-
// If either ARGC or ARGV is still zero or null, then either there
140-
// really are no arguments, or someone is asking for `args()`
141-
// before initialization has completed, and we return an empty
142-
// list.
143-
let argv = ARGV.load(Ordering::Relaxed);
144-
let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) };
145-
let mut args = Vec::with_capacity(argc as usize);
146-
for i in 0..argc {
147-
let ptr = *argv.offset(i) as *const libc::c_char;
148-
149-
// Some C commandline parsers (e.g. GLib and Qt) are replacing already
150-
// handled arguments in `argv` with `NULL` and move them to the end. That
151-
// means that `argc` might be bigger than the actual number of non-`NULL`
152-
// pointers in `argv` at this point.
153-
//
154-
// To handle this we simply stop iterating at the first `NULL` argument.
155-
//
156-
// `argv` is also guaranteed to be `NULL`-terminated so any non-`NULL` arguments
157-
// after the first `NULL` can safely be ignored.
158-
if ptr.is_null() {
159-
break;
160-
}
161-
162-
let cstr = CStr::from_ptr(ptr);
163-
args.push(OsStringExt::from_vec(cstr.to_bytes().to_vec()));
164-
}
165-
166-
args
167-
}
167+
pub fn argc_argv() -> (isize, *const *const c_char) {
168+
// Load ARGC and ARGV, which hold the unmodified system-provided
169+
// argc/argv, so we can read the pointed-to memory without atomics or
170+
// synchronization.
171+
//
172+
// If either ARGC or ARGV is still zero or null, then either there
173+
// really are no arguments, or someone is asking for `args()` before
174+
// initialization has completed, and we return an empty list.
175+
let argv = ARGV.load(Ordering::Relaxed);
176+
let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) };
177+
178+
// Cast from `*mut *const u8` to `*const *const c_char`
179+
(argc, argv.cast())
168180
}
169181
}
170182

183+
// Use `_NSGetArgc` and `_NSGetArgv` on Apple platforms.
184+
//
185+
// Even though these have underscores in their names, they've been available
186+
// since since the first versions of both macOS and iOS, and are declared in
187+
// the header `crt_externs.h`.
188+
//
189+
// NOTE: This header was added to the iOS 13.0 SDK, which has been the source
190+
// of a great deal of confusion in the past about the availability of these
191+
// APIs.
192+
//
193+
// NOTE(madsmtm): This has not strictly been verified to not cause App Store
194+
// rejections; if this is found to be the case, the previous implementation
195+
// of this used `[[NSProcessInfo processInfo] arguments]`.
171196
#[cfg(target_vendor = "apple")]
172197
mod imp {
173-
use super::Args;
174-
use crate::ffi::CStr;
198+
use crate::ffi::{c_char, c_int};
175199

176-
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
177-
178-
#[cfg(target_os = "macos")]
179-
pub fn args() -> Args {
180-
use crate::os::unix::prelude::*;
181-
extern "C" {
182-
// These functions are in crt_externs.h.
183-
fn _NSGetArgc() -> *mut libc::c_int;
184-
fn _NSGetArgv() -> *mut *mut *mut libc::c_char;
185-
}
186-
187-
let vec = unsafe {
188-
let (argc, argv) =
189-
(*_NSGetArgc() as isize, *_NSGetArgv() as *const *const libc::c_char);
190-
(0..argc as isize)
191-
.map(|i| {
192-
let bytes = CStr::from_ptr(*argv.offset(i)).to_bytes().to_vec();
193-
OsStringExt::from_vec(bytes)
194-
})
195-
.collect::<Vec<_>>()
196-
};
197-
Args { iter: vec.into_iter() }
200+
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
201+
// No need to initialize anything in here, `libdyld.dylib` has already
202+
// done the work for us.
198203
}
199204

200-
// As _NSGetArgc and _NSGetArgv aren't mentioned in iOS docs
201-
// and use underscores in their names - they're most probably
202-
// are considered private and therefore should be avoided.
203-
// Here is another way to get arguments using the Objective-C
204-
// runtime.
205-
//
206-
// In general it looks like:
207-
// res = Vec::new()
208-
// let args = [[NSProcessInfo processInfo] arguments]
209-
// for i in (0..[args count])
210-
// res.push([args objectAtIndex:i])
211-
// res
212-
#[cfg(not(target_os = "macos"))]
213-
pub fn args() -> Args {
214-
use crate::ffi::{c_char, c_void, OsString};
215-
use crate::mem;
216-
use crate::str;
217-
218-
type Sel = *const c_void;
219-
type NsId = *const c_void;
220-
type NSUInteger = usize;
221-
205+
pub fn argc_argv() -> (isize, *const *const c_char) {
222206
extern "C" {
223-
fn sel_registerName(name: *const c_char) -> Sel;
224-
fn objc_getClass(class_name: *const c_char) -> NsId;
225-
226-
// This must be transmuted to an appropriate function pointer type before being called.
227-
fn objc_msgSend();
228-
}
229-
230-
const MSG_SEND_PTR: unsafe extern "C" fn() = objc_msgSend;
231-
const MSG_SEND_NO_ARGUMENTS_RETURN_PTR: unsafe extern "C" fn(NsId, Sel) -> *const c_void =
232-
unsafe { mem::transmute(MSG_SEND_PTR) };
233-
const MSG_SEND_NO_ARGUMENTS_RETURN_NSUINTEGER: unsafe extern "C" fn(
234-
NsId,
235-
Sel,
236-
) -> NSUInteger = unsafe { mem::transmute(MSG_SEND_PTR) };
237-
const MSG_SEND_NSINTEGER_ARGUMENT_RETURN_PTR: unsafe extern "C" fn(
238-
NsId,
239-
Sel,
240-
NSUInteger,
241-
)
242-
-> *const c_void = unsafe { mem::transmute(MSG_SEND_PTR) };
243-
244-
let mut res = Vec::new();
245-
246-
unsafe {
247-
let process_info_sel = sel_registerName(c"processInfo".as_ptr());
248-
let arguments_sel = sel_registerName(c"arguments".as_ptr());
249-
let count_sel = sel_registerName(c"count".as_ptr());
250-
let object_at_index_sel = sel_registerName(c"objectAtIndex:".as_ptr());
251-
let utf8string_sel = sel_registerName(c"UTF8String".as_ptr());
252-
253-
let klass = objc_getClass(c"NSProcessInfo".as_ptr());
254-
// `+[NSProcessInfo processInfo]` returns an object with +0 retain count, so no need to manually `retain/release`.
255-
let info = MSG_SEND_NO_ARGUMENTS_RETURN_PTR(klass, process_info_sel);
256-
257-
// `-[NSProcessInfo arguments]` returns an object with +0 retain count, so no need to manually `retain/release`.
258-
let args = MSG_SEND_NO_ARGUMENTS_RETURN_PTR(info, arguments_sel);
259-
260-
let cnt = MSG_SEND_NO_ARGUMENTS_RETURN_NSUINTEGER(args, count_sel);
261-
for i in 0..cnt {
262-
// `-[NSArray objectAtIndex:]` returns an object whose lifetime is tied to the array, so no need to manually `retain/release`.
263-
let ns_string =
264-
MSG_SEND_NSINTEGER_ARGUMENT_RETURN_PTR(args, object_at_index_sel, i);
265-
// The lifetime of this pointer is tied to the NSString, as well as the current autorelease pool, which is why we heap-allocate the string below.
266-
let utf_c_str: *const c_char =
267-
MSG_SEND_NO_ARGUMENTS_RETURN_PTR(ns_string, utf8string_sel).cast();
268-
let bytes = CStr::from_ptr(utf_c_str).to_bytes();
269-
res.push(OsString::from(str::from_utf8(bytes).unwrap()))
270-
}
207+
// These functions are in crt_externs.h.
208+
fn _NSGetArgc() -> *mut c_int;
209+
fn _NSGetArgv() -> *mut *mut *mut c_char;
271210
}
272211

273-
Args { iter: res.into_iter() }
212+
// SAFETY: The returned pointer points to a static initialized early
213+
// in the program lifetime by `libdyld.dylib`, and as such is always
214+
// valid.
215+
//
216+
// NOTE: Similar to `_NSGetEnviron`, there technically isn't anything
217+
// protecting us against concurrent modifications to this, and there
218+
// doesn't exist a lock that we can take. Instead, it is generally
219+
// expected that it's only modified in `main` / before other code
220+
// runs, so reading this here should be fine.
221+
let argc = unsafe { _NSGetArgc().read() };
222+
// SAFETY: Same as above.
223+
let argv = unsafe { _NSGetArgv().read() };
224+
225+
// Cast from `*mut *mut c_char` to `*const *const c_char`
226+
(argc as isize, argv.cast())
274227
}
275228
}
276229

277230
#[cfg(any(target_os = "espidf", target_os = "vita"))]
278231
mod imp {
279-
use super::Args;
232+
use crate::ffi::c_char;
233+
use crate::ptr;
280234

281235
#[inline(always)]
282236
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
283237

284-
pub fn args() -> Args {
285-
Args { iter: Vec::new().into_iter() }
238+
pub fn argc_argv() -> (isize, *const *const c_char) {
239+
(0, ptr::null())
286240
}
287241
}

library/std/src/sys/pal/unix/mod.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -399,14 +399,13 @@ cfg_if::cfg_if! {
399399
// Use libumem for the (malloc-compatible) allocator
400400
#[link(name = "umem")]
401401
extern "C" {}
402-
} else if #[cfg(target_os = "macos")] {
402+
} else if #[cfg(target_vendor = "apple")] {
403+
// Link to `libSystem.dylib`.
404+
//
405+
// Don't get confused by the presence of `System.framework`,
406+
// it is a deprecated wrapper over the dynamic library.
403407
#[link(name = "System")]
404408
extern "C" {}
405-
} else if #[cfg(all(target_vendor = "apple", not(target_os = "macos")))] {
406-
#[link(name = "System")]
407-
#[link(name = "objc")]
408-
#[link(name = "Foundation", kind = "framework")]
409-
extern "C" {}
410409
} else if #[cfg(target_os = "fuchsia")] {
411410
#[link(name = "zircon")]
412411
#[link(name = "fdio")]

library/std/src/sys/pal/unix/os.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -576,12 +576,36 @@ impl Iterator for Env {
576576
}
577577
}
578578

579-
#[cfg(target_os = "macos")]
579+
// Use `_NSGetEnviron` on Apple platforms.
580+
//
581+
// `_NSGetEnviron` is the documented alternative (see `man environ`), and has
582+
// been available since the first versions of both macOS and iOS.
583+
//
584+
// Nowadays, specifically since macOS 10.8, `environ` has been exposed through
585+
// `libdyld.dylib`, which is linked via. `libSystem.dylib`:
586+
// <https://github.com/apple-oss-distributions/dyld/blob/dyld-1160.6/libdyld/libdyldGlue.cpp#L913>
587+
//
588+
// So in the end, it likely doesn't really matter which option we use, but the
589+
// performance cost of using `_NSGetEnviron` is extremely miniscule, and it
590+
// might be ever so slightly more supported, so let's just use that.
591+
//
592+
// NOTE: The header where this is defined (`crt_externs.h`) was added to the
593+
// iOS 13.0 SDK, which has been the source of a great deal of confusion in the
594+
// past about the availability of this API.
595+
//
596+
// NOTE(madsmtm): Neither this nor using `environ` has been verified to not
597+
// cause App Store rejections; if this is found to be the case, an alternative
598+
// implementation of this is possible using `[NSProcessInfo environment]`
599+
// - which internally uses `_NSGetEnviron` and a system-wide lock on the
600+
// environment variables to protect against `setenv`, so using that might be
601+
// desirable anyhow? Though it also means that we have to link to Foundation.
602+
#[cfg(target_vendor = "apple")]
580603
pub unsafe fn environ() -> *mut *const *const c_char {
581604
libc::_NSGetEnviron() as *mut *const *const c_char
582605
}
583606

584-
#[cfg(not(target_os = "macos"))]
607+
// Use the `environ` static which is part of POSIX.
608+
#[cfg(not(target_vendor = "apple"))]
585609
pub unsafe fn environ() -> *mut *const *const c_char {
586610
extern "C" {
587611
static mut environ: *const *const c_char;

0 commit comments

Comments
 (0)