use vstd::prelude::*;
use crate::spec::cbpf::*;
use super::builder::Builder;
verus! {
impl Instr {
pub(super) open spec fn simple(self) -> bool {
match self {
Instr::LdAbs(_) => true,
Instr::Alu(_, Src::K(_)) => true,
Instr::Ja(_) => true,
Instr::Jmp { src: Src::K(_), .. } => true,
Instr::Ret(RetVal::K(_)) => true,
_ => false,
}
}
pub(super) proof fn lemma_advances(self, data: &[u8], st: MachineState)
ensures self.step(data, st) matches Ok(next) ==> next.pc > st.pc
{
}
pub(super) proof fn lemma_simple(self, data: &[u8], st: MachineState, other: MachineState)
requires self.simple(), st.a == other.a
ensures
self.step(data, st) matches Ok(next) ==> {
&&& self.step(data, other) matches Ok(then)
&&& next.a == then.a
&&& next.pc - st.pc == then.pc - other.pc
&&& next.x == st.x
&&& next.mem == st.mem
},
self.step(data, st) matches Err(outcome)
==> self.step(data, other) == Err::<MachineState, Outcome>(outcome),
{
}
}
impl Builder {
pub(super) open spec fn extends(rev: Seq<Instr>, ext: Seq<Instr>) -> bool {
&&& rev.len() <= ext.len()
&&& forall |i: int| #![trigger ext[i]] 0 <= i < rev.len() ==> ext[i] == rev[i]
}
pub(super) open spec fn word(data: &[u8], k: u32) -> u32 {
(data@[k as int] as u32)
| ((data@[k + 1] as u32) << 8)
| ((data@[k + 2] as u32) << 16)
| ((data@[k + 3] as u32) << 24)
}
pub(super) open spec fn run(rev: Seq<Instr>, data: &[u8], label: nat, a: u32) -> Outcome
decreases label
via Self::run_decreases
{
if label == 0 || label > rev.len() {
Outcome::RuntimeError
} else {
match rev[label - 1].step(data, Self::state(label, a)) {
Ok(next) =>
if next.pc > 2 * label {
Outcome::RuntimeError
} else {
Self::run(rev, data, (2 * label - next.pc) as nat, next.a)
},
Err(outcome) => outcome,
}
}
}
pub(super) open spec fn state(label: nat, a: u32) -> MachineState {
MachineState { pc: label, a, x: 0, mem: Seq::empty() }
}
#[via_fn]
proof fn run_decreases(rev: Seq<Instr>, data: &[u8], label: nat, a: u32) {
if label != 0 && label <= rev.len() {
rev[label - 1].lemma_advances(data, Self::state(label, a));
}
}
pub(super) open spec fn goes_to(rev: Seq<Instr>, data: &[u8], from: nat, a: u32, to: nat, b: u32) -> bool {
forall |ext: Seq<Instr>| Self::extends(rev, ext)
==> #[trigger] Self::run(ext, data, from, a) == Self::run(ext, data, to, b)
}
pub(super) open spec fn goes_to_all(rev: Seq<Instr>, data: &[u8], from: nat, to: nat, b: u32) -> bool {
forall |a: u32| #[trigger] Self::goes_to(rev, data, from, a, to, b)
}
pub(super) open spec fn lands(rev: Seq<Instr>, data: &[u8], from: nat, a: u32, to: nat) -> bool {
exists |b: u32| Self::goes_to(rev, data, from, a, to, b)
}
pub(super) open spec fn returns(rev: Seq<Instr>, data: &[u8], from: nat, a: u32, ret: u32) -> bool {
forall |ext: Seq<Instr>| Self::extends(rev, ext)
==> #[trigger] Self::run(ext, data, from, a) == Outcome::Return(ret)
}
pub(super) open spec fn returns_all(rev: Seq<Instr>, data: &[u8], from: nat, ret: u32) -> bool {
forall |a: u32| #[trigger] Self::returns(rev, data, from, a, ret)
}
pub(super) open spec fn instr_ok(instr: Instr, i: nat) -> bool {
&&& instr.simple()
&&& match instr {
Instr::LdAbs(k) => k % 4 == 0,
Instr::Alu(AluOp::Div, Src::K(k)) => k != 0,
Instr::Alu(AluOp::Lsh, Src::K(k)) => k < 32,
Instr::Alu(AluOp::Rsh, Src::K(k)) => k < 32,
Instr::Ja(k) => k < i,
Instr::Jmp { jt, jf, .. } => jt < i && jf < i,
_ => true,
}
}
pub(super) open spec fn wf(self) -> bool {
forall |i: int| #![trigger self.rev@[i]]
0 <= i < self.rev@.len() ==> Self::instr_ok(self.rev@[i], i as nat)
}
pub(super) proof fn lemma_wf(self, prog: Program)
requires
self.wf(),
0 < self.rev@.len(),
self.rev@[0] is Ret,
prog.instrs@ == self.rev@.reverse(),
ensures
prog.wf(),
prog.instrs@.reverse() == self.rev@,
forall |i: int| 0 <= i < prog.instrs@.len() ==> #[trigger] prog.instrs@[i].simple(),
{
let len = self.rev@.len();
assert forall |pc: int| #![trigger prog.instrs@[pc]] 0 <= pc < len implies
prog.instrs@[pc].wf(pc as nat, len) && prog.instrs@[pc].simple() by {
assert(Self::instr_ok(self.rev@[len - 1 - pc], (len - 1 - pc) as nat));
}
assert(prog.instrs@.reverse() =~= self.rev@);
}
pub(super) proof fn lemma_mono(rev: Seq<Instr>, ext: Seq<Instr>, data: &[u8], from: nat, a: u32, to: nat, b: u32)
requires Self::extends(rev, ext)
ensures
Self::goes_to(rev, data, from, a, to, b) ==> Self::goes_to(ext, data, from, a, to, b),
Self::lands(rev, data, from, a, to) ==> Self::lands(ext, data, from, a, to),
Self::returns(rev, data, from, a, b) ==> Self::returns(ext, data, from, a, b),
{
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies Self::extends(rev, far) by {
}
if Self::goes_to(rev, data, from, a, to, b) {
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies
#[trigger] Self::run(far, data, from, a) == Self::run(far, data, to, b) by {
}
}
if Self::lands(rev, data, from, a, to) {
let c = choose |c: u32| Self::goes_to(rev, data, from, a, to, c);
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies
#[trigger] Self::run(far, data, from, a) == Self::run(far, data, to, c) by {
}
assert(Self::goes_to(ext, data, from, a, to, c));
}
if Self::returns(rev, data, from, a, b) {
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies
#[trigger] Self::run(far, data, from, a) == Outcome::Return(b) by {
}
}
}
pub(super) proof fn lemma_then(rev: Seq<Instr>, ext: Seq<Instr>, data: &[u8], from: nat, a: u32, mid: nat, m: u32, to: nat, b: u32)
requires
Self::extends(rev, ext),
Self::goes_to(ext, data, from, a, mid, m),
ensures
Self::goes_to(rev, data, mid, m, to, b) ==> Self::goes_to(ext, data, from, a, to, b),
Self::lands(rev, data, mid, m, to) ==> Self::lands(ext, data, from, a, to),
Self::returns(rev, data, mid, m, b) ==> Self::returns(ext, data, from, a, b),
{
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies Self::extends(rev, far) by {
}
if Self::goes_to(rev, data, mid, m, to, b) {
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies
#[trigger] Self::run(far, data, from, a) == Self::run(far, data, to, b) by {
}
}
if Self::lands(rev, data, mid, m, to) {
let c = choose |c: u32| Self::goes_to(rev, data, mid, m, to, c);
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies
#[trigger] Self::run(far, data, from, a) == Self::run(far, data, to, c) by {
}
assert(Self::goes_to(ext, data, from, a, to, c));
}
if Self::returns(rev, data, mid, m, b) {
assert forall |far: Seq<Instr>| Self::extends(ext, far) implies
#[trigger] Self::run(far, data, from, a) == Outcome::Return(b) by {
}
}
}
pub(super) proof fn lemma_then_any(rev: Seq<Instr>, ext: Seq<Instr>, data: &[u8], from: nat, a: u32, mid: nat, to: nat, b: u32)
requires
Self::extends(rev, ext),
Self::lands(ext, data, from, a, mid),
ensures
Self::goes_to_all(rev, data, mid, to, b) ==> Self::goes_to(ext, data, from, a, to, b),
Self::returns_all(rev, data, mid, b) ==> Self::returns(ext, data, from, a, b),
{
let m = choose |m: u32| Self::goes_to(ext, data, from, a, mid, m);
if Self::goes_to_all(rev, data, mid, to, b) {
assert(Self::goes_to(rev, data, mid, m, to, b));
Self::lemma_then(rev, ext, data, from, a, mid, m, to, b);
}
if Self::returns_all(rev, data, mid, b) {
assert(Self::returns(rev, data, mid, m, b));
Self::lemma_then(rev, ext, data, from, a, mid, m, to, b);
}
}
pub(super) proof fn lemma_ld(rev: Seq<Instr>, k: u32)
requires
0 < rev.len(),
rev[rev.len() - 1] == Instr::LdAbs(k),
ensures forall |data: &[u8]| k + 4 <= data@.len() ==>
#[trigger] Self::goes_to_all(rev, data, rev.len(), (rev.len() - 1) as nat, Self::word(data, k))
{
assert forall |data: &[u8]| k + 4 <= data@.len() implies
#[trigger] Self::goes_to_all(rev, data, rev.len(), (rev.len() - 1) as nat, Self::word(data, k)) by {
assert forall |a: u32| #[trigger] Self::goes_to(rev, data, rev.len(), a,
(rev.len() - 1) as nat, Self::word(data, k)) by {
assert forall |ext: Seq<Instr>| Self::extends(rev, ext) implies
#[trigger] Self::run(ext, data, rev.len(), a)
== Self::run(ext, data, (rev.len() - 1) as nat, Self::word(data, k)) by {
assert(ext[rev.len() - 1] == Instr::LdAbs(k));
}
}
}
}
pub(super) proof fn lemma_alu(rev: Seq<Instr>, op: AluOp, k: u32)
requires
0 < rev.len(),
rev[rev.len() - 1] == Instr::Alu(op, Src::K(k)),
!(op is Div && k == 0),
ensures forall |data: &[u8], a: u32|
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1) as nat, op.eval(a, k))
{
assert forall |data: &[u8], a: u32|
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1) as nat, op.eval(a, k)) by {
assert forall |ext: Seq<Instr>| Self::extends(rev, ext) implies
#[trigger] Self::run(ext, data, rev.len(), a)
== Self::run(ext, data, (rev.len() - 1) as nat, op.eval(a, k)) by {
assert(ext[rev.len() - 1] == Instr::Alu(op, Src::K(k)));
}
}
}
pub(super) proof fn lemma_ret(rev: Seq<Instr>, k: u32)
requires 0 < rev.len(), rev[rev.len() - 1] == Instr::Ret(RetVal::K(k))
ensures forall |data: &[u8]| #[trigger] Self::returns_all(rev, data, rev.len(), k)
{
assert forall |data: &[u8]| #[trigger] Self::returns_all(rev, data, rev.len(), k) by {
assert forall |a: u32| #[trigger] Self::returns(rev, data, rev.len(), a, k) by {
assert forall |ext: Seq<Instr>| Self::extends(rev, ext) implies
#[trigger] Self::run(ext, data, rev.len(), a) == Outcome::Return(k) by {
assert(ext[rev.len() - 1] == Instr::Ret(RetVal::K(k)));
}
}
}
}
pub(super) proof fn lemma_ja(rev: Seq<Instr>, k: u32)
requires 0 < rev.len(), rev[rev.len() - 1] == Instr::Ja(k), k < rev.len() - 1
ensures forall |data: &[u8], a: u32|
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1 - k) as nat, a)
{
assert forall |data: &[u8], a: u32|
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1 - k) as nat, a) by {
assert forall |ext: Seq<Instr>| Self::extends(rev, ext) implies
#[trigger] Self::run(ext, data, rev.len(), a)
== Self::run(ext, data, (rev.len() - 1 - k) as nat, a) by {
assert(ext[rev.len() - 1] == Instr::Ja(k));
}
}
}
pub(super) proof fn lemma_jmp(rev: Seq<Instr>, op: JmpOp, k: u32, jt: u8, jf: u8)
requires
0 < rev.len(),
rev[rev.len() - 1] == (Instr::Jmp { op, src: Src::K(k), jt, jf }),
jt < rev.len() - 1,
jf < rev.len() - 1,
ensures
forall |data: &[u8], a: u32| op.eval(a, k) ==>
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1 - jt) as nat, a),
forall |data: &[u8], a: u32| !op.eval(a, k) ==>
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1 - jf) as nat, a),
{
assert forall |data: &[u8], a: u32| op.eval(a, k) implies
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1 - jt) as nat, a) by {
assert forall |ext: Seq<Instr>| Self::extends(rev, ext) implies
#[trigger] Self::run(ext, data, rev.len(), a)
== Self::run(ext, data, (rev.len() - 1 - jt) as nat, a) by {
assert(ext[rev.len() - 1] == (Instr::Jmp { op, src: Src::K(k), jt, jf }));
}
}
assert forall |data: &[u8], a: u32| !op.eval(a, k) implies
#[trigger] Self::goes_to(rev, data, rev.len(), a, (rev.len() - 1 - jf) as nat, a) by {
assert forall |ext: Seq<Instr>| Self::extends(rev, ext) implies
#[trigger] Self::run(ext, data, rev.len(), a)
== Self::run(ext, data, (rev.len() - 1 - jf) as nat, a) by {
assert(ext[rev.len() - 1] == (Instr::Jmp { op, src: Src::K(k), jt, jf }));
}
}
}
}
impl Program {
pub(super) proof fn lemma_run_from(self, data: &[u8], st: MachineState)
requires
self.wf(),
st.pc <= self.instrs@.len(),
forall |i: int| 0 <= i < self.instrs@.len() ==> #[trigger] self.instrs@[i].simple(),
ensures self.eval_from(data, st)
== Builder::run(self.instrs@.reverse(), data, (self.instrs@.len() - st.pc) as nat, st.a)
decreases self.instrs@.len() - st.pc
{
let len = self.instrs@.len();
if st.pc < len {
let instr = self.instrs@[st.pc as int];
instr.lemma_simple(data, st, Builder::state((len - st.pc) as nat, st.a));
if instr.step(data, st) is Ok {
self.lemma_run_from(data, instr.step(data, st)->Ok_0);
}
}
}
pub(super) proof fn lemma_run(self, data: &[u8])
requires
self.wf(),
forall |i: int| 0 <= i < self.instrs@.len() ==> #[trigger] self.instrs@[i].simple(),
ensures self.eval(data) == Builder::run(self.instrs@.reverse(), data, self.instrs@.len(), 0)
{
self.lemma_run_from(data, MachineState::init());
}
}
}