use std::{ffi::OsStr, io::Write, path::Path, process::ExitStatus};
use tempfile::NamedTempFile;
use crate::{constants::RESPONSE_FILE_ARGUMENT_THRESHOLD, error::Error};
use super::execute_command_for_status;
const CHARACTERS_NEEDING_ESCAPE: &[char] = &['\\', '"', '\'', ' ', '\t', '\n', '\r'];
fn escape_response_file_argument(argument: &str) -> String {
let mut escaped = String::with_capacity(argument.len());
for character in argument.chars() {
if CHARACTERS_NEEDING_ESCAPE.contains(&character) {
escaped.push('\\');
}
escaped.push(character);
}
escaped
}
fn needs_response_file(argument_count: usize) -> bool {
argument_count > RESPONSE_FILE_ARGUMENT_THRESHOLD
}
pub(crate) fn execute_llvm_tool<P>(
program_filepath: P,
args: &[String],
) -> Result<ExitStatus, Error>
where
P: AsRef<Path>,
{
if !needs_response_file(args.len()) {
return execute_command_for_status(program_filepath, args);
}
let mut response_file = NamedTempFile::new().map_err(Error::Io)?;
for arg in args {
writeln!(response_file, "{}", escape_response_file_argument(arg)).map_err(Error::Io)?;
}
response_file.flush().map_err(Error::Io)?;
tracing::debug!(
"Passing {} arguments to {:?} through the response file {:?}",
args.len(),
program_filepath.as_ref(),
response_file.path()
);
let response_argument = {
let mut argument = OsStr::new("@").to_os_string();
argument.push(response_file.path());
argument
};
execute_command_for_status(program_filepath, &[response_argument])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escaping_leaves_an_ordinary_path_alone() {
assert_eq!(
escape_response_file_argument("/tmp/build/foo.bc"),
"/tmp/build/foo.bc"
);
}
#[test]
fn escaping_covers_separators_and_quoting() {
assert_eq!(
escape_response_file_argument("/tmp/a dir/x.bc"),
r"/tmp/a\ dir/x.bc"
);
assert_eq!(
escape_response_file_argument("/tmp/it's/x.bc"),
r"/tmp/it\'s/x.bc"
);
assert_eq!(
escape_response_file_argument("/tmp/say\"hi\"/x.bc"),
r#"/tmp/say\"hi\"/x.bc"#
);
assert_eq!(
escape_response_file_argument(r"C:\tmp\x.bc"),
r"C:\\tmp\\x.bc"
);
assert_eq!(
escape_response_file_argument("/tmp/tab\there/x.bc"),
"/tmp/tab\\\there/x.bc"
);
}
#[test]
fn the_threshold_is_the_last_count_passed_directly() {
assert!(!needs_response_file(RESPONSE_FILE_ARGUMENT_THRESHOLD - 1));
assert!(!needs_response_file(RESPONSE_FILE_ARGUMENT_THRESHOLD));
assert!(needs_response_file(RESPONSE_FILE_ARGUMENT_THRESHOLD + 1));
}
}