Skip to content

Commit d1fa33d

Browse files
Rollup merge of rust-lang#141666 - lolbinarycat:rustdoc-source_span_for_markdown_range-bug-141665, r=GuillaumeGomez
source_span_for_markdown_range: fix utf8 violation it is non-trivial to reproduce this bug through rustdoc, which uses this function less than clippy, so the regression test was added as a unit test instead of an integration test. fixes rust-lang#141665 r? `@GuillaumeGomez`
2 parents ad649bd + a8b5e70 commit d1fa33d

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

compiler/rustc_resolve/src/rustdoc.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ use rustc_data_structures::fx::FxIndexMap;
1212
use rustc_data_structures::unord::UnordSet;
1313
use rustc_middle::ty::TyCtxt;
1414
use rustc_span::def_id::DefId;
15+
use rustc_span::source_map::SourceMap;
1516
use rustc_span::{DUMMY_SP, InnerSpan, Span, Symbol, sym};
1617
use thin_vec::ThinVec;
1718
use tracing::{debug, trace};
1819

20+
#[cfg(test)]
21+
mod tests;
22+
1923
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
2024
pub enum DocFragmentKind {
2125
/// A doc fragment created from a `///` or `//!` doc comment.
@@ -531,10 +535,20 @@ pub fn source_span_for_markdown_range(
531535
markdown: &str,
532536
md_range: &Range<usize>,
533537
fragments: &[DocFragment],
538+
) -> Option<Span> {
539+
let map = tcx.sess.source_map();
540+
source_span_for_markdown_range_inner(map, markdown, md_range, fragments)
541+
}
542+
543+
// inner function used for unit testing
544+
pub fn source_span_for_markdown_range_inner(
545+
map: &SourceMap,
546+
markdown: &str,
547+
md_range: &Range<usize>,
548+
fragments: &[DocFragment],
534549
) -> Option<Span> {
535550
use rustc_span::BytePos;
536551

537-
let map = tcx.sess.source_map();
538552
if let &[fragment] = &fragments
539553
&& fragment.kind == DocFragmentKind::RawDoc
540554
&& let Ok(snippet) = map.span_to_snippet(fragment.span)
@@ -570,7 +584,13 @@ pub fn source_span_for_markdown_range(
570584
{
571585
// If there is either a match in a previous fragment, or
572586
// multiple matches in this fragment, there is ambiguity.
573-
if match_data.is_none() && !snippet[match_start + 1..].contains(pat) {
587+
// the snippet cannot be zero-sized, because it matches
588+
// the pattern, which is checked to not be zero sized.
589+
if match_data.is_none()
590+
&& !snippet.as_bytes()[match_start + 1..]
591+
.windows(pat.len())
592+
.any(|s| s == pat.as_bytes())
593+
{
574594
match_data = Some((i, match_start));
575595
} else {
576596
// Heirustic produced ambiguity, return nothing.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::path::PathBuf;
2+
3+
use rustc_span::source_map::{FilePathMapping, SourceMap};
4+
use rustc_span::symbol::sym;
5+
use rustc_span::{BytePos, Span};
6+
7+
use super::{DocFragment, DocFragmentKind, source_span_for_markdown_range_inner};
8+
9+
#[test]
10+
fn single_backtick() {
11+
let sm = SourceMap::new(FilePathMapping::empty());
12+
sm.new_source_file(PathBuf::from("foo.rs").into(), r#"#[doc = "`"] fn foo() {}"#.to_string());
13+
let span = source_span_for_markdown_range_inner(
14+
&sm,
15+
"`",
16+
&(0..1),
17+
&[DocFragment {
18+
span: Span::with_root_ctxt(BytePos(8), BytePos(11)),
19+
item_id: None,
20+
kind: DocFragmentKind::RawDoc,
21+
doc: sym::empty, // unused placeholder
22+
indent: 0,
23+
}],
24+
)
25+
.unwrap();
26+
assert_eq!(span.lo(), BytePos(9));
27+
assert_eq!(span.hi(), BytePos(10));
28+
}
29+
30+
#[test]
31+
fn utf8() {
32+
// regression test for https://github.com/rust-lang/rust/issues/141665
33+
let sm = SourceMap::new(FilePathMapping::empty());
34+
sm.new_source_file(PathBuf::from("foo.rs").into(), r#"#[doc = "⚠"] fn foo() {}"#.to_string());
35+
let span = source_span_for_markdown_range_inner(
36+
&sm,
37+
"⚠",
38+
&(0..3),
39+
&[DocFragment {
40+
span: Span::with_root_ctxt(BytePos(8), BytePos(14)),
41+
item_id: None,
42+
kind: DocFragmentKind::RawDoc,
43+
doc: sym::empty, // unused placeholder
44+
indent: 0,
45+
}],
46+
)
47+
.unwrap();
48+
assert_eq!(span.lo(), BytePos(9));
49+
assert_eq!(span.hi(), BytePos(12));
50+
}

0 commit comments

Comments
 (0)