Skip to content

Commit 848e4de

Browse files
authored
Update handling of unreachable code and heap types (#1734)
* Update handling of unreachable code and heap types This commit updates validation of wasm modules with unreachable code using gc/heap types. It notably fixes cases with the shared-everything-threads proposal where existing instructions are polymorphic over `shared` and not and wasn't supported before. Specifically code was refactored to use `MaybeType` a bit more to propagate the bottom-ness and the `MaybeType::HeapBot` variant has grown a new `AbstractHeapType` payload for various instructions to use such as `any.convert_extern`. * Fix dead code warning
1 parent 2371dca commit 848e4de

6 files changed

Lines changed: 453 additions & 81 deletions

File tree

crates/wasmparser/src/readers/core/types.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1464,7 +1464,7 @@ pub enum AbstractHeapType {
14641464
}
14651465

14661466
impl AbstractHeapType {
1467-
const fn as_str(&self, nullable: bool) -> &str {
1467+
pub(crate) const fn as_str(&self, nullable: bool) -> &str {
14681468
use AbstractHeapType::*;
14691469
match (nullable, self) {
14701470
(_, Any) => "any",
@@ -1485,6 +1485,29 @@ impl AbstractHeapType {
14851485
(false, NoExn) => "noexn",
14861486
}
14871487
}
1488+
1489+
#[cfg(feature = "validate")]
1490+
pub(crate) fn is_subtype_of(&self, other: AbstractHeapType) -> bool {
1491+
use AbstractHeapType::*;
1492+
1493+
match (self, other) {
1494+
(a, b) if *a == b => true,
1495+
(Eq | I31 | Struct | Array | None, Any) => true,
1496+
(I31 | Struct | Array | None, Eq) => true,
1497+
(NoExtern, Extern) => true,
1498+
(NoFunc, Func) => true,
1499+
(None, I31 | Array | Struct) => true,
1500+
(NoExn, Exn) => true,
1501+
// Nothing else matches. (Avoid full wildcard matches so
1502+
// that adding/modifying variants is easier in the
1503+
// future.)
1504+
(
1505+
Func | Extern | Exn | Any | Eq | Array | I31 | Struct | None | NoFunc | NoExtern
1506+
| NoExn,
1507+
_,
1508+
) => false,
1509+
}
1510+
}
14881511
}
14891512

14901513
impl<'a> FromReader<'a> for StorageType {

crates/wasmparser/src/validator/operators.rs

Lines changed: 136 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,23 @@ pub struct OperatorValidatorAllocations {
160160

161161
/// Type storage within the validator.
162162
///
163-
/// This is used to manage the operand stack and notably isn't just `ValType` to
164-
/// handle unreachable code and the "bottom" type.
163+
/// This is used to manage the operand stack and notably isn't just `ValType`
164+
/// to handle unreachable code and the "bottom" type.
165165
#[derive(Debug, Copy, Clone)]
166-
enum MaybeType {
166+
enum MaybeType<T = ValType> {
167+
/// A "bottom" type which represents unconstrained unreachable code. There
168+
/// are no constraints on what this type may be.
167169
Bot,
168-
HeapBot,
169-
Type(ValType),
170+
/// A "bottom" type for specifically heap types.
171+
///
172+
/// This type is known to be a reference type, optionally the specific
173+
/// abstract heap type listed. This type can be interpeted as either
174+
/// `shared` or not-`shared`. Additionally it can be either nullable or
175+
/// not. Currently no further refinements are required for wasm
176+
/// instructions today, but this may grow in the future.
177+
HeapBot(Option<AbstractHeapType>),
178+
/// A known type with the type `T`.
179+
Type(T),
170180
}
171181

172182
// The validator is pretty performance-sensitive and `MaybeType` is the main
@@ -180,7 +190,14 @@ impl core::fmt::Display for MaybeType {
180190
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
181191
match self {
182192
MaybeType::Bot => write!(f, "bot"),
183-
MaybeType::HeapBot => write!(f, "heap-bot"),
193+
MaybeType::HeapBot(ty) => {
194+
write!(f, "(ref shared? ")?;
195+
match ty {
196+
Some(ty) => write!(f, "{}bot", ty.as_str(true))?,
197+
None => write!(f, "bot")?,
198+
}
199+
write!(f, ")")
200+
}
184201
MaybeType::Type(ty) => core::fmt::Display::fmt(ty, f),
185202
}
186203
}
@@ -198,6 +215,33 @@ impl From<RefType> for MaybeType {
198215
ty.into()
199216
}
200217
}
218+
impl From<MaybeType<RefType>> for MaybeType<ValType> {
219+
fn from(ty: MaybeType<RefType>) -> MaybeType<ValType> {
220+
match ty {
221+
MaybeType::Bot => MaybeType::Bot,
222+
MaybeType::HeapBot(ty) => MaybeType::HeapBot(ty),
223+
MaybeType::Type(t) => MaybeType::Type(t.into()),
224+
}
225+
}
226+
}
227+
228+
impl MaybeType<RefType> {
229+
fn as_non_null(&self) -> MaybeType<RefType> {
230+
match self {
231+
MaybeType::Bot => MaybeType::Bot,
232+
MaybeType::HeapBot(ty) => MaybeType::HeapBot(*ty),
233+
MaybeType::Type(ty) => MaybeType::Type(ty.as_non_null()),
234+
}
235+
}
236+
237+
fn is_maybe_shared(&self, resources: &impl WasmModuleResources) -> Option<bool> {
238+
match self {
239+
MaybeType::Bot => None,
240+
MaybeType::HeapBot(_) => None,
241+
MaybeType::Type(ty) => Some(resources.is_shared(*ty)),
242+
}
243+
}
244+
}
201245

202246
impl OperatorValidator {
203247
fn new(features: &WasmFeatures, allocs: OperatorValidatorAllocations) -> Self {
@@ -330,7 +374,7 @@ impl OperatorValidator {
330374
pub fn peek_operand_at(&self, depth: usize) -> Option<Option<ValType>> {
331375
Some(match self.operands.iter().rev().nth(depth)? {
332376
MaybeType::Type(t) => Some(*t),
333-
MaybeType::Bot | MaybeType::HeapBot => None,
377+
MaybeType::Bot | MaybeType::HeapBot(..) => None,
334378
})
335379
}
336380

@@ -570,10 +614,33 @@ where
570614
if let Some(expected) = expected {
571615
match (actual, expected) {
572616
// The bottom type matches all expectations
573-
(MaybeType::Bot, _)
617+
(MaybeType::Bot, _) => {}
618+
574619
// The "heap bottom" type only matches other references types,
575-
// but not any integer types.
576-
| (MaybeType::HeapBot, ValType::Ref(_)) => {}
620+
// but not any integer types. Note that if the heap bottom is
621+
// known to have a specific abstract heap type then a subtype
622+
// check is performed against hte expected type.
623+
(MaybeType::HeapBot(actual_ty), ValType::Ref(expected)) => {
624+
if let Some(actual) = actual_ty {
625+
let expected_shared = self.resources.is_shared(expected);
626+
let actual = RefType::new(
627+
false,
628+
HeapType::Abstract {
629+
shared: expected_shared,
630+
ty: actual,
631+
},
632+
)
633+
.unwrap();
634+
if !self.resources.is_subtype(actual.into(), expected.into()) {
635+
bail!(
636+
self.offset,
637+
"type mismatch: expected {}, found {}",
638+
ty_to_str(expected.into()),
639+
ty_to_str(actual.into())
640+
);
641+
}
642+
}
643+
}
577644

578645
// Use the `is_subtype` predicate to test if a found type matches
579646
// the expectation.
@@ -590,7 +657,7 @@ where
590657

591658
// A "heap bottom" type cannot match any numeric types.
592659
(
593-
MaybeType::HeapBot,
660+
MaybeType::HeapBot(..),
594661
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128,
595662
) => {
596663
bail!(
@@ -605,10 +672,11 @@ where
605672
}
606673

607674
/// Pop a reference type from the operand stack.
608-
fn pop_ref(&mut self, expected: Option<RefType>) -> Result<Option<RefType>> {
675+
fn pop_ref(&mut self, expected: Option<RefType>) -> Result<MaybeType<RefType>> {
609676
match self.pop_operand(expected.map(|t| t.into()))? {
610-
MaybeType::Bot | MaybeType::HeapBot => Ok(None),
611-
MaybeType::Type(ValType::Ref(rt)) => Ok(Some(rt)),
677+
MaybeType::Bot => Ok(MaybeType::HeapBot(None)),
678+
MaybeType::HeapBot(ty) => Ok(MaybeType::HeapBot(ty)),
679+
MaybeType::Type(ValType::Ref(rt)) => Ok(MaybeType::Type(rt)),
612680
MaybeType::Type(ty) => bail!(
613681
self.offset,
614682
"type mismatch: expected ref but found {}",
@@ -622,13 +690,22 @@ where
622690
///
623691
/// This function returns the popped reference type and its `shared`-ness,
624692
/// saving extra lookups for concrete types.
625-
fn pop_maybe_shared_ref(
626-
&mut self,
627-
expected: AbstractHeapType,
628-
) -> Result<Option<(RefType, bool)>> {
693+
fn pop_maybe_shared_ref(&mut self, expected: AbstractHeapType) -> Result<MaybeType<RefType>> {
629694
let actual = match self.pop_ref(None)? {
630-
Some(rt) => rt,
631-
None => return Ok(None),
695+
MaybeType::Bot => return Ok(MaybeType::Bot),
696+
MaybeType::HeapBot(None) => return Ok(MaybeType::HeapBot(None)),
697+
MaybeType::HeapBot(Some(actual)) => {
698+
if !actual.is_subtype_of(expected) {
699+
bail!(
700+
self.offset,
701+
"type mismatch: expected subtype of {}, found {}",
702+
expected.as_str(false),
703+
actual.as_str(false),
704+
)
705+
}
706+
return Ok(MaybeType::HeapBot(Some(actual)));
707+
}
708+
MaybeType::Type(ty) => ty,
632709
};
633710
// Change our expectation based on whether we're dealing with an actual
634711
// shared or unshared type.
@@ -651,7 +728,7 @@ where
651728
"type mismatch: expected subtype of {expected}, found {actual}",
652729
)
653730
}
654-
Ok(Some((actual, is_actual_shared)))
731+
Ok(MaybeType::Type(actual))
655732
}
656733

657734
/// Fetches the type for the local at `idx`, returning an error if it's out
@@ -1695,8 +1772,8 @@ where
16951772
let ty = match (ty1, ty2) {
16961773
// All heap-related types aren't allowed with the `select`
16971774
// instruction
1698-
(MaybeType::HeapBot, _)
1699-
| (_, MaybeType::HeapBot)
1775+
(MaybeType::HeapBot(..), _)
1776+
| (_, MaybeType::HeapBot(..))
17001777
| (MaybeType::Type(ValType::Ref(_)), _)
17011778
| (_, MaybeType::Type(ValType::Ref(_))) => {
17021779
bail!(
@@ -2666,18 +2743,12 @@ where
26662743
}
26672744

26682745
fn visit_ref_as_non_null(&mut self) -> Self::Output {
2669-
let ty = match self.pop_ref(None)? {
2670-
Some(ty) => MaybeType::Type(ValType::Ref(ty.as_non_null())),
2671-
None => MaybeType::HeapBot,
2672-
};
2746+
let ty = self.pop_ref(None)?.as_non_null();
26732747
self.push_operand(ty)?;
26742748
Ok(())
26752749
}
26762750
fn visit_br_on_null(&mut self, relative_depth: u32) -> Self::Output {
2677-
let ref_ty = match self.pop_ref(None)? {
2678-
None => MaybeType::HeapBot,
2679-
Some(ty) => MaybeType::Type(ValType::Ref(ty.as_non_null())),
2680-
};
2751+
let ref_ty = self.pop_ref(None)?.as_non_null();
26812752
let (ft, kind) = self.jump(relative_depth)?;
26822753
let label_types = self.label_types(ft, kind)?;
26832754
self.pop_push_label_types(label_types)?;
@@ -2734,19 +2805,21 @@ where
27342805
fn visit_ref_eq(&mut self) -> Self::Output {
27352806
let a = self.pop_maybe_shared_ref(AbstractHeapType::Eq)?;
27362807
let b = self.pop_maybe_shared_ref(AbstractHeapType::Eq)?;
2737-
match (a, b) {
2738-
(Some((_, is_a_shared)), Some((_, is_b_shared))) => {
2808+
let a_is_shared = a.is_maybe_shared(&self.resources);
2809+
let b_is_shared = b.is_maybe_shared(&self.resources);
2810+
match (a_is_shared, b_is_shared) {
2811+
// One or both of the types are from unreachable code; assume
2812+
// the shared-ness matches.
2813+
(None, Some(_)) | (Some(_), None) | (None, None) => {}
2814+
2815+
(Some(is_a_shared), Some(is_b_shared)) => {
27392816
if is_a_shared != is_b_shared {
27402817
bail!(
27412818
self.offset,
27422819
"type mismatch: expected `ref.eq` types to match `shared`-ness"
27432820
);
27442821
}
27452822
}
2746-
_ => {
2747-
// One or both of the types are from unreachable code; assume
2748-
// the shared-ness matches.
2749-
}
27502823
}
27512824
self.push_operand(ValType::I32)
27522825
}
@@ -4404,35 +4477,37 @@ where
44044477
Ok(())
44054478
}
44064479
fn visit_any_convert_extern(&mut self) -> Self::Output {
4407-
let extern_ref = self.pop_maybe_shared_ref(AbstractHeapType::Extern)?;
4408-
let (is_nullable, shared) = if let Some((extern_ref, shared)) = extern_ref {
4409-
(extern_ref.is_nullable(), shared)
4410-
} else {
4411-
// TODO: propagating unshared may be incorrect here
4412-
// (https://github.com/WebAssembly/shared-everything-threads/issues/80)
4413-
(false, false)
4414-
};
4415-
let heap_type = HeapType::Abstract {
4416-
shared,
4417-
ty: AbstractHeapType::Any,
4480+
let any_ref = match self.pop_maybe_shared_ref(AbstractHeapType::Extern)? {
4481+
MaybeType::Bot | MaybeType::HeapBot(_) => {
4482+
MaybeType::HeapBot(Some(AbstractHeapType::Any))
4483+
}
4484+
MaybeType::Type(ty) => {
4485+
let shared = self.resources.is_shared(ty);
4486+
let heap_type = HeapType::Abstract {
4487+
shared,
4488+
ty: AbstractHeapType::Any,
4489+
};
4490+
let any_ref = RefType::new(ty.is_nullable(), heap_type).unwrap();
4491+
MaybeType::Type(any_ref)
4492+
}
44184493
};
4419-
let any_ref = RefType::new(is_nullable, heap_type).unwrap();
44204494
self.push_operand(any_ref)
44214495
}
44224496
fn visit_extern_convert_any(&mut self) -> Self::Output {
4423-
let any_ref = self.pop_maybe_shared_ref(AbstractHeapType::Any)?;
4424-
let (is_nullable, shared) = if let Some((any_ref, shared)) = any_ref {
4425-
(any_ref.is_nullable(), shared)
4426-
} else {
4427-
// TODO: propagating unshared may be incorrect here
4428-
// (https://github.com/WebAssembly/shared-everything-threads/issues/80)
4429-
(false, false)
4430-
};
4431-
let heap_type = HeapType::Abstract {
4432-
shared,
4433-
ty: AbstractHeapType::Extern,
4497+
let extern_ref = match self.pop_maybe_shared_ref(AbstractHeapType::Any)? {
4498+
MaybeType::Bot | MaybeType::HeapBot(_) => {
4499+
MaybeType::HeapBot(Some(AbstractHeapType::Extern))
4500+
}
4501+
MaybeType::Type(ty) => {
4502+
let shared = self.resources.is_shared(ty);
4503+
let heap_type = HeapType::Abstract {
4504+
shared,
4505+
ty: AbstractHeapType::Extern,
4506+
};
4507+
let extern_ref = RefType::new(ty.is_nullable(), heap_type).unwrap();
4508+
MaybeType::Type(extern_ref)
4509+
}
44344510
};
4435-
let extern_ref = RefType::new(is_nullable, heap_type).unwrap();
44364511
self.push_operand(extern_ref)
44374512
}
44384513
fn visit_ref_test_non_null(&mut self, heap_type: HeapType) -> Self::Output {

crates/wasmparser/src/validator/types.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2798,25 +2798,7 @@ impl TypeList {
27982798
shared: b_shared,
27992799
ty: b_ty,
28002800
},
2801-
) => {
2802-
a_shared == b_shared
2803-
&& match (a_ty, b_ty) {
2804-
(Eq | I31 | Struct | Array | None, Any) => true,
2805-
(I31 | Struct | Array | None, Eq) => true,
2806-
(NoExtern, Extern) => true,
2807-
(NoFunc, Func) => true,
2808-
(None, I31 | Array | Struct) => true,
2809-
(NoExn, Exn) => true,
2810-
// Nothing else matches. (Avoid full wildcard matches so
2811-
// that adding/modifying variants is easier in the
2812-
// future.)
2813-
(
2814-
Func | Extern | Exn | Any | Eq | Array | I31 | Struct | None | NoFunc
2815-
| NoExtern | NoExn,
2816-
_,
2817-
) => false,
2818-
}
2819-
}
2801+
) => a_shared == b_shared && a_ty.is_subtype_of(b_ty),
28202802

28212803
(HT::Concrete(a), HT::Abstract { shared, ty }) => {
28222804
let a_ty = &subtype(a_group, a).composite_type;

0 commit comments

Comments
 (0)