Skip to content

fmt::Arguments is Send+Sync regardless of its captured arguments #45197

Closed
@cuviper

Description

@cuviper

fmt::Arguments<'a> is only parameterized on lifetime. The types of the captured arguments are erased in the contained [ArgumentV1<'a>]. Nothing restricts the Arguments aggregate from being Send or Sync, so by OIBIT it's both. Thus this compiles:

fn send<T: Send>(_: T) {}
fn sync<T: Sync>(_: T) {}

fn main() {
    // `Cell` is not `Sync`, so `&Cell` is neither `Sync` nor `Send`,
    // yet `std::fmt::Arguments` forgets this...
    let c = std::cell::Cell::new(42);
    send(format_args!("{:?}", c));
    sync(format_args!("{:?}", c));
}

I'm not sure if there are any realistic ways to accidentally abuse this, but here's a deliberate example. The spawned thread will read the Cell through the arguments, even while the main thread modifies it.

extern crate crossbeam;

use std::io::Write;
use std::cell::Cell;

fn main() {
    let c = Cell::new(1);
    threader(&c, format_args!("{:?}\n", c));
}

fn threader(c: &Cell<i32>, a: std::fmt::Arguments) {
    crossbeam::scope(|scope| {
        let delay = std::time::Duration::from_millis(100);
        
        let guard = scope.spawn(move || {
            for _ in 0..10 {
                std::thread::sleep(delay);
                std::io::stdout().write_fmt(a).unwrap();
            }
        });
        
        for _ in 0..10 {
            std::thread::sleep(delay);
            c.set(c.get() * 2);
        }
        
        guard.join();
    });
}

playground

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions