|
2 | 2 |
|
3 | 3 | use std::convert::TryFrom;
|
4 | 4 | use std::ffi::OsStr;
|
| 5 | +use std::iter; |
5 | 6 |
|
6 | 7 | use log::info;
|
7 | 8 |
|
@@ -202,17 +203,8 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
|
202 | 203 | // Store command line as UTF-16 for Windows `GetCommandLineW`.
|
203 | 204 | {
|
204 | 205 | // Construct a command string with all the aguments.
|
205 |
| - let mut cmd = String::new(); |
206 |
| - for arg in config.args.iter() { |
207 |
| - if !cmd.is_empty() { |
208 |
| - cmd.push(' '); |
209 |
| - } |
210 |
| - cmd.push_str(&*shell_escape::windows::escape(arg.as_str().into())); |
211 |
| - } |
212 |
| - // Don't forget `0` terminator. |
213 |
| - cmd.push(std::char::from_u32(0).unwrap()); |
| 206 | + let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter()); |
214 | 207 |
|
215 |
| - let cmd_utf16: Vec<u16> = cmd.encode_utf16().collect(); |
216 | 208 | let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
|
217 | 209 | let cmd_place =
|
218 | 210 | ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
|
@@ -353,3 +345,106 @@ pub fn eval_entry<'tcx>(
|
353 | 345 | Err(e) => report_error(&ecx, e),
|
354 | 346 | }
|
355 | 347 | }
|
| 348 | + |
| 349 | +/// Turns an array of arguments into a Windows command line string. |
| 350 | +/// |
| 351 | +/// The string will be UTF-16 encoded and NUL terminated. |
| 352 | +/// |
| 353 | +/// Panics if the zeroth argument contains the `"` character because doublequotes |
| 354 | +/// in argv[0] cannot be encoded using the standard command line parsing rules. |
| 355 | +/// |
| 356 | +/// Further reading: |
| 357 | +/// * [Parsing C++ command-line arguments](https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-160#parsing-c-command-line-arguments) |
| 358 | +/// * [The C/C++ Parameter Parsing Rules](https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES) |
| 359 | +fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16> |
| 360 | +where |
| 361 | + I: Iterator<Item = T>, |
| 362 | + T: AsRef<str>, |
| 363 | +{ |
| 364 | + // Parse argv[0]. Slashes aren't escaped. Literal double quotes are not allowed. |
| 365 | + let mut cmd = { |
| 366 | + let arg0 = if let Some(arg0) = args.next() { |
| 367 | + arg0 |
| 368 | + } else { |
| 369 | + return vec![0]; |
| 370 | + }; |
| 371 | + let arg0 = arg0.as_ref(); |
| 372 | + if arg0.contains('"') { |
| 373 | + panic!("argv[0] cannot contain a doublequote (\") character"); |
| 374 | + } else { |
| 375 | + // Always surround argv[0] with quotes. |
| 376 | + let mut s = String::new(); |
| 377 | + s.push('"'); |
| 378 | + s.push_str(arg0); |
| 379 | + s.push('"'); |
| 380 | + s |
| 381 | + } |
| 382 | + }; |
| 383 | + |
| 384 | + // Build the other arguments. |
| 385 | + for arg in args { |
| 386 | + let arg = arg.as_ref(); |
| 387 | + cmd.push(' '); |
| 388 | + if arg.is_empty() { |
| 389 | + cmd.push_str("\"\""); |
| 390 | + } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) { |
| 391 | + // No quote, tab, or space -- no escaping required. |
| 392 | + cmd.push_str(arg); |
| 393 | + } else { |
| 394 | + // Spaces and tabs are escaped by surrounding them in quotes. |
| 395 | + // Quotes are themselves escaped by using backslashes when in a |
| 396 | + // quoted block. |
| 397 | + // Backslashes only need to be escaped when one or more are directly |
| 398 | + // followed by a quote. Otherwise they are taken literally. |
| 399 | + |
| 400 | + cmd.push('"'); |
| 401 | + let mut chars = arg.chars().peekable(); |
| 402 | + loop { |
| 403 | + let mut nslashes = 0; |
| 404 | + while let Some(&'\\') = chars.peek() { |
| 405 | + chars.next(); |
| 406 | + nslashes += 1; |
| 407 | + } |
| 408 | + |
| 409 | + match chars.next() { |
| 410 | + Some('"') => { |
| 411 | + cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1)); |
| 412 | + cmd.push('"'); |
| 413 | + } |
| 414 | + Some(c) => { |
| 415 | + cmd.extend(iter::repeat('\\').take(nslashes)); |
| 416 | + cmd.push(c); |
| 417 | + } |
| 418 | + None => { |
| 419 | + cmd.extend(iter::repeat('\\').take(nslashes * 2)); |
| 420 | + break; |
| 421 | + } |
| 422 | + } |
| 423 | + } |
| 424 | + cmd.push('"'); |
| 425 | + } |
| 426 | + } |
| 427 | + |
| 428 | + if cmd.contains('\0') { |
| 429 | + panic!("interior null in command line arguments"); |
| 430 | + } |
| 431 | + cmd.encode_utf16().chain(iter::once(0)).collect() |
| 432 | +} |
| 433 | + |
| 434 | +#[cfg(test)] |
| 435 | +mod tests { |
| 436 | + use super::*; |
| 437 | + #[test] |
| 438 | + #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")] |
| 439 | + fn windows_argv0_panic_on_quote() { |
| 440 | + args_to_utf16_command_string(["\""].iter()); |
| 441 | + } |
| 442 | + #[test] |
| 443 | + fn windows_argv0_no_escape() { |
| 444 | + // Ensure that a trailing backslash in argv[0] is not escaped. |
| 445 | + let cmd = String::from_utf16_lossy(&args_to_utf16_command_string( |
| 446 | + [r"C:\Program Files\", "arg1"].iter(), |
| 447 | + )); |
| 448 | + assert_eq!(cmd.trim_end_matches("\0"), r#""C:\Program Files\" arg1"#); |
| 449 | + } |
| 450 | +} |
0 commit comments