diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 7e88b58c0e2..00f2f37146d 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -939,6 +939,7 @@ pub fn expand_preparsed_format_args( let msg = "format argument must be a string literal"; let fmt_sp = efmt.span; + let efmt_kind_is_lit: bool = matches!(efmt.kind, ast::ExprKind::Lit(_)); let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) { Ok(mut fmt) if append_newline => { fmt.0 = Symbol::intern(&format!("{}\n", fmt.0)); @@ -989,7 +990,19 @@ pub fn expand_preparsed_format_args( if !parser.errors.is_empty() { let err = parser.errors.remove(0); - let sp = fmt_span.from_inner(err.span); + let sp = if efmt_kind_is_lit { + fmt_span.from_inner(err.span) + } else { + // The format string could be another macro invocation, e.g.: + // format!(concat!("abc", "{}"), 4); + // However, `err.span` is an inner span relative to the *result* of + // the macro invocation, which is why we would get a nonsensical + // result calling `fmt_span.from_inner(err.span)` as above, and + // might even end up inside a multibyte character (issue #86085). + // Therefore, we conservatively report the error for the entire + // argument span here. + fmt_span + }; let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", err.description)); e.span_label(sp, err.label + " in format string"); if let Some(note) = err.note { diff --git a/src/test/ui/fmt/format-concat-span.rs b/src/test/ui/fmt/format-concat-span.rs new file mode 100644 index 00000000000..ce92df0ad92 --- /dev/null +++ b/src/test/ui/fmt/format-concat-span.rs @@ -0,0 +1,15 @@ +// If the format string is another macro invocation, rustc would previously +// compute nonsensical spans, such as: +// +// error: invalid format string: unmatched `}` found +// --> test.rs:2:17 +// | +// 2 | format!(concat!("abc}")); +// | ^ unmatched `}` in format string +// +// This test checks that this behavior has been fixed. + +fn main() { + format!(concat!("abc}")); + //~^ ERROR: invalid format string: unmatched `}` found +} diff --git a/src/test/ui/fmt/format-concat-span.stderr b/src/test/ui/fmt/format-concat-span.stderr new file mode 100644 index 00000000000..da46f40abcb --- /dev/null +++ b/src/test/ui/fmt/format-concat-span.stderr @@ -0,0 +1,11 @@ +error: invalid format string: unmatched `}` found + --> $DIR/format-concat-span.rs:13:13 + | +LL | format!(concat!("abc}")); + | ^^^^^^^^^^^^^^^ unmatched `}` in format string + | + = note: if you intended to print `}`, you can escape it using `}}` + = note: this error originates in the macro `concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/test/ui/fmt/issue-86085.rs b/src/test/ui/fmt/issue-86085.rs new file mode 100644 index 00000000000..63d42b76969 --- /dev/null +++ b/src/test/ui/fmt/issue-86085.rs @@ -0,0 +1,6 @@ +// Tests for an ICE with the fuzzed input below. + +fn main ( ) { +format ! ( concat ! ( r#"lJ𐏿Æ�.𐏿�"# , "r} {}" ) ) ; +//~^ ERROR: invalid format string: unmatched `}` found +} diff --git a/src/test/ui/fmt/issue-86085.stderr b/src/test/ui/fmt/issue-86085.stderr new file mode 100644 index 00000000000..ee7d8a5cc23 --- /dev/null +++ b/src/test/ui/fmt/issue-86085.stderr @@ -0,0 +1,11 @@ +error: invalid format string: unmatched `}` found + --> $DIR/issue-86085.rs:4:12 + | +LL | format ! ( concat ! ( r#"lJ𐏿Æ�.𐏿�"# , "r} {}" ) ) ; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unmatched `}` in format string + | + = note: if you intended to print `}`, you can escape it using `}}` + = note: this error originates in the macro `concat` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error +