Skip to content

Commit 7eae522

Browse files
authored
Fix bugs involving the combination of branching and subtyping (#1403)
* wasmparser: Fix behavior of branching to labels and subtyping Addresses issues discussed in WebAssembly/gc#516 * wasm-smith: Properly erase subtype information after conditional branches When, for example, `[a i32]` is on the stack and we have a `br_if @label` where `@label`'s type is `[b]` and `a <: b`, then whole `br_if` is typed `[b i32] -> [b]`. Because `a <: b` the `br_if` is valid. However, `a <: b` does *not* mean that the instruction results in `[a]`. It "erases" the `a` and pushes a `b`. This addresses WebAssembly/gc#516 in `wasm-smith`.
1 parent 5928201 commit 7eae522

File tree

5 files changed

+127
-60
lines changed

5 files changed

+127
-60
lines changed

crates/wasm-smith/src/core/code_builder.rs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,33 @@ impl CodeBuilder<'_> {
922922
self.allocs.operands.push(ty);
923923
}
924924

925+
fn pop_label_types(&mut self, module: &Module, target: u32) {
926+
let target = usize::try_from(target).unwrap();
927+
let control = &self.allocs.controls[self.allocs.controls.len() - 1 - target];
928+
debug_assert!(self.label_types_on_stack(module, control));
929+
self.allocs
930+
.operands
931+
.truncate(self.allocs.operands.len() - control.label_types().len());
932+
}
933+
934+
fn push_label_types(&mut self, target: u32) {
935+
let target = usize::try_from(target).unwrap();
936+
let control = &self.allocs.controls[self.allocs.controls.len() - 1 - target];
937+
self.allocs
938+
.operands
939+
.extend(control.label_types().iter().copied().map(Some));
940+
}
941+
942+
/// Pop the target label types, and then push them again.
943+
///
944+
/// This is not a no-op due to subtyping: if we have a `T <: U` on the
945+
/// stack, and the target label's type is `[U]`, then this will erase the
946+
/// information about `T` and subsequent operations may only operate on `U`.
947+
fn pop_push_label_types(&mut self, module: &Module, target: u32) {
948+
self.pop_label_types(module, target);
949+
self.push_label_types(target)
950+
}
951+
925952
fn label_types_on_stack(&self, module: &Module, to_check: &Control) -> bool {
926953
self.types_on_stack(module, to_check.label_types())
927954
}
@@ -1710,10 +1737,9 @@ fn br(
17101737
.filter(|(_, l)| builder.label_types_on_stack(module, l))
17111738
.nth(i)
17121739
.unwrap();
1713-
let control = &builder.allocs.controls[builder.allocs.controls.len() - 1 - target];
1714-
let tys = control.label_types().to_vec();
1715-
builder.pop_operands(module, &tys);
1716-
instructions.push(Instruction::Br(target as u32));
1740+
let target = u32::try_from(target).unwrap();
1741+
builder.pop_label_types(module, target);
1742+
instructions.push(Instruction::Br(target));
17171743
Ok(())
17181744
}
17191745

@@ -1757,7 +1783,9 @@ fn br_if(
17571783
.filter(|(_, l)| builder.label_types_on_stack(module, l))
17581784
.nth(i)
17591785
.unwrap();
1760-
instructions.push(Instruction::BrIf(target as u32));
1786+
let target = u32::try_from(target).unwrap();
1787+
builder.pop_push_label_types(module, target);
1788+
instructions.push(Instruction::BrIf(target));
17611789
Ok(())
17621790
}
17631791

@@ -2203,13 +2231,15 @@ fn br_on_null(
22032231
.filter(|(_, l)| builder.label_types_on_stack(module, l))
22042232
.nth(i)
22052233
.unwrap();
2234+
let target = u32::try_from(target).unwrap();
22062235

2236+
builder.pop_push_label_types(module, target);
22072237
builder.push_operands(&[ValType::Ref(RefType {
22082238
nullable: false,
22092239
heap_type,
22102240
})]);
22112241

2212-
instructions.push(Instruction::BrOnNull(u32::try_from(target).unwrap()));
2242+
instructions.push(Instruction::BrOnNull(target));
22132243
Ok(())
22142244
}
22152245

@@ -2270,9 +2300,11 @@ fn br_on_non_null(
22702300
.filter(|(_, l)| is_valid_br_on_non_null_control(module, l, builder))
22712301
.nth(i)
22722302
.unwrap();
2303+
let target = u32::try_from(target).unwrap();
22732304

2305+
builder.pop_push_label_types(module, target);
22742306
builder.pop_ref_type();
2275-
instructions.push(Instruction::BrOnNonNull(u32::try_from(target).unwrap()));
2307+
instructions.push(Instruction::BrOnNonNull(target));
22762308
Ok(())
22772309
}
22782310

@@ -2354,6 +2386,7 @@ fn br_on_cast(
23542386
.unwrap();
23552387
let relative_depth = u32::try_from(relative_depth).unwrap();
23562388

2389+
let num_label_types = control.label_types().len();
23572390
let to_ref_type = match control.label_types().last() {
23582391
Some(ValType::Ref(r)) => *r,
23592392
_ => unreachable!(),
@@ -2363,6 +2396,15 @@ fn br_on_cast(
23632396
let from_ref_type = from_ref_type.unwrap_or(to_ref_type);
23642397
let from_ref_type = module.arbitrary_super_type_of_ref_type(u, from_ref_type)?;
23652398

2399+
// Do `pop_push_label_types` but without its debug assert that the types are
2400+
// on the stack, since we know that we have a `from_ref_type` but the label
2401+
// requires a `to_ref_type`.
2402+
for _ in 0..num_label_types {
2403+
builder.pop_operand();
2404+
}
2405+
builder.push_label_types(relative_depth);
2406+
2407+
// Replace the label's `to_ref_type` with the type difference.
23662408
builder.pop_operand();
23672409
builder.push_operands(&[ValType::Ref(ref_type_difference(
23682410
from_ref_type,
@@ -2433,7 +2475,7 @@ fn br_on_cast_fail(
24332475
debug_assert!(n > 0);
24342476

24352477
let i = u.int_in_range(0..=n - 1)?;
2436-
let (target, control) = builder
2478+
let (relative_depth, control) = builder
24372479
.allocs
24382480
.controls
24392481
.iter()
@@ -2442,6 +2484,7 @@ fn br_on_cast_fail(
24422484
.filter(|(_, l)| is_valid_br_on_cast_fail_control(module, builder, l, from_ref_type))
24432485
.nth(i)
24442486
.unwrap();
2487+
let relative_depth = u32::try_from(relative_depth).unwrap();
24452488

24462489
let from_ref_type =
24472490
from_ref_type.unwrap_or_else(|| match control.label_types().last().unwrap() {
@@ -2450,13 +2493,16 @@ fn br_on_cast_fail(
24502493
});
24512494
let to_ref_type = module.arbitrary_matching_ref_type(u, from_ref_type)?;
24522495

2496+
// Pop-push the label types and then replace its last reference type with
2497+
// our `to_ref_type`.
2498+
builder.pop_push_label_types(module, relative_depth);
24532499
builder.pop_operand();
24542500
builder.push_operand(Some(ValType::Ref(to_ref_type)));
24552501

24562502
instructions.push(Instruction::BrOnCastFail {
24572503
from_ref_type,
24582504
to_ref_type,
2459-
relative_depth: u32::try_from(target).unwrap(),
2505+
relative_depth,
24602506
});
24612507
Ok(())
24622508
}

crates/wasmparser/src/validator/operators.rs

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,6 @@ impl MaybeType {
187187
Self::Bot | Self::HeapBot => None,
188188
}
189189
}
190-
191-
fn or(self, alternative: impl Into<MaybeType>) -> MaybeType {
192-
match self {
193-
Self::Type(_) => self,
194-
Self::Bot | Self::HeapBot => alternative.into(),
195-
}
196-
}
197190
}
198191

199192
impl OperatorValidator {
@@ -409,11 +402,6 @@ impl<R> DerefMut for OperatorValidatorTemp<'_, '_, R> {
409402
}
410403
}
411404

412-
enum PopPushBottomBehavior {
413-
PushBottom,
414-
OrExpected,
415-
}
416-
417405
impl<'resources, R> OperatorValidatorTemp<'_, 'resources, R>
418406
where
419407
R: WasmModuleResources,
@@ -482,41 +470,12 @@ where
482470
fn pop_push_label_types(
483471
&mut self,
484472
label_types: impl PreciseIterator<Item = ValType>,
485-
bottom_behavior: PopPushBottomBehavior,
486473
) -> Result<()> {
487-
// When popping off the expected types from the stack for a branch
488-
// label, the actual types might be subtypes of those expected types,
489-
// and we don't want to accidentally erase that type information by
490-
// pushing the label's expected types, so we have to save the actual
491-
// popped types.
492-
493-
debug_assert!(self.popped_types_tmp.is_empty());
494-
self.popped_types_tmp.reserve(label_types.len());
495-
for expected_ty in label_types.rev() {
496-
let actual_ty = self.pop_operand(Some(expected_ty))?;
497-
498-
let actual_ty = match bottom_behavior {
499-
// However, in addition, if we pop bottom, then we generally
500-
// don't want to push bottom again! That would allow for the
501-
// "same" stack slot to be used as an `i32` in one unreachable
502-
// instruction and as an `i64` in a different unreachable
503-
// instruction, which leads to invalid test cases to be treated
504-
// as valid.
505-
PopPushBottomBehavior::OrExpected => actual_ty.or(expected_ty),
506-
// The only exception to the above is when we are handling
507-
// `br_table` and we aren't logically pushing the *same* value
508-
// back to the stack again, but instead resetting the stack to
509-
// its previous state, as if we didn't branch to the previous
510-
// target. In this case, we can interpret bottom as `i32` for
511-
// one branch target, and as `i64` for another branch target, so
512-
// we really do want to re-push bottom again.
513-
PopPushBottomBehavior::PushBottom => actual_ty,
514-
};
515-
516-
self.popped_types_tmp.push(actual_ty);
474+
for ty in label_types.clone().rev() {
475+
self.pop_operand(Some(ty))?;
517476
}
518-
for ty in self.inner.popped_types_tmp.drain(..).rev() {
519-
self.inner.operands.push(ty.into());
477+
for ty in label_types {
478+
self.push_operand(ty)?;
520479
}
521480
Ok(())
522481
}
@@ -1473,7 +1432,7 @@ where
14731432
self.pop_operand(Some(ValType::I32))?;
14741433
let (ty, kind) = self.jump(relative_depth)?;
14751434
let label_types = self.label_types(ty, kind)?;
1476-
self.pop_push_label_types(label_types, PopPushBottomBehavior::OrExpected)?;
1435+
self.pop_push_label_types(label_types)?;
14771436
Ok(())
14781437
}
14791438
fn visit_br_table(&mut self, table: BrTable) -> Self::Output {
@@ -1490,7 +1449,16 @@ where
14901449
"type mismatch: br_table target labels have different number of types"
14911450
);
14921451
}
1493-
self.pop_push_label_types(label_tys, PopPushBottomBehavior::PushBottom)?;
1452+
1453+
debug_assert!(self.popped_types_tmp.is_empty());
1454+
self.popped_types_tmp.reserve(label_tys.len());
1455+
for expected_ty in label_tys.rev() {
1456+
let actual_ty = self.pop_operand(Some(expected_ty))?;
1457+
self.popped_types_tmp.push(actual_ty);
1458+
}
1459+
for ty in self.inner.popped_types_tmp.drain(..).rev() {
1460+
self.inner.operands.push(ty.into());
1461+
}
14941462
}
14951463
for ty in default_types.rev() {
14961464
self.pop_operand(Some(ty))?;
@@ -2471,7 +2439,7 @@ where
24712439
};
24722440
let (ft, kind) = self.jump(relative_depth)?;
24732441
let label_types = self.label_types(ft, kind)?;
2474-
self.pop_push_label_types(label_types, PopPushBottomBehavior::OrExpected)?;
2442+
self.pop_push_label_types(label_types)?;
24752443
self.push_operand(ref_ty)?;
24762444
Ok(())
24772445
}
@@ -2506,7 +2474,7 @@ where
25062474
),
25072475
}
25082476

2509-
self.pop_push_label_types(label_types, PopPushBottomBehavior::OrExpected)?;
2477+
self.pop_push_label_types(label_types)?;
25102478
Ok(())
25112479
}
25122480
fn visit_ref_is_null(&mut self) -> Self::Output {
@@ -3940,7 +3908,7 @@ where
39403908
),
39413909
};
39423910

3943-
self.pop_push_label_types(label_types, PopPushBottomBehavior::OrExpected)?;
3911+
self.pop_push_label_types(label_types)?;
39443912
let diff_ty = RefType::difference(from_ref_type, to_ref_type);
39453913
self.push_operand(diff_ty)?;
39463914
Ok(())
@@ -3984,7 +3952,7 @@ where
39843952
),
39853953
}
39863954

3987-
self.pop_push_label_types(label_tys, PopPushBottomBehavior::OrExpected)?;
3955+
self.pop_push_label_types(label_tys)?;
39883956
self.push_operand(to_ref_type)?;
39893957
Ok(())
39903958
}

tests/local/gc/invalid.wast

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
;; https://github.com/WebAssembly/gc/issues/516
2+
3+
(assert_invalid
4+
(module
5+
(func (param anyref) (result anyref)
6+
ref.null struct
7+
local.get 0
8+
;; The label pushes an anyref, even though we really have a structref.
9+
br_on_null 0
10+
drop
11+
br_on_cast_fail 0 structref structref
12+
)
13+
)
14+
"type mismatch: expected structref, found anyref"
15+
)
16+
17+
(assert_invalid
18+
(module
19+
(func (param anyref) (result anyref)
20+
ref.null struct
21+
;; Adding an `unreachable` shouldn't change the fact that the `br_on_null`
22+
;; pushes an `anyref` and the `br_on_cast_fail` expects a `structref`.
23+
unreachable
24+
local.get 0
25+
br_on_null 0
26+
drop
27+
br_on_cast_fail 0 structref structref
28+
)
29+
)
30+
"type mismatch: expected structref, found anyref"
31+
)

tests/local/unreachable-valid.wast

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(module
2+
(func (export "f")
3+
f32.const 1.0
4+
f32.const 2.0
5+
br 0
6+
i32.add
7+
drop
8+
)
9+
)
10+
11+
(assert_trap (invoke "f") "unreachable")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(module
2+
(type (;0;) (func))
3+
(func (;0;) (type 0)
4+
f32.const 0x1p+0 (;=1;)
5+
f32.const 0x1p+1 (;=2;)
6+
br 0 (;@0;)
7+
i32.add
8+
drop
9+
)
10+
(export "f" (func 0))
11+
)

0 commit comments

Comments
 (0)