@@ -23,7 +23,7 @@ use rustc_middle::mir::{
2323use rustc_middle:: ty:: TyCtxt ;
2424use rustc_span:: def_id:: LocalDefId ;
2525use rustc_span:: source_map:: SourceMap ;
26- use rustc_span:: { Span , Symbol } ;
26+ use rustc_span:: { BytePos , Pos , RelativeBytePos , Span , Symbol } ;
2727
2828/// Inserts `StatementKind::Coverage` statements that either instrument the binary with injected
2929/// counters, via intrinsic `llvm.instrprof.increment`, and/or inject metadata used during codegen
@@ -107,6 +107,12 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
107107 ) ;
108108
109109 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+
110116 self . inject_coverage_statements ( bcb_has_coverage_spans, & coverage_counters) ;
111117
112118 self . mir_body . function_coverage_info = Some ( Box :: new ( FunctionCoverageInfo {
@@ -148,9 +154,9 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
148154 // Flatten the spans into individual term/span pairs.
149155 . flat_map ( |( term, spans) | spans. iter ( ) . map ( move |& span| ( term, span) ) )
150156 // 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 } )
154160 } )
155161 . collect :: < Vec < _ > > ( )
156162 }
@@ -252,41 +258,85 @@ fn inject_statement(mir_body: &mut mir::Body<'_>, counter_kind: CoverageKind, bb
252258 data. statements . insert ( 0 , statement) ;
253259}
254260
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.
256271fn make_code_region (
257272 source_map : & SourceMap ,
258273 file_name : Symbol ,
259274 span : Span ,
260275 body_span : Span ,
261- ) -> CodeRegion {
276+ ) -> Option < CodeRegion > {
262277 debug ! (
263278 "Called make_code_region(file_name={}, span={}, body_span={})" ,
264279 file_name,
265280 source_map. span_to_diagnostic_string( span) ,
266281 source_map. span_to_diagnostic_string( body_span)
267282 ) ;
268283
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 ) )
278301 } ;
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.
282326 }
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 {
284334 file_name,
285335 start_line : start_line as u32 ,
286336 start_col : start_col as u32 ,
287337 end_line : end_line as u32 ,
288338 end_col : end_col as u32 ,
289- }
339+ } )
290340}
291341
292342fn is_eligible_for_coverage ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
0 commit comments