Skip to content

Commit

Permalink
syntax: Expand format!() deterministically
Browse files Browse the repository at this point in the history
Previously, format!("{a}{b}", a=foo(), b=bar()) has foo() and bar() run in a
nondeterminisc order. This is clearly a non-desirable property, so this commit
uses iteration over a list instead of iteration over a hash map to provide
deterministic code generation of these format arguments.
  • Loading branch information
alexcrichton committed Feb 28, 2014
1 parent ec57db0 commit 017c504
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 13 deletions.
3 changes: 2 additions & 1 deletion src/libsyntax/ext/deriving/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,6 @@ fn show_substructure(cx: &mut ExtCtxt, span: Span,
// phew, not our responsibility any more!
format::expand_preparsed_format_args(cx, span,
format_closure,
format_string, exprs, HashMap::new())
format_string, exprs, ~[],
HashMap::new())
}
36 changes: 24 additions & 12 deletions src/libsyntax/ext/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ struct Context<'a> {
// them.
args: ~[@ast::Expr],
arg_types: ~[Option<ArgumentType>],
// Parsed named expressions and the types that we've found for them so far
// Parsed named expressions and the types that we've found for them so far.
// Note that we keep a side-array of the ordering of the named arguments
// found to be sure that we can translate them in the same order that they
// were declared in.
names: HashMap<~str, @ast::Expr>,
name_types: HashMap<~str, ArgumentType>,
name_ordering: ~[~str],

// Collection of the compiled `rt::Piece` structures
pieces: ~[@ast::Expr],
Expand All @@ -63,12 +67,15 @@ struct Context<'a> {
///
/// If parsing succeeds, the second return value is:
///
/// Some((fmtstr, unnamed arguments, named arguments))
fn parse_args(ecx: &mut ExtCtxt, sp: Span,
tts: &[ast::TokenTree]) -> (@ast::Expr, Option<(@ast::Expr, ~[@ast::Expr],
HashMap<~str, @ast::Expr>)>) {
/// Some((fmtstr, unnamed arguments, ordering of named arguments,
/// named arguments))
fn parse_args(ecx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
-> (@ast::Expr, Option<(@ast::Expr, ~[@ast::Expr], ~[~str],
HashMap<~str, @ast::Expr>)>)
{
let mut args = ~[];
let mut names = HashMap::<~str, @ast::Expr>::new();
let mut order = ~[];

let mut p = rsparse::new_parser_from_tts(ecx.parse_sess(),
ecx.cfg(),
Expand Down Expand Up @@ -125,12 +132,13 @@ fn parse_args(ecx: &mut ExtCtxt, sp: Span,
continue
}
}
order.push(name.to_str());
names.insert(name.to_str(), e);
} else {
args.push(p.parse_expr());
}
}
return (extra, Some((fmtstr, args, names)));
return (extra, Some((fmtstr, args, order, names)));
}

impl<'a> Context<'a> {
Expand Down Expand Up @@ -661,10 +669,11 @@ impl<'a> Context<'a> {
locals.push(self.format_arg(e.span, Exact(i),
self.ecx.expr_ident(e.span, name)));
}
for (name, &e) in self.names.iter() {
if !self.name_types.contains_key(name) {
continue
}
for name in self.name_ordering.iter() {
let e = match self.names.find(name) {
Some(&e) if self.name_types.contains_key(name) => e,
Some(..) | None => continue
};

let lname = self.ecx.ident_of(format!("__arg{}", *name));
pats.push(self.ecx.pat_ident(e.span, lname));
Expand Down Expand Up @@ -810,8 +819,9 @@ pub fn expand_args(ecx: &mut ExtCtxt, sp: Span,
tts: &[ast::TokenTree]) -> base::MacResult {

match parse_args(ecx, sp, tts) {
(extra, Some((efmt, args, names))) => {
MRExpr(expand_preparsed_format_args(ecx, sp, extra, efmt, args, names))
(extra, Some((efmt, args, order, names))) => {
MRExpr(expand_preparsed_format_args(ecx, sp, extra, efmt, args,
order, names))
}
(_, None) => MRExpr(ecx.expr_uint(sp, 2))
}
Expand All @@ -823,6 +833,7 @@ pub fn expand_args(ecx: &mut ExtCtxt, sp: Span,
pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
extra: @ast::Expr,
efmt: @ast::Expr, args: ~[@ast::Expr],
name_ordering: ~[~str],
names: HashMap<~str, @ast::Expr>) -> @ast::Expr {
let arg_types = vec::from_fn(args.len(), |_| None);
let mut cx = Context {
Expand All @@ -832,6 +843,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
names: names,
name_positions: HashMap::new(),
name_types: HashMap::new(),
name_ordering: name_ordering,
nest_level: 0,
next_arg: 0,
pieces: ~[],
Expand Down
16 changes: 16 additions & 0 deletions src/test/run-pass/ifmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub fn main() {

test_write();
test_print();
test_order();

// make sure that format! doesn't move out of local variables
let a = ~3;
Expand Down Expand Up @@ -202,3 +203,18 @@ fn test_format_args() {
let s = format_args!(fmt::format, "hello {}", "world");
t!(s, "hello world");
}

fn test_order() {
// Make sure format!() arguments are always evaluated in a left-to-right
// ordering
fn foo() -> int {
static mut FOO: int = 0;
unsafe {
FOO += 1;
FOO
}
}
assert_eq!(format!("{} {} {a} {b} {} {c}",
foo(), foo(), foo(), a=foo(), b=foo(), c=foo()),
~"1 2 4 5 3 6");
}

1 comment on commit 017c504

@alexcrichton
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r=brson,huonw

Please sign in to comment.