]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/windows/args.rs
Rollup merge of #84409 - mzohreva:mz/tls-dtors-before-join, r=jethrogb
[rust.git] / library / std / src / sys / windows / args.rs
1 #![allow(dead_code)] // runtime init functions not used during testing
2
3 #[cfg(test)]
4 mod tests;
5
6 use crate::ffi::OsString;
7 use crate::fmt;
8 use crate::os::windows::prelude::*;
9 use crate::path::PathBuf;
10 use crate::slice;
11 use crate::sys::c;
12 use crate::sys::windows::os::current_exe;
13 use crate::vec;
14
15 use core::iter;
16
17 pub fn args() -> Args {
18     unsafe {
19         let lp_cmd_line = c::GetCommandLineW();
20         let parsed_args_list = parse_lp_cmd_line(lp_cmd_line as *const u16, || {
21             current_exe().map(PathBuf::into_os_string).unwrap_or_else(|_| OsString::new())
22         });
23
24         Args { parsed_args_list: parsed_args_list.into_iter() }
25     }
26 }
27
28 /// Implements the Windows command-line argument parsing algorithm.
29 ///
30 /// Microsoft's documentation for the Windows CLI argument format can be found at
31 /// <https://docs.microsoft.com/en-us/previous-versions//17w5ykft(v=vs.85)>.
32 ///
33 /// Windows includes a function to do this in shell32.dll,
34 /// but linking with that DLL causes the process to be registered as a GUI application.
35 /// GUI applications add a bunch of overhead, even if no windows are drawn. See
36 /// <https://randomascii.wordpress.com/2018/12/03/a-not-called-function-can-cause-a-5x-slowdown/>.
37 ///
38 /// This function was tested for equivalence to the shell32.dll implementation in
39 /// Windows 10 Pro v1803, using an exhaustive test suite available at
40 /// <https://gist.github.com/notriddle/dde431930c392e428055b2dc22e638f5> or
41 /// <https://paste.gg/p/anonymous/47d6ed5f5bd549168b1c69c799825223>.
42 unsafe fn parse_lp_cmd_line<F: Fn() -> OsString>(
43     lp_cmd_line: *const u16,
44     exe_name: F,
45 ) -> Vec<OsString> {
46     const BACKSLASH: u16 = '\\' as u16;
47     const QUOTE: u16 = '"' as u16;
48     const TAB: u16 = '\t' as u16;
49     const SPACE: u16 = ' ' as u16;
50     let mut ret_val = Vec::new();
51     if lp_cmd_line.is_null() || *lp_cmd_line == 0 {
52         ret_val.push(exe_name());
53         return ret_val;
54     }
55     let mut cmd_line = {
56         let mut end = 0;
57         while *lp_cmd_line.offset(end) != 0 {
58             end += 1;
59         }
60         slice::from_raw_parts(lp_cmd_line, end as usize)
61     };
62     // The executable name at the beginning is special.
63     cmd_line = match cmd_line[0] {
64         // The executable name ends at the next quote mark,
65         // no matter what.
66         QUOTE => {
67             let args = {
68                 let mut cut = cmd_line[1..].splitn(2, |&c| c == QUOTE);
69                 if let Some(exe) = cut.next() {
70                     ret_val.push(OsString::from_wide(exe));
71                 }
72                 cut.next()
73             };
74             if let Some(args) = args {
75                 args
76             } else {
77                 return ret_val;
78             }
79         }
80         // Implement quirk: when they say whitespace here,
81         // they include the entire ASCII control plane:
82         // "However, if lpCmdLine starts with any amount of whitespace, CommandLineToArgvW
83         // will consider the first argument to be an empty string. Excess whitespace at the
84         // end of lpCmdLine is ignored."
85         0..=SPACE => {
86             ret_val.push(OsString::new());
87             &cmd_line[1..]
88         }
89         // The executable name ends at the next whitespace,
90         // no matter what.
91         _ => {
92             let args = {
93                 let mut cut = cmd_line.splitn(2, |&c| c > 0 && c <= SPACE);
94                 if let Some(exe) = cut.next() {
95                     ret_val.push(OsString::from_wide(exe));
96                 }
97                 cut.next()
98             };
99             if let Some(args) = args {
100                 args
101             } else {
102                 return ret_val;
103             }
104         }
105     };
106     let mut cur = Vec::new();
107     let mut in_quotes = false;
108     let mut was_in_quotes = false;
109     let mut backslash_count: usize = 0;
110     for &c in cmd_line {
111         match c {
112             // backslash
113             BACKSLASH => {
114                 backslash_count += 1;
115                 was_in_quotes = false;
116             }
117             QUOTE if backslash_count % 2 == 0 => {
118                 cur.extend(iter::repeat(b'\\' as u16).take(backslash_count / 2));
119                 backslash_count = 0;
120                 if was_in_quotes {
121                     cur.push('"' as u16);
122                     was_in_quotes = false;
123                 } else {
124                     was_in_quotes = in_quotes;
125                     in_quotes = !in_quotes;
126                 }
127             }
128             QUOTE if backslash_count % 2 != 0 => {
129                 cur.extend(iter::repeat(b'\\' as u16).take(backslash_count / 2));
130                 backslash_count = 0;
131                 was_in_quotes = false;
132                 cur.push(b'"' as u16);
133             }
134             SPACE | TAB if !in_quotes => {
135                 cur.extend(iter::repeat(b'\\' as u16).take(backslash_count));
136                 if !cur.is_empty() || was_in_quotes {
137                     ret_val.push(OsString::from_wide(&cur[..]));
138                     cur.truncate(0);
139                 }
140                 backslash_count = 0;
141                 was_in_quotes = false;
142             }
143             _ => {
144                 cur.extend(iter::repeat(b'\\' as u16).take(backslash_count));
145                 backslash_count = 0;
146                 was_in_quotes = false;
147                 cur.push(c);
148             }
149         }
150     }
151     cur.extend(iter::repeat(b'\\' as u16).take(backslash_count));
152     // include empty quoted strings at the end of the arguments list
153     if !cur.is_empty() || was_in_quotes || in_quotes {
154         ret_val.push(OsString::from_wide(&cur[..]));
155     }
156     ret_val
157 }
158
159 pub struct Args {
160     parsed_args_list: vec::IntoIter<OsString>,
161 }
162
163 impl fmt::Debug for Args {
164     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165         self.parsed_args_list.as_slice().fmt(f)
166     }
167 }
168
169 impl Iterator for Args {
170     type Item = OsString;
171     fn next(&mut self) -> Option<OsString> {
172         self.parsed_args_list.next()
173     }
174     fn size_hint(&self) -> (usize, Option<usize>) {
175         self.parsed_args_list.size_hint()
176     }
177 }
178
179 impl DoubleEndedIterator for Args {
180     fn next_back(&mut self) -> Option<OsString> {
181         self.parsed_args_list.next_back()
182     }
183 }
184
185 impl ExactSizeIterator for Args {
186     fn len(&self) -> usize {
187         self.parsed_args_list.len()
188     }
189 }