Skip to content

Commit ee66bcd

Browse files
committed
Ensure a sole string-literal passes to panic! is not a fmt string.
To accomplish this, adds `ensure_not_fmt_string_literal!` macro that will fail the compile if its argument is a fmt string literal. `ensure_not_fmt_string_literal!` takes a name along with expr; this allows for better error messages at its usage sites (like `panic!`). ---- Since this is making a certain kind of use of `panic!` illegal, it is a: [breaking-change] In particular, a panic like this: ```rust panic!("Is it stringified code: { or is it a ill-formed fmt arg? }"); ``` must be rewritten; one easy rewrite is to add parentheses: ```rust panic!(("Is it stringified code: { or is it a ill-formed fmt arg? }")); ``` ---- Fix #22932.
1 parent 2a54e2c commit ee66bcd

File tree

4 files changed

+112
-10
lines changed

4 files changed

+112
-10
lines changed

src/libcore/macros.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,22 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
// SNAP 5520801
12+
#[cfg(stage0)]
13+
#[macro_export]
14+
macro_rules! ensure_not_fmt_string_literal {
15+
($name:expr, $e:expr) => { $e }
16+
}
17+
1118
/// Entry point of task panic, for details, see std::macros
1219
#[macro_export]
1320
macro_rules! panic {
1421
() => (
1522
panic!("explicit panic")
1623
);
1724
($msg:expr) => ({
18-
static _MSG_FILE_LINE: (&'static str, &'static str, u32) = ($msg, file!(), line!());
25+
static _MSG_FILE_LINE: (&'static str, &'static str, u32) =
26+
(ensure_not_fmt_string_literal!("panic!", $msg), file!(), line!());
1927
::core::panicking::panic(&_MSG_FILE_LINE)
2028
});
2129
($fmt:expr, $($arg:tt)*) => ({

src/libstd/macros.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616
1717
#![unstable(feature = "std_misc")]
1818

19+
// SNAP 5520801
20+
#[cfg(stage0)]
21+
#[macro_export]
22+
macro_rules! ensure_not_fmt_string_literal {
23+
($name:expr, $e:expr) => { $e }
24+
}
25+
1926
/// The entry point for panic of Rust tasks.
2027
///
2128
/// This macro is used to inject panic into a Rust task, causing the task to
@@ -43,7 +50,7 @@ macro_rules! panic {
4350
panic!("explicit panic")
4451
});
4552
($msg:expr) => ({
46-
$crate::rt::begin_unwind($msg, {
53+
$crate::rt::begin_unwind(ensure_not_fmt_string_literal!("panic!", $msg), {
4754
// static requires less code at runtime, more constant data
4855
static _FILE_LINE: (&'static str, usize) = (file!(), line!() as usize);
4956
&_FILE_LINE

src/libsyntax/ext/base.rs

+30-8
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,9 @@ fn initial_syntax_expander_table<'feat>(ecfg: &expand::ExpansionConfig<'feat>)
456456
syntax_expanders.insert(intern("format_args"),
457457
// format_args uses `unstable` things internally.
458458
NormalTT(Box::new(ext::format::expand_format_args), None, true));
459+
syntax_expanders.insert(intern("ensure_not_fmt_string_literal"),
460+
builtin_normal_expander(
461+
ext::format::ensure_not_fmt_string_literal));
459462
syntax_expanders.insert(intern("env"),
460463
builtin_normal_expander(
461464
ext::env::expand_env));
@@ -756,19 +759,38 @@ impl<'a> ExtCtxt<'a> {
756759
}
757760
}
758761

762+
pub type ExprStringLitResult =
763+
Result<(P<ast::Expr>, InternedString, ast::StrStyle), P<ast::Expr>>;
764+
765+
/// Extract a string literal from macro expanded version of `expr`.
766+
///
767+
/// if `expr` is not string literal, then Err with span for expanded
768+
/// input, but does not emit any messages nor stop compilation.
769+
pub fn expr_string_lit(cx: &mut ExtCtxt, expr: P<ast::Expr>) -> ExprStringLitResult
770+
{
771+
// we want to be able to handle e.g. concat("foo", "bar")
772+
let expr = cx.expander().fold_expr(expr);
773+
let lit = match expr.node {
774+
ast::ExprLit(ref l) => match l.node {
775+
ast::LitStr(ref s, style) => Some(((*s).clone(), style)),
776+
_ => None
777+
},
778+
_ => None
779+
};
780+
match lit {
781+
Some(lit) => Ok((expr, lit.0, lit.1)),
782+
None => Err(expr),
783+
}
784+
}
785+
759786
/// Extract a string literal from the macro expanded version of `expr`,
760787
/// emitting `err_msg` if `expr` is not a string literal. This does not stop
761788
/// compilation on error, merely emits a non-fatal error and returns None.
762789
pub fn expr_to_string(cx: &mut ExtCtxt, expr: P<ast::Expr>, err_msg: &str)
763790
-> Option<(InternedString, ast::StrStyle)> {
764-
// we want to be able to handle e.g. concat("foo", "bar")
765-
let expr = cx.expander().fold_expr(expr);
766-
match expr.node {
767-
ast::ExprLit(ref l) => match l.node {
768-
ast::LitStr(ref s, style) => return Some(((*s).clone(), style)),
769-
_ => cx.span_err(l.span, err_msg)
770-
},
771-
_ => cx.span_err(expr.span, err_msg)
791+
match expr_string_lit(cx, expr) {
792+
Ok((_, s, style)) => return Some((s, style)),
793+
Err(e) => cx.span_err(e.span, err_msg),
772794
}
773795
None
774796
}

src/libsyntax/ext/format.rs

+65
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use codemap::{Span, respan};
1616
use ext::base::*;
1717
use ext::base;
1818
use ext::build::AstBuilder;
19+
use fold::Folder;
1920
use fmt_macros as parse;
2021
use parse::token::special_idents;
2122
use parse::token;
@@ -627,6 +628,70 @@ impl<'a, 'b> Context<'a, 'b> {
627628
}
628629
}
629630

631+
/// Expands `ensure_not_fmt_string_literal!(<where-literal>, <expr>)`
632+
/// into `<expr>`, but ensures that if `<expr>` is a string-literal,
633+
/// then it is not a potential format string literal.
634+
pub fn ensure_not_fmt_string_literal<'cx>(cx: &'cx mut ExtCtxt,
635+
sp: Span,
636+
tts: &[ast::TokenTree])
637+
-> Box<base::MacResult+'cx> {
638+
let takes_two_args = |cx: &ExtCtxt, rest| {
639+
cx.span_err(sp, &format!("`ensure_not_fmt_string_literal!` \
640+
takes 2 arguments, {}", rest));
641+
DummyResult::expr(sp)
642+
};
643+
let mut p = cx.new_parser_from_tts(tts);
644+
if p.token == token::Eof { return takes_two_args(cx, "given 0"); }
645+
let arg1 = cx.expander().fold_expr(p.parse_expr());
646+
if p.token != token::Comma { return takes_two_args(cx, "comma-separated"); }
647+
p.bump();
648+
if p.token == token::Eof { return takes_two_args(cx, "given 1"); }
649+
let arg2 = cx.expander().fold_expr(p.parse_expr());
650+
if p.token != token::Eof {
651+
takes_two_args(cx, "given too many");
652+
// (but do not return; handle two provided, nonetheless)
653+
}
654+
655+
// First argument is name of where this was invoked (for error messages).
656+
let lit_str_with_extended_lifetime;
657+
let name: &str = match expr_string_lit(cx, arg1) {
658+
Ok((_, lit_str, _)) => {
659+
lit_str_with_extended_lifetime = lit_str;
660+
&lit_str_with_extended_lifetime
661+
}
662+
Err(expr) => {
663+
let msg = "first argument to `ensure_not_fmt_string_literal!` \
664+
must be string literal";
665+
cx.span_err(expr.span, msg);
666+
// Compile proceeds using "ensure_not_fmt_string_literal"
667+
// as the name.
668+
"ensure_not_fmt_string_literal!"
669+
}
670+
};
671+
672+
// Second argument is the expression we are checking.
673+
let expr = match expr_string_lit(cx, arg2) {
674+
Err(expr) => {
675+
// input was not a string literal; just ignore it.
676+
expr
677+
}
678+
Ok((expr, lit_str, _style)) => {
679+
let m = format!("{} input cannot be format string literal", name);
680+
for c in lit_str.chars() {
681+
if c == '{' || c == '}' {
682+
cx.span_err(sp, &m);
683+
break;
684+
}
685+
}
686+
// we still return the expr itself, to allow catching of
687+
// further errors in the input.
688+
expr
689+
}
690+
};
691+
692+
MacEager::expr(expr)
693+
}
694+
630695
pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt, sp: Span,
631696
tts: &[ast::TokenTree])
632697
-> Box<base::MacResult+'cx> {

0 commit comments

Comments
 (0)