coverage: Convert and check span coordinates without a local file ID
For expansion region support, we will want to be able to convert and check spans before creating a corresponding local file ID. If we create local file IDs eagerly, but some expansion turns out to have no successfully-converted spans, LLVM will complain about that expansion's file ID having no regions.
This commit is contained in:
parent
d07ef5b0e1
commit
2e36990881
2 changed files with 40 additions and 27 deletions
|
@ -120,9 +120,14 @@ fn fill_region_tables<'tcx>(
|
||||||
// Associate that global file ID with a local file ID for this function.
|
// Associate that global file ID with a local file ID for this function.
|
||||||
let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
|
let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
|
||||||
|
|
||||||
let make_cov_span =
|
// In rare cases, _all_ of a function's spans are discarded, and coverage
|
||||||
|span: Span| spans::make_coverage_span(local_file_id, source_map, &source_file, span);
|
// codegen needs to handle that gracefully to avoid #133606.
|
||||||
|
// It's hard for tests to trigger this organically, so instead we set
|
||||||
|
// `-Zcoverage-options=discard-all-spans-in-codegen` to force it to occur.
|
||||||
let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
|
let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen();
|
||||||
|
let make_coords = |span: Span| {
|
||||||
|
if discard_all { None } else { spans::make_coords(source_map, &source_file, span) }
|
||||||
|
};
|
||||||
|
|
||||||
let ffi::Regions {
|
let ffi::Regions {
|
||||||
code_regions,
|
code_regions,
|
||||||
|
@ -145,17 +150,8 @@ fn fill_region_tables<'tcx>(
|
||||||
ffi::Counter::from_term(term)
|
ffi::Counter::from_term(term)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert the `Span` into coordinates that we can pass to LLVM, or
|
let Some(coords) = make_coords(span) else { continue };
|
||||||
// discard the span if conversion fails. In rare, cases _all_ of a
|
let cov_span = coords.make_coverage_span(local_file_id);
|
||||||
// function's spans are discarded, and the rest of coverage codegen
|
|
||||||
// needs to handle that gracefully to avoid a repeat of #133606.
|
|
||||||
// We don't have a good test case for triggering that organically, so
|
|
||||||
// instead we set `-Zcoverage-options=discard-all-spans-in-codegen`
|
|
||||||
// to force it to occur.
|
|
||||||
let Some(cov_span) = make_cov_span(span) else { continue };
|
|
||||||
if discard_all {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match *kind {
|
match *kind {
|
||||||
MappingKind::Code { bcb } => {
|
MappingKind::Code { bcb } => {
|
||||||
|
|
|
@ -5,22 +5,40 @@ use tracing::debug;
|
||||||
use crate::coverageinfo::ffi;
|
use crate::coverageinfo::ffi;
|
||||||
use crate::coverageinfo::mapgen::LocalFileId;
|
use crate::coverageinfo::mapgen::LocalFileId;
|
||||||
|
|
||||||
|
/// Line and byte-column coordinates of a source code span within some file.
|
||||||
|
/// The file itself must be tracked separately.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct Coords {
|
||||||
|
/// 1-based starting line of the source code span.
|
||||||
|
pub(crate) start_line: u32,
|
||||||
|
/// 1-based starting column (in bytes) of the source code span.
|
||||||
|
pub(crate) start_col: u32,
|
||||||
|
/// 1-based ending line of the source code span.
|
||||||
|
pub(crate) end_line: u32,
|
||||||
|
/// 1-based ending column (in bytes) of the source code span. High bit must be unset.
|
||||||
|
pub(crate) end_col: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coords {
|
||||||
|
/// Attaches a local file ID to these coordinates to produce an `ffi::CoverageSpan`.
|
||||||
|
pub(crate) fn make_coverage_span(&self, local_file_id: LocalFileId) -> ffi::CoverageSpan {
|
||||||
|
let &Self { start_line, start_col, end_line, end_col } = self;
|
||||||
|
let file_id = local_file_id.as_u32();
|
||||||
|
ffi::CoverageSpan { file_id, start_line, start_col, end_line, end_col }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts the span into its start line and column, and end line and column.
|
/// Converts the span into its start line and column, and end line and column.
|
||||||
///
|
///
|
||||||
/// Line numbers and column numbers are 1-based. Unlike most column numbers emitted by
|
/// Line numbers and column numbers are 1-based. Unlike most column numbers emitted by
|
||||||
/// the compiler, these column numbers are denoted in **bytes**, because that's what
|
/// the compiler, these column numbers are denoted in **bytes**, because that's what
|
||||||
/// LLVM's `llvm-cov` tool expects to see in coverage maps.
|
/// LLVM's `llvm-cov` tool expects to see in coverage maps.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the conversion failed for some reason. This shouldn't happen,
|
/// Returns `None` if the conversion failed for some reason. This should be uncommon,
|
||||||
/// but it's hard to rule out entirely (especially in the presence of complex macros
|
/// but it's hard to rule out entirely (especially in the presence of complex macros
|
||||||
/// or other expansions), and if it does happen then skipping a span or function is
|
/// or other expansions), and if it does happen then skipping a span or function is
|
||||||
/// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
|
/// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
|
||||||
pub(crate) fn make_coverage_span(
|
pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span) -> Option<Coords> {
|
||||||
file_id: LocalFileId,
|
|
||||||
source_map: &SourceMap,
|
|
||||||
file: &SourceFile,
|
|
||||||
span: Span,
|
|
||||||
) -> Option<ffi::CoverageSpan> {
|
|
||||||
let span = ensure_non_empty_span(source_map, span)?;
|
let span = ensure_non_empty_span(source_map, span)?;
|
||||||
|
|
||||||
let lo = span.lo();
|
let lo = span.lo();
|
||||||
|
@ -44,8 +62,7 @@ pub(crate) fn make_coverage_span(
|
||||||
start_line = source_map.doctest_offset_line(&file.name, start_line);
|
start_line = source_map.doctest_offset_line(&file.name, start_line);
|
||||||
end_line = source_map.doctest_offset_line(&file.name, end_line);
|
end_line = source_map.doctest_offset_line(&file.name, end_line);
|
||||||
|
|
||||||
check_coverage_span(ffi::CoverageSpan {
|
check_coords(Coords {
|
||||||
file_id: file_id.as_u32(),
|
|
||||||
start_line: start_line as u32,
|
start_line: start_line as u32,
|
||||||
start_col: start_col as u32,
|
start_col: start_col as u32,
|
||||||
end_line: end_line as u32,
|
end_line: end_line as u32,
|
||||||
|
@ -80,8 +97,8 @@ fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
|
||||||
/// it will immediately exit with a fatal error. To prevent that from happening,
|
/// it will immediately exit with a fatal error. To prevent that from happening,
|
||||||
/// discard regions that are improperly ordered, or might be interpreted in a
|
/// discard regions that are improperly ordered, or might be interpreted in a
|
||||||
/// way that makes them improperly ordered.
|
/// way that makes them improperly ordered.
|
||||||
fn check_coverage_span(cov_span: ffi::CoverageSpan) -> Option<ffi::CoverageSpan> {
|
fn check_coords(coords: Coords) -> Option<Coords> {
|
||||||
let ffi::CoverageSpan { file_id: _, start_line, start_col, end_line, end_col } = cov_span;
|
let Coords { start_line, start_col, end_line, end_col } = coords;
|
||||||
|
|
||||||
// Line/column coordinates are supposed to be 1-based. If we ever emit
|
// Line/column coordinates are supposed to be 1-based. If we ever emit
|
||||||
// coordinates of 0, `llvm-cov` might misinterpret them.
|
// coordinates of 0, `llvm-cov` might misinterpret them.
|
||||||
|
@ -94,17 +111,17 @@ fn check_coverage_span(cov_span: ffi::CoverageSpan) -> Option<ffi::CoverageSpan>
|
||||||
let is_ordered = (start_line, start_col) <= (end_line, end_col);
|
let is_ordered = (start_line, start_col) <= (end_line, end_col);
|
||||||
|
|
||||||
if all_nonzero && end_col_has_high_bit_unset && is_ordered {
|
if all_nonzero && end_col_has_high_bit_unset && is_ordered {
|
||||||
Some(cov_span)
|
Some(coords)
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
?cov_span,
|
?coords,
|
||||||
?all_nonzero,
|
?all_nonzero,
|
||||||
?end_col_has_high_bit_unset,
|
?end_col_has_high_bit_unset,
|
||||||
?is_ordered,
|
?is_ordered,
|
||||||
"Skipping source region that would be misinterpreted or rejected by LLVM"
|
"Skipping source region that would be misinterpreted or rejected by LLVM"
|
||||||
);
|
);
|
||||||
// If this happens in a debug build, ICE to make it easier to notice.
|
// If this happens in a debug build, ICE to make it easier to notice.
|
||||||
debug_assert!(false, "Improper source region: {cov_span:?}");
|
debug_assert!(false, "Improper source region: {coords:?}");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue