Skip to content

Commit f72722a

Browse files
authored
Unrolled build for #160232
Rollup merge of #160232 - lazureykis:fix/rustdoc-escape-char-boundary, r=notriddle rustdoc: fix ICE when a grapheme cluster joins a Prepend-class character to `_` or `:` Fixes #160231 `EscapeBodyTextWithWbr` iterates `text.grapheme_indices(true)`, so `i` is the start of a grapheme cluster, but the `_`/`:` word-break arm sliced at `i + 1` — hard-coding the assumption that a cluster containing `_` or `:` is exactly one byte long. UAX#29 GB9b joins a `Prepend`-class character (`U+0600`–`U+0605`, `U+0D4E`, `U+111C2`, …) with the character that follows it, so a cluster can start with a multi-byte character and still contain `_`. `i + 1` then lands inside that character and `str` indexing panics. `U+0D4E` is `XID_Continue`, so this is reachable from an ordinary item name that rustc accepts: ```rust pub struct abcൎ_defgh; ``` rustdoc ICEs on that with `end byte index 4 is not a char boundary; it is inside 'ൎ' (bytes 3..6 of string)`, which means `cargo doc` cannot document the crate at all. Break after the whole cluster (`i + s.len()`) instead. The adjacent CamelCase arm already slices at `i`, which is always a cluster boundary, so it needed no change. The `i + 1` dates to 3bf8bcf (which added the `:` arm) and was extended to `_` by ac303df, both in #126247. This also fixes a smaller, non-panicking case: when a combining mark trails the `_` (`first_◌̀second`), the old code inserted the `<wbr>` between `_` and its combining mark, splitting a grapheme cluster. Tests: unit cases in `src/librustdoc/html/escape/tests.rs` covering a `Prepend`+`_` cluster, a `Prepend`+`:` cluster, and the trailing-combining-mark case; plus an end-to-end regression test in `tests/rustdoc-html/` so the ICE itself stays fixed. The existing tests missed this because they only cover `Extend`-class clusters, which join backwards onto an ASCII base character and so keep `i + 1` on a boundary (`E("ṼẽçÑñéå")`, `E("V\u{0300}e\u{0300}…")`). `Prepend` is the one class that joins forwards. The property test `escape_body_text_with_wbr_makes_sense` can't reach it either — its alphabet is `[b'a', b'A', b'_']`. r? rustdoc
2 parents 8ab9fdf + 54941be commit f72722a

3 files changed

Lines changed: 17 additions & 2 deletions

File tree

‎src/librustdoc/html/escape.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@ impl fmt::Display for EscapeBodyTextWithWbr<'_> {
7979
} else if (s.contains(':') && !next_is_colon())
8080
|| (s.contains('_') && !next_is_underscore())
8181
{
82-
EscapeBodyText(&text[last..i + 1]).fmt(fmt)?;
82+
// `i + s.len()`, not `i + 1`: a cluster containing `_` or `:` can still be
83+
// several bytes long, because UAX#29 GB9b joins a `Prepend` character to the
84+
// one that follows it.
85+
EscapeBodyText(&text[last..i + s.len()]).fmt(fmt)?;
8386
fmt.write_str("<wbr>")?;
84-
last = i + 1;
87+
last = i + s.len();
8588
}
8689
}
8790
if last < text.len() {

‎src/librustdoc/html/escape/tests.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ fn escape_body_text_with_wbr() {
4040
assert_eq!(&E("ṼẽçÑñéå").to_string(), "Ṽẽç<wbr>Ññéå");
4141
assert_eq!(&E("V\u{0300}e\u{0300}c\u{0300}D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}").to_string(), "V\u{0300}e\u{0300}c\u{0300}<wbr>D\u{0300}e\u{0300}q\u{0300}u\u{0300}e\u{0300}u\u{0300}e\u{0300}");
4242
assert_eq!(&E("LPFNACCESSIBLEOBJECTFROMWINDOW").to_string(), "LPFNACCESSIBLEOBJECTFROMWINDOW");
43+
// clusters where the `_` or `:` is not the first byte (UAX#29 GB9b `Prepend`)
44+
assert_eq!(&E("abc\u{0D4E}_defgh").to_string(), "abc\u{0D4E}_<wbr>defgh");
45+
assert_eq!(&E("abc\u{0605}:defgh").to_string(), "abc\u{0605}:<wbr>defgh");
46+
assert_eq!(&E("first_\u{0300}second").to_string(), "first_\u{0300}<wbr>second");
4347
}
4448
// property test
4549
#[test]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Regression test for #160231: `<wbr>` insertion sliced mid-character on an item name whose
2+
// grapheme cluster starts with a `Prepend` character and contains `_`.
3+
4+
#![crate_name = "foo"]
5+
#![allow(mixed_script_confusables, non_camel_case_types)]
6+
7+
//@ hasraw foo/index.html 'abcൎ_<wbr>defgh'
8+
pub struct abcൎ_defgh;

0 commit comments

Comments
 (0)