5
5
6
6
#![ allow( dead_code) ] // runtime init functions not used during testing
7
7
8
- use crate :: ffi:: OsString ;
8
+ use crate :: ffi:: { CStr , OsString } ;
9
9
use crate :: fmt;
10
+ use crate :: os:: unix:: ffi:: OsStringExt ;
10
11
use crate :: vec;
11
12
12
13
/// One-time global initialization.
@@ -16,7 +17,46 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) {
16
17
17
18
/// Returns the command line arguments
18
19
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 ( ) }
20
60
}
21
61
22
62
pub struct Args {
@@ -75,9 +115,7 @@ impl DoubleEndedIterator for Args {
75
115
target_os = "hurd" ,
76
116
) ) ]
77
117
mod imp {
78
- use super :: Args ;
79
- use crate :: ffi:: { CStr , OsString } ;
80
- use crate :: os:: unix:: prelude:: * ;
118
+ use crate :: ffi:: c_char;
81
119
use crate :: ptr;
82
120
use crate :: sync:: atomic:: { AtomicIsize , AtomicPtr , Ordering } ;
83
121
@@ -126,162 +164,78 @@ mod imp {
126
164
init_wrapper
127
165
} ;
128
166
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 ( ) )
168
180
}
169
181
}
170
182
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]`.
171
196
#[ cfg( target_vendor = "apple" ) ]
172
197
mod imp {
173
- use super :: Args ;
174
- use crate :: ffi:: CStr ;
198
+ use crate :: ffi:: { c_char, c_int} ;
175
199
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.
198
203
}
199
204
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 ) {
222
206
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 ;
271
210
}
272
211
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 ( ) )
274
227
}
275
228
}
276
229
277
230
#[ cfg( any( target_os = "espidf" , target_os = "vita" ) ) ]
278
231
mod imp {
279
- use super :: Args ;
232
+ use crate :: ffi:: c_char;
233
+ use crate :: ptr;
280
234
281
235
#[ inline( always) ]
282
236
pub unsafe fn init ( _argc : isize , _argv : * const * const u8 ) { }
283
237
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 ( ) )
286
240
}
287
241
}
0 commit comments