Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions compiler/rustc_parse_format/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,24 +307,46 @@ impl<'input> Parser<'input> {

let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
if let Some(nr_hashes) = style {
// snippet is a raw string, which starts with 'r', a number of hashes, and a quote
// and ends with a quote and the same number of hashes
(true, snippet.len() - nr_hashes - 1, vec![])
// snippet is a raw string

// validate snippet because a proc macro may have
// respanned it to something completely different (fixes #114865)
let prefix_len = nr_hashes + 2; // r + hashes + opening "
let suffix_len = nr_hashes + 1; // closing " + hashes
let snippet_bytes = snippet.as_bytes();
if snippet.len() >= prefix_len + suffix_len // is sufficiently long
&& snippet_bytes[0] == b'r'
&& snippet_bytes[1..1 + nr_hashes].iter().all(|&c| c == b'#')
&& snippet_bytes[1 + nr_hashes] == b'"'
{
let snippet_without_quotes = &snippet[prefix_len..snippet.len() - suffix_len];
Comment thread
gurry marked this conversation as resolved.
Outdated
let input_without_newline =
if appended_newline { &input[..input.len() - 1] } else { input };
if snippet_without_quotes == input_without_newline {
(true, snippet.len() - suffix_len, vec![])
} else {
(false, snippet.len(), vec![])
}
} else {
(false, snippet.len(), vec![])
}
} else {
// snippet is not a raw string
if snippet.starts_with('"') {
// snippet looks like an ordinary string literal
// check whether it is the escaped version of input
let without_quotes = &snippet[1..snippet.len() - 1];
let snippet_without_quotes = &snippet[1..snippet.len() - 1];
let (mut ok, mut vec) = (true, vec![]);
let mut chars = input.chars();
rustc_literal_escaper::unescape_str(without_quotes, |range, res| match res {
Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
vec.push((range, ch));
}
_ => {
ok = false;
vec = vec![];
rustc_literal_escaper::unescape_str(snippet_without_quotes, |range, res| {
match res {
Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
vec.push((range, ch));
}
_ => {
ok = false;
vec = vec![];
}
}
});
let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/proc-macro/auxiliary/ice-wrong-span-114865.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
extern crate proc_macro;

use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use std::iter::FromIterator;

/// Expands to a call to `println!` having a format string as its argument
/// but with the format string's span set to that of the proc macro's input token
#[proc_macro]
pub fn foo(input: TokenStream) -> TokenStream {
let token = input.into_iter().next().unwrap();
let mut lit: Literal = r#"r"{}""#.parse().unwrap();
lit.set_span(token.span());
FromIterator::<TokenTree>::from_iter([
Ident::new("println", Span::mixed_site()).into(),
Punct::new('!', Spacing::Alone).into(),
Group::new(Delimiter::Parenthesis, TokenTree::from(lit).into()).into(),
])
}


/// Same as `foo` but with the format string containing multiple `#`s
#[proc_macro]
pub fn foo2(input: TokenStream) -> TokenStream {
let token = input.into_iter().next().unwrap();
let mut lit: Literal = r###"r##"{}"##"###.parse().unwrap();
lit.set_span(token.span());
FromIterator::<TokenTree>::from_iter([
Ident::new("println", Span::mixed_site()).into(),
Punct::new('!', Spacing::Alone).into(),
Group::new(Delimiter::Parenthesis, TokenTree::from(lit).into()).into(),
])
}
21 changes: 21 additions & 0 deletions tests/ui/proc-macro/ice-wrong-span-114865.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Regression test for ICE https://github.com/rust-lang/rust/issues/114865

// Tests that we do not ICE when a proc macro expands
// to a string formatting macro (like println!) and respans
// this formatting macro's arg to that of its own input which
// happens to be a multi-byte string (see the auxiliary file
// ice-wrong-span-114865.rs).

//@ proc-macro: ice-wrong-span-114865.rs

extern crate ice_wrong_span_114865;

use ice_wrong_span_114865::{foo, foo2};

fn main() {
foo!("字"); //~ ERROR 1 positional argument in format string, but no arguments were given
foo!("r字字"); //~ ERROR 1 positional argument in format string, but no arguments were given

foo2!("字"); //~ ERROR 1 positional argument in format string, but no arguments were given
foo2!("r字字"); //~ ERROR 1 positional argument in format string, but no arguments were given
}
26 changes: 26 additions & 0 deletions tests/ui/proc-macro/ice-wrong-span-114865.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
error: 1 positional argument in format string, but no arguments were given
--> $DIR/ice-wrong-span-114865.rs:16:10
|
LL | foo!("字");
| ^^^^

error: 1 positional argument in format string, but no arguments were given
--> $DIR/ice-wrong-span-114865.rs:17:10
|
LL | foo!("r字字");
| ^^^^^^^

error: 1 positional argument in format string, but no arguments were given
--> $DIR/ice-wrong-span-114865.rs:19:11
|
LL | foo2!("字");
| ^^^^

error: 1 positional argument in format string, but no arguments were given
--> $DIR/ice-wrong-span-114865.rs:20:11
|
LL | foo2!("r字字");
| ^^^^^^^

error: aborting due to 4 previous errors

Loading