@@ -23,7 +23,7 @@ use rustc_middle::mir::{
23
23
use rustc_middle:: ty:: TyCtxt ;
24
24
use rustc_span:: def_id:: LocalDefId ;
25
25
use rustc_span:: source_map:: SourceMap ;
26
- use rustc_span:: { Span , Symbol } ;
26
+ use rustc_span:: { BytePos , Pos , RelativeBytePos , Span , Symbol } ;
27
27
28
28
/// Inserts `StatementKind::Coverage` statements that either instrument the binary with injected
29
29
/// counters, via intrinsic `llvm.instrprof.increment`, and/or inject metadata used during codegen
@@ -107,6 +107,12 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
107
107
) ;
108
108
109
109
let mappings = self . create_mappings ( & coverage_spans, & coverage_counters) ;
110
+ if mappings. is_empty ( ) {
111
+ // No spans could be converted into valid mappings, so skip this function.
112
+ debug ! ( "no spans could be converted into valid mappings; skipping" ) ;
113
+ return ;
114
+ }
115
+
110
116
self . inject_coverage_statements ( bcb_has_coverage_spans, & coverage_counters) ;
111
117
112
118
self . mir_body . function_coverage_info = Some ( Box :: new ( FunctionCoverageInfo {
@@ -148,9 +154,9 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
148
154
// Flatten the spans into individual term/span pairs.
149
155
. flat_map ( |( term, spans) | spans. iter ( ) . map ( move |& span| ( term, span) ) )
150
156
// Convert each span to a code region, and create the final mapping.
151
- . map ( |( term, span) | {
152
- let code_region = make_code_region ( source_map, file_name, span, body_span) ;
153
- Mapping { term, code_region }
157
+ . filter_map ( |( term, span) | {
158
+ let code_region = make_code_region ( source_map, file_name, span, body_span) ? ;
159
+ Some ( Mapping { term, code_region } )
154
160
} )
155
161
. collect :: < Vec < _ > > ( )
156
162
}
@@ -252,41 +258,85 @@ fn inject_statement(mir_body: &mut mir::Body<'_>, counter_kind: CoverageKind, bb
252
258
data. statements . insert ( 0 , statement) ;
253
259
}
254
260
255
- /// Convert the Span into its file name, start line and column, and end line and column
261
+ /// Convert the Span into its file name, start line and column, and end line and column.
262
+ ///
263
+ /// Line numbers and column numbers are 1-based. Unlike most column numbers emitted by
264
+ /// the compiler, these column numbers are denoted in **bytes**, because that's what
265
+ /// LLVM's `llvm-cov` tool expects to see in coverage maps.
266
+ ///
267
+ /// Returns `None` if the conversion failed for some reason. This shouldn't happen,
268
+ /// but it's hard to rule out entirely (especially in the presence of complex macros
269
+ /// or other expansions), and if it does happen then skipping a span or function is
270
+ /// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
256
271
fn make_code_region (
257
272
source_map : & SourceMap ,
258
273
file_name : Symbol ,
259
274
span : Span ,
260
275
body_span : Span ,
261
- ) -> CodeRegion {
276
+ ) -> Option < CodeRegion > {
262
277
debug ! (
263
278
"Called make_code_region(file_name={}, span={}, body_span={})" ,
264
279
file_name,
265
280
source_map. span_to_diagnostic_string( span) ,
266
281
source_map. span_to_diagnostic_string( body_span)
267
282
) ;
268
283
269
- let ( file, mut start_line, mut start_col, mut end_line, mut end_col) =
270
- source_map. span_to_location_info ( span) ;
271
- if span. hi ( ) == span. lo ( ) {
272
- // Extend an empty span by one character so the region will be counted.
273
- if span. hi ( ) == body_span. hi ( ) {
274
- start_col = start_col. saturating_sub ( 1 ) ;
275
- } else {
276
- end_col = start_col + 1 ;
277
- }
284
+ let lo = span. lo ( ) ;
285
+ let hi = span. hi ( ) ;
286
+
287
+ let file = source_map. lookup_source_file ( lo) ;
288
+ if !file. contains ( hi) {
289
+ debug ! ( ?span, ?file, ?lo, ?hi, "span crosses multiple files; skipping" ) ;
290
+ return None ;
291
+ }
292
+
293
+ // Column numbers need to be in bytes, so we can't use the more convenient
294
+ // `SourceMap` methods for looking up file coordinates.
295
+ let rpos_and_line_and_byte_column = |pos : BytePos | -> Option < ( RelativeBytePos , usize , usize ) > {
296
+ let rpos = file. relative_position ( pos) ;
297
+ let line_index = file. lookup_line ( rpos) ?;
298
+ let line_start = file. lines ( ) [ line_index] ;
299
+ // Line numbers and column numbers are 1-based, so add 1 to each.
300
+ Some ( ( rpos, line_index + 1 , ( rpos - line_start) . to_usize ( ) + 1 ) )
278
301
} ;
279
- if let Some ( file) = file {
280
- start_line = source_map. doctest_offset_line ( & file. name , start_line) ;
281
- end_line = source_map. doctest_offset_line ( & file. name , end_line) ;
302
+
303
+ let ( lo_rpos, mut start_line, mut start_col) = rpos_and_line_and_byte_column ( lo) ?;
304
+ let ( hi_rpos, mut end_line, mut end_col) = rpos_and_line_and_byte_column ( hi) ?;
305
+
306
+ // If the span is empty, try to expand it horizontally by one character's
307
+ // worth of bytes, so that it is more visible in `llvm-cov` reports.
308
+ // We do this after resolving line/column numbers, so that empty spans at the
309
+ // end of a line get an extra column instead of wrapping to the next line.
310
+ if span. is_empty ( )
311
+ && body_span. contains ( span)
312
+ && let Some ( src) = & file. src
313
+ {
314
+ // Prefer to expand the end position, if it won't go outside the body span.
315
+ if hi < body_span. hi ( ) {
316
+ let hi_rpos = hi_rpos. to_usize ( ) ;
317
+ let nudge_bytes = src. ceil_char_boundary ( hi_rpos + 1 ) - hi_rpos;
318
+ end_col += nudge_bytes;
319
+ } else if lo > body_span. lo ( ) {
320
+ let lo_rpos = lo_rpos. to_usize ( ) ;
321
+ let nudge_bytes = lo_rpos - src. floor_char_boundary ( lo_rpos - 1 ) ;
322
+ // Subtract the nudge, but don't go below column 1.
323
+ start_col = start_col. saturating_sub ( nudge_bytes) . max ( 1 ) ;
324
+ }
325
+ // If neither nudge could be applied, stick with the empty span coordinates.
282
326
}
283
- CodeRegion {
327
+
328
+ // Apply an offset so that code in doctests has correct line numbers.
329
+ // FIXME(#79417): Currently we have no way to offset doctest _columns_.
330
+ start_line = source_map. doctest_offset_line ( & file. name , start_line) ;
331
+ end_line = source_map. doctest_offset_line ( & file. name , end_line) ;
332
+
333
+ Some ( CodeRegion {
284
334
file_name,
285
335
start_line : start_line as u32 ,
286
336
start_col : start_col as u32 ,
287
337
end_line : end_line as u32 ,
288
338
end_col : end_col as u32 ,
289
- }
339
+ } )
290
340
}
291
341
292
342
fn is_eligible_for_coverage ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
0 commit comments