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.
|
||||
let local_file_id = covfun.virtual_file_mapping.local_id_for_global(global_file_id);
|
||||
|
||||
let make_cov_span =
|
||||
|span: Span| spans::make_coverage_span(local_file_id, source_map, &source_file, span);
|
||||
// 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
|
||||
// `-Zcoverage-options=discard-all-spans-in-codegen` to force it to occur.
|
||||
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 {
|
||||
code_regions,
|
||||
|
@ -145,17 +150,8 @@ fn fill_region_tables<'tcx>(
|
|||
ffi::Counter::from_term(term)
|
||||
};
|
||||
|
||||
// Convert the `Span` into coordinates that we can pass to LLVM, or
|
||||
// discard the span if conversion fails. In rare, cases _all_ of a
|
||||
// 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;
|
||||
}
|
||||
let Some(coords) = make_coords(span) else { continue };
|
||||
let cov_span = coords.make_coverage_span(local_file_id);
|
||||
|
||||
match *kind {
|
||||
MappingKind::Code { bcb } => {
|
||||
|
|
|
@ -5,22 +5,40 @@ use tracing::debug;
|
|||
use crate::coverageinfo::ffi;
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
/// 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
|
||||
/// 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.
|
||||
pub(crate) fn make_coverage_span(
|
||||
file_id: LocalFileId,
|
||||
source_map: &SourceMap,
|
||||
file: &SourceFile,
|
||||
span: Span,
|
||||
) -> Option<ffi::CoverageSpan> {
|
||||
pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span) -> Option<Coords> {
|
||||
let span = ensure_non_empty_span(source_map, span)?;
|
||||
|
||||
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);
|
||||
end_line = source_map.doctest_offset_line(&file.name, end_line);
|
||||
|
||||
check_coverage_span(ffi::CoverageSpan {
|
||||
file_id: file_id.as_u32(),
|
||||
check_coords(Coords {
|
||||
start_line: start_line as u32,
|
||||
start_col: start_col 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,
|
||||
/// discard regions that are improperly ordered, or might be interpreted in a
|
||||
/// way that makes them improperly ordered.
|
||||
fn check_coverage_span(cov_span: ffi::CoverageSpan) -> Option<ffi::CoverageSpan> {
|
||||
let ffi::CoverageSpan { file_id: _, start_line, start_col, end_line, end_col } = cov_span;
|
||||
fn check_coords(coords: Coords) -> Option<Coords> {
|
||||
let Coords { start_line, start_col, end_line, end_col } = coords;
|
||||
|
||||
// Line/column coordinates are supposed to be 1-based. If we ever emit
|
||||
// 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);
|
||||
|
||||
if all_nonzero && end_col_has_high_bit_unset && is_ordered {
|
||||
Some(cov_span)
|
||||
Some(coords)
|
||||
} else {
|
||||
debug!(
|
||||
?cov_span,
|
||||
?coords,
|
||||
?all_nonzero,
|
||||
?end_col_has_high_bit_unset,
|
||||
?is_ordered,
|
||||
"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.
|
||||
debug_assert!(false, "Improper source region: {cov_span:?}");
|
||||
debug_assert!(false, "Improper source region: {coords:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue