Skip to content

Commit

Permalink
Replace UnsafeCell with Cell in DateServiceInner (#1325)
Browse files Browse the repository at this point in the history
* Replace `UnsafeCell` with `Cell` in `DateServiceInner`

This ensures that it's impossible to cause undefined behavior by
accidentally violating Rust's aliasing rules (e.g. passing a closure to
`set_date` which ends up invoking `reset` or `update` on the inner
`DateServiceInner`).

There might be a tiny amount of overhead from copying the `Option<(Date,
Instant)>` rather than taking a reference, but it shouldn't be
measurable.

Since the wrapped type is `Copy`, a `Cell` can be used, avoiding the
runtime overhead of a `RefCell`.

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
  • Loading branch information
Aaron1011 and JohnTitor authored Jan 29, 2020
1 parent 664f9a8 commit 276a5a3
Showing 1 changed file with 21 additions and 8 deletions.
29 changes: 21 additions & 8 deletions actix-http/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::cell::UnsafeCell;
use std::cell::Cell;
use std::fmt::Write;
use std::rc::Rc;
use std::time::Duration;
Expand Down Expand Up @@ -228,24 +228,24 @@ impl fmt::Write for Date {
struct DateService(Rc<DateServiceInner>);

struct DateServiceInner {
current: UnsafeCell<Option<(Date, Instant)>>,
current: Cell<Option<(Date, Instant)>>,
}

impl DateServiceInner {
fn new() -> Self {
DateServiceInner {
current: UnsafeCell::new(None),
current: Cell::new(None),
}
}

fn reset(&self) {
unsafe { (&mut *self.current.get()).take() };
self.current.take();
}

fn update(&self) {
let now = Instant::now();
let date = Date::new();
*(unsafe { &mut *self.current.get() }) = Some((date, now));
self.current.set(Some((date, now)));
}
}

Expand All @@ -255,7 +255,7 @@ impl DateService {
}

fn check_date(&self) {
if unsafe { (&*self.0.current.get()).is_none() } {
if self.0.current.get().is_none() {
self.0.update();

// periodic date update
Expand All @@ -269,19 +269,32 @@ impl DateService {

fn now(&self) -> Instant {
self.check_date();
unsafe { (&*self.0.current.get()).as_ref().unwrap().1 }
self.0.current.get().unwrap().1
}

fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
self.check_date();
f(&unsafe { (&*self.0.current.get()).as_ref().unwrap().0 })
f(&self.0.current.get().unwrap().0);
}
}

#[cfg(test)]
mod tests {
use super::*;


// Test modifying the date from within the closure
// passed to `set_date`
#[test]
fn test_evil_date() {
let service = DateService::new();
// Make sure that `check_date` doesn't try to spawn a task
service.0.update();
service.set_date(|_| {
service.0.reset()
});
}

#[test]
fn test_date_len() {
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
Expand Down

0 comments on commit 276a5a3

Please sign in to comment.