Skip to content

Commit 7d0c6cf

Browse files
committed
Make NULL check in argument parsing the same on all unix platforms
1 parent cfaf500 commit 7d0c6cf

File tree

1 file changed

+64
-76
lines changed

1 file changed

+64
-76
lines changed

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

+64-76
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,45 +164,19 @@ 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,16 +195,14 @@ mod imp {
183195
// of this used `[[NSProcessInfo processInfo] arguments]`.
184196
#[cfg(target_vendor = "apple")]
185197
mod imp {
186-
use super::Args;
187-
use crate::ffi::{c_char, c_int, CStr};
188-
use crate::os::unix::prelude::*;
198+
use crate::ffi::{c_char, c_int};
189199

190200
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
191201
// No need to initialize anything in here, `libdyld.dylib` has already
192202
// done the work for us.
193203
}
194204

195-
pub fn args() -> Args {
205+
pub fn argc_argv() -> (isize, *const *const c_char) {
196206
extern "C" {
197207
// These functions are in crt_externs.h.
198208
fn _NSGetArgc() -> *mut c_int;
@@ -212,42 +222,20 @@ mod imp {
212222
// SAFETY: Same as above.
213223
let argv = unsafe { _NSGetArgv().read() };
214224

215-
let mut vec = Vec::with_capacity(argc as usize);
216-
217-
for i in 0..argc {
218-
// SAFETY: `argv` is at least as long as `argc`, so reading from
219-
// it should be safe.
220-
let ptr = unsafe { argv.offset(i as isize).read() };
221-
222-
// Entries may have been removed from `argv` by setting them to
223-
// NULL, without updating `argc`.
224-
if ptr.is_null() {
225-
// We continue instead of break here, as an argument may have
226-
// been set to `NULL` in the middle, instead of at the end of
227-
// the list.
228-
//
229-
// This is the same as what `-[NSProcessInfo arguments]` does.
230-
continue;
231-
}
232-
233-
// SAFETY: Just checked that the pointer is not NULL, and
234-
// arguments are otherwise guaranteed to be valid C strings.
235-
let cstr = unsafe { CStr::from_ptr(ptr) };
236-
vec.push(OsStringExt::from_vec(cstr.to_bytes().to_vec()));
237-
}
238-
239-
Args { iter: vec.into_iter() }
225+
// Cast from `*mut *mut c_char` to `*const *const c_char`
226+
(argc as isize, argv.cast())
240227
}
241228
}
242229

243230
#[cfg(any(target_os = "espidf", target_os = "vita"))]
244231
mod imp {
245-
use super::Args;
232+
use crate::ffi::c_char;
233+
use crate::ptr;
246234

247235
#[inline(always)]
248236
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
249237

250-
pub fn args() -> Args {
251-
Args { iter: Vec::new().into_iter() }
238+
pub fn argc_argv() -> (isize, *const *const c_char) {
239+
(0, ptr::null())
252240
}
253241
}

0 commit comments

Comments
 (0)