mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 04:14:26 +00:00
Plug-ins: the fuzz test reads the runtime's fuel slice instead of a copy
vectorcraft_plugins exports FUEL_SLICE and BYTES_COPIED_PER_FUEL (hidden from the docs), and the bulk memory fuzz cases use them for the passes around one slice, the lengths around one unit of fuel and the fuel estimate, in place of the copied 20,000,000 and 64.
This commit is contained in:
@@ -15,7 +15,7 @@ use proptest::prelude::*;
|
||||
use serde_json::{Map, Value, json};
|
||||
use vectorcraft_engine::Session;
|
||||
use vectorcraft_plugins::wat as tpl;
|
||||
use vectorcraft_plugins::{Error, Kind, Limits, Plugin};
|
||||
use vectorcraft_plugins::{BYTES_COPIED_PER_FUEL, Error, FUEL_SLICE, Kind, Limits, Plugin};
|
||||
use vectorcraft_testkit::catch_quiet;
|
||||
use vectorcraft_testkit::fixtures::rich_session;
|
||||
|
||||
@@ -187,9 +187,6 @@ fn arb_paint() -> impl Strategy<Value = Value> {
|
||||
/// Bytes in the passive data segment that bulk memory cases initialize memory from.
|
||||
const SEGMENT_LEN: u32 = 4096;
|
||||
|
||||
/// Fuel the host hands a run per slice (`FUEL_SLICE` in `vectorcraft_plugins`' runtime).
|
||||
const FUEL_SLICE: u64 = 20_000_000;
|
||||
|
||||
/// Where a bulk memory plug-in keeps its output (the manifest sits at 16).
|
||||
const BULK_OUTPUT_AT: u32 = 0x8000;
|
||||
|
||||
@@ -319,10 +316,10 @@ impl BulkCase {
|
||||
}
|
||||
}
|
||||
|
||||
/// A generous estimate of the fuel the instructions use: a unit per 64 bytes moved, and a few
|
||||
/// per pass.
|
||||
/// A generous estimate of the fuel the instructions use: a unit per [`BYTES_COPIED_PER_FUEL`]
|
||||
/// bytes moved, and a few per pass.
|
||||
fn fuel(self) -> u64 {
|
||||
u64::from(self.runs()) * (u64::from(self.len) / 64 + 16)
|
||||
u64::from(self.runs()) * (u64::from(self.len) / u64::from(BYTES_COPIED_PER_FUEL) + 16)
|
||||
}
|
||||
|
||||
/// The plug-in module in WAT, with `manifest`.
|
||||
@@ -352,11 +349,12 @@ impl BulkCase {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lengths: zero, a byte, around the 64 bytes a unit of fuel pays for, the data segment's length,
|
||||
/// a page, a mebibyte, or anything up to four pages.
|
||||
/// Lengths: zero, a byte, around the bytes a unit of fuel pays for, the data segment's length, a
|
||||
/// page, a mebibyte, or anything up to four pages.
|
||||
fn arb_bulk_len() -> impl Strategy<Value = u32> {
|
||||
let per = BYTES_COPIED_PER_FUEL;
|
||||
prop_oneof![
|
||||
3 => prop::sample::select(vec![0u32, 1, 63, 64, 65, SEGMENT_LEN - 1, SEGMENT_LEN, SEGMENT_LEN + 1, 0xffff, 0x1_0000, 0x1_0001, 1 << 20]),
|
||||
3 => prop::sample::select(vec![0u32, 1, per - 1, per, per + 1, SEGMENT_LEN - 1, SEGMENT_LEN, SEGMENT_LEN + 1, 0xffff, 0x1_0000, 0x1_0001, 1 << 20]),
|
||||
1 => 0u32..0x4_0000,
|
||||
]
|
||||
}
|
||||
@@ -394,7 +392,7 @@ fn arb_bulk_case() -> impl Strategy<Value = BulkCase> {
|
||||
let repeat = match repeat {
|
||||
Repeat::Times(n) => n,
|
||||
Repeat::AroundSlice(k) => {
|
||||
let slice = (FUEL_SLICE * 64 / u64::from(len.max(1))) as i64;
|
||||
let slice = (FUEL_SLICE * u64::from(BYTES_COPIED_PER_FUEL) / u64::from(len.max(1))) as i64;
|
||||
(slice + k).clamp(1, 2_000) as u32
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,6 +54,8 @@ pub use dialog::ParamUi;
|
||||
pub use host::{LogLevel, LogLine, Report};
|
||||
pub use manifest::{Budget, Command, InputKind, Kind, Manifest, Param, ParamSpec};
|
||||
pub use runtime::{ABI_VERSION, HEAVY_FUEL, HEAVY_WALL_TIME_MS, Limits, Plugin, heavy_terms};
|
||||
#[doc(hidden)]
|
||||
pub use runtime::{BYTES_COPIED_PER_FUEL, FUEL_SLICE};
|
||||
|
||||
/// What the host puts before a plug-in's id to name what it adds: its live effect
|
||||
/// (`plugin.<id>`, [`effect`]) or its file format ([`export`], [`import`]).
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{Error, Kind, Result};
|
||||
pub const ABI_VERSION: i32 = 1;
|
||||
|
||||
/// Fuel handed out per slice; between slices the host checks the deadline.
|
||||
const FUEL_SLICE: u64 = 20_000_000;
|
||||
pub const FUEL_SLICE: u64 = 20_000_000;
|
||||
|
||||
/// wasmi's fuel costs, but compiling costs nothing. wasmi compiles a function when it is first
|
||||
/// called, and charges the run that calls it by the function's size. When the slice a run was in
|
||||
@@ -24,6 +24,9 @@ const FUEL_SLICE: u64 = 20_000_000;
|
||||
/// the next. Copying keeps wasmi's default cost, a unit for every 64 bytes.
|
||||
const FUEL_COSTS: CustomFuelCosts = CustomFuelCosts { bytes_copied_per_fuel: 64, fuel_per_bytes_translated: 0, fuel_per_bytes_validated: 0 };
|
||||
|
||||
/// Bytes a bulk memory instruction copies or fills for each unit of fuel.
|
||||
pub const BYTES_COPIED_PER_FUEL: u32 = FUEL_COSTS.bytes_copied_per_fuel;
|
||||
|
||||
/// Instructions a heavy filter's run may use besides [`Limits::fuel_per_byte`] for each byte of
|
||||
/// input ([`crate::Budget::Heavy`]): 20 times a light one's. Its output's size can't add to it:
|
||||
/// the host learns that only once the run has ended.
|
||||
|
||||
@@ -1648,6 +1648,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Heavy filter plug-ins (G17):** a filter's manifest may ask for `"budget": "heavy"`: 1 G instructions (20 times a light run's) and 300 s a run, for routing, packing or simulation. Install Plug-in… and File › Open ask the user first, `plugin.install` needs `allowHeavy: true` (`plugin.inspect` tells beforehand), the Additional Plug-ins Folder skips such filters, and the plug-in store keeps the answer. Its dialog has no Preview; the app runs it on a thread of its own with a progress window (Cancel, Continue in Background) and applies the result as `plugin.run` with the same parameters, one undo step, unless the document or the plug-in changed meanwhile. Regressions: `crates/plugins/tests/abi.rs`, `crates/plugins/src/manifest.rs`, `crates/engine/src/tests/plugins.rs`, `crates/ui-egui/src/tests/plugin_runs.rs`. Scores and hours unchanged.
|
||||
- **Safe Mode for plug-ins (G17):** `vectorcraft --safe-mode` and `vectorcraft-cli --safe-mode <subcommand>` start without plug-ins. When a launch stopped while a plug-in was loading or drawing its first live effect, the next launch names that plug-in, without saying it caused the stop, and offers Disable and Continue, Start Normally or Start in Safe Mode. Help › Safe Mode lists the disabled plug-ins with Enable on Re-launch, which takes effect at the next launch, and the app bar and the window title show Safe Mode. The help page (https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf, pages 65 to 69) describes the same choice between diagnosing and starting normally, a Safe Mode with the problem item off and the rest of the app working, and turning the item back on at the next launch. In Safe Mode no plug-in loads or runs, and a live-effect plug-in draws its object's geometry unchanged while the object keeps the effect, which the Appearance panel marks "(Safe Mode)" with the plug-in's id in its tooltip and `effect.list` marks `skipped`; Expand Appearance refuses such an object. The web build keeps no plug-ins between page loads and has no marker. Each desktop launch with preferences keeps a marker in the `Launch` folder next to them; `vectorcraft-cli` and a desktop launch without preferences keep none, so a stop there leads to no choice at the next launch. Disabled plug-ins are listed in `Plug-ins/disabled.json`. `plugin.safeMode` reports Safe Mode and the disabled plug-ins, and `plugin.setEnabled` changes the list. Regressions: `crates/plugins/src/safe_mode.rs`, `crates/plugins/tests/safe_mode.rs`, `crates/engine/tests/plugin_safe_mode.rs`, `crates/ui-egui/tests/safe_mode.rs`. Scores and hours unchanged.
|
||||
- **Plug-in instruction budget at a bulk memory instruction:** a run that pauses where one slice of its instruction budget ends keeps the instructions the slice still held. A `memory.fill` or `memory.copy` that the slice can't pay for in full runs on the next slice, so a run whose budget covers its work finishes; a run ends with `Limit("instruction budget")` only when the instructions it has left can't pay for the next instruction. Regressions: `crates/plugins/tests/abi.rs`. Scores and hours unchanged.
|
||||
- **Bulk memory fuzz cases use the host's fuel constants:** `crates/engine/tests/plugin_fuzz.rs` samples runs around one slice of the instruction budget and lengths around the bytes one unit of fuel pays for with `vectorcraft_plugins`' own `FUEL_SLICE` and `BYTES_COPIED_PER_FUEL`, so the sampled cases move when either value changes. Regressions: `crates/engine/tests/plugin_fuzz.rs`. Scores and hours unchanged.
|
||||
- **Plug-in permissions (G17):** a manifest may list `permissions` (`doc.read`, `doc.write`, `state`, `effect.render`, `export.write`; the first three follow storytold/photocraft#662) and an `api` level; without the list a plug-in gets what its kind implies and installs as before. Install Plug-in… and File › Open show the name, id, version, file SHA-256 and each permission in plain words, with control and bidirectional characters escaped, and install nothing on Cancel; for a plug-in that lists permissions, `plugin.install` needs `grant` and folders install it only when it is granted. The grant is kept with the module's hash, a module asking for more asks again, and a filter run refuses output its permissions don't allow. Regressions: `crates/plugins/src/permissions.rs`, `crates/plugins/src/manifest.rs`, `crates/engine/src/tests/plugins.rs`, `crates/engine/tests/plugin_fuzz.rs`, `crates/ui-egui/src/tests/plugins.rs`. Scores and hours unchanged.
|
||||
- **Filter plug-ins read type, images and symbol instances (G17, #4333):** a filter at plug-in API level 2 lists `inputs` (`type`, `image`, `symbolInstance`) with `doc.read` in its `permissions`, so installing it asks for approval, and is also given the selected objects of those kinds, read only: type with its runs, character and paragraph attributes (no text past 256 KiB or 4,096 runs or paragraphs, or 16 MiB of runs over the input), images with their size, color mode and, when embedded and at most 1 MiB in base64 (16 MiB over the input), their RGBA pixels (a CMYK image's through the working CMYK profile; never a linked file's), and symbol instances with their symbol and transform. The objects come in paint order from the bottom, and an image that reaches the decoder counts toward the 16 MiB of pixels even when decoding fails. Every object counts toward the input's half of the memory cap; one whose fields besides its pixels and runs don't fit fails the run naming it and its size, a path or compound path that doesn't fit fails it too, and so do more than 100,000 of these objects. Its output returns each as `{id}` or exactly as given; any change or omission is refused before the document changes. Filters without `inputs` get the same input as before, except that compound paths that clip a clip group and paths inside a group that clips one are left out for every filter. Regressions: `crates/plugins/src/objects/tests.rs`, `crates/plugins/src/manifest.rs`, `crates/engine/src/tests/plugins.rs`, `crates/engine/tests/plugin_fuzz.rs`. Scores and hours unchanged.
|
||||
- **Plug-in grants cover one module (G17):** a grant counts only for the module with the id and SHA-256 it records. A module that lists permissions and has other bytes under the same id needs a new approval, even with the same permissions: `plugin.inspect` reports `needsApproval`, `plugin.install` refuses it without `grant`, Install Plug-in… and File › Open ask again, and the plug-in store and plug-in folders skip it. A module with a missing or empty permissions list needs no grant; without the list, it gets its kind's implied permissions. A grant with a malformed hash covers nothing. Regressions: `crates/plugins/src/permissions.rs`, `crates/engine/src/tests/plugins.rs`, `crates/ui-egui/src/tests/plugins.rs`. Scores and hours unchanged.
|
||||
|
||||
Reference in New Issue
Block a user