/
operators.rs
3466 lines (3350 loc) · 121 KB
/
operators.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright 2019 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// The basic validation algorithm here is copied from the "Validation
// Algorithm" section of the WebAssembly specification -
// https://webassembly.github.io/spec/core/appendix/algorithm.html.
//
// That algorithm is followed pretty closely here, namely `push_operand`,
// `pop_operand`, `push_ctrl`, and `pop_ctrl`. If anything here is a bit
// confusing it's recommended to read over that section to see how it maps to
// the various methods here.
use crate::{
limits::MAX_WASM_FUNCTION_LOCALS, BinaryReaderError, BlockType, BrTable, HeapType, Ieee32,
Ieee64, MemArg, RefType, Result, ValType, VisitOperator, WasmFeatures, WasmFuncType,
WasmModuleResources, V128,
};
use std::ops::{Deref, DerefMut};
pub(crate) struct OperatorValidator {
pub(super) locals: Locals,
pub(super) local_inits: Vec<bool>,
// This is a list of flags for wasm features which are used to gate various
// instructions.
pub(crate) features: WasmFeatures,
// Temporary storage used during the validation of `br_table`.
br_table_tmp: Vec<MaybeType>,
/// The `control` list is the list of blocks that we're currently in.
control: Vec<Frame>,
/// The `operands` is the current type stack.
operands: Vec<MaybeType>,
/// When local_inits is modified, the relevant index is recorded here to be
/// undone when control pops
inits: Vec<u32>,
/// Offset of the `end` instruction which emptied the `control` stack, which
/// must be the end of the function.
end_which_emptied_control: Option<usize>,
}
// No science was performed in the creation of this number, feel free to change
// it if you so like.
const MAX_LOCALS_TO_TRACK: usize = 50;
pub(super) struct Locals {
// Total number of locals in the function.
num_locals: u32,
// The first MAX_LOCALS_TO_TRACK locals in a function. This is used to
// optimize the theoretically common case where most functions don't have
// many locals and don't need a full binary search in the entire local space
// below.
first: Vec<ValType>,
// This is a "compressed" list of locals for this function. The list of
// locals are represented as a list of tuples. The second element is the
// type of the local, and the first element is monotonically increasing as
// you visit elements of this list. The first element is the maximum index
// of the local, after the previous index, of the type specified.
//
// This allows us to do a binary search on the list for a local's index for
// `local.{get,set,tee}`. We do a binary search for the index desired, and
// it either lies in a "hole" where the maximum index is specified later,
// or it's at the end of the list meaning it's out of bounds.
all: Vec<(u32, ValType)>,
}
/// A Wasm control flow block on the control flow stack during Wasm validation.
//
// # Dev. Note
//
// This structure corresponds to `ctrl_frame` as specified at in the validation
// appendix of the wasm spec
#[derive(Debug, Copy, Clone)]
pub struct Frame {
/// Indicator for what kind of instruction pushed this frame.
pub kind: FrameKind,
/// The type signature of this frame, represented as a singular return type
/// or a type index pointing into the module's types.
pub block_type: BlockType,
/// The index, below which, this frame cannot modify the operand stack.
pub height: usize,
/// Whether this frame is unreachable so far.
pub unreachable: bool,
/// The number of initializations in the stack at the time of its creation
pub init_height: usize,
}
/// The kind of a control flow [`Frame`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FrameKind {
/// A Wasm `block` control block.
Block,
/// A Wasm `if` control block.
If,
/// A Wasm `else` control block.
Else,
/// A Wasm `loop` control block.
Loop,
/// A Wasm `try` control block.
///
/// # Note
///
/// This belongs to the Wasm exception handling proposal.
Try,
/// A Wasm `catch` control block.
///
/// # Note
///
/// This belongs to the Wasm exception handling proposal.
Catch,
/// A Wasm `catch_all` control block.
///
/// # Note
///
/// This belongs to the Wasm exception handling proposal.
CatchAll,
}
struct OperatorValidatorTemp<'validator, 'resources, T> {
offset: usize,
inner: &'validator mut OperatorValidator,
resources: &'resources T,
}
#[derive(Default)]
pub struct OperatorValidatorAllocations {
br_table_tmp: Vec<MaybeType>,
control: Vec<Frame>,
operands: Vec<MaybeType>,
local_inits: Vec<bool>,
inits: Vec<u32>,
locals_first: Vec<ValType>,
locals_all: Vec<(u32, ValType)>,
}
/// Type storage within the validator.
///
/// This is used to manage the operand stack and notably isn't just `ValType` to
/// handle unreachable code and the "bottom" type.
#[derive(Debug, Copy, Clone)]
enum MaybeType {
Bot,
HeapBot,
Type(ValType),
}
// The validator is pretty performance-sensitive and `MaybeType` is the main
// unit of storage, so assert that it doesn't exceed 4 bytes which is the
// current expected size.
const _: () = {
assert!(std::mem::size_of::<MaybeType>() == 4);
};
impl From<ValType> for MaybeType {
fn from(ty: ValType) -> MaybeType {
MaybeType::Type(ty)
}
}
impl From<RefType> for MaybeType {
fn from(ty: RefType) -> MaybeType {
let ty: ValType = ty.into();
ty.into()
}
}
impl OperatorValidator {
fn new(features: &WasmFeatures, allocs: OperatorValidatorAllocations) -> Self {
let OperatorValidatorAllocations {
br_table_tmp,
control,
operands,
local_inits,
inits,
locals_first,
locals_all,
} = allocs;
debug_assert!(br_table_tmp.is_empty());
debug_assert!(control.is_empty());
debug_assert!(operands.is_empty());
debug_assert!(local_inits.is_empty());
debug_assert!(inits.is_empty());
debug_assert!(locals_first.is_empty());
debug_assert!(locals_all.is_empty());
OperatorValidator {
locals: Locals {
num_locals: 0,
first: locals_first,
all: locals_all,
},
local_inits,
inits,
features: *features,
br_table_tmp,
operands,
control,
end_which_emptied_control: None,
}
}
/// Creates a new operator validator which will be used to validate a
/// function whose type is the `ty` index specified.
///
/// The `resources` are used to learn about the function type underlying
/// `ty`.
pub fn new_func<T>(
ty: u32,
offset: usize,
features: &WasmFeatures,
resources: &T,
allocs: OperatorValidatorAllocations,
) -> Result<Self>
where
T: WasmModuleResources,
{
let mut ret = OperatorValidator::new(features, allocs);
ret.control.push(Frame {
kind: FrameKind::Block,
block_type: BlockType::FuncType(ty),
height: 0,
unreachable: false,
init_height: 0,
});
let params = OperatorValidatorTemp {
// This offset is used by the `func_type_at` and `inputs`.
offset,
inner: &mut ret,
resources,
}
.func_type_at(ty)?
.inputs();
for ty in params {
ret.locals.define(1, ty);
ret.local_inits.push(true);
}
Ok(ret)
}
/// Creates a new operator validator which will be used to validate an
/// `init_expr` constant expression which should result in the `ty`
/// specified.
pub fn new_const_expr(
features: &WasmFeatures,
ty: ValType,
allocs: OperatorValidatorAllocations,
) -> Self {
let mut ret = OperatorValidator::new(features, allocs);
ret.control.push(Frame {
kind: FrameKind::Block,
block_type: BlockType::Type(ty),
height: 0,
unreachable: false,
init_height: 0,
});
ret
}
pub fn define_locals(
&mut self,
offset: usize,
count: u32,
ty: ValType,
resources: &impl WasmModuleResources,
) -> Result<()> {
resources.check_value_type(ty, &self.features, offset)?;
if count == 0 {
return Ok(());
}
if !self.locals.define(count, ty) {
return Err(BinaryReaderError::new(
"too many locals: locals exceed maximum",
offset,
));
}
self.local_inits
.resize(self.local_inits.len() + count as usize, ty.is_defaultable());
Ok(())
}
/// Returns the current operands stack height.
pub fn operand_stack_height(&self) -> usize {
self.operands.len()
}
/// Returns the optional value type of the value operand at the given
/// `depth` from the top of the operand stack.
///
/// - Returns `None` if the `depth` is out of bounds.
/// - Returns `Some(None)` if there is a value with unknown type
/// at the given `depth`.
///
/// # Note
///
/// A `depth` of 0 will refer to the last operand on the stack.
pub fn peek_operand_at(&self, depth: usize) -> Option<Option<ValType>> {
Some(match self.operands.iter().rev().nth(depth)? {
MaybeType::Type(t) => Some(*t),
MaybeType::Bot | MaybeType::HeapBot => None,
})
}
/// Returns the number of frames on the control flow stack.
pub fn control_stack_height(&self) -> usize {
self.control.len()
}
pub fn get_frame(&self, depth: usize) -> Option<&Frame> {
self.control.iter().rev().nth(depth)
}
/// Create a temporary [`OperatorValidatorTemp`] for validation.
pub fn with_resources<'a, 'validator, 'resources, T>(
&'validator mut self,
resources: &'resources T,
offset: usize,
) -> impl VisitOperator<'a, Output = Result<()>> + 'validator
where
T: WasmModuleResources,
'resources: 'validator,
{
WasmProposalValidator(OperatorValidatorTemp {
offset,
inner: self,
resources,
})
}
pub fn finish(&mut self, offset: usize) -> Result<()> {
if self.control.last().is_some() {
bail!(
offset,
"control frames remain at end of function: END opcode expected"
);
}
// The `end` opcode is one byte which means that the `offset` here
// should point just beyond the `end` opcode which emptied the control
// stack. If not that means more instructions were present after the
// control stack was emptied.
if offset != self.end_which_emptied_control.unwrap() + 1 {
return Err(self.err_beyond_end(offset));
}
Ok(())
}
fn err_beyond_end(&self, offset: usize) -> BinaryReaderError {
format_err!(offset, "operators remaining after end of function")
}
pub fn into_allocations(self) -> OperatorValidatorAllocations {
fn truncate<T>(mut tmp: Vec<T>) -> Vec<T> {
tmp.truncate(0);
tmp
}
OperatorValidatorAllocations {
br_table_tmp: truncate(self.br_table_tmp),
control: truncate(self.control),
operands: truncate(self.operands),
local_inits: truncate(self.local_inits),
inits: truncate(self.inits),
locals_first: truncate(self.locals.first),
locals_all: truncate(self.locals.all),
}
}
}
impl<R> Deref for OperatorValidatorTemp<'_, '_, R> {
type Target = OperatorValidator;
fn deref(&self) -> &OperatorValidator {
self.inner
}
}
impl<R> DerefMut for OperatorValidatorTemp<'_, '_, R> {
fn deref_mut(&mut self) -> &mut OperatorValidator {
self.inner
}
}
impl<'resources, R: WasmModuleResources> OperatorValidatorTemp<'_, 'resources, R> {
/// Pushes a type onto the operand stack.
///
/// This is used by instructions to represent a value that is pushed to the
/// operand stack. This can fail, but only if `Type` is feature gated.
/// Otherwise the push operation always succeeds.
fn push_operand<T>(&mut self, ty: T) -> Result<()>
where
T: Into<MaybeType>,
{
let maybe_ty = ty.into();
self.operands.push(maybe_ty);
Ok(())
}
/// Attempts to pop a type from the operand stack.
///
/// This function is used to remove types from the operand stack. The
/// `expected` argument can be used to indicate that a type is required, or
/// simply that something is needed to be popped.
///
/// If `expected` is `Some(T)` then this will be guaranteed to return
/// `T`, and it will only return success if the current block is
/// unreachable or if `T` was found at the top of the operand stack.
///
/// If `expected` is `None` then it indicates that something must be on the
/// operand stack, but it doesn't matter what's on the operand stack. This
/// is useful for polymorphic instructions like `select`.
///
/// If `Some(T)` is returned then `T` was popped from the operand stack and
/// matches `expected`. If `None` is returned then it means that `None` was
/// expected and a type was successfully popped, but its exact type is
/// indeterminate because the current block is unreachable.
fn pop_operand(&mut self, expected: Option<ValType>) -> Result<MaybeType> {
// This method is one of the hottest methods in the validator so to
// improve codegen this method contains a fast-path success case where
// if the top operand on the stack is as expected it's returned
// immediately. This is the most common case where the stack will indeed
// have the expected type and all we need to do is pop it off.
//
// Note that this still has to be careful to be correct, though. For
// efficiency an operand is unconditionally popped and on success it is
// matched against the state of the world to see if we could actually
// pop it. If we shouldn't have popped it then it's passed to the slow
// path to get pushed back onto the stack.
let popped = match self.operands.pop() {
Some(MaybeType::Type(actual_ty)) => {
if Some(actual_ty) == expected {
if let Some(control) = self.control.last() {
if self.operands.len() >= control.height {
return Ok(MaybeType::Type(actual_ty));
}
}
}
Some(MaybeType::Type(actual_ty))
}
other => other,
};
self._pop_operand(expected, popped)
}
// This is the "real" implementation of `pop_operand` which is 100%
// spec-compliant with little attention paid to efficiency since this is the
// slow-path from the actual `pop_operand` function above.
#[cold]
fn _pop_operand(
&mut self,
expected: Option<ValType>,
popped: Option<MaybeType>,
) -> Result<MaybeType> {
self.operands.extend(popped);
let control = match self.control.last() {
Some(c) => c,
None => return Err(self.err_beyond_end(self.offset)),
};
let actual = if self.operands.len() == control.height && control.unreachable {
MaybeType::Bot
} else {
if self.operands.len() == control.height {
let desc = match expected {
Some(ty) => ty_to_str(ty),
None => "a type",
};
bail!(
self.offset,
"type mismatch: expected {desc} but nothing on stack"
)
} else {
self.operands.pop().unwrap()
}
};
if let Some(expected) = expected {
match (actual, expected) {
// The bottom type matches all expectations
(MaybeType::Bot, _)
// The "heap bottom" type only matches other references types,
// but not any integer types.
| (MaybeType::HeapBot, ValType::Ref(_)) => {}
// Use the `matches` predicate to test if a found type matches
// the expectation.
(MaybeType::Type(actual), expected) => {
if !self.resources.matches(actual, expected) {
bail!(
self.offset,
"type mismatch: expected {}, found {}",
ty_to_str(expected),
ty_to_str(actual)
);
}
}
// A "heap bottom" type cannot match any numeric types.
(
MaybeType::HeapBot,
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128,
) => {
bail!(
self.offset,
"type mismatch: expected {}, found heap type",
ty_to_str(expected)
)
}
}
}
Ok(actual)
}
fn pop_ref(&mut self) -> Result<Option<RefType>> {
match self.pop_operand(None)? {
MaybeType::Bot | MaybeType::HeapBot => Ok(None),
MaybeType::Type(ValType::Ref(rt)) => Ok(Some(rt)),
MaybeType::Type(ty) => bail!(
self.offset,
"type mismatch: expected ref but found {}",
ty_to_str(ty)
),
}
}
/// Fetches the type for the local at `idx`, returning an error if it's out
/// of bounds.
fn local(&self, idx: u32) -> Result<ValType> {
match self.locals.get(idx) {
Some(ty) => Ok(ty),
None => bail!(
self.offset,
"unknown local {}: local index out of bounds",
idx
),
}
}
/// Flags the current control frame as unreachable, additionally truncating
/// the currently active operand stack.
fn unreachable(&mut self) -> Result<()> {
let control = match self.control.last_mut() {
Some(frame) => frame,
None => return Err(self.err_beyond_end(self.offset)),
};
control.unreachable = true;
let new_height = control.height;
self.operands.truncate(new_height);
Ok(())
}
/// Pushes a new frame onto the control stack.
///
/// This operation is used when entering a new block such as an if, loop,
/// or block itself. The `kind` of block is specified which indicates how
/// breaks interact with this block's type. Additionally the type signature
/// of the block is specified by `ty`.
fn push_ctrl(&mut self, kind: FrameKind, ty: BlockType) -> Result<()> {
// Push a new frame which has a snapshot of the height of the current
// operand stack.
let height = self.operands.len();
let init_height = self.inits.len();
self.control.push(Frame {
kind,
block_type: ty,
height,
unreachable: false,
init_height,
});
// All of the parameters are now also available in this control frame,
// so we push them here in order.
for ty in self.params(ty)? {
self.push_operand(ty)?;
}
Ok(())
}
/// Pops a frame from the control stack.
///
/// This function is used when exiting a block and leaves a block scope.
/// Internally this will validate that blocks have the correct result type.
fn pop_ctrl(&mut self) -> Result<Frame> {
// Read the expected type and expected height of the operand stack the
// end of the frame.
let frame = match self.control.last() {
Some(f) => f,
None => return Err(self.err_beyond_end(self.offset)),
};
let ty = frame.block_type;
let height = frame.height;
let init_height = frame.init_height;
// reset_locals in the spec
for init in self.inits.split_off(init_height) {
self.local_inits[init as usize] = false;
}
// Pop all the result types, in reverse order, from the operand stack.
// These types will, possibly, be transferred to the next frame.
for ty in self.results(ty)?.rev() {
self.pop_operand(Some(ty))?;
}
// Make sure that the operand stack has returned to is original
// height...
if self.operands.len() != height {
bail!(
self.offset,
"type mismatch: values remaining on stack at end of block"
);
}
// And then we can remove it!
Ok(self.control.pop().unwrap())
}
/// Validates a relative jump to the `depth` specified.
///
/// Returns the type signature of the block that we're jumping to as well
/// as the kind of block if the jump is valid. Otherwise returns an error.
fn jump(&self, depth: u32) -> Result<(BlockType, FrameKind)> {
if self.control.is_empty() {
return Err(self.err_beyond_end(self.offset));
}
match (self.control.len() - 1).checked_sub(depth as usize) {
Some(i) => {
let frame = &self.control[i];
Ok((frame.block_type, frame.kind))
}
None => bail!(self.offset, "unknown label: branch depth too large"),
}
}
/// Validates that `memory_index` is valid in this module, and returns the
/// type of address used to index the memory specified.
fn check_memory_index(&self, memory_index: u32) -> Result<ValType> {
match self.resources.memory_at(memory_index) {
Some(mem) => Ok(mem.index_type()),
None => bail!(self.offset, "unknown memory {}", memory_index),
}
}
/// Validates a `memarg for alignment and such (also the memory it
/// references), and returns the type of index used to address the memory.
fn check_memarg(&self, memarg: MemArg) -> Result<ValType> {
let index_ty = self.check_memory_index(memarg.memory)?;
if memarg.align > memarg.max_align {
bail!(self.offset, "alignment must not be larger than natural");
}
if index_ty == ValType::I32 && memarg.offset > u64::from(u32::MAX) {
bail!(self.offset, "offset out of range: must be <= 2**32");
}
Ok(index_ty)
}
fn check_floats_enabled(&self) -> Result<()> {
if !self.features.floats {
bail!(self.offset, "floating-point instruction disallowed");
}
Ok(())
}
fn check_shared_memarg(&self, memarg: MemArg) -> Result<ValType> {
if memarg.align != memarg.max_align {
bail!(
self.offset,
"atomic instructions must always specify maximum alignment"
);
}
self.check_memory_index(memarg.memory)
}
fn check_simd_lane_index(&self, index: u8, max: u8) -> Result<()> {
if index >= max {
bail!(self.offset, "SIMD index out of bounds");
}
Ok(())
}
/// Validates a block type, primarily with various in-flight proposals.
fn check_block_type(&self, ty: BlockType) -> Result<()> {
match ty {
BlockType::Empty => Ok(()),
BlockType::Type(t) => self
.resources
.check_value_type(t, &self.features, self.offset),
BlockType::FuncType(idx) => {
if !self.features.multi_value {
bail!(
self.offset,
"blocks, loops, and ifs may only produce a resulttype \
when multi-value is not enabled",
);
}
self.func_type_at(idx)?;
Ok(())
}
}
}
/// Validates a `call` instruction, ensuring that the function index is
/// in-bounds and the right types are on the stack to call the function.
fn check_call(&mut self, function_index: u32) -> Result<()> {
let ty = match self.resources.type_index_of_function(function_index) {
Some(i) => i,
None => {
bail!(
self.offset,
"unknown function {function_index}: function index out of bounds",
);
}
};
self.check_call_ty(ty)
}
fn check_call_ty(&mut self, type_index: u32) -> Result<()> {
let ty = match self.resources.func_type_at(type_index) {
Some(i) => i,
None => {
bail!(
self.offset,
"unknown type {type_index}: type index out of bounds",
);
}
};
for ty in ty.inputs().rev() {
self.pop_operand(Some(ty))?;
}
for ty in ty.outputs() {
self.push_operand(ty)?;
}
Ok(())
}
/// Validates a call to an indirect function, very similar to `check_call`.
fn check_call_indirect(&mut self, index: u32, table_index: u32) -> Result<()> {
match self.resources.table_at(table_index) {
None => {
bail!(self.offset, "unknown table: table index out of bounds");
}
Some(tab) => {
if !self
.resources
.matches(ValType::Ref(tab.element_type), ValType::FUNCREF)
{
bail!(
self.offset,
"indirect calls must go through a table with type <= funcref",
);
}
}
}
let ty = self.func_type_at(index)?;
self.pop_operand(Some(ValType::I32))?;
for ty in ty.inputs().rev() {
self.pop_operand(Some(ty))?;
}
for ty in ty.outputs() {
self.push_operand(ty)?;
}
Ok(())
}
/// Validates a `return` instruction, popping types from the operand
/// stack that the function needs.
fn check_return(&mut self) -> Result<()> {
if self.control.is_empty() {
return Err(self.err_beyond_end(self.offset));
}
for ty in self.results(self.control[0].block_type)?.rev() {
self.pop_operand(Some(ty))?;
}
self.unreachable()?;
Ok(())
}
/// Checks the validity of a common comparison operator.
fn check_cmp_op(&mut self, ty: ValType) -> Result<()> {
self.pop_operand(Some(ty))?;
self.pop_operand(Some(ty))?;
self.push_operand(ValType::I32)?;
Ok(())
}
/// Checks the validity of a common float comparison operator.
fn check_fcmp_op(&mut self, ty: ValType) -> Result<()> {
debug_assert!(matches!(ty, ValType::F32 | ValType::F64));
self.check_floats_enabled()?;
self.check_cmp_op(ty)
}
/// Checks the validity of a common unary operator.
fn check_unary_op(&mut self, ty: ValType) -> Result<()> {
self.pop_operand(Some(ty))?;
self.push_operand(ty)?;
Ok(())
}
/// Checks the validity of a common unary float operator.
fn check_funary_op(&mut self, ty: ValType) -> Result<()> {
debug_assert!(matches!(ty, ValType::F32 | ValType::F64));
self.check_floats_enabled()?;
self.check_unary_op(ty)
}
/// Checks the validity of a common conversion operator.
fn check_conversion_op(&mut self, into: ValType, from: ValType) -> Result<()> {
self.pop_operand(Some(from))?;
self.push_operand(into)?;
Ok(())
}
/// Checks the validity of a common conversion operator.
fn check_fconversion_op(&mut self, into: ValType, from: ValType) -> Result<()> {
debug_assert!(matches!(into, ValType::F32 | ValType::F64));
self.check_floats_enabled()?;
self.check_conversion_op(into, from)
}
/// Checks the validity of a common binary operator.
fn check_binary_op(&mut self, ty: ValType) -> Result<()> {
self.pop_operand(Some(ty))?;
self.pop_operand(Some(ty))?;
self.push_operand(ty)?;
Ok(())
}
/// Checks the validity of a common binary float operator.
fn check_fbinary_op(&mut self, ty: ValType) -> Result<()> {
debug_assert!(matches!(ty, ValType::F32 | ValType::F64));
self.check_floats_enabled()?;
self.check_binary_op(ty)
}
/// Checks the validity of an atomic load operator.
fn check_atomic_load(&mut self, memarg: MemArg, load_ty: ValType) -> Result<()> {
let ty = self.check_shared_memarg(memarg)?;
self.pop_operand(Some(ty))?;
self.push_operand(load_ty)?;
Ok(())
}
/// Checks the validity of an atomic store operator.
fn check_atomic_store(&mut self, memarg: MemArg, store_ty: ValType) -> Result<()> {
let ty = self.check_shared_memarg(memarg)?;
self.pop_operand(Some(store_ty))?;
self.pop_operand(Some(ty))?;
Ok(())
}
/// Checks the validity of a common atomic binary operator.
fn check_atomic_binary_op(&mut self, memarg: MemArg, op_ty: ValType) -> Result<()> {
let ty = self.check_shared_memarg(memarg)?;
self.pop_operand(Some(op_ty))?;
self.pop_operand(Some(ty))?;
self.push_operand(op_ty)?;
Ok(())
}
/// Checks the validity of an atomic compare exchange operator.
fn check_atomic_binary_cmpxchg(&mut self, memarg: MemArg, op_ty: ValType) -> Result<()> {
let ty = self.check_shared_memarg(memarg)?;
self.pop_operand(Some(op_ty))?;
self.pop_operand(Some(op_ty))?;
self.pop_operand(Some(ty))?;
self.push_operand(op_ty)?;
Ok(())
}
/// Checks a [`V128`] splat operator.
fn check_v128_splat(&mut self, src_ty: ValType) -> Result<()> {
self.pop_operand(Some(src_ty))?;
self.push_operand(ValType::V128)?;
Ok(())
}
/// Checks a [`V128`] binary operator.
fn check_v128_binary_op(&mut self) -> Result<()> {
self.pop_operand(Some(ValType::V128))?;
self.pop_operand(Some(ValType::V128))?;
self.push_operand(ValType::V128)?;
Ok(())
}
/// Checks a [`V128`] binary float operator.
fn check_v128_fbinary_op(&mut self) -> Result<()> {
self.check_floats_enabled()?;
self.check_v128_binary_op()
}
/// Checks a [`V128`] binary operator.
fn check_v128_unary_op(&mut self) -> Result<()> {
self.pop_operand(Some(ValType::V128))?;
self.push_operand(ValType::V128)?;
Ok(())
}
/// Checks a [`V128`] binary operator.
fn check_v128_funary_op(&mut self) -> Result<()> {
self.check_floats_enabled()?;
self.check_v128_unary_op()
}
/// Checks a [`V128`] relaxed ternary operator.
fn check_v128_ternary_op(&mut self) -> Result<()> {
self.pop_operand(Some(ValType::V128))?;
self.pop_operand(Some(ValType::V128))?;
self.pop_operand(Some(ValType::V128))?;
self.push_operand(ValType::V128)?;
Ok(())
}
/// Checks a [`V128`] relaxed ternary operator.
fn check_v128_bitmask_op(&mut self) -> Result<()> {
self.pop_operand(Some(ValType::V128))?;
self.push_operand(ValType::I32)?;
Ok(())
}
/// Checks a [`V128`] relaxed ternary operator.
fn check_v128_shift_op(&mut self) -> Result<()> {
self.pop_operand(Some(ValType::I32))?;
self.pop_operand(Some(ValType::V128))?;
self.push_operand(ValType::V128)?;
Ok(())
}
/// Checks a [`V128`] common load operator.
fn check_v128_load_op(&mut self, memarg: MemArg) -> Result<()> {
let idx = self.check_memarg(memarg)?;
self.pop_operand(Some(idx))?;
self.push_operand(ValType::V128)?;
Ok(())
}
fn func_type_at(&self, at: u32) -> Result<&'resources R::FuncType> {
self.resources
.func_type_at(at)
.ok_or_else(|| format_err!(self.offset, "unknown type: type index out of bounds"))
}
fn tag_at(&self, at: u32) -> Result<&'resources R::FuncType> {
self.resources
.tag_at(at)
.ok_or_else(|| format_err!(self.offset, "unknown tag {}: tag index out of bounds", at))
}
fn params(&self, ty: BlockType) -> Result<impl PreciseIterator<Item = ValType> + 'resources> {
Ok(match ty {
BlockType::Empty | BlockType::Type(_) => Either::B(None.into_iter()),
BlockType::FuncType(t) => Either::A(self.func_type_at(t)?.inputs()),
})
}
fn results(&self, ty: BlockType) -> Result<impl PreciseIterator<Item = ValType> + 'resources> {
Ok(match ty {
BlockType::Empty => Either::B(None.into_iter()),
BlockType::Type(t) => Either::B(Some(t).into_iter()),
BlockType::FuncType(t) => Either::A(self.func_type_at(t)?.outputs()),
})
}
fn label_types(
&self,
ty: BlockType,
kind: FrameKind,
) -> Result<impl PreciseIterator<Item = ValType> + 'resources> {
Ok(match kind {
FrameKind::Loop => Either::A(self.params(ty)?),
_ => Either::B(self.results(ty)?),
})
}
}
pub fn ty_to_str(ty: ValType) -> &'static str {
match ty {
ValType::I32 => "i32",
ValType::I64 => "i64",
ValType::F32 => "f32",
ValType::F64 => "f64",
ValType::V128 => "v128",
ValType::Ref(r) => r.wat(),
}
}
/// A wrapper "visitor" around the real operator validator internally which
/// exists to check that the required wasm feature is enabled to proceed with
/// validation.
///