wiggle: borrow checker lives in own crate, and supports both mut/immut
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -2680,6 +2680,7 @@ dependencies = [
|
|||||||
"wasmtime",
|
"wasmtime",
|
||||||
"wasmtime-wiggle-macro",
|
"wasmtime-wiggle-macro",
|
||||||
"wiggle",
|
"wiggle",
|
||||||
|
"wiggle-borrow",
|
||||||
"witx",
|
"witx",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2752,6 +2753,13 @@ dependencies = [
|
|||||||
"witx",
|
"witx",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wiggle-borrow"
|
||||||
|
version = "0.21.0"
|
||||||
|
dependencies = [
|
||||||
|
"wiggle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wiggle-generate"
|
name = "wiggle-generate"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
@@ -2778,7 +2786,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wiggle-test"
|
name = "wiggle-test"
|
||||||
version = "0.19.0"
|
version = "0.21.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger 0.8.1",
|
"env_logger 0.8.1",
|
||||||
"proptest",
|
"proptest",
|
||||||
@@ -2786,6 +2794,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"wiggle",
|
"wiggle",
|
||||||
|
"wiggle-borrow",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
17
crates/wiggle/borrow/Cargo.toml
Normal file
17
crates/wiggle/borrow/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "wiggle-borrow"
|
||||||
|
version = "0.21.0"
|
||||||
|
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkonk@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "Apache-2.0 WITH LLVM-exception"
|
||||||
|
description = "A run-time borrow checker for use with Wiggle"
|
||||||
|
categories = ["wasm"]
|
||||||
|
keywords = ["webassembly", "wasm"]
|
||||||
|
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||||
|
include = ["src/**/*", "LICENSE"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wiggle = { path = "..", version = "0.21.0" }
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
@@ -3,6 +3,8 @@ use std::collections::HashMap;
|
|||||||
use wiggle::{BorrowHandle, GuestError, Region};
|
use wiggle::{BorrowHandle, GuestError, Region};
|
||||||
|
|
||||||
pub struct BorrowChecker {
|
pub struct BorrowChecker {
|
||||||
|
/// Unfortunately, since the terminology of std::cell and the problem domain of borrow checking
|
||||||
|
/// overlap, the method calls on this member will be confusing.
|
||||||
bc: RefCell<InnerBorrowChecker>,
|
bc: RefCell<InnerBorrowChecker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,14 +25,16 @@ impl BorrowChecker {
|
|||||||
pub fn has_outstanding_borrows(&self) -> bool {
|
pub fn has_outstanding_borrows(&self) -> bool {
|
||||||
self.bc.borrow().has_outstanding_borrows()
|
self.bc.borrow().has_outstanding_borrows()
|
||||||
}
|
}
|
||||||
|
pub fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
pub(crate) fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
self.bc.borrow_mut().immut_borrow(r)
|
||||||
self.bc.borrow_mut().borrow(r)
|
|
||||||
}
|
}
|
||||||
pub(crate) fn unborrow(&self, h: BorrowHandle) {
|
pub fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
self.bc.borrow_mut().mut_borrow(r)
|
||||||
|
}
|
||||||
|
pub fn unborrow(&self, h: BorrowHandle) {
|
||||||
self.bc.borrow_mut().unborrow(h)
|
self.bc.borrow_mut().unborrow(h)
|
||||||
}
|
}
|
||||||
pub(crate) fn is_borrowed(&self, r: Region) -> bool {
|
pub fn is_borrowed(&self, r: Region) -> bool {
|
||||||
self.bc.borrow().is_borrowed(r)
|
self.bc.borrow().is_borrowed(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,11 +43,12 @@ impl BorrowChecker {
|
|||||||
/// This is a pretty naive way to account for borrows. This datastructure
|
/// This is a pretty naive way to account for borrows. This datastructure
|
||||||
/// could be made a lot more efficient with some effort.
|
/// could be made a lot more efficient with some effort.
|
||||||
struct InnerBorrowChecker {
|
struct InnerBorrowChecker {
|
||||||
/// Map from handle to region borrowed. A HashMap is probably not ideal
|
/// Maps from handle to region borrowed. A HashMap is probably not ideal
|
||||||
/// for this but it works. It would be more efficient if we could
|
/// for this but it works. It would be more efficient if we could
|
||||||
/// check `is_borrowed` without an O(n) iteration, by organizing borrows
|
/// check `is_borrowed` without an O(n) iteration, by organizing borrows
|
||||||
/// by an ordering of Region.
|
/// by an ordering of Region.
|
||||||
borrows: HashMap<BorrowHandle, Region>,
|
immut_borrows: HashMap<BorrowHandle, Region>,
|
||||||
|
mut_borrows: HashMap<BorrowHandle, Region>,
|
||||||
/// Handle to give out for the next borrow. This is the bare minimum of
|
/// Handle to give out for the next borrow. This is the bare minimum of
|
||||||
/// bookkeeping of free handles, and in a pathological case we could run
|
/// bookkeeping of free handles, and in a pathological case we could run
|
||||||
/// out, hence [`GuestError::BorrowCheckerOutOfHandles`]
|
/// out, hence [`GuestError::BorrowCheckerOutOfHandles`]
|
||||||
@@ -53,22 +58,27 @@ struct InnerBorrowChecker {
|
|||||||
impl InnerBorrowChecker {
|
impl InnerBorrowChecker {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
InnerBorrowChecker {
|
InnerBorrowChecker {
|
||||||
borrows: HashMap::new(),
|
immut_borrows: HashMap::new(),
|
||||||
|
mut_borrows: HashMap::new(),
|
||||||
next_handle: BorrowHandle(0),
|
next_handle: BorrowHandle(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_outstanding_borrows(&self) -> bool {
|
fn has_outstanding_borrows(&self) -> bool {
|
||||||
!self.borrows.is_empty()
|
!(self.immut_borrows.is_empty() && self.mut_borrows.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
!self.borrows.values().all(|b| !b.overlaps(r))
|
!self
|
||||||
|
.immut_borrows
|
||||||
|
.values()
|
||||||
|
.chain(self.mut_borrows.values())
|
||||||
|
.all(|b| !b.overlaps(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_handle(&mut self) -> Result<BorrowHandle, GuestError> {
|
fn new_handle(&mut self) -> Result<BorrowHandle, GuestError> {
|
||||||
// Reset handles to 0 if all handles have been returned.
|
// Reset handles to 0 if all handles have been returned.
|
||||||
if self.borrows.is_empty() {
|
if self.immut_borrows.is_empty() && self.mut_borrows.is_empty() {
|
||||||
self.next_handle = BorrowHandle(0);
|
self.next_handle = BorrowHandle(0);
|
||||||
}
|
}
|
||||||
let h = self.next_handle;
|
let h = self.next_handle;
|
||||||
@@ -84,17 +94,29 @@ impl InnerBorrowChecker {
|
|||||||
Ok(h)
|
Ok(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn immut_borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
if !self.mut_borrows.values().all(|b| !b.overlaps(r)) {
|
||||||
|
return Err(GuestError::PtrBorrowed(r));
|
||||||
|
}
|
||||||
|
let h = self.new_handle()?;
|
||||||
|
self.immut_borrows.insert(h, r);
|
||||||
|
Ok(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mut_borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
if self.is_borrowed(r) {
|
if self.is_borrowed(r) {
|
||||||
return Err(GuestError::PtrBorrowed(r));
|
return Err(GuestError::PtrBorrowed(r));
|
||||||
}
|
}
|
||||||
let h = self.new_handle()?;
|
let h = self.new_handle()?;
|
||||||
self.borrows.insert(h, r);
|
self.mut_borrows.insert(h, r);
|
||||||
Ok(h)
|
Ok(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unborrow(&mut self, h: BorrowHandle) {
|
fn unborrow(&mut self, h: BorrowHandle) {
|
||||||
let _ = self.borrows.remove(&h);
|
let removed = self.mut_borrows.remove(&h);
|
||||||
|
if removed.is_none() {
|
||||||
|
let _ = self.immut_borrows.remove(&h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,15 +129,15 @@ mod test {
|
|||||||
let r1 = Region::new(0, 10);
|
let r1 = Region::new(0, 10);
|
||||||
let r2 = Region::new(10, 10);
|
let r2 = Region::new(10, 10);
|
||||||
assert!(!r1.overlaps(r2));
|
assert!(!r1.overlaps(r2));
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
bs.mut_borrow(r1).expect("can borrow r1");
|
||||||
bs.borrow(r2).expect("can borrow r2");
|
bs.mut_borrow(r2).expect("can borrow r2");
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
let mut bs = InnerBorrowChecker::new();
|
||||||
let r1 = Region::new(10, 10);
|
let r1 = Region::new(10, 10);
|
||||||
let r2 = Region::new(0, 10);
|
let r2 = Region::new(0, 10);
|
||||||
assert!(!r1.overlaps(r2));
|
assert!(!r1.overlaps(r2));
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
bs.mut_borrow(r1).expect("can borrow r1");
|
||||||
bs.borrow(r2).expect("can borrow r2");
|
bs.mut_borrow(r2).expect("can borrow r2");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -124,29 +146,33 @@ mod test {
|
|||||||
let r1 = Region::new(0, 10);
|
let r1 = Region::new(0, 10);
|
||||||
let r2 = Region::new(9, 10);
|
let r2 = Region::new(9, 10);
|
||||||
assert!(r1.overlaps(r2));
|
assert!(r1.overlaps(r2));
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
bs.immut_borrow(r1).expect("can borrow r1");
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
assert!(bs.mut_borrow(r2).is_err(), "cant mut borrow r2");
|
||||||
|
bs.immut_borrow(r2).expect("can immut borrow r2");
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
let mut bs = InnerBorrowChecker::new();
|
||||||
let r1 = Region::new(0, 10);
|
let r1 = Region::new(0, 10);
|
||||||
let r2 = Region::new(2, 5);
|
let r2 = Region::new(2, 5);
|
||||||
assert!(r1.overlaps(r2));
|
assert!(r1.overlaps(r2));
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
bs.immut_borrow(r1).expect("can borrow r1");
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
assert!(bs.mut_borrow(r2).is_err(), "cant borrow r2");
|
||||||
|
bs.immut_borrow(r2).expect("can immut borrow r2");
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
let mut bs = InnerBorrowChecker::new();
|
||||||
let r1 = Region::new(9, 10);
|
let r1 = Region::new(9, 10);
|
||||||
let r2 = Region::new(0, 10);
|
let r2 = Region::new(0, 10);
|
||||||
assert!(r1.overlaps(r2));
|
assert!(r1.overlaps(r2));
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
bs.immut_borrow(r1).expect("can borrow r1");
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
assert!(bs.mut_borrow(r2).is_err(), "cant borrow r2");
|
||||||
|
bs.immut_borrow(r2).expect("can immut borrow r2");
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
let mut bs = InnerBorrowChecker::new();
|
||||||
let r1 = Region::new(2, 5);
|
let r1 = Region::new(2, 5);
|
||||||
let r2 = Region::new(0, 10);
|
let r2 = Region::new(0, 10);
|
||||||
assert!(r1.overlaps(r2));
|
assert!(r1.overlaps(r2));
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
bs.immut_borrow(r1).expect("can borrow r1");
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
assert!(bs.mut_borrow(r2).is_err(), "cant borrow r2");
|
||||||
|
bs.immut_borrow(r2).expect("can immut borrow r2");
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
let mut bs = InnerBorrowChecker::new();
|
||||||
let r1 = Region::new(2, 5);
|
let r1 = Region::new(2, 5);
|
||||||
@@ -154,10 +180,11 @@ mod test {
|
|||||||
let r3 = Region::new(15, 5);
|
let r3 = Region::new(15, 5);
|
||||||
let r4 = Region::new(0, 10);
|
let r4 = Region::new(0, 10);
|
||||||
assert!(r1.overlaps(r4));
|
assert!(r1.overlaps(r4));
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
bs.immut_borrow(r1).expect("can borrow r1");
|
||||||
bs.borrow(r2).expect("can borrow r2");
|
bs.immut_borrow(r2).expect("can borrow r2");
|
||||||
bs.borrow(r3).expect("can borrow r3");
|
bs.immut_borrow(r3).expect("can borrow r3");
|
||||||
assert!(bs.borrow(r4).is_err(), "cant borrow r4");
|
assert!(bs.mut_borrow(r4).is_err(), "cant mut borrow r4");
|
||||||
|
bs.immut_borrow(r4).expect("can immut borrow r4");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -167,11 +194,11 @@ mod test {
|
|||||||
let r2 = Region::new(10, 10);
|
let r2 = Region::new(10, 10);
|
||||||
assert!(!r1.overlaps(r2));
|
assert!(!r1.overlaps(r2));
|
||||||
assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
|
assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
|
||||||
let h1 = bs.borrow(r1).expect("can borrow r1");
|
let h1 = bs.mut_borrow(r1).expect("can borrow r1");
|
||||||
assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
|
assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
|
||||||
let h2 = bs.borrow(r2).expect("can borrow r2");
|
let h2 = bs.mut_borrow(r2).expect("can borrow r2");
|
||||||
|
|
||||||
assert!(bs.borrow(r2).is_err(), "can't borrow r2 twice");
|
assert!(bs.mut_borrow(r2).is_err(), "can't borrow r2 twice");
|
||||||
bs.unborrow(h2);
|
bs.unborrow(h2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bs.has_outstanding_borrows(),
|
bs.has_outstanding_borrows(),
|
||||||
@@ -182,7 +209,29 @@ mod test {
|
|||||||
assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
|
assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
|
||||||
|
|
||||||
let _h3 = bs
|
let _h3 = bs
|
||||||
.borrow(r2)
|
.mut_borrow(r2)
|
||||||
.expect("can borrow r2 again now that its been unborrowed");
|
.expect("can borrow r2 again now that its been unborrowed");
|
||||||
|
|
||||||
|
// Lets try again with immut:
|
||||||
|
|
||||||
|
let mut bs = InnerBorrowChecker::new();
|
||||||
|
let r1 = Region::new(0, 10);
|
||||||
|
let r2 = Region::new(10, 10);
|
||||||
|
assert!(!r1.overlaps(r2));
|
||||||
|
assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
|
||||||
|
let h1 = bs.immut_borrow(r1).expect("can borrow r1");
|
||||||
|
assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
|
||||||
|
let h2 = bs.immut_borrow(r2).expect("can borrow r2");
|
||||||
|
let h3 = bs.immut_borrow(r2).expect("can immut borrow r2 twice");
|
||||||
|
|
||||||
|
bs.unborrow(h2);
|
||||||
|
assert_eq!(
|
||||||
|
bs.has_outstanding_borrows(),
|
||||||
|
true,
|
||||||
|
"h1, h3 still outstanding"
|
||||||
|
);
|
||||||
|
bs.unborrow(h1);
|
||||||
|
bs.unborrow(h3);
|
||||||
|
assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,8 @@ pub unsafe trait GuestMemory {
|
|||||||
/// `GuestMemory::has_outstanding_borrows` is true for the duration of the
|
/// `GuestMemory::has_outstanding_borrows` is true for the duration of the
|
||||||
/// borrow, and that `GuestMemory::is_borrowed` of any overlapping region
|
/// borrow, and that `GuestMemory::is_borrowed` of any overlapping region
|
||||||
/// is false for the duration of the borrow.
|
/// is false for the duration of the borrow.
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError>;
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError>;
|
||||||
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError>;
|
||||||
/// Unborrow a previously borrowed region. As long as `GuestSlice` and
|
/// Unborrow a previously borrowed region. As long as `GuestSlice` and
|
||||||
/// `GuestStr` are implemented correctly, a `BorrowHandle` should only be
|
/// `GuestStr` are implemented correctly, a `BorrowHandle` should only be
|
||||||
/// unborrowed once.
|
/// unborrowed once.
|
||||||
@@ -189,8 +190,11 @@ unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a T {
|
|||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
T::is_borrowed(self, r)
|
T::is_borrowed(self, r)
|
||||||
}
|
}
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
T::borrow(self, r)
|
T::mut_borrow(self, r)
|
||||||
|
}
|
||||||
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::immut_borrow(self, r)
|
||||||
}
|
}
|
||||||
fn unborrow(&self, h: BorrowHandle) {
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
T::unborrow(self, h)
|
T::unborrow(self, h)
|
||||||
@@ -207,8 +211,11 @@ unsafe impl<'a, T: ?Sized + GuestMemory> GuestMemory for &'a mut T {
|
|||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
T::is_borrowed(self, r)
|
T::is_borrowed(self, r)
|
||||||
}
|
}
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
T::borrow(self, r)
|
T::mut_borrow(self, r)
|
||||||
|
}
|
||||||
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::immut_borrow(self, r)
|
||||||
}
|
}
|
||||||
fn unborrow(&self, h: BorrowHandle) {
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
T::unborrow(self, h)
|
T::unborrow(self, h)
|
||||||
@@ -225,8 +232,11 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Box<T> {
|
|||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
T::is_borrowed(self, r)
|
T::is_borrowed(self, r)
|
||||||
}
|
}
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
T::borrow(self, r)
|
T::mut_borrow(self, r)
|
||||||
|
}
|
||||||
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::immut_borrow(self, r)
|
||||||
}
|
}
|
||||||
fn unborrow(&self, h: BorrowHandle) {
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
T::unborrow(self, h)
|
T::unborrow(self, h)
|
||||||
@@ -243,8 +253,11 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Rc<T> {
|
|||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
T::is_borrowed(self, r)
|
T::is_borrowed(self, r)
|
||||||
}
|
}
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
T::borrow(self, r)
|
T::mut_borrow(self, r)
|
||||||
|
}
|
||||||
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::immut_borrow(self, r)
|
||||||
}
|
}
|
||||||
fn unborrow(&self, h: BorrowHandle) {
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
T::unborrow(self, h)
|
T::unborrow(self, h)
|
||||||
@@ -261,8 +274,11 @@ unsafe impl<T: ?Sized + GuestMemory> GuestMemory for Arc<T> {
|
|||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
T::is_borrowed(self, r)
|
T::is_borrowed(self, r)
|
||||||
}
|
}
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
T::borrow(self, r)
|
T::mut_borrow(self, r)
|
||||||
|
}
|
||||||
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
T::immut_borrow(self, r)
|
||||||
}
|
}
|
||||||
fn unborrow(&self, h: BorrowHandle) {
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
T::unborrow(self, h)
|
T::unborrow(self, h)
|
||||||
@@ -489,7 +505,7 @@ impl<'a, T> GuestPtr<'a, [T]> {
|
|||||||
self.mem
|
self.mem
|
||||||
.validate_size_align(self.pointer.0, T::guest_align(), len)? as *mut T;
|
.validate_size_align(self.pointer.0, T::guest_align(), len)? as *mut T;
|
||||||
|
|
||||||
let borrow = self.mem.borrow(Region {
|
let borrow = self.mem.mut_borrow(Region {
|
||||||
start: self.pointer.0,
|
start: self.pointer.0,
|
||||||
len,
|
len,
|
||||||
})?;
|
})?;
|
||||||
@@ -617,7 +633,7 @@ impl<'a> GuestPtr<'a, str> {
|
|||||||
.mem
|
.mem
|
||||||
.validate_size_align(self.pointer.0, 1, self.pointer.1)?;
|
.validate_size_align(self.pointer.0, 1, self.pointer.1)?;
|
||||||
|
|
||||||
let borrow = self.mem.borrow(Region {
|
let borrow = self.mem.mut_borrow(Region {
|
||||||
start: self.pointer.0,
|
start: self.pointer.0,
|
||||||
len: self.pointer.1,
|
len: self.pointer.1,
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wiggle-test"
|
name = "wiggle-test"
|
||||||
version = "0.19.0"
|
version = "0.21.0"
|
||||||
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
|
authors = ["Pat Hickey <phickey@fastly.com>", "Jakub Konka <kubkon@jakubkonka.com>", "Alex Crichton <alex@alexcrichton.com>"]
|
||||||
license = "Apache-2.0 WITH LLVM-exception"
|
license = "Apache-2.0 WITH LLVM-exception"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -14,6 +14,7 @@ publish = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
proptest = "0.10"
|
proptest = "0.10"
|
||||||
wiggle = { path = "..", features = ["tracing_log"] }
|
wiggle = { path = "..", features = ["tracing_log"] }
|
||||||
|
wiggle-borrow = { path = "../borrow" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use std::cell::UnsafeCell;
|
|||||||
use std::marker;
|
use std::marker;
|
||||||
use wiggle::{BorrowHandle, GuestMemory, Region};
|
use wiggle::{BorrowHandle, GuestMemory, Region};
|
||||||
|
|
||||||
mod borrow;
|
use wiggle_borrow::BorrowChecker;
|
||||||
use borrow::BorrowChecker;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MemAreas(Vec<MemArea>);
|
pub struct MemAreas(Vec<MemArea>);
|
||||||
@@ -129,8 +128,11 @@ unsafe impl GuestMemory for HostMemory {
|
|||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
self.bc.is_borrowed(r)
|
self.bc.is_borrowed(r)
|
||||||
}
|
}
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
self.bc.borrow(r)
|
self.bc.mut_borrow(r)
|
||||||
|
}
|
||||||
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
self.bc.immut_borrow(r)
|
||||||
}
|
}
|
||||||
fn unborrow(&self, h: BorrowHandle) {
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
self.bc.unborrow(h)
|
self.bc.unborrow(h)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ wasmtime = { path = "../../wasmtime", version = "0.21.0", default-features = fal
|
|||||||
wasmtime-wiggle-macro = { path = "./macro", version = "0.21.0" }
|
wasmtime-wiggle-macro = { path = "./macro", version = "0.21.0" }
|
||||||
witx = { path = "../../wasi-common/WASI/tools/witx", version = "0.8.7", optional = true }
|
witx = { path = "../../wasi-common/WASI/tools/witx", version = "0.8.7", optional = true }
|
||||||
wiggle = { path = "..", version = "0.21.0" }
|
wiggle = { path = "..", version = "0.21.0" }
|
||||||
|
wiggle-borrow = { path = "../borrow", version = "0.21.0" }
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use wiggle::{BorrowHandle, GuestError, Region};
|
|
||||||
|
|
||||||
pub struct BorrowChecker {
|
|
||||||
bc: RefCell<InnerBorrowChecker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BorrowChecker {
|
|
||||||
/// A `BorrowChecker` manages run-time validation of borrows from a `GuestMemory`. It keeps
|
|
||||||
/// track of regions of guest memory which are possible to alias with Rust references (via the
|
|
||||||
/// `GuestSlice` and `GuestStr` structs, which implement `std::ops::Deref` and
|
|
||||||
/// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` and `GuestPtr::write` do not
|
|
||||||
/// access memory with an outstanding borrow.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
BorrowChecker {
|
|
||||||
bc: RefCell::new(InnerBorrowChecker::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Indicates whether any outstanding borrows are known to the `BorrowChecker`. This function
|
|
||||||
/// must be `false` in order for it to be safe to recursively call into a WebAssembly module,
|
|
||||||
/// or to manipulate the WebAssembly memory by any other means.
|
|
||||||
pub fn has_outstanding_borrows(&self) -> bool {
|
|
||||||
self.bc.borrow().has_outstanding_borrows()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
|
||||||
self.bc.borrow_mut().borrow(r)
|
|
||||||
}
|
|
||||||
pub(crate) fn unborrow(&self, h: BorrowHandle) {
|
|
||||||
self.bc.borrow_mut().unborrow(h)
|
|
||||||
}
|
|
||||||
pub(crate) fn is_borrowed(&self, r: Region) -> bool {
|
|
||||||
self.bc.borrow().is_borrowed(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// This is a pretty naive way to account for borrows. This datastructure
|
|
||||||
/// could be made a lot more efficient with some effort.
|
|
||||||
struct InnerBorrowChecker {
|
|
||||||
/// Map from handle to region borrowed. A HashMap is probably not ideal
|
|
||||||
/// for this but it works. It would be more efficient if we could
|
|
||||||
/// check `is_borrowed` without an O(n) iteration, by organizing borrows
|
|
||||||
/// by an ordering of Region.
|
|
||||||
borrows: HashMap<BorrowHandle, Region>,
|
|
||||||
/// Handle to give out for the next borrow. This is the bare minimum of
|
|
||||||
/// bookkeeping of free handles, and in a pathological case we could run
|
|
||||||
/// out, hence [`GuestError::BorrowCheckerOutOfHandles`]
|
|
||||||
next_handle: BorrowHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerBorrowChecker {
|
|
||||||
fn new() -> Self {
|
|
||||||
InnerBorrowChecker {
|
|
||||||
borrows: HashMap::new(),
|
|
||||||
next_handle: BorrowHandle(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_outstanding_borrows(&self) -> bool {
|
|
||||||
!self.borrows.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_borrowed(&self, r: Region) -> bool {
|
|
||||||
!self.borrows.values().all(|b| !b.overlaps(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_handle(&mut self) -> Result<BorrowHandle, GuestError> {
|
|
||||||
// Reset handles to 0 if all handles have been returned.
|
|
||||||
if self.borrows.is_empty() {
|
|
||||||
self.next_handle = BorrowHandle(0);
|
|
||||||
}
|
|
||||||
let h = self.next_handle;
|
|
||||||
// Get the next handle. Since we don't recycle handles until all of
|
|
||||||
// them have been returned, there is a pathological case where a user
|
|
||||||
// may make a Very Large (usize::MAX) number of valid borrows and
|
|
||||||
// unborrows while always keeping at least one borrow outstanding, and
|
|
||||||
// we will run out of borrow handles.
|
|
||||||
self.next_handle = BorrowHandle(
|
|
||||||
h.0.checked_add(1)
|
|
||||||
.ok_or_else(|| GuestError::BorrowCheckerOutOfHandles)?,
|
|
||||||
);
|
|
||||||
Ok(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn borrow(&mut self, r: Region) -> Result<BorrowHandle, GuestError> {
|
|
||||||
if self.is_borrowed(r) {
|
|
||||||
return Err(GuestError::PtrBorrowed(r));
|
|
||||||
}
|
|
||||||
let h = self.new_handle()?;
|
|
||||||
self.borrows.insert(h, r);
|
|
||||||
Ok(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unborrow(&mut self, h: BorrowHandle) {
|
|
||||||
let _ = self.borrows.remove(&h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn nonoverlapping() {
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(0, 10);
|
|
||||||
let r2 = Region::new(10, 10);
|
|
||||||
assert!(!r1.overlaps(r2));
|
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
|
||||||
bs.borrow(r2).expect("can borrow r2");
|
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(10, 10);
|
|
||||||
let r2 = Region::new(0, 10);
|
|
||||||
assert!(!r1.overlaps(r2));
|
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
|
||||||
bs.borrow(r2).expect("can borrow r2");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn overlapping() {
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(0, 10);
|
|
||||||
let r2 = Region::new(9, 10);
|
|
||||||
assert!(r1.overlaps(r2));
|
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(0, 10);
|
|
||||||
let r2 = Region::new(2, 5);
|
|
||||||
assert!(r1.overlaps(r2));
|
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(9, 10);
|
|
||||||
let r2 = Region::new(0, 10);
|
|
||||||
assert!(r1.overlaps(r2));
|
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(2, 5);
|
|
||||||
let r2 = Region::new(0, 10);
|
|
||||||
assert!(r1.overlaps(r2));
|
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
|
||||||
assert!(bs.borrow(r2).is_err(), "cant borrow r2");
|
|
||||||
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(2, 5);
|
|
||||||
let r2 = Region::new(10, 5);
|
|
||||||
let r3 = Region::new(15, 5);
|
|
||||||
let r4 = Region::new(0, 10);
|
|
||||||
assert!(r1.overlaps(r4));
|
|
||||||
bs.borrow(r1).expect("can borrow r1");
|
|
||||||
bs.borrow(r2).expect("can borrow r2");
|
|
||||||
bs.borrow(r3).expect("can borrow r3");
|
|
||||||
assert!(bs.borrow(r4).is_err(), "cant borrow r4");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unborrowing() {
|
|
||||||
let mut bs = InnerBorrowChecker::new();
|
|
||||||
let r1 = Region::new(0, 10);
|
|
||||||
let r2 = Region::new(10, 10);
|
|
||||||
assert!(!r1.overlaps(r2));
|
|
||||||
assert_eq!(bs.has_outstanding_borrows(), false, "start with no borrows");
|
|
||||||
let h1 = bs.borrow(r1).expect("can borrow r1");
|
|
||||||
assert_eq!(bs.has_outstanding_borrows(), true, "h1 is outstanding");
|
|
||||||
let h2 = bs.borrow(r2).expect("can borrow r2");
|
|
||||||
|
|
||||||
assert!(bs.borrow(r2).is_err(), "can't borrow r2 twice");
|
|
||||||
bs.unborrow(h2);
|
|
||||||
assert_eq!(
|
|
||||||
bs.has_outstanding_borrows(),
|
|
||||||
true,
|
|
||||||
"h1 is still outstanding"
|
|
||||||
);
|
|
||||||
bs.unborrow(h1);
|
|
||||||
assert_eq!(bs.has_outstanding_borrows(), false, "no remaining borrows");
|
|
||||||
|
|
||||||
let _h3 = bs
|
|
||||||
.borrow(r2)
|
|
||||||
.expect("can borrow r2 again now that its been unborrowed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
pub use wasmtime_wiggle_macro::*;
|
pub use wasmtime_wiggle_macro::*;
|
||||||
pub use wiggle::*;
|
pub use wiggle::*;
|
||||||
|
|
||||||
mod borrow;
|
use wiggle_borrow::BorrowChecker;
|
||||||
|
|
||||||
use borrow::BorrowChecker;
|
|
||||||
|
|
||||||
/// Lightweight `wasmtime::Memory` wrapper so we can implement the
|
/// Lightweight `wasmtime::Memory` wrapper so we can implement the
|
||||||
/// `wiggle::GuestMemory` trait on it.
|
/// `wiggle::GuestMemory` trait on it.
|
||||||
@@ -40,8 +38,11 @@ unsafe impl GuestMemory for WasmtimeGuestMemory {
|
|||||||
fn is_borrowed(&self, r: Region) -> bool {
|
fn is_borrowed(&self, r: Region) -> bool {
|
||||||
self.bc.is_borrowed(r)
|
self.bc.is_borrowed(r)
|
||||||
}
|
}
|
||||||
fn borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
fn immut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
self.bc.borrow(r)
|
self.bc.immut_borrow(r)
|
||||||
|
}
|
||||||
|
fn mut_borrow(&self, r: Region) -> Result<BorrowHandle, GuestError> {
|
||||||
|
self.bc.mut_borrow(r)
|
||||||
}
|
}
|
||||||
fn unborrow(&self, h: BorrowHandle) {
|
fn unborrow(&self, h: BorrowHandle) {
|
||||||
self.bc.unborrow(h)
|
self.bc.unborrow(h)
|
||||||
|
|||||||
Reference in New Issue
Block a user