Merge pull request #6471 from dotCooCoo/fix/plugin-no-call-after-failure

Plug-ins: no command call is made after a run's failed call
This commit is contained in:
BFlat
2026-11-11 05:39:27 +07:00
committed by GitHub
3 changed files with 29 additions and 4 deletions
+9 -3
View File
@@ -121,8 +121,9 @@ pub const CALL_COMMAND_FAILED: i32 = -8;
pub const CALL_LIMIT: i32 = -9;
/// What `vc_call_result` returns: no call has a result yet.
pub const CALL_NO_RESULT: i32 = -10;
/// What `vc_call` returns: nothing can be called now (while the module is being loaded, or in a
/// run that isn't a filter's run on a document). In a run, this fails it too.
/// What `vc_call` returns: nothing can be called now (while the module is being loaded, in a run
/// that isn't a filter's run on a document, or after a call in the run failed). In a run, this
/// fails it too.
pub const CALL_NO_CONTEXT: i32 = -11;
/// Most `vc_call` calls one run makes.
@@ -429,9 +430,14 @@ impl Run {
/// `vc_call`: checks the call to the command at `id` with the arguments at `args` in `memory`
/// and leaves it in [`Run::pending`] for the host to make, or → the code for why it can't be
/// made, which fails the run. The size of the arguments is checked before they are read.
/// made, which fails the run. The size of the arguments is checked before they are read. After
/// the run's first failed call, every call returns [`CALL_NO_CONTEXT`] and none is made.
fn call_request(&mut self, memory: &[u8], id: (usize, usize), args: (usize, usize)) -> std::result::Result<(), i32> {
self.call_result = None;
// No call is made after the run's first failed call.
if self.call_failure.is_some() {
return Err(CALL_NO_CONTEXT);
}
if self.allowed.is_none() {
let why = "vc_call: no command can be called here (while the module loads, or in a run that can't make calls)";
return Err(self.fail(CALL_NO_CONTEXT, Error::Abi(why.into())));
+19
View File
@@ -138,6 +138,25 @@ fn a_failed_command_fails_the_run_with_its_reason() {
assert_eq!(out, Err(Error::Failed("vc_call `select.all`: nothing is selected".into())));
}
/// After the run's first failed call, a plug-in that goes on calling gets [`CALL_NO_CONTEXT`] for
/// each call and the host makes none of them; the run fails with the first call's reason.
#[test]
fn no_call_is_made_after_a_failed_one() {
let rest = [select_all(), select_all(), select_all()].map(|c| expect(&c, CALL_NO_CONTEXT)).concat();
let p = calling("test.call.after.failed", &format!("{}{rest}", expect(&select_all(), CALL_COMMAND_FAILED)));
let (out, seen) = run_with(&p, |_| Err(CallFailure { code: CALL_COMMAND_FAILED, why: "nothing is selected".into() }));
assert_eq!(out, Err(Error::Failed("vc_call `select.all`: nothing is selected".into())));
assert_eq!(seen.len(), 1);
// Also after a call the host refused without making it.
let p = calling(
"test.call.after.refused",
&format!("{}{}", expect(&call("0x2010", "13", "0", "0"), CALL_NOT_ALLOWED), expect(&select_all(), CALL_NO_CONTEXT)),
);
let (out, seen) = run_with(&p, |_| Ok(Value::Null));
assert!(out.unwrap_err().to_string().contains("vc_call `object.delete`: the manifest's `commandAllowlist` doesn't list it"));
assert!(seen.is_empty());
}
/// A result larger than [`MAX_CALL_DATA`] fails the run, and so do more than [`MAX_CALLS`] calls.
#[test]
fn results_and_calls_are_capped() {
+1 -1
View File
@@ -407,7 +407,7 @@ arguments as a JSON object, the params the command takes from any request:
`-3` when the arguments are not a JSON object, `-4` when `commandAllowlist` doesn't list the id, `-5` when no command
has the id, `-6` when a plug-in may not call the command, `-7` when the command can't run now (nothing is selected),
`-8` when the command ran and failed, `-9` when a limit below is passed, and `-11` when nothing can be called (from
the module's start function, or in a run that isn't a filter's run on a document).
the module's start function, in a run that isn't a filter's run on a document, or after a call in the run failed).
- Every code also fails the run, whatever the plug-in does next, and the error names the call and why it failed. The run's calls and its output are one undo step, named after the plug-in or its command. A run that fails,
traps, runs out of budget or is canceled leaves every document, its selection and its history as they were.