Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function Contracts: Interior Mutability Tests #3351

Merged
merged 38 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ed8fee1
add tests
Jul 17, 2024
0bbd78b
fix tests
Jul 17, 2024
35a9320
preserves memory
Jul 17, 2024
76a9c54
fmt
Jul 17, 2024
7ca72aa
OnceCell
Jul 17, 2024
4ce98ad
documentation
Jul 18, 2024
f97140e
stub
Jul 18, 2024
7b96733
more comments
Jul 18, 2024
fae9ff2
fix test
Jul 19, 2024
e2df787
Merge branch 'main' into int-mut-tests
pi314mm Jul 19, 2024
600a0f1
rfc update
Jul 19, 2024
7a0a554
unsafecell and cell can be reached through public api
Jul 22, 2024
759b6dc
Merge branch 'main' into int-mut-tests
pi314mm Jul 22, 2024
e704958
triple slash for comments
Jul 22, 2024
382e8fe
more tests
Jul 23, 2024
39cdc44
fmt
Jul 23, 2024
03f3d6d
fix tests
Jul 24, 2024
8925ffb
comments
Jul 24, 2024
519fe5c
comments
Jul 24, 2024
8cf4f6c
Merge branch 'main' into int-mut-tests
pi314mm Jul 24, 2024
2126d79
add explanation in docs
Jul 25, 2024
3fb40fc
delete
Jul 26, 2024
5201fc1
Merge branch 'main' into int-mut-tests
pi314mm Jul 26, 2024
0c7bda5
Merge branch 'main' into int-mut-tests
pi314mm Jul 26, 2024
13baa5b
Update tests/expected/function-contract/interior-mutability/api/unsaf…
feliperodri Jul 30, 2024
5feba8c
Update tests/expected/function-contract/interior-mutability/api/unsaf…
feliperodri Jul 30, 2024
7f84e89
Update tests/expected/function-contract/interior-mutability/api/cell_…
feliperodri Jul 30, 2024
882477c
Update tests/expected/function-contract/interior-mutability/api/cell_…
feliperodri Jul 30, 2024
82c3e6f
Update tests/expected/function-contract/interior-mutability/api/cell.rs
feliperodri Jul 30, 2024
1e12172
Update tests/expected/function-contract/interior-mutability/api/cell.rs
feliperodri Jul 30, 2024
42fd597
Update tests/expected/function-contract/interior-mutability/whole-str…
feliperodri Jul 30, 2024
60b0d8a
Update tests/expected/function-contract/interior-mutability/whole-str…
feliperodri Jul 30, 2024
dbf938f
Update tests/expected/function-contract/interior-mutability/whole-str…
feliperodri Jul 30, 2024
065b554
Update tests/expected/function-contract/interior-mutability/whole-str…
feliperodri Jul 30, 2024
19b4cbf
Update tests/expected/function-contract/interior-mutability/whole-str…
feliperodri Jul 30, 2024
aaf64ed
Update tests/expected/function-contract/interior-mutability/whole-str…
feliperodri Jul 30, 2024
172839f
Update tests/expected/function-contract/interior-mutability/whole-str…
feliperodri Jul 30, 2024
ba95719
Merge branch 'main' into int-mut-tests
feliperodri Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions rfc/src/rfcs/0009-function-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,22 @@ structures however are ubiquitous and users can avoid the unsoundness with
relative confidence by overprovisioning (generating inputs that are several
times larger than what they expect the function will touch).

### Interior Mutability
pi314mm marked this conversation as resolved.
Show resolved Hide resolved

In Rust, if a struct is immutable, all fields within it are immutable. However, we can still mutate some of these fields via an interface that allows us to do so: https://doc.rust-lang.org/std/cell/.

In Goto, there is no distinction between immutable and mutable fields, thus breaking this encapsulation that Rust provides. We can leverage this encapsulation breaking mechanic to write verified proofs of Rust interior mutability, as long as we preserve the overall properties that the encapsulation would have given us.

It is possible to represent the abstraction breaking mechanics within unsafe Rust for `UnsafeCell` and `Cell` as explained by the memory layout properies ensured during compilation: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html#memory-layout. While we cannot access private fields in Rust, we can interpret the fields as an arbitrary pointer to the type of the field via a transmute if we know the data layout of the field. It is thus possible to safely verify code for `UnsafeCell` and `Cell` within Kani by breaking the encapsulation with unsafe rust.

`OnceCell` follows the pattern of having only one field within the struct, thus access to this struct via an unsafe transmute is predictable within the Kani compiler, although this behavior is not explicitly explained in Rust documentation as it is for `UnsafeCell` and `Cell`. We can verify code for `OnceCell` within Kani, but we need to be more careful in assessing whether this is UB or not.

This is much harder for `RefCell` as it is UB to rely on the particular order of fields in a struct, and there might be inconsistent padding through compilation. It should be possible to attach a compilation tool within Kani to expose these private fields after they have been compiled to Goto, but this still creates risk that these private fields could be modified or accessed in a way that is UB to do so from the Rust standpoint.

We focus on single threaded computation, so we avoid verifying `RwLock`, `Mutex` and similar other constructs for now.

It is important to further investigate this discrepency between encapulsation at the Rust level and Goto level. When is it safe to break encapsulation and how should we break encapsulation in a way that preserves encapsulation properties? Can we represent and verify Rust encapsulation properties at the Goto level? Is there a modification to the `modifies` and `assigns` function contracts that would allow us to demonstrate these encapsulation properties? These questions are all important if we wish to perserve full abstraction correctness in the compilation between Rust and Goto.


## Open questions

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
assertion\
- Status: SUCCESS\
- Description: "|_| im.x.get() < 101"\
in function modify

VERIFICATION:- SUCCESSFUL
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

/// Mutating Cell via as_ptr in contracts
feliperodri marked this conversation as resolved.
Show resolved Hide resolved
use std::cell::Cell;

/// This struct is contains Cell which can be mutated
feliperodri marked this conversation as resolved.
Show resolved Hide resolved
struct InteriorMutability {
x: Cell<u32>,
}

/// contracts need to access im.x internal data through the api im.x.as_ptr
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
#[kani::requires(im.x.get() < 100)]
#[kani::modifies(im.x.as_ptr())]
#[kani::ensures(|_| im.x.get() < 101)]
///im is an immutable reference with interior mutability
fn modify(im: &InteriorMutability) {
/// valid rust methodology for getting and setting value without breaking encapsulation
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
im.x.set(im.x.get() + 1)
}

#[kani::proof_for_contract(modify)]
fn harness_for_modify() {
let im: InteriorMutability = InteriorMutability { x: Cell::new(kani::any()) };
modify(&im)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
assertion\
- Status: SUCCESS\
- Description: "|_| unsafe{ *im.x.get() } < 101"\
in function modify

VERIFICATION:- SUCCESSFUL
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

use std::cell::UnsafeCell;

/// This struct is contains UnsafeCell which can be mutated
feliperodri marked this conversation as resolved.
Show resolved Hide resolved
struct InteriorMutability {
x: UnsafeCell<u32>,
}

/// contracts need to access im.x internal data through im.x.get() with an unsafe raw pointer dereference
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
#[kani::requires(unsafe{*im.x.get()} < 100)]
#[kani::modifies(im.x.get())]
#[kani::ensures(|_| unsafe{*im.x.get()} < 101)]
///im is an immutable reference with interior mutability
feliperodri marked this conversation as resolved.
Show resolved Hide resolved
fn modify(im: &InteriorMutability) {
unsafe { *im.x.get() += 1 }
}

#[kani::proof_for_contract(modify)]
fn harness_for_modify() {
let im: InteriorMutability = InteriorMutability { x: UnsafeCell::new(kani::any()) };
modify(&im)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
assertion\
- Status: SUCCESS\
- Description: "|_| *unsafe{ im.x.expose() } < 101"\
in function modify

VERIFICATION:- SUCCESSFUL
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

/// The objective of this test is to check the modification of a Cell used as interior mutability in an immutable struct
pi314mm marked this conversation as resolved.
Show resolved Hide resolved

/// ---------------------------------------------------
/// Abstraction Breaking Functionality
/// ---------------------------------------------------
use std::cell::Cell;
use std::mem::transmute;

/// This exposes the underlying representation so that it can be added into a modifies clause within kani
trait Exposeable<T: ?Sized> {
unsafe fn expose(&self) -> &T;
}

/// This unsafe manipulation is valid due to Cell having the same underlying data layout as its internal T as explained here: https://doc.rust-lang.org/stable/std/cell/struct.Cell.html#memory-layout
impl<T: ?Sized> Exposeable<T> for Cell<T> {
unsafe fn expose(&self) -> &T {
transmute(self)
}
}

/// ---------------------------------------------------
/// Test Case
/// ---------------------------------------------------

/// This struct is contains Cell which can be mutated
struct InteriorMutability {
x: Cell<u32>,
}

/// contracts need to access im.x internal data through the unsafe function im.x.expose()
#[kani::requires(*unsafe{im.x.expose()} < 100)]
#[kani::modifies(im.x.expose())]
#[kani::ensures(|_| *unsafe{im.x.expose()} < 101)]
///im is an immutable reference with interior mutability
fn modify(im: &InteriorMutability) {
/// valid rust methodology for getting and setting value without breaking encapsulation
im.x.set(im.x.get() + 1)
}

#[kani::proof_for_contract(modify)]
fn harness_for_modify() {
let im: InteriorMutability = InteriorMutability { x: Cell::new(kani::any()) };
modify(&im)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
assertion\
- Status: SUCCESS\
- Description: "|_| old(*unsafe{ im.x.expose() } + *unsafe{ im.x.expose() }) ==\
*unsafe{ im.x.expose() }"\

assertion\
- Status: SUCCESS\
- Description: "|_|
old(*unsafe{ im.x.expose() } + *unsafe{ im.x.expose() } +\
*unsafe{ im.x.expose() } + *unsafe{ im.x.expose() }) ==\
*unsafe{ im.x.expose() }"\

VERIFICATION:- SUCCESSFUL
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

/// The objective of this test is to show that the contracts for double can be replaced as a stub within the contracts for quadruple.
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
/// This shows that we can generate kani::any() for Cell safely by breaking encapsulation

/// ---------------------------------------------------
/// Abstraction Breaking Functionality
/// ---------------------------------------------------
use std::cell::Cell;
use std::mem::transmute;

/// This exposes the underlying representation so that it can be added into a modifies clause within kani
trait Exposeable<T: ?Sized> {
unsafe fn expose(&self) -> &T;
}

/// This unsafe manipulation is valid due to Cell having the same underlying data layout as its internal T as explained here: https://doc.rust-lang.org/stable/std/cell/struct.Cell.html#memory-layout
impl<T: ?Sized> Exposeable<T> for Cell<T> {
unsafe fn expose(&self) -> &T {
transmute(self)
}
}

/// ---------------------------------------------------
/// Test Case
/// ---------------------------------------------------

/// This struct is contains Cell which can be mutated
struct InteriorMutability {
x: Cell<u32>,
}

#[kani::ensures(|_| old(*unsafe{im.x.expose()} + *unsafe{im.x.expose()}) == *unsafe{im.x.expose()})]
#[kani::requires(*unsafe{im.x.expose()} < 100)]
#[kani::modifies(im.x.expose())]
fn double(im: &InteriorMutability) {
im.x.set(im.x.get() + im.x.get())
}

#[kani::proof_for_contract(double)]
fn double_harness() {
let im: InteriorMutability = InteriorMutability { x: Cell::new(kani::any()) };
double(&im);
}

#[kani::ensures(|_| old(*unsafe{im.x.expose()} + *unsafe{im.x.expose()} + *unsafe{im.x.expose()} + *unsafe{im.x.expose()}) == *unsafe{im.x.expose()})]
#[kani::requires(*unsafe{im.x.expose()} < 50)]
#[kani::modifies(im.x.expose())]
fn quadruple(im: &InteriorMutability) {
double(im);
double(im)
}

#[kani::proof_for_contract(quadruple)]
#[kani::stub_verified(double)]
fn quadruple_harness() {
let im: InteriorMutability = InteriorMutability { x: Cell::new(kani::any()) };
quadruple(&im);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
assertion\
- Status: SUCCESS\
- Description: "|_| unsafe{ im.x.expose() }.is_some()"\
in function modify

VERIFICATION:- SUCCESSFUL
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

/// The objective of this test is to check the modification of an OnceCell used as interior mutability in an immutable struct
pi314mm marked this conversation as resolved.
Show resolved Hide resolved

/// ---------------------------------------------------
/// Abstraction Breaking Functionality
/// ---------------------------------------------------
use std::cell::OnceCell;
use std::mem::transmute;

/// This exposes the underlying representation so that it can be added into a modifies clause within kani
trait Exposeable<T: ?Sized> {
unsafe fn expose(&self) -> &T;
}

/// While this is not explicitly labeled as safe in the Rust documentation, it works due to OnceCell having a single field in its struct definition
impl<T> Exposeable<Option<T>> for OnceCell<T> {
unsafe fn expose(&self) -> &Option<T> {
transmute(self)
}
}

/// ---------------------------------------------------
/// Test Case
/// ---------------------------------------------------

/// This struct is contains OnceCell which can be mutated
struct InteriorMutability {
x: OnceCell<u32>,
}

/// contracts need to access im.x internal data through the unsafe function im.x.expose()
#[kani::requires(unsafe{im.x.expose()}.is_none())]
#[kani::modifies(im.x.expose())]
#[kani::ensures(|_| unsafe{im.x.expose()}.is_some())]
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
fn modify(im: &InteriorMutability) {
/// method for setting value in OnceCell without breaking encapsulation
im.x.set(5).expect("")
}

#[kani::proof_for_contract(modify)]
fn harness_for_modify() {
let im: InteriorMutability = InteriorMutability { x: OnceCell::new() };
modify(&im)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
assertion\
- Status: SUCCESS\
- Description: "|_| *unsafe{ im.x.expose() } < 101"\
in function modify

VERIFICATION:- SUCCESSFUL
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Zfunction-contracts

/// The objective of this test is to check the modification of an UnsafeCell used as interior mutability in an immutable struct
pi314mm marked this conversation as resolved.
Show resolved Hide resolved

/// ---------------------------------------------------
/// Abstraction Breaking Functionality
/// ---------------------------------------------------
use std::cell::UnsafeCell;
use std::mem::transmute;

/// This exposes the underlying representation so that it can be added into a modifies clause within kani
trait Exposeable<T: ?Sized> {
unsafe fn expose(&self) -> &T;
}

/// This unsafe manipulation is valid due to UnsafeCell having the same underlying data layout as its internal T as explained here: https://doc.rust-lang.org/stable/std/cell/struct.UnsafeCell.html#memory-layout
impl<T: ?Sized> Exposeable<T> for UnsafeCell<T> {
unsafe fn expose(&self) -> &T {
transmute(self)
}
}

/// ---------------------------------------------------
/// Test Case
/// ---------------------------------------------------

/// This struct is contains UnsafeCell which can be mutated
struct InteriorMutability {
x: UnsafeCell<u32>,
}

/// contracts need to access im.x internal data through the unsafe function im.x.expose()
#[kani::requires(*unsafe{im.x.expose()} < 100)]
#[kani::modifies(im.x.expose())]
#[kani::ensures(|_| *unsafe{im.x.expose()} < 101)]
///im is an immutable reference with interior mutability
fn modify(im: &InteriorMutability) {
unsafe { *im.x.get() += 1 }
}

#[kani::proof_for_contract(modify)]
fn harness_for_modify() {
let im: InteriorMutability = InteriorMutability { x: UnsafeCell::new(kani::any()) };
modify(&im)
}
Loading