Skip to content

Commit 323c2df

Browse files
committed
Verify that spans point to char boundaries
This makes invalid spans a lot easier to debug. A quick and unscientific benchmarking test revealed that the performance impact of this is small at most.
1 parent b869e84 commit 323c2df

File tree

2 files changed

+47
-9
lines changed

2 files changed

+47
-9
lines changed

compiler/rustc_span/src/lib.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,18 +123,15 @@ pub fn set_session_globals_then<R>(session_globals: &SessionGlobals, f: impl FnO
123123
}
124124

125125
#[inline]
126-
pub fn create_default_session_if_not_set_then<R, F>(f: F) -> R
127-
where
128-
F: FnOnce(&SessionGlobals) -> R,
129-
{
126+
pub fn create_default_session_if_not_set_then<R>(f: impl FnOnce(&SessionGlobals) -> R) -> R {
130127
create_session_if_not_set_then(edition::DEFAULT_EDITION, f)
131128
}
132129

133130
#[inline]
134-
pub fn create_session_if_not_set_then<R, F>(edition: Edition, f: F) -> R
135-
where
136-
F: FnOnce(&SessionGlobals) -> R,
137-
{
131+
pub fn create_session_if_not_set_then<R>(
132+
edition: Edition,
133+
f: impl FnOnce(&SessionGlobals) -> R,
134+
) -> R {
138135
if !SESSION_GLOBALS.is_set() {
139136
let session_globals = SessionGlobals::new(edition);
140137
SESSION_GLOBALS.set(&session_globals, || SESSION_GLOBALS.with(f))
@@ -151,6 +148,13 @@ where
151148
SESSION_GLOBALS.with(f)
152149
}
153150

151+
pub struct NotSet;
152+
153+
#[inline]
154+
pub fn try_with_session_globals<R>(f: impl FnOnce(&SessionGlobals) -> R) -> Result<R, NotSet> {
155+
if SESSION_GLOBALS.is_set() { Ok(SESSION_GLOBALS.with(f)) } else { Err(NotSet) }
156+
}
157+
154158
#[inline]
155159
pub fn create_default_session_globals_then<R>(f: impl FnOnce() -> R) -> R {
156160
create_session_globals_then(edition::DEFAULT_EDITION, f)

compiler/rustc_span/src/span_encoding.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use crate::def_id::{DefIndex, LocalDefId};
88
use crate::hygiene::SyntaxContext;
9-
use crate::SPAN_TRACK;
9+
use crate::{try_with_session_globals, NotSet, Pos, SPAN_TRACK};
1010
use crate::{BytePos, SpanData};
1111

1212
use rustc_data_structures::fx::FxIndexSet;
@@ -96,6 +96,40 @@ impl Span {
9696
ctxt: SyntaxContext,
9797
parent: Option<LocalDefId>,
9898
) -> Self {
99+
// Make sure that the byte positions are at char boundaries.
100+
// Only do this if the session globals are set correctly, which they aren't in some unit tests.
101+
if cfg!(debug_assertions) {
102+
let _: Result<(), NotSet> = try_with_session_globals(|sess| {
103+
let sm = sess.source_map.lock();
104+
if let Some(sm) = &*sm {
105+
let offset = sm.lookup_byte_offset(lo);
106+
if let Some(file) = &offset.sf.src {
107+
// `is_char_boundary` already checks this, but asserting it seperately gives a better panic message.
108+
assert!(
109+
file.len() >= offset.pos.to_usize(),
110+
"start of span is out of bounds"
111+
);
112+
assert!(
113+
file.is_char_boundary(offset.pos.to_usize()),
114+
"start of span not on char boundary"
115+
);
116+
}
117+
118+
let offset = sm.lookup_byte_offset(hi);
119+
if let Some(file) = &offset.sf.src {
120+
assert!(
121+
file.len() >= offset.pos.to_usize(),
122+
"end of span is out of bounds"
123+
);
124+
assert!(
125+
file.is_char_boundary(offset.pos.to_usize()),
126+
"end of span not on char boundary"
127+
);
128+
}
129+
}
130+
});
131+
}
132+
99133
if lo > hi {
100134
std::mem::swap(&mut lo, &mut hi);
101135
}

0 commit comments

Comments
 (0)