mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 08:52:26 +00:00
Scripts: an error $.evalFile could not run, caught and thrown again, is reported where it was thrown again
A failure $.evalFile raised for a file it could not run was placed by matching the uncaught error's message against the last such failure. A script that caught the failure and threw it again later, or threw a new error whose message held its text, was reported at the failed file's line instead of the second throw. The failure now carries a mark (HostError::Marked) with the exception. A catch clause drops the mark, a throw raises the value without one, and a finally block that ends normally throws it on with its mark, nested finally blocks included. Realm::error_mark gives the mark of the error a run ended with, and the engine looks up the file, line and column by it.
This commit is contained in:
@@ -496,14 +496,11 @@ struct Host {
|
||||
script_map: RefCell<include::SourceMap>,
|
||||
/// The same for each source `$.evalFile` gave, in order.
|
||||
eval_maps: RefCell<Vec<include::SourceMap>>,
|
||||
/// The last error `$.evalFile` raised for a file it could not run, with the file, line and
|
||||
/// column that error is at.
|
||||
eval_failure: RefCell<Option<EvalFailure>>,
|
||||
/// The file, line and column of each error `$.evalFile` raised for a file it could not run, in
|
||||
/// order: the error's mark ([`HostError::Marked`]) is its index.
|
||||
eval_failures: RefCell<Vec<(Arc<str>, u32, u32)>>,
|
||||
}
|
||||
|
||||
/// An error `$.evalFile` raised, as thrown, and the file, line and column it is at.
|
||||
type EvalFailure = (String, Arc<str>, u32, u32);
|
||||
|
||||
/// A run's host calls by kind, as its report gives them in `hostCallCounts`.
|
||||
#[derive(Default)]
|
||||
struct Counts {
|
||||
@@ -580,6 +577,15 @@ impl Host {
|
||||
HostError::Abort(limit.to_string())
|
||||
}
|
||||
|
||||
/// `error`, marked as an error at `file`, `line` and `column`, the place [`located`] gives when
|
||||
/// the run ends with it uncaught.
|
||||
fn placed(&self, error: HostError, file: Arc<str>, line: u32, column: u32) -> HostError {
|
||||
let mut places = self.eval_failures.borrow_mut();
|
||||
let Ok(mark) = u32::try_from(places.len()) else { return error };
|
||||
places.push((file, line, column));
|
||||
HostError::Marked { error: Box::new(error), mark }
|
||||
}
|
||||
|
||||
fn charge_call(&self, bytes: u64) -> std::result::Result<(), HostError> {
|
||||
let calls = self.calls.get() + 1;
|
||||
self.calls.set(calls);
|
||||
@@ -987,14 +993,12 @@ fn eval_file(host: &Host, args: Vec<Data>) -> std::result::Result<Data, HostErro
|
||||
host.charge_call(path.len() as u64 + 2)?;
|
||||
let expanded = host.files.borrow_mut().eval_file(path).map_err(|f| {
|
||||
let message = if f.line > 0 { format!("{}:{}:{}: {}", f.file, f.line, f.column, f.message) } else { f.message };
|
||||
if f.line > 0 {
|
||||
*host.eval_failure.borrow_mut() = Some((message.clone(), f.file.clone(), f.line, f.column));
|
||||
}
|
||||
match f.kind {
|
||||
let error = match f.kind {
|
||||
include::FailKind::Limit => HostError::Abort(message),
|
||||
include::FailKind::Syntax => HostError::Syntax(message),
|
||||
include::FailKind::Error => HostError::Error(message),
|
||||
}
|
||||
};
|
||||
if f.line > 0 { host.placed(error, f.file, f.line, f.column) } else { error }
|
||||
})?;
|
||||
// A file without preprocessor lines is parsed here first, so its syntax error names its place.
|
||||
let options = vectorcraft_js::Options { max_source_units: include::MAX_TOTAL_BYTES, ..vectorcraft_js::Options::default() };
|
||||
@@ -1002,8 +1006,7 @@ fn eval_file(host: &Host, args: Vec<Data>) -> std::result::Result<Data, HostErro
|
||||
host.files.borrow_mut().end_eval();
|
||||
let (_, file, line, column) = expanded.map.locate(e.line, e.column);
|
||||
let message = format!("{file}:{line}:{column}: {}", e.message);
|
||||
*host.eval_failure.borrow_mut() = Some((message.clone(), file, line, column));
|
||||
return Err(HostError::Syntax(message));
|
||||
return Err(host.placed(HostError::Syntax(message), file, line, column));
|
||||
}
|
||||
host.eval_maps.borrow_mut().push(expanded.map);
|
||||
Ok(Data::String(expanded.source))
|
||||
@@ -1095,18 +1098,20 @@ fn preprocessor_error(f: include::Failure) -> (RunError, Option<Arc<str>>) {
|
||||
(RunError { kind, message, line: f.line, column: f.column }, file)
|
||||
}
|
||||
|
||||
/// `e`, which the realm raised in source `tag` ([`Realm::error_source`]), at the line and column of
|
||||
/// the file it is in, with that file's name unless it is the script's own.
|
||||
fn located(host: &Host, tag: u32, e: &RunError) -> (RunError, Option<Arc<str>>) {
|
||||
/// `e`, which the realm raised in source `tag` ([`Realm::error_source`]) with host error mark `mark`
|
||||
/// ([`Realm::error_mark`]), at the line and column of the file it is in, with that file's name
|
||||
/// unless it is the script's own.
|
||||
fn located(host: &Host, tag: u32, mark: Option<u32>, e: &RunError) -> (RunError, Option<Arc<str>>) {
|
||||
let map = |m: &include::SourceMap, own: bool| {
|
||||
let (index, file, line, column) = m.locate(e.line, e.column);
|
||||
let message = if e.kind == RunErrorKind::Syntax { relocated(&e.message, e.line, e.column, line, column) } else { e.message.clone() };
|
||||
(RunError { kind: e.kind.clone(), message, line, column }, (!own || index > 0).then_some(file))
|
||||
};
|
||||
// An error `$.evalFile` raised for a file it could not run is at that file's place.
|
||||
if let Some((_, file, line, column)) = host.eval_failure.borrow().as_ref().filter(|(thrown, ..)| e.message.contains(thrown.as_str())) {
|
||||
let message = if e.kind == RunErrorKind::Syntax { relocated(&e.message, e.line, e.column, *line, *column) } else { e.message.clone() };
|
||||
return (RunError { kind: e.kind.clone(), message, line: *line, column: *column }, Some(file.clone()));
|
||||
// An error `$.evalFile` raised for a file it could not run, and no script caught, is at that
|
||||
// file's place.
|
||||
if let Some((file, line, column)) = mark.and_then(|m| host.eval_failures.borrow().get(m as usize).cloned()) {
|
||||
let message = if e.kind == RunErrorKind::Syntax { relocated(&e.message, e.line, e.column, line, column) } else { e.message.clone() };
|
||||
return (RunError { kind: e.kind.clone(), message, line, column }, Some(file));
|
||||
}
|
||||
if e.line == 0 {
|
||||
return (e.clone(), None);
|
||||
@@ -1301,7 +1306,7 @@ impl Session {
|
||||
files: RefCell::new(files),
|
||||
script_map: RefCell::default(),
|
||||
eval_maps: RefCell::default(),
|
||||
eval_failure: RefCell::default(),
|
||||
eval_failures: RefCell::default(),
|
||||
});
|
||||
let args = to_data(&Value::Object(request.args), &DataLimits::default(), 0, &mut (0, 0))
|
||||
.map_err(|place| EngineError::BadParams { cmd: "script.run".into(), msg: format!("`args{place}` is nested more than 64 levels deep") })?;
|
||||
@@ -1316,7 +1321,7 @@ impl Session {
|
||||
.and_then(|()| realm.define_host_object("$", dollar_object(&host)))
|
||||
.and_then(|()| dialogs::install(&mut realm, &host))
|
||||
.and_then(|()| realm.start(&expanded.source))
|
||||
.map_err(|e| located(&host, realm.error_source(), &e))
|
||||
.map_err(|e| located(&host, realm.error_source(), realm.error_mark(), &e))
|
||||
}
|
||||
};
|
||||
if let Err((e, file)) = started {
|
||||
@@ -1388,7 +1393,7 @@ impl Session {
|
||||
fn script_failed(&mut self, job: Job, e: &RunError) -> Result<Value> {
|
||||
job.host.dialogs.stopped();
|
||||
let fuel_left = job.realm.fuel_left();
|
||||
let (e, file) = located(&job.host, job.realm.error_source(), e);
|
||||
let (e, file) = located(&job.host, job.realm.error_source(), job.realm.error_mark(), e);
|
||||
let mut report = failure_report(&job.name, &e, file.as_deref(), &job.host, fuel_left, job.realm.limits().fuel.saturating_sub(fuel_left));
|
||||
close_run(self, &job.host, &mut report, ending(&e.kind));
|
||||
drop(job);
|
||||
|
||||
Reference in New Issue
Block a user