Skip to content

Commit a2582c1

Browse files
authored
[wasmparser] add atomic validator feature (#2429)
* [validator] add atomic feature FuncValidator::atomic_op validates an operator, leaving the validator unchanged if the operator is invalid. * Respond to review * Test the try_op feature with a custom cfg
1 parent 1ed898a commit a2582c1

10 files changed

Lines changed: 321 additions & 25 deletions

File tree

.github/actions/install-rust/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ runs:
4040
EOF
4141
4242
# Deny warnings on CI to keep our code warning-free as it lands in-tree.
43-
echo RUSTFLAGS="-D warnings" >> "$GITHUB_ENV"
43+
echo RUSTFLAGS="-D warnings $RUSTFLAGS" >> "$GITHUB_ENV"
4444
4545
- run: rustup target add wasm32-wasip2
4646
if: inputs.toolchain != 'msrv'

.github/workflows/main.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ jobs:
132132
rust: stable
133133
env:
134134
RUST_BACKTRACE: 1
135+
# test the 'try_op' feature
136+
- os: ubuntu-latest
137+
rust: stable
138+
env:
139+
RUSTFLAGS: --cfg=debug_check_try_op
140+
flags: -F wasmparser/try-op
135141
env: ${{ matrix.env || fromJSON('{}') }}
136142
steps:
137143
- uses: actions/checkout@v6
@@ -154,6 +160,7 @@ jobs:
154160
- run: cargo test --locked -p wasm-encoder --all-features
155161
- run: cargo test -p wasm-smith --features wasmparser
156162
- run: cargo test -p wasm-smith --features component-model
163+
- run: cargo test --locked -p wasmparser --features try-op
157164

158165
test_capi:
159166
name: Test the C API
@@ -276,6 +283,7 @@ jobs:
276283
- run: cargo check --no-default-features -p wasmparser --target x86_64-unknown-none --features validate,serde,prefer-btree-collections
277284
- run: cargo check --no-default-features -p wasmparser --features std
278285
- run: cargo check --no-default-features -p wasmparser --features validate
286+
- run: cargo check --no-default-features -p wasmparser --features validate,try-op
279287
- run: cargo check --no-default-features -p wasmparser --features features
280288
- run: cargo check --no-default-features -p wasmparser --features features,validate
281289
- run: cargo check --no-default-features -p wasmparser --features prefer-btree-collections

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ rust-2024-incompatible-pat = 'warn'
6666
missing-unsafe-on-extern = 'warn'
6767
unsafe-op-in-unsafe-fn = 'warn'
6868

69-
unexpected_cfgs = { level = 'warn', check-cfg = ['cfg(fuzzing)'] }
69+
unexpected_cfgs = { level = 'warn', check-cfg = ['cfg(fuzzing)', 'cfg(debug_check_try_op)'] }
7070

7171
[workspace.lints.clippy]
7272
# The default set of lints in Clippy is viewed as "too noisy" right now so

crates/wasmparser/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,7 @@ component-model = ['dep:semver']
8787
# proposals for WebAssembly. This is enabled by default but if your use case is
8888
# only interested in working on non-SIMD code then this feature can be disabled.
8989
simd = []
90+
91+
# A feature that allows validating an operator atomically, leaving the validator
92+
# unchanged if the operator is invalid.
93+
try-op = []

crates/wasmparser/src/features.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ macro_rules! define_wasm_features {
2727
/// This is the disabled zero-size version of this structure because the
2828
/// `features` feature was disabled at compile time of this crate.
2929
#[cfg(not(feature = "features"))]
30-
#[derive(Clone, Debug, Default, Hash, Copy)]
30+
#[derive(Clone, Debug, Default, Hash, Copy, PartialEq)]
3131
pub struct WasmFeatures {
3232
_priv: (),
3333
}

crates/wasmparser/src/validator/core.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,7 @@ impl WasmModuleResources for OperatorValidatorResources<'_> {
11331133

11341134
/// The implementation of [`WasmModuleResources`] used by
11351135
/// [`Validator`](crate::Validator).
1136-
#[derive(Debug)]
1136+
#[derive(Clone, Debug)]
11371137
pub struct ValidatorResources(pub(crate) Arc<Module>);
11381138

11391139
impl WasmModuleResources for ValidatorResources {

crates/wasmparser/src/validator/func.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ impl<T: WasmModuleResources> FuncToValidate<T> {
5757
///
5858
/// This is a finalized validator which is ready to process a [`FunctionBody`].
5959
/// This is created from the [`FuncToValidate::into_validator`] method.
60+
#[derive(Clone)]
6061
pub struct FuncValidator<T> {
6162
validator: OperatorValidator,
6263
resources: T,
@@ -121,6 +122,19 @@ impl<T: WasmModuleResources> FuncValidator<T> {
121122
reader.set_features(self.validator.features);
122123
}
123124
while !reader.eof() {
125+
// In a `debug_check_try_op` build, verify that `rollback` successfully returns the
126+
// validator to its previous state after each (valid or invalid) operator.
127+
#[cfg(all(debug_check_try_op, feature = "try-op"))]
128+
{
129+
let snapshot = self.validator.clone();
130+
let op = reader.peek_operator(&self.visitor(reader.original_position()))?;
131+
self.validator.begin_try_op();
132+
let _ = self.op(reader.original_position(), &op);
133+
self.validator.rollback();
134+
self.validator.pop_push_log.clear();
135+
assert!(self.validator == snapshot);
136+
}
137+
124138
// In a debug build, verify that the validator's pops and pushes to and from
125139
// the operand stack match the operator's arity.
126140
#[cfg(debug_assertions)]
@@ -194,14 +208,30 @@ arity mismatch in validation
194208

195209
/// Validates the next operator in a function.
196210
///
197-
/// This functions is expected to be called once-per-operator in a
211+
/// This function is expected to be called once-per-operator in a
198212
/// WebAssembly function. Each operator's offset in the original binary and
199213
/// the operator itself are passed to this function to provide more useful
200-
/// error messages.
214+
/// error messages. On error, the validator may be left in an undefined
215+
/// state and should not be reused.
201216
pub fn op(&mut self, offset: usize, operator: &Operator<'_>) -> Result<()> {
202217
self.visitor(offset).visit_operator(operator)
203218
}
204219

220+
/// Validates the next operator in a function, rolling back the validator
221+
/// to its previous state if this is unsuccesful. The validator may be reused
222+
/// even after an error.
223+
#[cfg(feature = "try-op")]
224+
pub fn try_op(&mut self, offset: usize, operator: &Operator<'_>) -> Result<()> {
225+
self.validator.begin_try_op();
226+
let res = self.op(offset, operator);
227+
if res.is_ok() {
228+
self.validator.commit();
229+
} else {
230+
self.validator.rollback();
231+
}
232+
res
233+
}
234+
205235
/// Get the operator visitor for the next operator in the function.
206236
///
207237
/// The returned visitor is intended to visit just one instruction at the `offset`.

0 commit comments

Comments
 (0)