Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
497 changes: 480 additions & 17 deletions src/compiler/c.rs

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions src/compiler/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ counted_array!(pub static ARGS: [ArgInfo<gcc::ArgData>; _] = [
take_arg!("-fprofile-instr-use", PathBuf, Concatenated(b'='), ClangProfileUse),
// Note: this overrides the -fprofile-use option in gcc.rs.
take_arg!("-fprofile-use", PathBuf, Concatenated(b'='), ClangProfileUse),
// The seed file's contents decide struct field ordering, so two builds that
// differ only in this file must not share an object.
take_arg!(
"-frandomize-layout-seed-file",
PathBuf,
Concatenated(b'='),
ExtraHashFile
),
take_arg!("-fsanitize-blacklist", PathBuf, Concatenated(b'='), ExtraHashFile),
take_arg!("-fsanitize-ignorelist", PathBuf, Concatenated(b'='), ExtraHashFile),
flag!("-fuse-ctor-homing", PassThroughFlag),
Expand Down Expand Up @@ -1011,6 +1019,42 @@ mod test {
);
}

/// The seed file decides struct field ordering under randstruct, so two builds
/// differing only in its contents must not share an object.
#[test]
fn test_parse_frandomize_layout_seed_file() {
let a = parses!(
"-c",
"foo.c",
"-o",
"foo.o",
"-frandomize-layout-seed-file=seed.txt"
);
assert_eq!(
ovec!["-frandomize-layout-seed-file=seed.txt"],
a.common_args
);
assert_eq!(
ovec![std::env::current_dir().unwrap().join("seed.txt")],
a.extra_hash_files
);
}

/// The string form carries its value in the argument itself, which is already
/// hashed, so it must not be mistaken for the file form.
#[test]
fn test_parse_frandomize_layout_seed_string_not_a_file() {
let a = parses!(
"-c",
"foo.c",
"-o",
"foo.o",
"-frandomize-layout-seed=abcdef"
);
assert!(a.extra_hash_files.is_empty());
assert_eq!(ovec!["-frandomize-layout-seed=abcdef"], a.common_args);
}

#[test]
fn test_parse_fsanitize_blacklist() {
let a = parses!(
Expand Down
27 changes: 24 additions & 3 deletions src/compiler/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ where
out_pretty,
fmt_duration_as_secs(&start.elapsed())
);
let (key, compilation, weak_toolchain_key) = match result {
let (key, key_cacheable, compilation, weak_toolchain_key) = match result {
Err(e) => {
return match e.downcast::<ProcessError>() {
Ok(ProcessError(output)) => {
Expand All @@ -568,15 +568,19 @@ where
}
Ok(HashResult {
key,
cacheable,
compilation,
weak_toolchain_key,
}) => (key, compilation, weak_toolchain_key),
}) => (key, cacheable, compilation, weak_toolchain_key),
};
debug!("[{}]: Hash key: {}", out_pretty, key);
// If `ForceRecache` is enabled, we won't check the cache.
let start = Instant::now();
let cache_status = async {
if cache_control == CacheControl::ForceNoCache {
// A key that does not cover every dependency must not be looked up,
// an entry stored under it may have been produced from different
// inputs.
if key_cacheable == Cacheable::No || cache_control == CacheControl::ForceNoCache {
Ok(Cache::None)
} else if cache_control == CacheControl::ForceRecache {
Ok(Cache::Recache)
Expand Down Expand Up @@ -746,6 +750,18 @@ where
compiler_result,
));
}
if key_cacheable != Cacheable::Yes {
// The hash key does not cover every dependency, so we cannot cache it.
debug!(
"[{}]: Compiled in {}, but the hash key is not cacheable",
out_pretty,
fmt_duration_as_secs(&duration_compilation)
);
return Ok((
CompileResult::NotCacheable(dist_type, duration_compilation),
compiler_result,
));
}
if miss_type == MissType::ForcedNoCache {
// Do not cache
debug!(
Expand Down Expand Up @@ -1151,6 +1167,11 @@ where
{
/// The hash key of the inputs.
pub key: String,
/// Whether `key` accounts for everything the compilation depends on.
///
/// `Cacheable::No` means a dependency was found that the key cannot cover,
/// so the cache must be neither read nor written for this compilation.
pub cacheable: Cacheable,
/// An object to use for the actual compilation, if necessary.
pub compilation: Box<dyn Compilation<T> + 'static>,
/// A weak key that may be used to identify the toolchain
Expand Down
96 changes: 95 additions & 1 deletion src/compiler/gcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ ArgData! { pub
Coverage,
ModuleOnlyFlag,
ExtraHashFile(PathBuf),
// For arguments naming a file whose contents are an input, where the value is
// a path only when it contains a directory separator; otherwise the compiler
// resolves the bare name against a search path of its own that we cannot
// reproduce. Used by -fplugin and -specs.
ExtraHashFileRequiringPath(PathBuf),
// Only valid for clang, but this needs to be here since clang shares gcc's arg parsing.
// For -fmodule-file which can be either "path" or "name=path"
ExtraHashFileClangModuleFile(OsString),
Expand All @@ -186,6 +191,9 @@ counted_array!(pub static ARGS: [ArgInfo<ArgData>; _] = [
flag!("--save-temps=cwd", TooHardFlag),
flag!("--save-temps=obj", TooHardFlag),
take_arg!("--serialize-diagnostics", PathBuf, Separated, SerializeDiagnostics),
// A spec file rewrites the command line, so it can change anything about the
// compilation while the arguments we hash stay the same.
take_arg!("--specs", PathBuf, CanBeConcatenated(b'='), ExtraHashFileRequiringPath),
take_arg!("--sysroot", PathBuf, Separated, PassThroughPath),
take_arg!("-A", OsString, Separated, PassThrough),
take_arg!("-B", PathBuf, CanBeSeparated, PassThroughPath),
Expand Down Expand Up @@ -227,7 +235,11 @@ counted_array!(pub static ARGS: [ArgInfo<ArgData>; _] = [
flag!("-fno-profile-generate", TooHardFlag),
flag!("-fno-profile-use", TooHardFlag),
flag!("-fno-working-directory", PreprocessorArgumentFlag),
flag!("-fplugin=libcc1plugin", TooHardFlag),
// A plugin's code runs as part of the compilation, so its contents are an
// input rather than just the path naming it. This also covers the one plugin
// that used to be singled out here, libcc1plugin, which is passed as a bare
// name and so lands in the same bucket as any other name we cannot resolve.
take_arg!("-fplugin", PathBuf, CanBeConcatenated(b'='), ExtraHashFileRequiringPath),
flag!("-fprofile-arcs", ProfileGenerate),
flag!("-fprofile-generate", ProfileGenerate),
take_arg!("-fprofile-use", OsString, Concatenated, TooHard),
Expand Down Expand Up @@ -260,6 +272,7 @@ counted_array!(pub static ARGS: [ArgInfo<ArgData>; _] = [
flag!("-save-temps", TooHardFlag),
flag!("-save-temps=cwd", TooHardFlag),
flag!("-save-temps=obj", TooHardFlag),
take_arg!("-specs", PathBuf, CanBeConcatenated(b'='), ExtraHashFileRequiringPath),
take_arg!("-std", OsString, Concatenated(b'='), Standard),
take_arg!("-stdlib", OsString, Concatenated(b'='), PreprocessorArgument),
flag!("-trigraphs", PreprocessorArgumentFlag),
Expand Down Expand Up @@ -420,6 +433,7 @@ where
serialize_diagnostics = Some(path.clone());
}
Some(ExtraHashFile(_))
| Some(ExtraHashFileRequiringPath(_))
| Some(ExtraHashFileClangModuleFile(_))
| Some(PassThroughFlag)
| Some(PreprocessorArgumentFlag)
Expand Down Expand Up @@ -502,6 +516,19 @@ where
extra_hash_files.push(cwd.join(path));
&mut common_args
}
Some(ExtraHashFileRequiringPath(path)) => {
// Without a directory separator the compiler resolves this name
// itself, against its plugin directory or its spec search path. We
// cannot reproduce that, and guessing at the working directory
// could hash an unrelated file that happens to share the name.
// (Note: ccache queries the compiler with -print-file-name to resolve
// spec files, we settle for not caching.)
if path.components().count() < 2 {
cannot_cache!("file argument without a path");
}
extra_hash_files.push(cwd.join(path));
&mut common_args
}
Some(ExtraHashFileClangModuleFile(val)) => {
// -fmodule-file can be either "path" or "name=path"
let val_str = val.to_string_lossy();
Expand Down Expand Up @@ -571,6 +598,7 @@ where
| Some(ClangModuleOutput(_))
| Some(TooHardFlag)
| Some(XClang(_))
| Some(ExtraHashFileRequiringPath(_))
| Some(TooHard(_)) => cannot_cache!(
arg.flag_str()
.unwrap_or("Can't handle complex arguments through clang",)
Expand Down Expand Up @@ -1809,6 +1837,72 @@ mod test {
);
}

/// A gcc plugin's code runs as part of the compilation, so the object depends
/// on the plugin's contents, not just on the path naming it.
#[test]
fn test_parse_arguments_fplugin_hashes_the_plugin() {
for args in [
stringvec!["-c", "foo.c", "-o", "foo.o", "-fplugin=./plugin.so"],
stringvec!["-c", "foo.c", "-o", "foo.o", "-fplugin", "./plugin.so"],
stringvec![
"-c",
"foo.c",
"-o",
"foo.o",
"-fplugin=scripts/gcc-plugins/structleak_plugin.so"
],
] {
let a = match parse_arguments_(args.clone(), false) {
CompilerArguments::Ok(a) => a,
o => panic!("Got unexpected parse result: {o:?} for {args:?}"),
};
assert_eq!(1, a.extra_hash_files.len(), "{args:?}");
}
}

/// A spec file rewrites the command line, so its contents are an input even
/// though the arguments we hash do not change with it.
#[test]
fn test_parse_arguments_specs_hashes_the_spec_file() {
for args in [
stringvec!["-c", "foo.c", "-o", "foo.o", "-specs=./hardened.spec"],
stringvec!["-c", "foo.c", "-o", "foo.o", "-specs", "./hardened.spec"],
stringvec![
"-c",
"foo.c",
"-o",
"foo.o",
"--specs=/usr/lib/rpm/cc1.spec"
],
] {
let a = match parse_arguments_(args.clone(), false) {
CompilerArguments::Ok(a) => a,
o => panic!("Got unexpected parse result: {o:?} for {args:?}"),
};
assert_eq!(1, a.extra_hash_files.len(), "{args:?}");
}
}

/// A plugin or spec file named without a path is resolved by the compiler
/// against a search path of its own, so there is nothing for us to hash.
/// libcc1plugin is one of these.
#[test]
fn test_parse_arguments_file_argument_without_path_refused() {
for arg in [
"-fplugin=libcc1plugin",
"-fplugin=structleak_plugin.so",
"-specs=hardened.spec",
] {
let args = stringvec!["-c", "foo.c", "-o", "foo.o", arg];
match parse_arguments_(args, false) {
CompilerArguments::CannotCache(reason, None) => {
assert_eq!(reason, "file argument without a path", "{arg}");
}
o => panic!("Got unexpected parse result: {o:?} for {arg}"),
}
}
}

#[test]
fn test_parse_arguments_too_hard() {
let too_hard_flags = stringvec![
Expand Down
1 change: 1 addition & 0 deletions src/compiler/msvc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,7 @@ pub fn parse_arguments(
| Some(ClangModuleOutput(_))
| Some(ExtraHashFileClangModuleFile(_))
| Some(ModuleOnlyFlag)
| Some(ExtraHashFileRequiringPath(_))
| Some(TooHard(_)) => cannot_cache!(
arg.flag_str()
.unwrap_or("Can't handle complex arguments through clang",)
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/preprocessor_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use super::Language;

/// The current format is 1 header byte for the version + bincode encoding
/// of the [`PreprocessorCacheEntry`] struct.
const FORMAT_VERSION: u8 = 0;
const FORMAT_VERSION: u8 = 1;
const MAX_PREPROCESSOR_CACHE_ENTRIES: usize = 100;
const MAX_PREPROCESSOR_CACHE_FILE_INFO_ENTRIES: usize = 10000;

Expand Down
1 change: 1 addition & 0 deletions src/compiler/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,7 @@ where

Ok(HashResult {
key: m.finish(),
cacheable: Cacheable::Yes,
compilation: Box::new(RustCompilation {
executable: self.executable.clone(),
host: self.host.clone(),
Expand Down
Loading
Loading