fix(vm): proper error reporting (for library usage)
This commit is contained in:
parent
6c75da9cd2
commit
cdebdd2ce9
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
async-trait = "0.1"
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1"
|
||||
|
||||
[dependencies.fogtix-bytecode]
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue