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