Skip to content

Commit 6103438

Browse files
committed
fix(content_store): saturating SourceSpan::len (codex P2 #581)
SourceSpan's fields are public, so a consumer can build end < start (bypassing new()'s clamp); the old `end - start` panicked in debug and wrapped to a huge u32 in release, inconsistent with is_empty(). Use saturating_sub so len() reports 0 for a malformed span, matching is_empty()/is_cited(). +1 test (malformed_span_len_saturates_not_panics). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGXeWN4XfVjteBVcVeuLo4
1 parent 10b9bb5 commit 6103438

1 file changed

Lines changed: 15 additions & 2 deletions

File tree

crates/lance-graph-contract/src/content_store.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,13 @@ impl SourceSpan {
9191
Self { content, start, end: end.max(start) }
9292
}
9393

94-
/// Span length in bytes.
94+
/// Span length in bytes. Saturating: a malformed span (`end < start`, only
95+
/// constructible by bypassing [`new`](Self::new) via the public fields)
96+
/// reports `0`, consistent with [`is_empty`](Self::is_empty) — never panics
97+
/// (debug) or wraps to a huge value (release).
9598
#[must_use]
9699
pub fn len(self) -> u32 {
97-
self.end - self.start
100+
self.end.saturating_sub(self.start)
98101
}
99102

100103
/// Whether the span covers zero bytes.
@@ -237,4 +240,14 @@ mod tests {
237240
assert!(!SourceSpan::new(ContentId(7), 5, 5).is_cited());
238241
assert!(SourceSpan::new(ContentId(7), 0, 5).is_cited());
239242
}
243+
244+
#[test]
245+
fn malformed_span_len_saturates_not_panics() {
246+
// Public fields let a consumer build end < start, bypassing new()'s clamp.
247+
// len() must saturate to 0 (consistent with is_empty), never panic/wrap.
248+
let bad = SourceSpan { content: ContentId(7), start: 13, end: 0 };
249+
assert_eq!(bad.len(), 0);
250+
assert!(bad.is_empty());
251+
assert!(!bad.is_cited());
252+
}
240253
}

0 commit comments

Comments
 (0)