diff --git a/crates/plugins/src/runtime.rs b/crates/plugins/src/runtime.rs index aff7676a50..31adb7a2bd 100644 --- a/crates/plugins/src/runtime.rs +++ b/crates/plugins/src/runtime.rs @@ -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 { + /// 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 { 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 diff --git a/crates/plugins/tests/abi.rs b/crates/plugins/tests/abi.rs index c9c0adb4cc..5f2820a5ed 100644 --- a/crates/plugins/tests/abi.rs +++ b/crates/plugins/tests/abi.rs @@ -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]