Plug-ins: a run keeps what a failing start function reported

A module's start function runs before vc_run. When it logged or reported progress and then failed, the run's report came back empty. The report now holds what the start function reported.
This commit is contained in:
dotCooCoo
2026-11-08 13:21:16 +07:00
committed by bflatastic
parent b071fb109c
commit 0383c9f408
2 changed files with 33 additions and 5 deletions
+12 -5
View File
@@ -154,7 +154,7 @@ impl Plugin {
let mut p = Plugin { manifest: Manifest::placeholder(), engine, module, limits, size: bytes.len(), hash, source: None, serial: 0 };
let deadline = Deadline::new(p.limits.wall_time_ms);
// The manifest isn't read yet: every host import is linked, and what it reports is dropped.
let mut live = p.instantiate(Run::new("", false, false, deadline), true)?;
let mut live = p.instantiate(Run::new("", false, false, deadline), true, &mut Report::default())?;
let version: TypedFunc<(), i32> = live.func("vc_abi_version")?;
let v = p.call(&mut live, &version, (), p.limits.fuel_base, &deadline)?;
if v != ABI_VERSION {
@@ -214,8 +214,9 @@ impl Plugin {
self.serial = serial;
}
/// A new instance for `run`, with the host imports linked when `granted`.
fn instantiate(&self, run: Run, granted: bool) -> Result<Live> {
/// A new instance for `run`, with the host imports linked when `granted`. When the module's
/// start function fails, what it reported through the host imports goes to `report`.
fn instantiate(&self, run: Run, granted: bool, report: &mut Report) -> Result<Live> {
let limits =
StoreLimitsBuilder::new().memory_size(self.limits.max_memory_bytes).memories(1).tables(4).table_elements(100_000).instances(1).build();
let mut store = Store::new(&self.engine, Host { limits, run });
@@ -224,7 +225,13 @@ impl Plugin {
// the deadline or a cancel between slices, so a heavy budget would let it run for minutes.
store.set_fuel(self.limits.fuel_base.min(FUEL_SLICE)).map_err(wasm_err)?;
let linker = host::linker(&self.engine, granted);
let instance = linker.instantiate_and_start(&mut store, &self.module).map_err(wasm_err)?;
let instance = match linker.instantiate_and_start(&mut store, &self.module) {
Ok(instance) => instance,
Err(e) => {
*report = std::mem::take(&mut store.data_mut().run.report);
return Err(wasm_err(e));
}
};
let memory =
instance.get_memory(&store, "memory").ok_or_else(|| Error::Abi("the module must export its linear memory as `memory`".into()))?;
Ok(Live { store, instance, memory })
@@ -287,7 +294,7 @@ impl Plugin {
let deadline = Deadline::new(limits.wall_time_ms);
let m = &self.manifest;
let run = Run::new(&m.id, m.kind != Kind::Effect, true, deadline);
let mut live = self.instantiate(run, m.permissions().contains(&Permission::Progress))?;
let mut live = self.instantiate(run, m.permissions().contains(&Permission::Progress), report)?;
let out = self.run_live(&mut live, input, params, limits, &deadline);
*report = std::mem::take(&mut live.store.data_mut().run.report);
out
+21
View File
@@ -442,6 +442,27 @@ fn log_lines_are_capped_and_bad_text_is_an_error_the_plugin_sees() {
assert_eq!(report.lines.iter().filter(|l| l.text == host::LOG_TRUNCATED).count(), 1);
}
/// What a module's start function reports through the host imports before it fails is kept in the
/// run's report. This start function logs, reports progress and traps only when the run was
/// cancelled, so the module loads.
#[test]
fn what_a_start_function_reports_before_it_fails_is_kept() {
let start = r#"(data (i32.const 0x2000) "hi")
(func $start (if (call $vc_cancelled) (then
(drop (call $vc_log (i32.const 2) (i32.const 0x2000) (i32.const 2)))
(call $vc_progress (i64.const 1) (i64.const 4))
unreachable)))
(start $start)"#;
let imports = format!("{} {start}", tpl::HOST_IMPORTS);
let p = Plugin::load(&build(&tpl::with_imports(LEVEL2, &imports, "", INPUT)), Limits::default()).unwrap();
let token = vectorcraft_doc::cancel::CancelToken::new();
token.cancel();
let (out, report) = vectorcraft_doc::cancel::scope(&token, None, || p.run_reporting(INPUT.as_bytes(), b"{}", p.limits()));
assert!(out.is_err(), "{out:?}");
assert_eq!(report.lines, [vectorcraft_plugins::LogLine { level: vectorcraft_plugins::LogLevel::Info, text: "hi".into() }]);
assert_eq!(report.progress, Some((1, 4)));
}
/// A heavy filter installs once the user agrees, which loading a folder doesn't ask: it is skipped
/// and reported.
#[test]