Skip to content

Commit 605ef3b

Browse files
authored
[Stack Switching] Fuzz Stack Switching (#7834)
* Disable stack switching in ClusterFuzz, as V8 isn't ready yet. Also in our V8 fuzzing. * Save a list of valid exception tags (not every tag can be thrown/caught) and use those from exception handling places. * Other minor fuzzer fixes as needed.
1 parent de5ff1a commit 605ef3b

File tree

7 files changed

+95
-90
lines changed

7 files changed

+95
-90
lines changed

scripts/bundle_clusterfuzz.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
'--disable-shared-everything',
109109
'--disable-fp16',
110110
'--disable-strings',
111+
'--disable-stack-switching',
111112
]
112113

113114
with tarfile.open(output_file, "w:gz") as tar:

scripts/clusterfuzz/run.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
'--disable-shared-everything',
9494
'--disable-fp16',
9595
'--disable-strings',
96+
'--disable-stack-switching',
9697
]
9798

9899

scripts/fuzz_opt.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,13 @@ def randomize_feature_opts():
152152
# The shared-everything feature is new and we want to fuzz it, but it
153153
# also currently disables fuzzing V8, so disable it most of the time.
154154
# Same with strings. Relaxed SIMD's nondeterminism disables much but not
155-
# all of our V8 fuzzing, so avoid it too.
155+
# all of our V8 fuzzing, so avoid it too. Stack Switching, as well, is
156+
# not yet ready in V8.
156157
if random.random() < 0.9:
157158
FEATURE_OPTS.append('--disable-shared-everything')
158159
FEATURE_OPTS.append('--disable-strings')
159160
FEATURE_OPTS.append('--disable-relaxed-simd')
161+
FEATURE_OPTS.append('--disable-stack-switching')
160162

161163
print('randomized feature opts:', '\n ' + '\n '.join(FEATURE_OPTS))
162164

@@ -824,8 +826,9 @@ def run(self, wasm, extra_d8_flags=[]):
824826
def can_run(self, wasm):
825827
# V8 does not support shared memories when running with
826828
# shared-everything enabled, so do not fuzz shared-everything
827-
# for now. It also does not yet support strings.
828-
return all_disallowed(['shared-everything', 'strings'])
829+
# for now. It also does not yet support strings, nor stack
830+
# switching
831+
return all_disallowed(['shared-everything', 'strings', 'stack-switching'])
829832

830833
def can_compare_to_self(self):
831834
# With nans, VM differences can confuse us, so only very simple VMs
@@ -1624,7 +1627,7 @@ def can_run_on_wasm(self, wasm):
16241627
return False
16251628

16261629
# see D8.can_run
1627-
return all_disallowed(['shared-everything', 'strings'])
1630+
return all_disallowed(['shared-everything', 'strings', 'stack-switching'])
16281631

16291632

16301633
# Check that the text format round-trips without error.
@@ -1832,7 +1835,7 @@ def can_run_on_wasm(self, wasm):
18321835
return False
18331836
if NANS:
18341837
return False
1835-
return all_disallowed(['shared-everything', 'strings'])
1838+
return all_disallowed(['shared-everything', 'strings', 'stack-switching'])
18361839

18371840

18381841
# Test --fuzz-preserve-imports-exports, which never modifies imports or exports.

scripts/test/fuzzing.py

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,6 @@
7070
# the fuzzer does not support imported memories
7171
'multi-memory-lowering-import.wast',
7272
'multi-memory-lowering-import-error.wast',
73-
# the fuzzer does not support typed continuations
74-
'typed_continuations.wast',
75-
'typed_continuations_resume.wast',
76-
'typed_continuations_contnew.wast',
77-
'typed_continuations_contbind.wast',
78-
'typed_continuations_suspend.wast',
7973
# the fuzzer does not support struct RMW ops
8074
'gc-atomics.wast',
8175
'gc-atomics-null-refs.wast',
@@ -96,32 +90,14 @@
9690
# it removes unknown imports
9791
'string-lifting.wast',
9892
'string-lifting-custom-module.wast',
99-
# TODO: fuzzer support for stack switching
100-
'tag_linked.wast',
101-
'stack_switching.wast',
102-
'stack_switching_contnew.wast',
103-
'stack_switching_contbind.wast',
104-
'stack_switching_suspend.wast',
105-
'stack_switching_resume.wast',
106-
'stack_switching_resume_throw.wast',
107-
'stack_switching_switch.wast',
108-
'stack_switching_switch_2.wast',
109-
'O3_stack-switching.wast',
110-
'coalesce-locals-stack-switching.wast',
111-
'dce-stack-switching.wast',
112-
'local-cse-cont.wast',
93+
# TODO: fuzzer support for remaining stack switching instructions: switch,
94+
# cont.bind
95+
'cont.wast',
11396
'precompute-stack-switching.wast',
97+
'coalesce-locals-stack-switching.wast',
98+
'stack_switching_switch_2.wast',
99+
'stack_switching_switch.wast',
114100
'unsubtyping-stack-switching.wast',
115-
'vacuum-stack-switching.wast',
116-
'cont.wast',
117-
'cont_simple.wast',
118-
'gufa-cont.wast',
119-
'cont_many_unhandled.wast',
120-
'cont_export.wast',
121-
'cont_export_throw.wast',
122-
'type-merging-cont.wast',
123-
'remove-unused-module-elements-cont.wast',
124-
'abstract-type-refining-cont.wast',
125101
# TODO: fix split_wast() on tricky escaping situations like a string ending
126102
# in \\" (the " is not escaped - there is an escaped \ before it)
127103
'string-lifting-section.wast',

src/tools/fuzzing.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ class TranslateToFuzzReader {
211211
// All arrays that are mutable.
212212
std::vector<HeapType> mutableArrays;
213213

214+
// All tags that are valid as exception tags (which cannot have results).
215+
std::vector<Tag*> exceptionTags;
216+
214217
Index numAddedFunctions = 0;
215218

216219
// The name of an empty tag.

src/tools/fuzzing/fuzzing.cpp

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ void TranslateToFuzzReader::setupHeapTypes() {
491491
auto eq = HeapTypes::eq.getBasic(share);
492492
auto any = HeapTypes::any.getBasic(share);
493493
auto func = HeapTypes::func.getBasic(share);
494+
auto cont = HeapTypes::cont.getBasic(share);
494495
switch (type.getKind()) {
495496
case HeapTypeKind::Func:
496497
interestingHeapSubTypes[func].push_back(type);
@@ -517,7 +518,8 @@ void TranslateToFuzzReader::setupHeapTypes() {
517518
}
518519
break;
519520
case HeapTypeKind::Cont:
520-
WASM_UNREACHABLE("TODO: cont");
521+
interestingHeapSubTypes[cont].push_back(type);
522+
break;
521523
case HeapTypeKind::Basic:
522524
WASM_UNREACHABLE("unexpected kind");
523525
}
@@ -672,6 +674,7 @@ void TranslateToFuzzReader::setupGlobals() {
672674
// Create new random globals.
673675
for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) {
674676
auto type = getConcreteType();
677+
675678
// Prefer immutable ones as they can be used in global.gets in other
676679
// globals, for more interesting patterns.
677680
auto mutability = oneIn(3) ? Builder::Mutable : Builder::Immutable;
@@ -680,12 +683,16 @@ void TranslateToFuzzReader::setupGlobals() {
680683
// initializer.
681684
auto* init = makeTrivial(type);
682685

683-
if (!FindAll<RefAs>(init).list.empty()) {
686+
if (!FindAll<RefAs>(init).list.empty() ||
687+
!FindAll<ContNew>(init).list.empty()) {
684688
// When creating this initial value we ended up emitting a RefAs, which
685689
// means we had to stop in the middle of an overly-nested struct or array,
686690
// which we can break out of using ref.as_non_null of a nullable ref. That
687691
// traps in normal code, which is bad enough, but it does not even
688692
// validate in a global. Switch to something safe instead.
693+
//
694+
// Likewise, if we see cont.new, we must switch as well. That can happen
695+
// if a nested struct we create has a continuation field, for example.
689696
type = getMVPType();
690697
init = makeConst(type);
691698
} else if (type.isTuple() && !init->is<TupleMake>()) {
@@ -708,6 +715,9 @@ void TranslateToFuzzReader::setupTags() {
708715
if (tag->imported() && !preserveImportsAndExports) {
709716
tag->module = tag->base = Name();
710717
}
718+
if (tag->results() == Type::none) {
719+
exceptionTags.push_back(tag.get());
720+
}
711721
}
712722

713723
// Add some random tags.
@@ -736,7 +746,8 @@ void TranslateToFuzzReader::setupTags() {
736746
void TranslateToFuzzReader::addTag() {
737747
auto tag = builder.makeTag(Names::getValidTagName(wasm, "tag$"),
738748
Signature(getControlFlowType(), Type::none));
739-
wasm.addTag(std::move(tag));
749+
auto* tagg = wasm.addTag(std::move(tag));
750+
exceptionTags.push_back(tagg);
740751
}
741752

742753
void TranslateToFuzzReader::finalizeMemory() {
@@ -2432,10 +2443,10 @@ Expression* TranslateToFuzzReader::makeTry(Type type) {
24322443
auto numTags = upTo(fuzzParams->MAX_TRY_CATCHES);
24332444
std::unordered_set<Tag*> usedTags;
24342445
for (Index i = 0; i < numTags; i++) {
2435-
if (wasm.tags.empty()) {
2446+
if (exceptionTags.empty()) {
24362447
addTag();
24372448
}
2438-
auto* tag = pick(wasm.tags).get();
2449+
auto* tag = pick(exceptionTags);
24392450
if (usedTags.count(tag)) {
24402451
continue;
24412452
}
@@ -2482,7 +2493,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) {
24822493
return builder.makeTryTable(body, {}, {}, {});
24832494
}
24842495

2485-
if (wasm.tags.empty()) {
2496+
if (exceptionTags.empty()) {
24862497
addTag();
24872498
}
24882499

@@ -2497,7 +2508,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) {
24972508
Type tagType;
24982509
if (i < numCatches) {
24992510
// Look for a specific tag.
2500-
auto& tag = pick(wasm.tags);
2511+
auto* tag = pick(exceptionTags);
25012512
tagName = tag->name;
25022513
tagType = tag->params();
25032514
} else {
@@ -3449,7 +3460,15 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) {
34493460
return makeRefFuncConst(type);
34503461
}
34513462
case HeapType::cont: {
3452-
WASM_UNREACHABLE("not implemented");
3463+
// Most of the time, avoid null continuations, as they will trap.
3464+
if (type.isNullable() && oneIn(4)) {
3465+
return builder.makeRefNull(HeapTypes::cont.getBasic(share));
3466+
}
3467+
// Emit the simplest possible continuation.
3468+
auto funcSig = Signature(Type::none, Type::none);
3469+
auto funcType = Type(funcSig, NonNullable);
3470+
auto contType = Continuation(funcSig);
3471+
return builder.makeContNew(contType, makeRefFuncConst(funcType));
34533472
}
34543473
case HeapType::any: {
34553474
// Choose a subtype we can materialize a constant for. We cannot
@@ -3673,8 +3692,10 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) {
36733692
builder.makeConst(int32_t(upTo(fuzzParams->MAX_ARRAY_SIZE)));
36743693
return builder.makeArrayNew(type.getHeapType(), count, init);
36753694
}
3676-
case HeapTypeKind::Cont:
3677-
WASM_UNREACHABLE("TODO: cont");
3695+
case HeapTypeKind::Cont: {
3696+
auto funcType = heapType.getContinuation().type;
3697+
return builder.makeContNew(heapType, makeTrappingRefUse(funcType));
3698+
}
36783699
case HeapTypeKind::Basic:
36793700
break;
36803701
}
@@ -5222,10 +5243,10 @@ Expression* TranslateToFuzzReader::makeThrow(Type type) {
52225243
}
52235244
} else {
52245245
// Get a random tag, adding a random one if necessary.
5225-
if (wasm.tags.empty()) {
5246+
if (exceptionTags.empty()) {
52265247
addTag();
52275248
}
5228-
tag = pick(wasm.tags).get();
5249+
tag = pick(exceptionTags);
52295250
}
52305251
auto tagType = tag->params();
52315252
std::vector<Expression*> operands;
Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,57 @@
11
Metrics
22
total
3-
[exports] : 14
4-
[funcs] : 20
3+
[exports] : 10
4+
[funcs] : 14
55
[globals] : 26
66
[imports] : 12
77
[memories] : 1
88
[memory-data] : 16
9-
[table-data] : 6
9+
[table-data] : 3
1010
[tables] : 2
11-
[tags] : 1
12-
[total] : 734
13-
[vars] : 45
14-
ArrayNewFixed : 5
11+
[tags] : 2
12+
[total] : 642
13+
[vars] : 48
14+
ArrayNewFixed : 6
1515
AtomicCmpxchg : 1
16-
AtomicFence : 1
17-
AtomicNotify : 2
18-
AtomicRMW : 1
19-
Binary : 43
20-
Block : 115
21-
BrOn : 1
22-
Break : 11
23-
Call : 27
24-
CallRef : 2
25-
Const : 144
26-
Drop : 15
27-
GlobalGet : 66
28-
GlobalSet : 50
16+
AtomicFence : 2
17+
Binary : 40
18+
Block : 106
19+
Break : 8
20+
Call : 26
21+
CallRef : 1
22+
Const : 109
23+
DataDrop : 2
24+
Drop : 14
25+
GlobalGet : 57
26+
GlobalSet : 44
2927
If : 32
30-
Load : 5
28+
Load : 6
3129
LocalGet : 20
32-
LocalSet : 11
33-
Loop : 5
30+
LocalSet : 16
31+
Loop : 8
3432
MemoryCopy : 1
35-
MemoryFill : 1
36-
MemoryInit : 1
37-
Nop : 13
38-
RefEq : 3
39-
RefFunc : 11
40-
RefI31 : 12
41-
RefNull : 11
33+
Nop : 11
34+
Pop : 1
35+
RefAs : 2
36+
RefCast : 2
37+
RefEq : 4
38+
RefFunc : 5
39+
RefI31 : 8
40+
RefNull : 8
4241
RefTest : 1
4342
Return : 6
44-
SIMDExtract : 2
45-
Select : 3
46-
Store : 3
47-
StringConst : 13
48-
StringEq : 2
49-
StringWTF16Get : 1
50-
StructNew : 14
43+
SIMDExtract : 1
44+
Select : 1
45+
Store : 1
46+
StringConst : 8
47+
StringEncode : 1
48+
StringMeasure : 1
49+
StructNew : 9
50+
Switch : 1
5151
Throw : 1
52-
Try : 5
53-
TryTable : 6
54-
TupleExtract : 1
55-
TupleMake : 8
56-
Unary : 33
57-
Unreachable : 25
52+
Try : 1
53+
TryTable : 4
54+
TupleExtract : 2
55+
TupleMake : 6
56+
Unary : 36
57+
Unreachable : 22

0 commit comments

Comments
 (0)