Skip to content

Commit 3d11cf5

Browse files
author
Remi Delmas
committed
Implement SourceRegion locally in Kani
The original SourceRegion was moved to the llvm backend and made private by rust-lang/rust#134497. We implement our own local version of it to maintain our handling of SourceRegion for coverage properties.
1 parent faf89f1 commit 3d11cf5

File tree

5 files changed

+143
-3
lines changed

5 files changed

+143
-3
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/assert.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
//! 7. `codegen_sanity` : `assert` but not normally displayed as failure would be a Kani bug
1919
//!
2020
21+
use super::source_region::SourceRegion;
2122
use crate::codegen_cprover_gotoc::GotocCtx;
2223
use cbmc::InternedString;
2324
use cbmc::goto_program::{Expr, Location, Stmt, Type};
24-
use rustc_middle::mir::coverage::SourceRegion;
2525
use stable_mir::mir::{Place, ProjectionElem};
2626
use stable_mir::ty::{Span as SpanStable, Ty};
2727
use strum_macros::{AsRefStr, EnumString};

kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,10 @@ impl GotocCtx<'_> {
218218
}
219219

220220
pub mod rustc_smir {
221+
use crate::codegen_cprover_gotoc::codegen::source_region::{SourceRegion, make_source_region};
221222
use crate::stable_mir::CrateDef;
222223
use rustc_middle::mir::coverage::CovTerm;
223224
use rustc_middle::mir::coverage::MappingKind::Code;
224-
use rustc_middle::mir::coverage::SourceRegion;
225225
use rustc_middle::ty::TyCtxt;
226226
use rustc_smir::rustc_internal;
227227
use stable_mir::mir::mono::Instance;
@@ -258,9 +258,12 @@ pub mod rustc_smir {
258258
// Iterate over the coverage mappings and match with the coverage term.
259259
for mapping in &cov_info.mappings {
260260
let Code(term) = mapping.kind else { unreachable!() };
261+
let source_map = tcx.sess.source_map();
262+
let file = source_map.lookup_source_file(cov_info.body_span.lo());
261263
if term == coverage {
262264
return Some((
263-
mapping.source_region.clone(),
265+
make_source_region(source_map, cov_info, &file, mapping.span.clone())
266+
.unwrap(),
264267
rustc_internal::stable(cov_info.body_span).get_filename(),
265268
));
266269
}

kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod intrinsic;
1212
mod operand;
1313
mod place;
1414
mod rvalue;
15+
mod source_region;
1516
mod span;
1617
mod statement;
1718
mod static_var;
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This file contains our own local version of
5+
//! the `Span` to `CoverageRegion` conversion defined in
6+
//! https://github.com/rust-lang/rust/tree/master/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/spans.rs
7+
8+
use rustc_middle::mir::coverage::FunctionCoverageInfo;
9+
use rustc_span::Span;
10+
use rustc_span::source_map::SourceMap;
11+
use rustc_span::{BytePos, SourceFile};
12+
use std::fmt::{self, Debug, Formatter};
13+
use tracing::debug;
14+
15+
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
16+
pub struct SourceRegion {
17+
pub start_line: u32,
18+
pub start_col: u32,
19+
pub end_line: u32,
20+
pub end_col: u32,
21+
}
22+
23+
impl Debug for SourceRegion {
24+
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
25+
let &Self { start_line, start_col, end_line, end_col } = self;
26+
write!(fmt, "{start_line}:{start_col} - {end_line}:{end_col}")
27+
}
28+
}
29+
30+
fn ensure_non_empty_span(
31+
source_map: &SourceMap,
32+
fn_cov_info: &FunctionCoverageInfo,
33+
span: Span,
34+
) -> Option<Span> {
35+
if !span.is_empty() {
36+
return Some(span);
37+
}
38+
let lo = span.lo();
39+
let hi = span.hi();
40+
// The span is empty, so try to expand it to cover an adjacent '{' or '}',
41+
// but only within the bounds of the body span.
42+
let try_next = hi < fn_cov_info.body_span.hi();
43+
let try_prev = fn_cov_info.body_span.lo() < lo;
44+
if !(try_next || try_prev) {
45+
return None;
46+
}
47+
source_map
48+
.span_to_source(span, |src, start, end| try {
49+
// Adjusting span endpoints by `BytePos(1)` is normally a bug,
50+
// but in this case we have specifically checked that the character
51+
// we're skipping over is one of two specific ASCII characters, so
52+
// adjusting by exactly 1 byte is correct.
53+
if try_next && src.as_bytes()[end] == b'{' {
54+
Some(span.with_hi(hi + BytePos(1)))
55+
} else if try_prev && src.as_bytes()[start - 1] == b'}' {
56+
Some(span.with_lo(lo - BytePos(1)))
57+
} else {
58+
None
59+
}
60+
})
61+
.ok()?
62+
}
63+
64+
/// If `llvm-cov` sees a source region that is improperly ordered (end < start),
65+
/// it will immediately exit with a fatal error. To prevent that from happening,
66+
/// discard regions that are improperly ordered, or might be interpreted in a
67+
/// way that makes them improperly ordered.
68+
fn check_source_region(source_region: SourceRegion) -> Option<SourceRegion> {
69+
let SourceRegion { start_line, start_col, end_line, end_col } = source_region;
70+
// Line/column coordinates are supposed to be 1-based. If we ever emit
71+
// coordinates of 0, `llvm-cov` might misinterpret them.
72+
let all_nonzero = [start_line, start_col, end_line, end_col].into_iter().all(|x| x != 0);
73+
// Coverage mappings use the high bit of `end_col` to indicate that a
74+
// region is actually a "gap" region, so make sure it's unset.
75+
let end_col_has_high_bit_unset = (end_col & (1 << 31)) == 0;
76+
// If a region is improperly ordered (end < start), `llvm-cov` will exit
77+
// with a fatal error, which is inconvenient for users and hard to debug.
78+
let is_ordered = (start_line, start_col) <= (end_line, end_col);
79+
if all_nonzero && end_col_has_high_bit_unset && is_ordered {
80+
Some(source_region)
81+
} else {
82+
debug!(
83+
?source_region,
84+
?all_nonzero,
85+
?end_col_has_high_bit_unset,
86+
?is_ordered,
87+
"Skipping source region that would be misinterpreted or rejected by LLVM"
88+
);
89+
// If this happens in a debug build, ICE to make it easier to notice.
90+
debug_assert!(false, "Improper source region: {source_region:?}");
91+
None
92+
}
93+
}
94+
95+
/// Converts the span into its start line and column, and end line and column.
96+
///
97+
/// Line numbers and column numbers are 1-based. Unlike most column numbers emitted by
98+
/// the compiler, these column numbers are denoted in **bytes**, because that's what
99+
/// LLVM's `llvm-cov` tool expects to see in coverage maps.
100+
///
101+
/// Returns `None` if the conversion failed for some reason. This shouldn't happen,
102+
/// but it's hard to rule out entirely (especially in the presence of complex macros
103+
/// or other expansions), and if it does happen then skipping a span or function is
104+
/// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
105+
pub(crate) fn make_source_region(
106+
source_map: &SourceMap,
107+
fn_cov_info: &FunctionCoverageInfo,
108+
file: &SourceFile,
109+
span: Span,
110+
) -> Option<SourceRegion> {
111+
let span = ensure_non_empty_span(source_map, fn_cov_info, span)?;
112+
let lo = span.lo();
113+
let hi = span.hi();
114+
// Column numbers need to be in bytes, so we can't use the more convenient
115+
// `SourceMap` methods for looking up file coordinates.
116+
let line_and_byte_column = |pos: BytePos| -> Option<(usize, usize)> {
117+
let rpos = file.relative_position(pos);
118+
let line_index = file.lookup_line(rpos)?;
119+
let line_start = file.lines()[line_index];
120+
// Line numbers and column numbers are 1-based, so add 1 to each.
121+
Some((line_index + 1, ((rpos - line_start).0 as usize) + 1))
122+
};
123+
let (mut start_line, start_col) = line_and_byte_column(lo)?;
124+
let (mut end_line, end_col) = line_and_byte_column(hi)?;
125+
// Apply an offset so that code in doctests has correct line numbers.
126+
// FIXME(#79417): Currently we have no way to offset doctest _columns_.
127+
start_line = source_map.doctest_offset_line(&file.name, start_line);
128+
end_line = source_map.doctest_offset_line(&file.name, end_line);
129+
check_source_region(SourceRegion {
130+
start_line: start_line as u32,
131+
start_col: start_col as u32,
132+
end_line: end_line as u32,
133+
end_col: end_col as u32,
134+
})
135+
}

kani-compiler/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#![feature(f16)]
1818
#![feature(non_exhaustive_omitted_patterns_lint)]
1919
#![feature(float_next_up_down)]
20+
#![feature(try_blocks)]
2021
extern crate rustc_abi;
2122
extern crate rustc_ast;
2223
extern crate rustc_ast_pretty;

0 commit comments

Comments
 (0)