Skip to content

Commit 3cfb798

Browse files
committed
add kani proofs for Step impl of VirtAddr
The last two commits introduced some shortcuts that are less obviously correct. Using unsafe is inherently risky, so we better be sure that the code is correct. The proof harnesses added in this patch can be used to proof that our implementation is correct for all inputs.
1 parent bdf9ee0 commit 3cfb798

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

src/addr.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,3 +782,159 @@ mod tests {
782782
assert_eq!(VirtAddr::from_ptr(slice), VirtAddr::from_ptr(&slice[0]));
783783
}
784784
}
785+
786+
#[cfg(kani)]
787+
mod proofs {
788+
use super::*;
789+
790+
// The next two proof harnesses prove the correctness of the `forward`
791+
// implementation of VirtAddr.
792+
793+
// This harness proves that our implementation can correctly take 0 or 1
794+
// step starting from any address.
795+
#[kani::proof]
796+
fn forward_base_case() {
797+
let start_raw: u64 = kani::any();
798+
let Ok(start) = VirtAddr::try_new(start_raw) else {
799+
return;
800+
};
801+
802+
// Adding 0 to any address should always yield the same address.
803+
let same = Step::forward(start, 0);
804+
assert!(start == same);
805+
806+
// Verify that we can add 1 to any address.
807+
let expected = match start_raw {
808+
// Adding 1 to addresses in this range don't require gap jumps, so
809+
// we can just add 1.
810+
0x0000_0000_0000_0000..=0x0000_7fff_ffff_fffe => Some(start_raw + 1),
811+
// Adding 1 to this address jumps the gap.
812+
0x0000_7fff_ffff_ffff => Some(0xffff_8000_0000_0000),
813+
// The range of non-canonical addresses.
814+
0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff => unreachable!(),
815+
// Adding 1 to addresses in this range don't require gap jumps, so
816+
// we can just add 1.
817+
0xffff_8000_0000_0000..=0xffff_ffff_ffff_fffe => Some(start_raw + 1),
818+
// Adding 1 to this address causes an overflow.
819+
0xffff_ffff_ffff_ffff => None,
820+
};
821+
let next = Step::forward_checked(start, 1);
822+
assert!(next.map(VirtAddr::as_u64) == expected);
823+
}
824+
825+
// This harness proves that the result of taking two small steps is the
826+
// same as taking one combined large step.
827+
#[kani::proof]
828+
fn forward_induction_step() {
829+
let start_raw: u64 = kani::any();
830+
let Ok(start) = VirtAddr::try_new(start_raw) else {
831+
return;
832+
};
833+
834+
let count1: usize = kani::any();
835+
let count2: usize = kani::any();
836+
// If we can take two small steps...
837+
let Some(next1) = Step::forward_checked(start, count1) else {
838+
return;
839+
};
840+
let Some(next2) = Step::forward_checked(next1, count2) else {
841+
return;
842+
};
843+
844+
// ...then we can also take one combined large step.
845+
let count_both = count1 + count2;
846+
let next_both = Step::forward(start, count_both);
847+
assert!(next2 == next_both);
848+
}
849+
850+
// The next two proof harnesses prove the correctness of the `backward`
851+
// implementation of VirtAddr using the `forward` implementation which
852+
// we've already proven to be correct.
853+
// They do this by proving the symmetry between those two functions.
854+
855+
// This harness proves the correctness of the implementation of `backward`
856+
// for all inputs for which `forward_checked` succeeds.
857+
#[kani::proof]
858+
fn forward_implies_backward() {
859+
let start_raw: u64 = kani::any();
860+
let Ok(start) = VirtAddr::try_new(start_raw) else {
861+
return;
862+
};
863+
let count: usize = kani::any();
864+
865+
// If `forward_checked` succeeds...
866+
let Some(end) = Step::forward_checked(start, count) else {
867+
return;
868+
};
869+
870+
// ...then `backward` succeeds as well.
871+
let start2 = Step::backward(end, count);
872+
assert!(start == start2);
873+
}
874+
875+
// This harness proves that for all inputs for which `backward_checked`
876+
// succeeds, `forward` succeeds as well.
877+
#[kani::proof]
878+
fn backward_implies_forward() {
879+
let end_raw: u64 = kani::any();
880+
let Ok(end) = VirtAddr::try_new(end_raw) else {
881+
return;
882+
};
883+
let count: usize = kani::any();
884+
885+
// If `backward_checked` succeeds...
886+
let Some(start) = Step::backward_checked(end, count) else {
887+
return;
888+
};
889+
890+
// ...then `forward` succeeds as well.
891+
let end2 = Step::forward(start, count);
892+
assert!(end == end2);
893+
}
894+
895+
// The next two proof harnesses prove the correctness of the
896+
// `steps_between` implementation of VirtAddr using the `forward`
897+
// implementation which we've already proven to be correct.
898+
// They do this by proving the symmetry between those two functions.
899+
900+
// This harness proves the correctness of the implementation of
901+
// `steps_between` for all inputs for which `forward_checked` succeeds.
902+
#[kani::proof]
903+
fn forward_implies_steps_between() {
904+
let start: u64 = kani::any();
905+
let Ok(start) = VirtAddr::try_new(start) else {
906+
return;
907+
};
908+
let count: usize = kani::any();
909+
910+
// If `forward_checked` succeeds...
911+
let Some(end) = Step::forward_checked(start, count) else {
912+
return;
913+
};
914+
915+
// ...then `steps_between` succeeds as well.
916+
assert!(Step::steps_between(&start, &end) == Some(count));
917+
}
918+
919+
// This harness proves that for all inputs for which `steps_between`
920+
// succeeds, `forward` succeeds as well.
921+
#[kani::proof]
922+
fn steps_between_implies_forward() {
923+
let start: u64 = kani::any();
924+
let Ok(start) = VirtAddr::try_new(start) else {
925+
return;
926+
};
927+
let end: u64 = kani::any();
928+
let Ok(end) = VirtAddr::try_new(end) else {
929+
return;
930+
};
931+
932+
// If `steps_between` succeeds...
933+
let Some(count) = Step::steps_between(&start, &end) else {
934+
return;
935+
};
936+
937+
// ...then `forward` succeeds as well.
938+
assert!(Step::forward(start, count) == end);
939+
}
940+
}

0 commit comments

Comments
 (0)