coverage: Detect unused local file IDs to avoid an LLVM assertion

This case can't actually happen yet (other than via a testing flag), because
currently all of a function's spans must belong to the same file and expansion.
But this will be an important edge case when adding expansion region support.
This commit is contained in:
Zalathar
2025-05-09 20:57:17 +10:00
parent 8cd8b23b9e
commit 078144fdfa
8 changed files with 87 additions and 6 deletions

View File

@@ -155,6 +155,20 @@ pub(crate) struct Regions {
impl Regions {
/// Returns true if none of this structure's tables contain any regions.
pub(crate) fn has_no_regions(&self) -> bool {
// Every region has a span, so if there are no spans then there are no regions.
self.all_cov_spans().next().is_none()
}
pub(crate) fn all_cov_spans(&self) -> impl Iterator<Item = &CoverageSpan> {
macro_rules! iter_cov_spans {
( $( $regions:expr ),* $(,)? ) => {
std::iter::empty()
$(
.chain( $regions.iter().map(|region| &region.cov_span) )
)*
}
}
let Self {
code_regions,
expansion_regions,
@@ -163,11 +177,13 @@ impl Regions {
mcdc_decision_regions,
} = self;
code_regions.is_empty()
&& expansion_regions.is_empty()
&& branch_regions.is_empty()
&& mcdc_branch_regions.is_empty()
&& mcdc_decision_regions.is_empty()
iter_cov_spans!(
code_regions,
expansion_regions,
branch_regions,
mcdc_branch_regions,
mcdc_decision_regions,
)
}
}

View File

@@ -11,6 +11,7 @@ use rustc_abi::Align;
use rustc_codegen_ssa::traits::{
BaseTypeCodegenMethods as _, ConstCodegenMethods, StaticCodegenMethods,
};
use rustc_index::IndexVec;
use rustc_middle::mir::coverage::{
BasicCoverageBlock, CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping,
MappingKind, Op,
@@ -125,6 +126,12 @@ fn fill_region_tables<'tcx>(
let local_file_id = covfun.virtual_file_mapping.push_file(&source_file);
// If this testing flag is set, add an extra unused entry to the local
// file table, to help test the code for detecting unused file IDs.
if tcx.sess.coverage_inject_unused_local_file() {
covfun.virtual_file_mapping.push_file(&source_file);
}
// In rare cases, _all_ of a function's spans are discarded, and coverage
// codegen needs to handle that gracefully to avoid #133606.
// It's hard for tests to trigger this organically, so instead we set
@@ -177,6 +184,19 @@ fn fill_region_tables<'tcx>(
}
}
/// LLVM requires all local file IDs to have at least one mapping region.
/// If that's not the case, skip this function, to avoid an assertion failure
/// (or worse) in LLVM.
fn check_local_file_table(covfun: &CovfunRecord<'_>) -> bool {
let mut local_file_id_seen =
IndexVec::<u32, _>::from_elem_n(false, covfun.virtual_file_mapping.local_file_table.len());
for cov_span in covfun.regions.all_cov_spans() {
local_file_id_seen[cov_span.file_id] = true;
}
local_file_id_seen.into_iter().all(|seen| seen)
}
/// Generates the contents of the covfun record for this function, which
/// contains the function's coverage mapping data. The record is then stored
/// as a global variable in the `__llvm_covfun` section.
@@ -185,6 +205,10 @@ pub(crate) fn generate_covfun_record<'tcx>(
global_file_table: &GlobalFileTable,
covfun: &CovfunRecord<'tcx>,
) {
if !check_local_file_table(covfun) {
return;
}
let &CovfunRecord {
mangled_function_name,
source_hash,