Skip to content

Commit 72cfa0a

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 ce1f2cc commit 72cfa0a

File tree

2 files changed

+47
-5
lines changed

2 files changed

+47
-5
lines changed

compiler/rustc_span/src/lib.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ pub fn set_session_globals_then<R>(session_globals: &SessionGlobals, f: impl FnO
142142
SESSION_GLOBALS.set(session_globals, f)
143143
}
144144

145-
pub fn create_session_if_not_set_then<R, F>(edition: Edition, f: F) -> R
146-
where
147-
F: FnOnce(&SessionGlobals) -> R,
148-
{
145+
146+
pub fn create_session_if_not_set_then<R>(
147+
edition: Edition,
148+
f: impl FnOnce(&SessionGlobals) -> R,
149+
) -> R {
149150
if !SESSION_GLOBALS.is_set() {
150151
let session_globals = SessionGlobals::new(edition);
151152
SESSION_GLOBALS.set(&session_globals, || SESSION_GLOBALS.with(f))
@@ -160,7 +161,14 @@ where
160161
{
161162
SESSION_GLOBALS.with(f)
162163
}
164+
pub struct NotSet;
165+
166+
#[inline]
167+
pub fn try_with_session_globals<R>(f: impl FnOnce(&SessionGlobals) -> R) -> Result<R, NotSet> {
168+
if SESSION_GLOBALS.is_set() { Ok(SESSION_GLOBALS.with(f)) } else { Err(NotSet) }
169+
}
163170

171+
#[inline]
164172
pub fn create_default_session_globals_then<R>(f: impl FnOnce() -> R) -> R {
165173
create_session_globals_then(edition::DEFAULT_EDITION, f)
166174
}

compiler/rustc_span/src/span_encoding.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::def_id::{DefIndex, LocalDefId};
22
use crate::hygiene::SyntaxContext;
3-
use crate::SPAN_TRACK;
3+
use crate::{try_with_session_globals, NotSet, Pos, SPAN_TRACK};
44
use crate::{BytePos, SpanData};
55

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

0 commit comments

Comments
 (0)