From 2fed35e9f980d8b637de467ac74f23720fad2f5c Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Tue, 8 Sep 2026 21:57:16 +0200 Subject: [PATCH 1/2] Fix llvm_tools::tests::test_llvm_aggregate_profraws against nightly https://github.com/rust-lang/rust/pull/161517 (part of 2026-09-07) changed the coverage output slightly, this accepts both versions. --- src/llvm_tools.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/llvm_tools.rs b/src/llvm_tools.rs index 094066254..0cb3cd7ad 100644 --- a/src/llvm_tools.rs +++ b/src/llvm_tools.rs @@ -446,22 +446,33 @@ mod tests { let lcov = String::from_utf8_lossy(&lcovs[0]); - let lcov_entries = [ + let mut lcov_entries = vec![ "FNF:1", // # of function found "FNH:1", // # of function hit "DA:1,2", // Line 1 hit 2 times "DA:2,2", // Line 2 hit 2 times "DA:3,1", // Line 3 hit 1 time - "DA:4,1", // Line 4 hit 1 time "DA:5,1", // Line 5 hit 1 time - "DA:6,1", // Line 6 hit 1 time "DA:7,2", // Line 7 hit 2 time "BRF:0", // # of branch found "BRH:0", // # of branch hit - "LF:7", // # of line found - "LH:7", // # of line hit ]; + // Starting with rustc nightly-2026-09-07, lines 4 and 6 are not counted as code lines anymore. + if lcov.contains("DA:4,1\n") { + lcov_entries.extend_from_slice(&[ + "DA:4,1", // Line 4 hit 1 time + "DA:6,1", // Line 6 hit 1 time + "LF:7", // # of line found + "LH:7", // # of line hit + ]); + } else { + lcov_entries.extend_from_slice(&[ + "LF:5", // # of line found + "LH:5", // # of line hit + ]); + }; + for entry in lcov_entries { assert!(lcov.contains(&format!("{entry}\n"))); } From da7e65c3a536ebb0ab997db2bed863ae0809af1b Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Tue, 8 Sep 2026 16:07:20 +0200 Subject: [PATCH 2/2] Bug 2033336 - Build absolute path relative to full GCNO path when normalize_path fails. When building Firefox with unified build, the GCNO files embed paths relative to the GCNO file itself. For instance when building media/libopus, the build system generates obj-x86_64-pc-linux-gnu/media/libopus/Unified_c_media_libopus0.c, which includes ../../../media/libopus/celt/bands.c, and the GCNO ends up with the path ./../../../media/libopus/celt/bands.c. Grcov needs to convert this back to the full path to $srcdir/media/ libopus/celt/bands.c or it fails later on when get_abs_path calls normalize_path. --- src/defs.rs | 1 + src/lib.rs | 13 +++++++++++++ src/producer.rs | 29 +++++++++++++++++++---------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/defs.rs b/src/defs.rs index bc2a10982..73a4a0257 100644 --- a/src/defs.rs +++ b/src/defs.rs @@ -34,6 +34,7 @@ pub enum ItemFormat { #[derive(Debug)] pub struct GcnoBuffers { pub stem: String, + pub full_path: Option, pub gcno_buf: Vec, pub gcda_buf: Vec>, } diff --git a/src/lib.rs b/src/lib.rs index e7495fba0..17d61d40e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,6 +136,17 @@ fn add_results( } } +/// Replace file paths which have more .. components than normalize_path can handle. +fn rename_external_files(results: &mut [(String, CovResult)], full_path: Option<&Path>) { + if let Some(gcno_parent) = full_path.and_then(Path::parent) { + for (file, _) in results.iter_mut() { + if normalize_path(Path::new(file)).is_none() { + *file = gcno_parent.join(&file).to_str().unwrap().to_string(); + } + } + } +} + fn rename_single_files(results: &mut [(String, CovResult)], stem: &str) { // sometimes the gcno just contains foo.c // so in such case (with option --guess-directory-when-missing) @@ -244,6 +255,7 @@ pub fn consumer( new_results }; + rename_external_files(&mut new_results, Some(&gcno_path)); if guess_directory { rename_single_files(&mut new_results, &stem); } @@ -258,6 +270,7 @@ pub fn consumer( branch_enabled, ) { Ok(mut r) => { + rename_external_files(&mut r, buffers.full_path.as_deref()); if guess_directory { rename_single_files(&mut r, &buffers.stem); } diff --git a/src/producer.rs b/src/producer.rs index 453200db4..fb8c3d02b 100644 --- a/src/producer.rs +++ b/src/producer.rs @@ -23,8 +23,10 @@ pub struct Archive { } #[derive(Debug, PartialEq, Eq, Hash)] -pub struct GCNOStem { +pub struct GCNOInfos { pub stem: String, + /// Full path to the GCNO file, unavailable when the file is in an archive. + pub full_path: Option, pub llvm: bool, } @@ -59,7 +61,8 @@ impl Archive { &'a self, file: Option<&mut impl Read>, path: &Path, - gcno_stem_archives: &RefCell>, + full_path: Option<&Path>, + gcno_stem_archives: &RefCell>, gcda_stem_archives: &RefCell>>, profdatas: &RefCell>>, profraws: &RefCell>>, @@ -75,8 +78,9 @@ impl Archive { let llvm = is_llvm || Archive::check_file(file, &Archive::is_gcno_llvm); let filename = clean_path(&path.with_extension("")); gcno_stem_archives.borrow_mut().insert( - GCNOStem { + GCNOInfos { stem: filename, + full_path: full_path.map(ToOwned::to_owned), llvm, }, self, @@ -162,7 +166,7 @@ impl Archive { pub fn explore<'a>( &'a mut self, - gcno_stem_archives: &RefCell>, + gcno_stem_archives: &RefCell>, gcda_stem_archives: &RefCell>>, profdatas: &RefCell>>, profraws: &RefCell>>, @@ -181,6 +185,7 @@ impl Archive { self.handle_file( Some(&mut file), &path, + None, gcno_stem_archives, gcda_stem_archives, profdatas, @@ -208,6 +213,7 @@ impl Archive { self.handle_file( file.as_mut(), path, + Some(full_path), gcno_stem_archives, gcda_stem_archives, profdatas, @@ -228,6 +234,7 @@ impl Archive { self.handle_file( file.as_mut(), full_path, + Some(full_path), gcno_stem_archives, gcda_stem_archives, profdatas, @@ -326,7 +333,7 @@ impl Archive { fn gcno_gcda_producer( tmp_dir: &Path, - gcno_stem_archives: &FxHashMap, + gcno_stem_archives: &FxHashMap, gcda_stem_archives: &FxHashMap>, sender: &JobSender, ignore_orphan_gcno: bool, @@ -341,13 +348,13 @@ fn gcno_gcda_producer( .unwrap() }; - for (gcno_stem, gcno_archive) in gcno_stem_archives { - let stem = &gcno_stem.stem; + for (gcno_infos, gcno_archive) in gcno_stem_archives { + let stem = &gcno_infos.stem; if let Some(gcda_archives) = gcda_stem_archives.get(stem) { let gcno_archive = *gcno_archive; let gcno = format!("{stem}.gcno").to_string(); let physical_gcno_path = tmp_dir.join(format!("{}_{}.gcno", stem, 1)); - if gcno_stem.llvm { + if gcno_infos.llvm { let mut gcda_buffers: Vec> = Vec::with_capacity(gcda_archives.len()); if let Some(gcno_buffer) = gcno_archive.read(&gcno) { for gcda_archive in gcda_archives { @@ -359,6 +366,7 @@ fn gcno_gcda_producer( send_job( ItemType::Buffers(GcnoBuffers { stem: stem.clone(), + full_path: gcno_infos.full_path.clone(), gcno_buf: gcno_buffer, gcda_buf: gcda_buffers, }), @@ -391,11 +399,12 @@ fn gcno_gcda_producer( } else if !ignore_orphan_gcno { let gcno_archive = *gcno_archive; let gcno = format!("{stem}.gcno").to_string(); - if gcno_stem.llvm { + if gcno_infos.llvm { if let Some(gcno_buf) = gcno_archive.read(&gcno) { send_job( ItemType::Buffers(GcnoBuffers { stem: stem.clone(), + full_path: gcno_infos.full_path.clone(), gcno_buf, gcda_buf: Vec::new(), }), @@ -562,7 +571,7 @@ pub fn producer( }); } - let gcno_stems_archives: RefCell> = + let gcno_stems_archives: RefCell> = RefCell::new(FxHashMap::default()); let gcda_stems_archives: RefCell>> = RefCell::new(FxHashMap::default());