fix(vm): proper error reporting (for library usage)

This commit is contained in:
Alain Zscheile 2022-09-27 18:22:00 +02:00
parent 6c75da9cd2
commit cdebdd2ce9
4 changed files with 116 additions and 143 deletions

21
Cargo.lock generated
View file

@ -125,6 +125,7 @@ dependencies = [
"futures-lite",
"once_cell",
"proptest",
"thiserror",
"tracing",
]
@ -442,6 +443,26 @@ version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]]
name = "thiserror"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"

View file

@ -22,16 +22,17 @@ fn main() {
.expect("unable to open/load main module"),
);
futures_lite::future::block_on(
fogtix_vm::Process {
korigs: fogtix_vm::make_default_origins(),
stack: Vec::new(),
callstack: Vec::new(),
instrp: fogtix_vm::InstrPtr {
m: main_mod,
pos: 0,
},
}
.run(None),
);
let mut p = fogtix_vm::Process {
korigs: fogtix_vm::make_default_origins(),
stack: Vec::new(),
callstack: Vec::new(),
instrp: fogtix_vm::InstrPtr {
m: main_mod,
pos: 0,
},
};
if let Err(e) = futures_lite::future::block_on(p.run(None)) {
eprintln!("ERROR@{}: {}", p.instrp.pos, e);
}
}

View file

@ -7,6 +7,7 @@ publish = false
[dependencies]
async-trait = "0.1"
thiserror = "1.0"
tracing = "0.1"
[dependencies.fogtix-bytecode]

View file

@ -69,6 +69,30 @@ pub enum StackEntValue {
Pointer(Pointer),
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("`{0}` arrived at non-jump target `{1}` @ {2}")]
InvalidJumpTarget(&'static str, String, usize),
#[error("instruction pointer out-of-bounds")]
InstrpOutOfBounds,
#[error("reached unparsable instruction: {0:?}")]
UnparsableInstruction(Vec<u8>),
#[error("out of fuel")]
OutOfFuel,
#[error("not enough operands on stack")]
NotEnoughStacked,
#[error("call-r called with invalid/poisoned origin {0:x}")]
InvalidOrigin(u16),
#[error("operand from stack doesn't have correct data type: expected={expected}, got={got}")]
StackedInvalidType { expected: &'static str, got: String },
}
pub struct Process {
pub korigs: KnownOrigins,
pub stack: Vec<StackEntValue>,
@ -76,54 +100,42 @@ pub struct Process {
pub instrp: InstrPtr,
}
fn verify_jumptarget_explicit(previptr: usize, jinstr: &str, jtip: &InstrPtr) -> bool {
fn verify_jumptarget_explicit(
previptr: usize,
jinstr: &'static str,
jtip: &InstrPtr,
) -> Result<(), Error> {
if let Some(Ok((_, trgi))) = jtip.next_instr() {
if trgi != Instr::Label {
tracing::error!(
"`{}` arrived at non-jump target {:?} @ {}",
return Err(Error::InvalidJumpTarget(
jinstr,
trgi,
format!("{:?}", trgi),
previptr,
);
return false;
));
}
}
true
Ok(())
}
impl Process {
fn verify_jumptarget(&self, previptr: usize, jinstr: &str) -> bool {
fn verify_jumptarget(&self, previptr: usize, jinstr: &'static str) -> Result<(), Error> {
verify_jumptarget_explicit(previptr, jinstr, &self.instrp)
}
pub async fn run(&mut self, mut fuel: Option<&mut u64>) {
pub async fn run(&mut self, mut fuel: Option<&mut u64>) -> Result<(), Error> {
loop {
use fogtix_bytecode::consts::MathBinOp;
let previptr = self.instrp.pos;
tracing::trace!("previptr = {}", previptr);
if let Some(ref mut x) = fuel {
if **x == 0 {
tracing::info!("out of fuel");
break;
return Err(Error::OutOfFuel);
}
**x -= 1;
}
let (nxtidelta, nxti) = match next_instr(&self.instrp.m, self.instrp.pos) {
None => {
tracing::error!(
"reached EOF of module or jumped out of bounds -> {}",
previptr
);
break;
}
Some(Err(code)) => {
tracing::error!(
"reached unparsable instruction {:?} @ {}",
code,
self.instrp.pos
);
break;
}
None => return Err(Error::InstrpOutOfBounds),
Some(Err(code)) => return Err(Error::UnparsableInstruction(code.to_vec())),
Some(Ok(x)) => x,
};
self.instrp.pos += nxtidelta;
@ -133,16 +145,16 @@ impl Process {
if !self.instrp.is_call2jump() {
self.callstack.push(self.instrp.clone());
}
self.verify_jumptarget(previptr, "call-r")?;
match self.stack.pop() {
None => {
tracing::error!("`call-r` invoked on empty stack @ {}", previptr);
break;
return Err(Error::NotEnoughStacked);
}
Some(StackEntValue::Pointer(mut wp)) => {
let ssl = self.stack.len();
let mut args = self.stack.drain(ssl - usize::from(arity)..).collect();
let origin_id = usize::from(wp.origin());
let origin = self.korigs[origin_id].read();
let origin_id = wp.origin();
let origin = self.korigs[usize::from(origin_id)].read();
match origin {
Ok(origin) => match origin.as_ref() {
Some(origin) => {
@ -150,37 +162,18 @@ impl Process {
self.instrp = origin.call(&wp, &atom, &mut args).await;
self.stack.extend(args);
}
None => {
tracing::error!(
"`call-r` invoked on invalid origin {:x} @ {}",
origin_id,
previptr
);
break;
}
None => return Err(Error::InvalidOrigin(origin_id)),
},
Err(_) => {
tracing::error!(
"`call-r` invoked on poisoned origin {:x} @ {}",
origin_id,
previptr
);
break;
}
Err(_) => return Err(Error::InvalidOrigin(origin_id)),
}
}
Some(x) => {
tracing::error!(
"`call-r` invoked on non-pointer {:?} @ {}",
x,
previptr
);
break;
return Err(Error::StackedInvalidType {
expected: "pointer",
got: format!("{:?}", x),
});
}
}
if !self.verify_jumptarget(previptr, "call-r") {
break;
}
}
Instr::CallLocal(x) => {
if !self.instrp.is_call2jump() {
@ -188,18 +181,9 @@ impl Process {
}
self.instrp.pos = match x.try_into() {
Ok(y) => y,
Err(_) => {
tracing::error!(
"jump to out-of-bounds address @ {} -> {}",
previptr,
x
);
break;
}
Err(_) => return Err(Error::InstrpOutOfBounds),
};
if !self.verify_jumptarget(previptr, "call-l") {
break;
}
self.verify_jumptarget(previptr, "call-l")?;
}
Instr::CallLDefer(x) => match x.try_into() {
Ok(pos) => {
@ -207,64 +191,39 @@ impl Process {
m: self.instrp.m.clone(),
pos,
};
if !verify_jumptarget_explicit(previptr, "call-l-defer", &jtip) {
break;
}
verify_jumptarget_explicit(previptr, "call-l-defer", &jtip)?;
if self.instrp.is_call2jump() {
self.instrp = jtip;
} else {
self.callstack.push(jtip);
}
}
Err(_) => {
tracing::error!("jump to out-of-bounds address @ {} -> {}", previptr, x);
break;
}
Err(_) => return Err(Error::InstrpOutOfBounds),
},
Instr::JumpCond(x) => {
let x: usize = match x.try_into() {
Ok(y) => y,
Err(_) => {
tracing::error!(
"jump to out-of-bounds address @ {} -> {}",
previptr,
x
);
break;
}
Err(_) => return Err(Error::InstrpOutOfBounds),
};
let doit = match self.stack.pop() {
None => {
tracing::error!(
"popped empty stack during condition jump eval @ {}",
previptr
);
break;
}
None => return Err(Error::NotEnoughStacked),
Some(StackEntValue::Int(i)) => i != 0,
Some(StackEntValue::Atom(a)) => a != Atom([0; 16]),
Some(z) => {
tracing::error!(
"encountered invalid condition value {:?} @ {}",
z,
previptr
);
break;
return Err(Error::StackedInvalidType {
expected: "atom | int",
got: format!("{:?}", z),
});
}
};
if doit {
self.instrp.pos = x;
if !self.verify_jumptarget(previptr, "jump-cond") {
break;
}
self.verify_jumptarget(previptr, "jump-cond")?;
}
}
Instr::Return => match self.callstack.pop() {
Some(x) => self.instrp = x,
None => {
//tracing::error!("return called on empty callstack @ {}", previptr);
break;
}
None => break Ok(()),
},
Instr::Push(v) => {
self.stack.push(match v {
@ -274,10 +233,7 @@ impl Process {
BcValue::Pointer(p) => StackEntValue::Pointer(p),
});
}
Instr::Pop(_) if self.stack.is_empty() => {
tracing::error!("popped empty stack @ {}", previptr);
break;
}
Instr::Pop(_) if self.stack.is_empty() => return Err(Error::NotEnoughStacked),
Instr::Pop(cnt) => {
let ssl = self.stack.len() - 1;
let cnt = usize::from(cnt);
@ -289,10 +245,7 @@ impl Process {
}
Instr::Dup(delta) => {
let x = match self.stack.len().checked_sub(usize::from(delta) + 1) {
None => {
tracing::error!("dup on too small stack @ {}", previptr);
break;
}
None => return Err(Error::NotEnoughStacked),
// SAFETY: the value x is always smaller than the stack height
Some(x) => self.stack[x].clone(),
};
@ -301,10 +254,7 @@ impl Process {
Instr::Swap(delta) => {
let ssl = self.stack.len();
let (y, z) = match ssl.checked_sub(usize::from(delta) + 2) {
None => {
tracing::error!("swap on too small stack @ {}", previptr);
break;
}
None => return Err(Error::NotEnoughStacked),
Some(ltrg) => self.stack[ltrg..].split_at_mut(1),
};
core::mem::swap(&mut y[0], &mut z[0]);
@ -317,8 +267,10 @@ impl Process {
self.stack.push(StackEntValue::Atom((b, a).into()));
}
x => {
tracing::error!("BIF atom:build @ {} called with {:?}", previptr, x);
break;
return Err(Error::StackedInvalidType {
expected: "[int,int]",
got: format!("{:?}", x),
})
}
}
}
@ -329,8 +281,10 @@ impl Process {
self.stack.push(StackEntValue::Int(a));
}
x => {
tracing::error!("BIF :atom:decon @ {} called with {:?}", previptr, x);
break;
return Err(Error::StackedInvalidType {
expected: "atom",
got: format!("{:?}", x),
})
}
},
Instr::DoMath2(mbo) => {
@ -364,23 +318,19 @@ impl Process {
a.append(&mut b);
a
}
y => {
tracing::error!(
"BIF :math2:*({:?}) @ {} called with {:?}",
y,
previptr,
(
Some(StackEntValue::Bytes(a)),
Some(StackEntValue::Bytes(b)),
),
);
break;
_ => {
return Err(Error::StackedInvalidType {
expected: "[int,int] | [atom,atom]",
got: "[bytes, bytes]".to_string(),
})
}
})
}
x => {
tracing::error!("BIF :math2:* @ {} called with {:?}", previptr, x);
break;
return Err(Error::StackedInvalidType {
expected: "[int,int] | [atom,atom] | [bytes,bytes]",
got: format!("{:?}", x),
})
}
});
}
@ -417,7 +367,7 @@ mod tests {
pos: 0,
},
};
futures_lite::future::block_on(p.run(Some(&mut 1024)));
let _ = futures_lite::future::block_on(p.run(Some(&mut 1024)));
}
}
}