Skip to content

Commit 877c0ad

Browse files
committed
State that arg in argfile must not contain newlines
1 parent beaaefd commit 877c0ad

File tree

1 file changed

+73
-20
lines changed

1 file changed

+73
-20
lines changed

crates/cargo-util/src/process_builder.rs

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub struct ProcessBuilder {
3737
/// `true` to include environment variable in display.
3838
display_env_vars: bool,
3939
/// `true` to retry with an argfile if hitting "command line too big" error.
40+
/// See [`ProcessBuilder::retry_with_argfile`] for more information.
4041
retry_with_argfile: bool,
4142
}
4243

@@ -185,6 +186,22 @@ impl ProcessBuilder {
185186
}
186187

187188
/// Enables retrying with an argfile if hitting "command line too big" error
189+
///
190+
/// This is primarily for the `@path` arg of rustc and rustdoc, which treat
191+
/// each line as an command-line argument, so `LF` and `CRLF` bytes are not
192+
/// valid as an argument for argfile at this moment.
193+
/// For example, `RUSTDOCFLAGS="--crate-version foo\nbar" cargo doc` is
194+
/// valid when invoking from command-line but not from argfile.
195+
///
196+
/// To sum up, the limitations of the argfile are:
197+
///
198+
/// - Must be valid UTF-8 encoded.
199+
/// - Must not contain any newlines in each argument.
200+
///
201+
/// Ref:
202+
///
203+
/// - https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path
204+
/// - https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path>
188205
pub fn retry_with_argfile(&mut self, enabled: bool) -> &mut Self {
189206
self.retry_with_argfile = enabled;
190207
self
@@ -393,11 +410,6 @@ impl ProcessBuilder {
393410

394411
/// Builds the command with an `@<path>` argfile that contains all the
395412
/// arguments. This is primarily served for rustc/rustdoc command family.
396-
///
397-
/// Ref:
398-
///
399-
/// - https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path
400-
/// - https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path>
401413
fn build_command_with_argfile(&self) -> io::Result<(Command, NamedTempFile)> {
402414
use std::io::Write as _;
403415

@@ -411,21 +423,26 @@ impl ProcessBuilder {
411423
log::debug!("created argfile at {path} for `{self}`");
412424

413425
let cap = self.get_args().map(|arg| arg.len() + 1).sum::<usize>();
414-
let mut buf = String::with_capacity(cap);
426+
let mut buf = Vec::with_capacity(cap);
415427
for arg in &self.args {
416-
let arg = arg
417-
.to_str()
418-
.ok_or_else(|| {
419-
io::Error::new(
420-
io::ErrorKind::Other,
421-
"argument contains invalid UTF-8 characters",
422-
)
423-
})?;
424-
// TODO: Shall we escape line feed?
425-
buf.push_str(arg);
426-
buf.push('\n');
428+
let arg = arg.to_str().ok_or_else(|| {
429+
io::Error::new(
430+
io::ErrorKind::Other,
431+
format!(
432+
"argument for argfile contains invalid UTF-8 characters: `{}`",
433+
arg.to_string_lossy()
434+
),
435+
)
436+
})?;
437+
if arg.contains('\n') {
438+
return Err(io::Error::new(
439+
io::ErrorKind::Other,
440+
format!("argument for argfile contains newlines: `{arg}`"),
441+
));
442+
}
443+
writeln!(buf, "{arg}")?;
427444
}
428-
tmp.write_all(buf.as_bytes())?;
445+
tmp.write_all(&mut buf)?;
429446
Ok((cmd, tmp))
430447
}
431448

@@ -552,10 +569,11 @@ mod imp {
552569

553570
#[cfg(test)]
554571
mod tests {
555-
use super::*;
572+
use super::ProcessBuilder;
556573
use std::fs;
574+
557575
#[test]
558-
fn test_argfile() {
576+
fn argfile_build_succeeds() {
559577
let mut cmd = ProcessBuilder::new("echo");
560578
cmd.args(["foo", "bar"].as_slice());
561579
let (cmd, argfile) = cmd.build_command_with_argfile().unwrap();
@@ -569,4 +587,39 @@ mod tests {
569587
let buf = fs::read_to_string(argfile.path()).unwrap();
570588
assert_eq!(buf, "foo\nbar\n");
571589
}
590+
591+
#[test]
592+
fn argfile_build_fails_if_arg_contains_newline() {
593+
let mut cmd = ProcessBuilder::new("echo");
594+
cmd.arg("foo\n");
595+
let err = cmd.build_command_with_argfile().unwrap_err();
596+
assert_eq!(
597+
err.to_string(),
598+
"argument for argfile contains newlines: `foo\n`"
599+
);
600+
}
601+
602+
#[test]
603+
fn argfile_build_fails_if_arg_contains_invalid_utf8() {
604+
let mut cmd = ProcessBuilder::new("echo");
605+
606+
#[cfg(windows)]
607+
let invalid_arg = {
608+
use std::os::windows::prelude::*;
609+
std::ffi::OsString::from_wide(&[0x0066, 0x006f, 0xD800, 0x006f])
610+
};
611+
612+
#[cfg(unix)]
613+
let invalid_arg = {
614+
use std::os::unix::ffi::OsStrExt;
615+
std::ffi::OsStr::from_bytes(&[0x66, 0x6f, 0x80, 0x6f]).to_os_string()
616+
};
617+
618+
cmd.arg(invalid_arg);
619+
let err = cmd.build_command_with_argfile().unwrap_err();
620+
assert_eq!(
621+
err.to_string(),
622+
"argument for argfile contains invalid UTF-8 characters: `fo�o`"
623+
);
624+
}
572625
}

0 commit comments

Comments
 (0)