Make Allowed require Copy and remove all the Drop-related complexity.
Originally, I build Allowed so it can work with non-Copy types, which allows Drop types. The semantics of storing non-Copy and non-Drop types in shared memory, however, are somewhat nonobvious. Based on code review feedback, and being particularly cautious because Allowed uses `unsafe`, I decided to add a Copy constraint to Allowed.
diff --git a/core/platform/src/allows/allowed.rs b/core/platform/src/allows/allowed.rs
index db8ba26..4d66c2b 100644
--- a/core/platform/src/allows/allowed.rs
+++ b/core/platform/src/allows/allowed.rs
@@ -1,9 +1,13 @@
/// An individual value that has been shared with the kernel using the `allow`
/// system call.
-// Design note: Allowed's implementation does not directly use the 'b lifetime.
-// Platform uses 'b to prevent the Allowed from accessing the buffer after the
-// buffer becomes invalid.
-pub struct Allowed<'b, T: 'b> {
+// Allowed's implementation does not directly use the 'b lifetime. Platform uses
+// 'b to prevent the Allowed from accessing the buffer after the buffer becomes
+// invalid.
+// Allowed requires T to be Copy due to concerns about the semantics of
+// non-copyable types in shared memory as well as concerns about unexpected
+// behavior with Drop types. See the following PR discussion for more
+// information: https://github.com/tock/libtock-rs/pull/222
+pub struct Allowed<'b, T: Copy + 'b> {
// Safety properties:
// 1. `buffer` remains valid and usable for the lifetime of this Allowed
// instance.
@@ -37,7 +41,7 @@
// need to be able to deconflict races between the kernel (which will never
// interrupt an instruction's execution) and this process. Therefore volatile
// accesses are sufficient to deconflict races.
-impl<'b, T: 'b> Allowed<'b, T> {
+impl<'b, T: Copy + 'b> Allowed<'b, T> {
// Allowed can only be constructed by the Platform. It is constructed after
// the `allow` system call, and as such must accept a raw pointer rather
// than a reference. The caller must make sure the following are true:
@@ -52,9 +56,7 @@
}
}
- // Sets the value in the buffer. Note that unlike `core::cell::Cell::set`,
- // `Allowed::set` will not drop the existing value. To drop the existing
- // value, use `replace` and drop the return value.
+ // Sets the value in the buffer.
pub fn set(&self, value: T) {
unsafe {
core::ptr::write_volatile(self.buffer.as_ptr(), value);
@@ -62,7 +64,7 @@
}
}
-impl<'b, T: crate::AllowReadable + 'b> Allowed<'b, T> {
+impl<'b, T: crate::AllowReadable + Copy + 'b> Allowed<'b, T> {
pub fn replace(&self, value: T) -> T {
let current = unsafe { core::ptr::read_volatile(self.buffer.as_ptr()) };
unsafe {
@@ -70,15 +72,13 @@
}
current
}
-}
-impl<'b, T: crate::AllowReadable + Copy + 'b> Allowed<'b, T> {
pub fn get(&self) -> T {
unsafe { core::ptr::read_volatile(self.buffer.as_ptr()) }
}
}
-impl<'b, T: crate::AllowReadable + Default + 'b> Allowed<'b, T> {
+impl<'b, T: crate::AllowReadable + Copy + Default + 'b> Allowed<'b, T> {
pub fn take(&self) -> T {
self.replace(T::default())
}
diff --git a/core/platform/src/allows/allowed_tests.rs b/core/platform/src/allows/allowed_tests.rs
index ec6a358..f1f0c08 100644
--- a/core/platform/src/allows/allowed_tests.rs
+++ b/core/platform/src/allows/allowed_tests.rs
@@ -1,4 +1,3 @@
-use crate::allows::test_util::DropCheck;
use crate::Allowed;
use core::marker::PhantomData;
use core::ptr::NonNull;
@@ -32,7 +31,7 @@
// interacting with a real Tock kernel.
//
// [1] https://plv.mpi-sws.org/rustbelt/stacked-borrows/paper.pdf
-struct KernelPtr<'b, T: 'b> {
+struct KernelPtr<'b, T: Copy + 'b> {
ptr: NonNull<T>,
// We need to consume the 'b lifetime. This is very similar to Allowed's
@@ -40,7 +39,7 @@
_phantom: PhantomData<&'b mut T>,
}
-impl<'b, T: 'b> KernelPtr<'b, T> {
+impl<'b, T: Copy + 'b> KernelPtr<'b, T> {
// The constructor for KernelPtr; simulates allow(). Returns both the
// Allowed instance the Platform would return and a KernelPtr the test can
// use to simulate a kernel.
@@ -59,98 +58,53 @@
(allowed, kernel_ptr)
}
- // Replaces the value in the buffer with a new one. Does not drop the
- // existing value.
+ // Replaces the value in the buffer with a new one.
pub fn set(&self, value: T) {
unsafe {
core::ptr::write(self.ptr.as_ptr(), value);
}
}
-}
-impl<'b, T: Copy + 'b> KernelPtr<'b, T> {
// Copies the contained value out of the buffer.
pub fn get(&self) -> T {
unsafe { core::ptr::read(self.ptr.as_ptr()) }
}
}
-impl<'b, 'f: 'b> KernelPtr<'b, DropCheck<'f>> {
- // Retrieves the value of the contained DropCheck.
- pub fn value(&self) -> usize {
- let drop_check = unsafe { core::ptr::read(self.ptr.as_ptr()) };
- let value = drop_check.value;
- core::mem::forget(drop_check);
- value
- }
-}
-
#[test]
fn set() {
- let dropped1 = core::cell::Cell::new(false);
- let dropped2 = core::cell::Cell::new(false);
- let dropped3 = core::cell::Cell::new(false);
- let mut buffer = DropCheck {
- flag: Some(&dropped1),
- value: 1,
- };
+ let mut buffer = 1;
let (allowed, kernel_ptr) = KernelPtr::allow(&mut buffer);
- assert_eq!(kernel_ptr.value(), 1);
- assert_eq!(dropped1.get(), false);
+ assert_eq!(kernel_ptr.get(), 1);
- // Simulate the kernel replacing the value in buffer. We don't drop the
- // existing value.
- kernel_ptr.set(DropCheck {
- flag: Some(&dropped2),
- value: 2,
- });
- allowed.set(DropCheck {
- flag: Some(&dropped3),
- value: 3,
- });
- assert_eq!(kernel_ptr.value(), 3);
- assert_eq!(dropped1.get(), false);
- assert_eq!(dropped2.get(), false);
- assert_eq!(dropped3.get(), false);
+ // Simulate the kernel replacing the value in buffer.
+ kernel_ptr.set(2);
+ allowed.set(3);
+ assert_eq!(kernel_ptr.get(), 3);
}
#[test]
fn replace() {
- let dropped1 = core::cell::Cell::new(false);
- let dropped2 = core::cell::Cell::new(false);
- let dropped3 = core::cell::Cell::new(false);
- let mut buffer = DropCheck {
- flag: Some(&dropped1),
- value: 1,
- };
+ let mut buffer = 1;
let (allowed, kernel_ptr) = KernelPtr::allow(&mut buffer);
- assert_eq!(kernel_ptr.value(), 1);
- assert_eq!(dropped1.get(), false);
+ assert_eq!(kernel_ptr.get(), 1);
- // Simulate the kernel replacing the value in buffer. We don't drop the
- // existing value.
- kernel_ptr.set(DropCheck {
- flag: Some(&dropped2),
- value: 2,
- });
- let returned = allowed.replace(DropCheck {
- flag: Some(&dropped3),
- value: 3,
- });
- assert_eq!(returned.value, 2);
- assert_eq!(kernel_ptr.value(), 3);
- assert_eq!(dropped1.get(), false);
- assert_eq!(dropped2.get(), false);
- assert_eq!(dropped3.get(), false);
+ // Simulate the kernel replacing the value in buffer.
+ kernel_ptr.set(2);
+ let returned = allowed.replace(3);
+ assert_eq!(returned, 2);
+ assert_eq!(kernel_ptr.get(), 3);
}
#[test]
fn get() {
- // We can't use DropCheck because Drop and Copy cannot both be implemented
- // on a single type.
let mut buffer = 1;
let (allowed, kernel_ptr) = KernelPtr::allow(&mut buffer);
assert_eq!(kernel_ptr.get(), 1);
+
+ assert_eq!(allowed.get(), 1);
+ assert_eq!(kernel_ptr.get(), 1);
+
kernel_ptr.set(2);
assert_eq!(allowed.get(), 2);
assert_eq!(kernel_ptr.get(), 2);
@@ -158,27 +112,13 @@
#[test]
fn take() {
- let dropped1 = core::cell::Cell::new(false);
- let dropped2 = core::cell::Cell::new(false);
- let dropped3 = core::cell::Cell::new(false);
- let mut buffer = DropCheck {
- flag: Some(&dropped1),
- value: 1,
- };
+ let mut buffer = 1;
let (allowed, kernel_ptr) = KernelPtr::allow(&mut buffer);
- assert_eq!(kernel_ptr.value(), 1);
- assert_eq!(dropped1.get(), false);
+ assert_eq!(kernel_ptr.get(), 1);
- // Simulate the kernel replacing the value in buffer. We don't drop the
- // existing value.
- kernel_ptr.set(DropCheck {
- flag: Some(&dropped2),
- value: 2,
- });
+ // Simulate the kernel replacing the value in buffer.
+ kernel_ptr.set(2);
let returned = allowed.take();
- assert_eq!(returned.value, 2);
- assert_eq!(kernel_ptr.value(), 0);
- assert_eq!(dropped1.get(), false);
- assert_eq!(dropped2.get(), false);
- assert_eq!(dropped3.get(), false);
+ assert_eq!(returned, 2);
+ assert_eq!(kernel_ptr.get(), 0);
}
diff --git a/core/platform/src/allows/mod.rs b/core/platform/src/allows/mod.rs
index 410d158..8cb2158 100644
--- a/core/platform/src/allows/mod.rs
+++ b/core/platform/src/allows/mod.rs
@@ -6,5 +6,3 @@
#[cfg(test)]
mod allowed_tests;
-#[cfg(test)]
-mod test_util;
diff --git a/core/platform/src/allows/test_util.rs b/core/platform/src/allows/test_util.rs
deleted file mode 100644
index 0f63d9c..0000000
--- a/core/platform/src/allows/test_util.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-//! Contains testing utilities for libtock_platform::allowed.
-
-// A value that can be placed in an Allowed that checks whether it has been
-// dropped.
-#[derive(Default)]
-pub(crate) struct DropCheck<'f> {
- pub flag: Option<&'f core::cell::Cell<bool>>,
- pub value: usize,
-}
-
-impl<'f> Drop for DropCheck<'f> {
- fn drop(&mut self) {
- if let Some(flag) = self.flag {
- flag.set(true);
- }
- }
-}
-
-// Note: DropCheck cannot be safely used in a non-test context, as DropCheck
-// does not satisfy AllowReadable's "every bit pattern is valid" requirement.
-// However, in the tests we aren't sharing the buffer with a real Tock kernel,
-// and instead we guarantee that only valid instances of DropCheck are loaded
-// into the buffer.
-unsafe impl<'f> crate::AllowReadable for DropCheck<'f> {}
-
-// Verify that DropCheck works, as none of the tests require it to actually set
-// the drop flag.
-#[test]
-fn drop_check() {
- let flag = core::cell::Cell::new(false);
- let drop_check = DropCheck {
- flag: Some(&flag),
- value: 0,
- };
- drop(drop_check);
- assert_eq!(flag.get(), true);
-}