Skip to content

Commit dd14f10

Browse files
committed
fix ICEs when string contents shorter than guard
1 parent c92eae1 commit dd14f10

7 files changed

+175
-34
lines changed

compiler/rustc_lexer/src/lib.rs

+24-11
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub mod unescape;
2929
#[cfg(test)]
3030
mod tests;
3131

32+
use std::num::NonZeroU8;
33+
3234
pub use crate::cursor::Cursor;
3335

3436
use self::LiteralKind::*;
@@ -189,9 +191,9 @@ pub enum LiteralKind {
189191
ByteStr { terminated: bool },
190192
/// `c"abc"`, `c"abc`
191193
CStr { terminated: bool },
192-
/// `#"abc"#`, `####"ab"###"c"####`, `#"a`, `##"a"#`. `None` indicates
193-
/// no closing quote.
194-
GuardedStr { n_hashes: Option<u8> },
194+
/// `#"abc"#`, `#"a`, `##"a"#`. `None` indicates no closing quote.
195+
/// Allows fewer hashes to close the string to support older editions.
196+
GuardedStr { n_start_hashes: Option<NonZeroU8>, n_end_hashes: u8 },
195197
/// `r"abc"`, `r#"abc"#`, `r####"ab"###"c"####`, `r#"a`. `None` indicates
196198
/// an invalid literal.
197199
RawStr { n_hashes: Option<u8> },
@@ -381,11 +383,24 @@ impl Cursor<'_> {
381383

382384
let res = self.guarded_double_quoted_string(n_start_hashes);
383385
let suffix_start = self.pos_within_token();
384-
if res.is_ok() {
386+
387+
if let (Ok(n_end_hashes), Ok(n)) = (res, u8::try_from(n_start_hashes)) {
385388
self.eat_literal_suffix();
389+
390+
Literal {
391+
kind: GuardedStr {
392+
n_start_hashes: NonZeroU8::new(n),
393+
// Always succeeds because `n_end_hashes <= n`
394+
n_end_hashes: n_end_hashes.try_into().unwrap(),
395+
},
396+
suffix_start,
397+
}
398+
} else {
399+
Literal {
400+
kind: GuardedStr { n_start_hashes: None, n_end_hashes: 0 },
401+
suffix_start,
402+
}
386403
}
387-
let kind = GuardedStr { n_hashes: n_start_hashes.try_into().ok() };
388-
Literal { kind, suffix_start }
389404
} else {
390405
// Not a guarded string, so restore old state.
391406
*self = backup;
@@ -801,19 +816,17 @@ impl Cursor<'_> {
801816
});
802817
}
803818

804-
// Check that amount of closing '#' symbols
805-
// is equal to the amount of opening ones.
819+
// Consume closing '#' symbols.
806820
// Note that this will not consume extra trailing `#` characters:
807-
// `###"abcde"####` is lexed as a `GuardedStr { n_end_hashes: 3 }`
821+
// `###"abcde"####` is lexed as a `GuardedStr { n_hashes: 3 }`
808822
// followed by a `#` token.
809823
let mut n_end_hashes = 0;
810824
while self.first() == '#' && n_end_hashes < n_start_hashes {
811825
n_end_hashes += 1;
812826
self.bump();
813827
}
814828

815-
// We could handle `n_end_hashes < n_start_hashes` here
816-
// but this whole token is an error anyways.
829+
// Handle `n_end_hashes < n_start_hashes` later.
817830
Ok(n_end_hashes)
818831
}
819832

compiler/rustc_parse/src/lexer/mod.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
242242
return (Token::new(self.ident(start), prefix_span), preceded_by_whitespace);
243243
}
244244
rustc_lexer::TokenKind::Literal {
245-
kind: rustc_lexer::LiteralKind::GuardedStr { n_hashes },
245+
kind: rustc_lexer::LiteralKind::GuardedStr { n_start_hashes, .. },
246246
suffix_start: _
247247
} if !self.mk_sp(start, self.pos).edition().at_least_rust_2024() => {
248248
// Check if previous char was `#`, so we don't
@@ -252,8 +252,8 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
252252
self.str_from_to(start - BytePos(1), start) == "#"
253253
) {
254254
let span = self.mk_sp(start, self.pos);
255-
let space_span = n_hashes.map(|n_hashes| {
256-
let space_pos = start + BytePos(n_hashes.into());
255+
let space_span = n_start_hashes.map(|n_hashes| {
256+
let space_pos = start + BytePos(n_hashes.get().into());
257257
self.mk_sp(space_pos, space_pos)
258258
});
259259

@@ -526,11 +526,12 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
526526
// using this syntax produces an error. In earlier editions, however, it
527527
// only results in an (allowed by default) lint, and is treated as
528528
// separate tokens.
529-
rustc_lexer::LiteralKind::GuardedStr { n_hashes } => {
529+
rustc_lexer::LiteralKind::GuardedStr { n_start_hashes, n_end_hashes } => {
530530
let span = self.mk_sp(start, self.pos);
531531

532-
if let Some(n_hashes) = n_hashes {
533-
let n = u32::from(n_hashes);
532+
if let Some(n_start_hashes) = n_start_hashes {
533+
let n = u32::from(n_start_hashes.get());
534+
let e = u32::from(n_end_hashes);
534535
let expn_data = span.ctxt().outer_expn_data();
535536

536537
let space_pos = start + BytePos(n);
@@ -543,7 +544,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
543544
};
544545

545546
self.dcx().emit_err(errors::ReservedGuardedString { span, sugg });
546-
self.cook_unicode(token::Str, Mode::Str, start, end, 1 + n, 1 + n) // ##" "##
547+
self.cook_unicode(token::Str, Mode::Str, start, end, 1 + n, 1 + e) // ##" "##
547548
} else {
548549
self.dcx().emit_fatal(errors::ReservedGuardedString { span, sugg: None });
549550
}

tests/ui/rust-2024/reserved-guarded-strings-migration.fixed

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
#![warn(rust_2024_guarded_string_incompatible_syntax)]
66

7+
macro_rules! demo1 {
8+
( $a:tt ) => { println!("one tokens") };
9+
}
10+
711
macro_rules! demo2 {
812
( $a:tt $b:tt ) => { println!("two tokens") };
913
}
@@ -29,6 +33,9 @@ macro_rules! demo7 {
2933
}
3034

3135
fn main() {
36+
demo1!("");
37+
demo2!(# "");
38+
demo3!(# ""#);
3239
demo2!(# "foo");
3340
demo3!(## "foo");
3441
demo3!(# "foo"#);
@@ -39,6 +46,15 @@ fn main() {
3946
demo2!("foo"#);
4047
demo4!("foo"###);
4148

49+
demo2!(# "");
50+
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
51+
//~| WARNING hard error in Rust 2024
52+
demo3!(# ""#);
53+
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
54+
//~| WARNING hard error in Rust 2024
55+
demo5!(#### "");
56+
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
57+
//~| WARNING hard error in Rust 2024
4258
demo2!(# "foo");
4359
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
4460
//~| WARNING hard error in Rust 2024

tests/ui/rust-2024/reserved-guarded-strings-migration.rs

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
#![warn(rust_2024_guarded_string_incompatible_syntax)]
66

7+
macro_rules! demo1 {
8+
( $a:tt ) => { println!("one tokens") };
9+
}
10+
711
macro_rules! demo2 {
812
( $a:tt $b:tt ) => { println!("two tokens") };
913
}
@@ -29,6 +33,9 @@ macro_rules! demo7 {
2933
}
3034

3135
fn main() {
36+
demo1!("");
37+
demo2!(# "");
38+
demo3!(# ""#);
3239
demo2!(# "foo");
3340
demo3!(## "foo");
3441
demo3!(# "foo"#);
@@ -39,6 +46,15 @@ fn main() {
3946
demo2!("foo"#);
4047
demo4!("foo"###);
4148

49+
demo2!(#"");
50+
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
51+
//~| WARNING hard error in Rust 2024
52+
demo3!(#""#);
53+
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
54+
//~| WARNING hard error in Rust 2024
55+
demo5!(####"");
56+
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
57+
//~| WARNING hard error in Rust 2024
4258
demo2!(#"foo");
4359
//~^ WARNING parsed as a guarded string in Rust 2024 [rust_2024_guarded_string_incompatible_syntax]
4460
//~| WARNING hard error in Rust 2024

tests/ui/rust-2024/reserved-guarded-strings-migration.stderr

+48-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
warning: will be parsed as a guarded string in Rust 2024
2-
--> $DIR/reserved-guarded-strings-migration.rs:42:12
2+
--> $DIR/reserved-guarded-strings-migration.rs:49:12
33
|
4-
LL | demo2!(#"foo");
5-
| ^^^^^^
4+
LL | demo2!(#"");
5+
| ^^^
66
|
77
= warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
88
= note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
@@ -13,11 +13,50 @@ LL | #![warn(rust_2024_guarded_string_incompatible_syntax)]
1313
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1414
help: insert whitespace here to avoid this being parsed as guarded string in Rust 2024
1515
|
16+
LL | demo2!(# "");
17+
| +
18+
19+
warning: will be parsed as a guarded string in Rust 2024
20+
--> $DIR/reserved-guarded-strings-migration.rs:52:12
21+
|
22+
LL | demo3!(#""#);
23+
| ^^^^
24+
|
25+
= warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
26+
= note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
27+
help: insert whitespace here to avoid this being parsed as guarded string in Rust 2024
28+
|
29+
LL | demo3!(# ""#);
30+
| +
31+
32+
warning: will be parsed as a guarded string in Rust 2024
33+
--> $DIR/reserved-guarded-strings-migration.rs:55:12
34+
|
35+
LL | demo5!(####"");
36+
| ^^^^^^
37+
|
38+
= warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
39+
= note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
40+
help: insert whitespace here to avoid this being parsed as guarded string in Rust 2024
41+
|
42+
LL | demo5!(#### "");
43+
| +
44+
45+
warning: will be parsed as a guarded string in Rust 2024
46+
--> $DIR/reserved-guarded-strings-migration.rs:58:12
47+
|
48+
LL | demo2!(#"foo");
49+
| ^^^^^^
50+
|
51+
= warning: this is accepted in the current edition (Rust 2021) but is a hard error in Rust 2024!
52+
= note: for more information, see issue #123735 <https://github.com/rust-lang/rust/issues/123735>
53+
help: insert whitespace here to avoid this being parsed as guarded string in Rust 2024
54+
|
1655
LL | demo2!(# "foo");
1756
| +
1857

1958
warning: will be parsed as a guarded string in Rust 2024
20-
--> $DIR/reserved-guarded-strings-migration.rs:45:12
59+
--> $DIR/reserved-guarded-strings-migration.rs:61:12
2160
|
2261
LL | demo4!(###"foo");
2362
| ^^^^^^^^
@@ -30,7 +69,7 @@ LL | demo4!(### "foo");
3069
| +
3170

3271
warning: will be parsed as a guarded string in Rust 2024
33-
--> $DIR/reserved-guarded-strings-migration.rs:48:12
72+
--> $DIR/reserved-guarded-strings-migration.rs:64:12
3473
|
3574
LL | demo3!(#"foo"#);
3675
| ^^^^^^^
@@ -43,7 +82,7 @@ LL | demo3!(# "foo"#);
4382
| +
4483

4584
warning: will be parsed as a guarded string in Rust 2024
46-
--> $DIR/reserved-guarded-strings-migration.rs:51:12
85+
--> $DIR/reserved-guarded-strings-migration.rs:67:12
4786
|
4887
LL | demo5!(###"foo"#);
4988
| ^^^^^^^^^
@@ -56,7 +95,7 @@ LL | demo5!(### "foo"#);
5695
| +
5796

5897
warning: will be parsed as a guarded string in Rust 2024
59-
--> $DIR/reserved-guarded-strings-migration.rs:54:12
98+
--> $DIR/reserved-guarded-strings-migration.rs:70:12
6099
|
61100
LL | demo6!(###"foo"##);
62101
| ^^^^^^^^^^
@@ -69,7 +108,7 @@ LL | demo6!(### "foo"##);
69108
| +
70109

71110
warning: will be parsed as a guarded string in Rust 2024
72-
--> $DIR/reserved-guarded-strings-migration.rs:57:12
111+
--> $DIR/reserved-guarded-strings-migration.rs:73:12
73112
|
74113
LL | demo7!(###"foo"###);
75114
| ^^^^^^^^^^^
@@ -81,5 +120,5 @@ help: insert whitespace here to avoid this being parsed as guarded string in Rus
81120
LL | demo7!(### "foo"###);
82121
| +
83122

84-
warning: 6 warnings emitted
123+
warning: 9 warnings emitted
85124

tests/ui/rust-2024/reserved-guarded-strings.rs

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ macro_rules! demo7 {
3030
}
3131

3232
fn main() {
33+
demo1!("");
34+
demo2!(# "");
35+
demo3!(# ""#);
3336
demo2!(# "foo");
3437
demo3!(## "foo");
3538
demo3!(# "foo"#);
@@ -40,6 +43,11 @@ fn main() {
4043
demo2!("foo"#);
4144
demo4!("foo"###);
4245

46+
demo2!(blah"xx"); //~ ERROR prefix `blah` is unknown
47+
48+
demo1!(#""); //~ ERROR invalid string literal
49+
demo1!(#""#); //~ ERROR invalid string literal
50+
demo1!(####""); //~ ERROR invalid string literal
4351
demo1!(#"foo"); //~ ERROR invalid string literal
4452
demo1!(###"foo"); //~ ERROR invalid string literal
4553
demo1!(#"foo"#); //~ ERROR invalid string literal

0 commit comments

Comments
 (0)