Skip to content

Commit eb4e29f

Browse files
committed
Respond to review
1 parent 1d57f7e commit eb4e29f

File tree

6 files changed

+199
-162
lines changed

6 files changed

+199
-162
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ jobs:
154154
- run: cargo test --locked -p wasm-encoder --all-features
155155
- run: cargo test -p wasm-smith --features wasmparser
156156
- run: cargo test -p wasm-smith --features component-model
157-
- run: cargo test --locked -p wasmparser --features atomic
158-
- run: cargo test --locked -F wasmparser/atomic
157+
- run: cargo test --locked -p wasmparser --features try-op
158+
- run: cargo test --locked -F wasmparser/try-op
159159

160160
test_capi:
161161
name: Test the C API
@@ -278,7 +278,7 @@ jobs:
278278
- run: cargo check --no-default-features -p wasmparser --target x86_64-unknown-none --features validate,serde,prefer-btree-collections
279279
- run: cargo check --no-default-features -p wasmparser --features std
280280
- run: cargo check --no-default-features -p wasmparser --features validate
281-
- run: cargo check --no-default-features -p wasmparser --features validate,atomic
281+
- run: cargo check --no-default-features -p wasmparser --features validate,try-op
282282
- run: cargo check --no-default-features -p wasmparser --features features
283283
- run: cargo check --no-default-features -p wasmparser --features features,validate
284284
- run: cargo check --no-default-features -p wasmparser --features prefer-btree-collections

crates/wasmparser/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ simd = []
9090

9191
# A feature that allows validating an operator atomically, leaving the validator
9292
# unchanged if the operator is invalid.
93-
atomic = []
93+
try-op = []

crates/wasmparser/src/validator/func.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ impl<T: WasmModuleResources> FuncValidator<T> {
124124
while !reader.eof() {
125125
// In a debug build, verify that `rollback` successfully returns the
126126
// validator to its previous state after each (valid or invalid) operator.
127-
#[cfg(all(debug_assertions, feature = "atomic"))]
127+
#[cfg(all(debug_assertions, feature = "try-op"))]
128128
{
129129
let snapshot = self.validator.clone();
130130
let op = reader.peek_operator(&self.visitor(reader.original_position()))?;
131-
self.validator.begin_atomic_op();
131+
self.validator.begin_try_op();
132132
let _ = self.op(reader.original_position(), &op);
133133
self.validator.rollback();
134134
self.validator.pop_push_log.clear();
@@ -208,19 +208,21 @@ arity mismatch in validation
208208

209209
/// Validates the next operator in a function.
210210
///
211-
/// 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
212212
/// WebAssembly function. Each operator's offset in the original binary and
213213
/// the operator itself are passed to this function to provide more useful
214-
/// error messages.
214+
/// error messages. On error, the validator may be left in an undefined
215+
/// state and should not be reused.
215216
pub fn op(&mut self, offset: usize, operator: &Operator<'_>) -> Result<()> {
216217
self.visitor(offset).visit_operator(operator)
217218
}
218219

219220
/// Validates the next operator in a function, rolling back the validator
220-
/// to its previous state if this is unsuccesful.
221-
#[cfg(feature = "atomic")]
222-
pub fn atomic_op(&mut self, offset: usize, operator: &Operator<'_>) -> Result<()> {
223-
self.validator.begin_atomic_op();
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();
224226
let res = self.op(offset, operator);
225227
if res.is_ok() {
226228
self.validator.commit();

crates/wasmparser/src/validator/operators.rs

Lines changed: 35 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ use core::{cmp, iter, mem};
3838
#[cfg(feature = "simd")]
3939
mod simd;
4040

41+
#[cfg(feature = "try-op")]
42+
mod transaction;
43+
#[cfg(not(feature = "try-op"))]
44+
mod transaction_disabled;
45+
#[cfg(not(feature = "try-op"))]
46+
use transaction_disabled as transaction;
47+
48+
use transaction::{RollbackLogAllocations, Transaction};
49+
4150
#[derive(Clone, PartialEq)]
4251
pub(crate) struct OperatorValidator {
4352
pub(super) locals: Locals,
@@ -65,128 +74,12 @@ pub(crate) struct OperatorValidator {
6574
#[cfg(debug_assertions)]
6675
pub(crate) pop_push_log: Vec<bool>,
6776

68-
/// When "atomic" validation of an operator is pending, this is a trace
77+
/// When "try-op" validation of an operator is pending, this is a trace
6978
/// of discarded info that can restore the OperatorValidator to its
7079
/// pre-operator state if necessary.
71-
#[cfg(feature = "atomic")]
7280
transaction: Transaction,
7381
}
7482

75-
#[cfg(feature = "atomic")]
76-
#[derive(Clone, PartialEq)]
77-
enum Transaction {
78-
Active(RollbackLog),
79-
Inactive(RollbackLogAllocations),
80-
}
81-
82-
#[cfg(feature = "atomic")]
83-
#[derive(Clone, Default, PartialEq)]
84-
struct RollbackLog {
85-
/// A trace of operands popped. Pushes are recorded as `None`.
86-
operands: Vec<Option<MaybeType>>,
87-
88-
/// A trace of frames popped. Pushes are recorded as `None`.
89-
frames: Vec<Option<Frame>>,
90-
91-
/// A trace of local init indices reset at the end of a frame.
92-
inits: Vec<u32>,
93-
94-
/// The local init height when transaction was begun.
95-
init_height: usize,
96-
97-
/// Whether the current frame was made unreachable.
98-
unreachable: bool,
99-
}
100-
101-
#[cfg(feature = "atomic")]
102-
#[derive(Clone, Default, PartialEq)]
103-
struct RollbackLogAllocations {
104-
operands: Vec<Option<MaybeType>>,
105-
frames: Vec<Option<Frame>>,
106-
inits: Vec<u32>,
107-
}
108-
109-
#[cfg(feature = "atomic")]
110-
impl Transaction {
111-
fn begin(&mut self, init_height: usize) {
112-
match self {
113-
Transaction::Active(_) => panic!("transaction already in progress"),
114-
Transaction::Inactive(allocs) => {
115-
*self = Transaction::Active(RollbackLog::new(init_height, mem::take(allocs)))
116-
}
117-
}
118-
}
119-
120-
fn end(&mut self) {
121-
if let Transaction::Active(log) = self {
122-
*self = Transaction::Inactive(mem::take(log).into_allocations())
123-
}
124-
}
125-
126-
fn into_allocations(self) -> RollbackLogAllocations {
127-
match self {
128-
Transaction::Active(log) => log.into_allocations(),
129-
Transaction::Inactive(allocs) => allocs,
130-
}
131-
}
132-
133-
fn map(&mut self, f: impl FnOnce(&mut RollbackLog)) {
134-
if let Transaction::Active(log) = self {
135-
f(log);
136-
}
137-
}
138-
}
139-
140-
#[cfg(feature = "atomic")]
141-
impl RollbackLog {
142-
fn new(init_height: usize, allocs: RollbackLogAllocations) -> Self {
143-
let RollbackLogAllocations {
144-
operands,
145-
frames,
146-
inits,
147-
} = allocs;
148-
debug_assert!(operands.is_empty());
149-
debug_assert!(frames.is_empty());
150-
debug_assert!(inits.is_empty());
151-
Self {
152-
operands,
153-
frames,
154-
inits,
155-
init_height,
156-
unreachable: false,
157-
}
158-
}
159-
160-
fn into_allocations(self) -> RollbackLogAllocations {
161-
fn clear<T>(mut tmp: Vec<T>) -> Vec<T> {
162-
tmp.clear();
163-
tmp
164-
}
165-
RollbackLogAllocations {
166-
operands: clear(self.operands),
167-
frames: clear(self.frames),
168-
inits: clear(self.inits),
169-
}
170-
}
171-
172-
fn record_push(&mut self) {
173-
self.operands.push(None);
174-
}
175-
176-
fn record_pop(&mut self, ty: MaybeType) {
177-
self.operands.push(Some(ty));
178-
}
179-
180-
fn push_ctrl(&mut self) {
181-
self.frames.push(None);
182-
}
183-
184-
fn pop_ctrl(&mut self, frame: Frame, inits: Vec<u32>) {
185-
self.frames.push(Some(frame));
186-
self.inits.extend(inits);
187-
}
188-
}
189-
19083
/// Captures the initialization of non-defaultable locals.
19184
#[derive(Clone, PartialEq)]
19285
struct LocalInits {
@@ -353,7 +246,6 @@ pub struct OperatorValidatorAllocations {
353246
local_inits: LocalInits,
354247
locals_first: Vec<ValType>,
355248
locals_uncached: Vec<(u32, ValType)>,
356-
#[cfg(feature = "atomic")]
357249
rollback_log: RollbackLogAllocations,
358250
}
359251

@@ -460,7 +352,6 @@ impl OperatorValidator {
460352
local_inits,
461353
locals_first,
462354
locals_uncached,
463-
#[cfg(feature = "atomic")]
464355
rollback_log,
465356
} = allocs;
466357
debug_assert!(popped_types_tmp.is_empty());
@@ -483,8 +374,7 @@ impl OperatorValidator {
483374
shared: false,
484375
#[cfg(debug_assertions)]
485376
pop_push_log: vec![],
486-
#[cfg(feature = "atomic")]
487-
transaction: Transaction::Inactive(rollback_log),
377+
transaction: Transaction::new(rollback_log),
488378
}
489379
}
490380

@@ -672,39 +562,46 @@ impl OperatorValidator {
672562
},
673563
locals_first: clear(self.locals.first),
674564
locals_uncached: clear(self.locals.uncached),
675-
#[cfg(feature = "atomic")]
676565
rollback_log: self.transaction.into_allocations(),
677566
}
678567
}
679568

680-
fn record_pop(&mut self) {
569+
// records a pop that mutated the operand stack
570+
fn record_pop(&mut self, ty: MaybeType) {
571+
self.transaction.map(|log| log.record_pop(ty));
572+
self.record_any_pop();
573+
}
574+
575+
// records any pop, including a Bottom synthesized from an empty polymorphic operand stack
576+
fn record_any_pop(&mut self) {
681577
#[cfg(debug_assertions)]
682578
{
683579
self.pop_push_log.push(false);
684580
}
685581
}
686582

687583
fn record_push(&mut self) {
584+
self.transaction.map(|log| log.record_push());
688585
#[cfg(debug_assertions)]
689586
{
690587
self.pop_push_log.push(true);
691588
}
692589
}
693590

694-
#[cfg(feature = "atomic")]
695-
pub(super) fn begin_atomic_op(&mut self) {
591+
#[allow(dead_code)]
592+
pub(super) fn begin_try_op(&mut self) {
696593
self.transaction.begin(self.local_inits.height());
697594
}
698595

699-
#[cfg(feature = "atomic")]
596+
#[allow(dead_code)]
700597
pub(super) fn commit(&mut self) {
701598
self.transaction.end();
702599
}
703600

704-
/// Reverse the actions in the rollback log. This is used by `FuncValidator::atomic_op()`
601+
/// Reverse the actions in the rollback log. This is used by `FuncValidator::try_op()`
705602
/// if validating the operator fails. The rollback log is sufficient to handle
706603
/// the mutations of any individual operator (but not necessarily multiple operators).
707-
#[cfg(feature = "atomic")]
604+
#[cfg(feature = "try-op")]
708605
pub(super) fn rollback(&mut self) {
709606
let Transaction::Active(rollback_log) = &self.transaction else {
710607
panic!("no transaction pending");
@@ -793,8 +690,6 @@ where
793690

794691
self.operands.push(maybe_ty);
795692
self.record_push();
796-
#[cfg(feature = "atomic")]
797-
self.transaction.map(|log| log.record_push());
798693
Ok(())
799694
}
800695

@@ -919,10 +814,7 @@ where
919814
if Some(actual_ty) == expected {
920815
if let Some(control) = self.control.last() {
921816
if self.operands.len() >= control.height {
922-
#[cfg(feature = "atomic")]
923-
self.transaction
924-
.map(|log| log.record_pop(MaybeType::Known(actual_ty)));
925-
self.record_pop();
817+
self.record_pop(MaybeType::Known(actual_ty));
926818
return Ok(MaybeType::Known(actual_ty));
927819
}
928820
}
@@ -947,6 +839,7 @@ where
947839
self.operands.extend(popped);
948840
let control = self.control.last().unwrap();
949841
let actual = if self.operands.len() == control.height && control.unreachable {
842+
self.record_any_pop();
950843
MaybeType::Bottom
951844
} else {
952845
if self.operands.len() == control.height {
@@ -960,8 +853,7 @@ where
960853
)
961854
} else {
962855
let ty = self.operands.pop().unwrap();
963-
#[cfg(feature = "atomic")]
964-
self.transaction.map(|log| log.record_pop(ty));
856+
self.record_pop(ty);
965857
ty
966858
}
967859
};
@@ -1022,7 +914,6 @@ where
1022914
}
1023915
}
1024916
}
1025-
self.record_pop();
1026917
Ok(actual)
1027918
}
1028919

@@ -1135,24 +1026,20 @@ where
11351026
/// Flags the current control frame as unreachable, additionally truncating
11361027
/// the currently active operand stack.
11371028
fn unreachable(&mut self) -> Result<()> {
1138-
#[cfg(feature = "atomic")]
11391029
if !self.control.last().unwrap().unreachable {
1140-
self.transaction.map(|log| log.unreachable = true);
1030+
self.transaction.map(|log| log.set_unreachable());
11411031
}
11421032

11431033
let control = self.control.last_mut().unwrap();
11441034
control.unreachable = true;
11451035
let new_height = control.height;
11461036

1147-
#[cfg(feature = "atomic")]
1148-
{
1149-
let ops = self.operands.split_off(new_height);
1150-
self.transaction.map(|log| {
1151-
for op in ops.iter().rev() {
1152-
log.record_pop(*op);
1153-
}
1154-
});
1155-
}
1037+
let operands = self.operands.split_off(new_height);
1038+
self.transaction.map(|log| {
1039+
for op in operands.iter().rev() {
1040+
log.record_pop(*op);
1041+
}
1042+
});
11561043

11571044
self.operands.truncate(new_height);
11581045
Ok(())
@@ -1188,7 +1075,6 @@ where
11881075
unreachable: false,
11891076
init_height,
11901077
});
1191-
#[cfg(feature = "atomic")]
11921078
self.transaction.map(|log| log.push_ctrl());
11931079
}
11941080

@@ -1221,7 +1107,6 @@ where
12211107
// And then we can remove it and reset_locals.
12221108
let frame = self.control.pop().unwrap();
12231109
let _inits = self.local_inits.pop_ctrl(frame.init_height);
1224-
#[cfg(feature = "atomic")]
12251110
self.transaction.map(|log| log.pop_ctrl(frame, _inits));
12261111

12271112
Ok(frame)

0 commit comments

Comments
 (0)