55
66#![ allow( dead_code) ] // runtime init functions not used during testing
77
8- use crate :: ffi:: OsString ;
8+ use crate :: ffi:: { CStr , OsString } ;
99use crate :: fmt;
10+ use crate :: os:: unix:: ffi:: OsStringExt ;
1011use 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
1819pub 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
2262pub struct Args {
@@ -75,9 +115,7 @@ impl DoubleEndedIterator for Args {
75115 target_os = "hurd" ,
76116) ) ]
77117mod 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" ) ]
172197mod 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" ) ) ]
278231mod 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}
0 commit comments