Skip to content

Commit 151db25

Browse files
Rollup merge of #78423 - tgnottingham:caching_source_map_bounds_check, r=oli-obk
rustc_span: improve bounds checks in byte_pos_to_line_and_col The effect of this change is to consider edge-case spans that start or end at the position one past the end of a file to be valid during span hashing and encoding. This change means that these spans will be preserved across incremental compilation sessions when they are part of a serialized query result, instead of causing the dummy span to be used.
2 parents 7fa9e39 + 47dad31 commit 151db25

File tree

2 files changed

+46
-17
lines changed

2 files changed

+46
-17
lines changed

compiler/rustc_span/src/caching_source_map_view.rs

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
use crate::source_map::SourceMap;
22
use crate::{BytePos, SourceFile};
33
use rustc_data_structures::sync::Lrc;
4+
use std::ops::Range;
45

56
#[derive(Clone)]
67
struct CacheEntry {
78
time_stamp: usize,
89
line_number: usize,
9-
line_start: BytePos,
10-
line_end: BytePos,
10+
// The line's byte position range in the `SourceMap`. This range will fail to contain a valid
11+
// position in certain edge cases. Spans often start/end one past something, and when that
12+
// something is the last character of a file (this can happen when a file doesn't end in a
13+
// newline, for example), we'd still like for the position to be considered within the last
14+
// line. However, it isn't according to the exclusive upper bound of this range. We cannot
15+
// change the upper bound to be inclusive, because for most lines, the upper bound is the same
16+
// as the lower bound of the next line, so there would be an ambiguity.
17+
//
18+
// Since the containment aspect of this range is only used to see whether or not the cache
19+
// entry contains a position, the only ramification of the above is that we will get cache
20+
// misses for these rare positions. A line lookup for the position via `SourceMap::lookup_line`
21+
// after a cache miss will produce the last line number, as desired.
22+
line: Range<BytePos>,
1123
file: Lrc<SourceFile>,
1224
file_index: usize,
1325
}
@@ -26,8 +38,7 @@ impl<'sm> CachingSourceMapView<'sm> {
2638
let entry = CacheEntry {
2739
time_stamp: 0,
2840
line_number: 0,
29-
line_start: BytePos(0),
30-
line_end: BytePos(0),
41+
line: BytePos(0)..BytePos(0),
3142
file: first_file,
3243
file_index: 0,
3344
};
@@ -47,13 +58,13 @@ impl<'sm> CachingSourceMapView<'sm> {
4758

4859
// Check if the position is in one of the cached lines
4960
for cache_entry in self.line_cache.iter_mut() {
50-
if pos >= cache_entry.line_start && pos < cache_entry.line_end {
61+
if cache_entry.line.contains(&pos) {
5162
cache_entry.time_stamp = self.time_stamp;
5263

5364
return Some((
5465
cache_entry.file.clone(),
5566
cache_entry.line_number,
56-
pos - cache_entry.line_start,
67+
pos - cache_entry.line.start,
5768
));
5869
}
5970
}
@@ -69,13 +80,13 @@ impl<'sm> CachingSourceMapView<'sm> {
6980
let cache_entry = &mut self.line_cache[oldest];
7081

7182
// If the entry doesn't point to the correct file, fix it up
72-
if pos < cache_entry.file.start_pos || pos >= cache_entry.file.end_pos {
83+
if !file_contains(&cache_entry.file, pos) {
7384
let file_valid;
7485
if self.source_map.files().len() > 0 {
7586
let file_index = self.source_map.lookup_source_file_idx(pos);
7687
let file = self.source_map.files()[file_index].clone();
7788

78-
if pos >= file.start_pos && pos < file.end_pos {
89+
if file_contains(&file, pos) {
7990
cache_entry.file = file;
8091
cache_entry.file_index = file_index;
8192
file_valid = true;
@@ -95,10 +106,19 @@ impl<'sm> CachingSourceMapView<'sm> {
95106
let line_bounds = cache_entry.file.line_bounds(line_index);
96107

97108
cache_entry.line_number = line_index + 1;
98-
cache_entry.line_start = line_bounds.0;
99-
cache_entry.line_end = line_bounds.1;
109+
cache_entry.line = line_bounds;
100110
cache_entry.time_stamp = self.time_stamp;
101111

102-
Some((cache_entry.file.clone(), cache_entry.line_number, pos - cache_entry.line_start))
112+
Some((cache_entry.file.clone(), cache_entry.line_number, pos - cache_entry.line.start))
103113
}
104114
}
115+
116+
#[inline]
117+
fn file_contains(file: &SourceFile, pos: BytePos) -> bool {
118+
// `SourceMap::lookup_source_file_idx` and `SourceFile::contains` both consider the position
119+
// one past the end of a file to belong to it. Normally, that's what we want. But for the
120+
// purposes of converting a byte position to a line and column number, we can't come up with a
121+
// line and column number if the file is empty, because an empty file doesn't contain any
122+
// lines. So for our purposes, we don't consider empty files to contain any byte position.
123+
file.contains(pos) && !file.is_empty()
124+
}

compiler/rustc_span/src/lib.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use std::cell::RefCell;
5252
use std::cmp::{self, Ordering};
5353
use std::fmt;
5454
use std::hash::Hash;
55-
use std::ops::{Add, Sub};
55+
use std::ops::{Add, Range, Sub};
5656
use std::path::{Path, PathBuf};
5757
use std::str::FromStr;
5858

@@ -1426,24 +1426,33 @@ impl SourceFile {
14261426
if line_index >= 0 { Some(line_index as usize) } else { None }
14271427
}
14281428

1429-
pub fn line_bounds(&self, line_index: usize) -> (BytePos, BytePos) {
1430-
if self.start_pos == self.end_pos {
1431-
return (self.start_pos, self.end_pos);
1429+
pub fn line_bounds(&self, line_index: usize) -> Range<BytePos> {
1430+
if self.is_empty() {
1431+
return self.start_pos..self.end_pos;
14321432
}
14331433

14341434
assert!(line_index < self.lines.len());
14351435
if line_index == (self.lines.len() - 1) {
1436-
(self.lines[line_index], self.end_pos)
1436+
self.lines[line_index]..self.end_pos
14371437
} else {
1438-
(self.lines[line_index], self.lines[line_index + 1])
1438+
self.lines[line_index]..self.lines[line_index + 1]
14391439
}
14401440
}
14411441

1442+
/// Returns whether or not the file contains the given `SourceMap` byte
1443+
/// position. The position one past the end of the file is considered to be
1444+
/// contained by the file. This implies that files for which `is_empty`
1445+
/// returns true still contain one byte position according to this function.
14421446
#[inline]
14431447
pub fn contains(&self, byte_pos: BytePos) -> bool {
14441448
byte_pos >= self.start_pos && byte_pos <= self.end_pos
14451449
}
14461450

1451+
#[inline]
1452+
pub fn is_empty(&self) -> bool {
1453+
self.start_pos == self.end_pos
1454+
}
1455+
14471456
/// Calculates the original byte position relative to the start of the file
14481457
/// based on the given byte position.
14491458
pub fn original_relative_byte_pos(&self, pos: BytePos) -> BytePos {

0 commit comments

Comments
 (0)