Skip to content

Commit 3fcfec4

Browse files
authored
Fuzzer: Use a WebAssembly.Tag from JS (#7277)
We now create a Tag in JS and import it in the wasm. We can then use that tag from JS when we throw (only some of the time, controlled by a new parameter to the throwing import).
1 parent 1de13c3 commit 3fcfec4

File tree

6 files changed

+143
-56
lines changed

6 files changed

+143
-56
lines changed

scripts/fuzz_shell.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,12 @@ var imports = {
268268
'log-v128': logValue,
269269

270270
// Throw an exception from JS.
271-
'throw': () => {
272-
throw 'some JS error';
271+
'throw': (which) => {
272+
if (!which) {
273+
throw 'some JS error';
274+
} else {
275+
throw new WebAssembly.Exception(jsTag, [which]);
276+
}
273277
},
274278

275279
// Table operations.
@@ -326,8 +330,14 @@ var imports = {
326330
},
327331
};
328332

329-
// If Tags are available, add the import j2wasm expects.
333+
// If Tags are available, add some.
330334
if (typeof WebAssembly.Tag !== 'undefined') {
335+
// A tag for general use in the fuzzer.
336+
var jsTag = imports['fuzzing-support']['tag'] = new WebAssembly.Tag({
337+
'parameters': ['i32']
338+
});
339+
340+
// This allows j2wasm content to run in the fuzzer.
331341
imports['imports'] = {
332342
'j2wasm.ExceptionUtils.tag': new WebAssembly.Tag({
333343
'parameters': ['externref']

src/tools/execution-results.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ struct LoggingExternalInterface : public ShellExternalInterface {
4242
Name exportedTable;
4343
Module& wasm;
4444

45+
// The name of the imported fuzzing tag.
46+
Name fuzzTag;
47+
4548
// The ModuleRunner and this ExternalInterface end up needing links both ways,
4649
// so we cannot init this in the constructor.
4750
ModuleRunner* instance = nullptr;
@@ -55,6 +58,13 @@ struct LoggingExternalInterface : public ShellExternalInterface {
5558
break;
5659
}
5760
}
61+
62+
for (auto& tag : wasm.tags) {
63+
if (tag->module == "fuzzing-support" && tag->base == "tag") {
64+
fuzzTag = tag->name;
65+
break;
66+
}
67+
}
5868
}
5969

6070
Literals callImport(Function* import, const Literals& arguments) override {
@@ -82,7 +92,15 @@ struct LoggingExternalInterface : public ShellExternalInterface {
8292
std::cout << "]\n";
8393
return {};
8494
} else if (import->base == "throw") {
85-
throwEmptyException();
95+
// Throw something, depending on the value of the argument. 0 means we
96+
// should throw a JS exception, and any other value means we should
97+
// throw a wasm exception (with that value as the payload).
98+
if (arguments[0].geti32() == 0) {
99+
throwEmptyException();
100+
} else {
101+
auto payload = std::make_shared<ExnData>(fuzzTag, arguments);
102+
throwException(WasmException{Literal(payload)});
103+
}
86104
} else if (import->base == "table-get") {
87105
// Check for errors here, duplicating tableLoad(), because that will
88106
// trap, and we just want to throw an exception (the same as JS would).

src/tools/fuzzing/fuzzing.cpp

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -634,8 +634,8 @@ void TranslateToFuzzReader::setupGlobals() {
634634
}
635635

636636
void TranslateToFuzzReader::setupTags() {
637-
// As in modifyInitialFunctions(), we can't allow tag imports as it would trap
638-
// when the fuzzing infrastructure doesn't know what to provide.
637+
// As in modifyInitialFunctions(), we can't allow arbitrary tag imports, which
638+
// would trap when the fuzzing infrastructure doesn't know what to provide.
639639
for (auto& tag : wasm.tags) {
640640
if (tag->imported()) {
641641
tag->module = tag->base = Name();
@@ -647,6 +647,15 @@ void TranslateToFuzzReader::setupTags() {
647647
for (size_t i = 0; i < num; i++) {
648648
addTag();
649649
}
650+
651+
// Add the fuzzing support tag manually sometimes.
652+
if (oneIn(2)) {
653+
auto tag = builder.makeTag(Names::getValidTagName(wasm, "tag"),
654+
Signature(Type::i32, Type::none));
655+
tag->module = "fuzzing-support";
656+
tag->base = "tag";
657+
wasm.addTag(std::move(tag));
658+
}
650659
}
651660

652661
void TranslateToFuzzReader::addTag() {
@@ -888,16 +897,14 @@ void TranslateToFuzzReader::addImportCallingSupport() {
888897
}
889898

890899
void TranslateToFuzzReader::addImportThrowingSupport() {
891-
// Throw some kind of exception from JS.
892-
// TODO: Send an index, which is which exported wasm Tag we should throw, or
893-
// something not exported if out of bounds. First we must also export
894-
// tags sometimes.
900+
// Throw some kind of exception from JS. If we send 0 then a pure JS
901+
// exception is thrown, and any other value is the value in a wasm tag.
895902
throwImportName = Names::getValidFunctionName(wasm, "throw");
896903
auto func = std::make_unique<Function>();
897904
func->name = throwImportName;
898905
func->module = "fuzzing-support";
899906
func->base = "throw";
900-
func->type = Signature(Type::none, Type::none);
907+
func->type = Signature(Type::i32, Type::none);
901908
wasm.addFunction(std::move(func));
902909
}
903910

@@ -1067,12 +1074,21 @@ Expression* TranslateToFuzzReader::makeImportLogging() {
10671074
}
10681075

10691076
Expression* TranslateToFuzzReader::makeImportThrowing(Type type) {
1077+
// TODO: This and makeThrow should probably be rare, as they halt the program.
1078+
10701079
// We throw from the import, so this call appears to be none and not
10711080
// unreachable.
10721081
assert(type == Type::none);
10731082

1074-
// TODO: This and makeThrow should probably be rare, as they halt the program.
1075-
return builder.makeCall(throwImportName, {}, Type::none);
1083+
// An argument of 0 means to throw a JS exception, and otherwise the value in
1084+
// a wasm tag. Emit 0 or non-zero with ~equal probability.
1085+
Expression* arg;
1086+
if (oneIn(2)) {
1087+
arg = builder.makeConst(int32_t(0));
1088+
} else {
1089+
arg = makeConst(Type::i32);
1090+
}
1091+
return builder.makeCall(throwImportName, {arg}, Type::none);
10761092
}
10771093

10781094
Expression* TranslateToFuzzReader::makeImportTableGet() {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
;; Test throwing from JS by calling the throw import.
2+
3+
(module
4+
(import "fuzzing-support" "throw" (func $throw (param i32)))
5+
6+
(func $throwing-js (export "throwing-js")
7+
;; Telling JS to throw with arg 0 leads to a JS exception thrown.
8+
(call $throw
9+
(i32.const 0)
10+
)
11+
)
12+
13+
(func $throwing-tag (export "throwing-tag")
14+
;; A non-0 arg leads to a wasm Tag being thrown.
15+
(call $throw
16+
(i32.const 42)
17+
)
18+
)
19+
)
20+
21+
;; Build to a binary wasm.
22+
;;
23+
;; RUN: wasm-opt %s -o %t.wasm -q
24+
25+
;; Run in node.
26+
;;
27+
;; RUN: v8 %S/../../../scripts/fuzz_shell.js -- %t.wasm | filecheck %s
28+
;;
29+
;; CHECK: [fuzz-exec] calling throwing-js
30+
;; CHECK: exception thrown: some JS error
31+
;; CHECK: [fuzz-exec] calling throwing-tag
32+
;; CHECK: exception thrown: [object WebAssembly.Exception]
33+
34+
35+

test/lit/exec/fuzzing-api.wast

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
(import "fuzzing-support" "log-i32" (func $log-i32 (param i32)))
99
(import "fuzzing-support" "log-f64" (func $log-f64 (param f64)))
1010

11-
(import "fuzzing-support" "throw" (func $throw))
11+
(import "fuzzing-support" "throw" (func $throw (param i32)))
1212

1313
(import "fuzzing-support" "table-set" (func $table.set (param i32 funcref)))
1414
(import "fuzzing-support" "table-get" (func $table.get (param i32) (result funcref)))
@@ -21,6 +21,8 @@
2121

2222
(import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32)))
2323

24+
(import "fuzzing-support" "tag" (tag $imported-tag (param i32)))
25+
2426
(table $table 10 20 funcref)
2527

2628
;; Note that the exported table appears first here, but in the binary and in
@@ -42,7 +44,19 @@
4244
;; CHECK: [fuzz-exec] calling throwing
4345
;; CHECK-NEXT: [exception thrown: __private ()]
4446
(func $throwing (export "throwing")
45-
(call $throw)
47+
;; Throwing 0 throws a JS ("private") exception.
48+
(call $throw
49+
(i32.const 0)
50+
)
51+
)
52+
53+
;; CHECK: [fuzz-exec] calling throwing-tag
54+
;; CHECK-NEXT: [exception thrown: imported-tag 42]
55+
(func $throwing-tag (export "throwing-tag")
56+
;; Throwing non-0 throws using the tag we imported.
57+
(call $throw
58+
(i32.const 42)
59+
)
4660
)
4761

4862
;; CHECK: [fuzz-exec] calling table.setting
@@ -315,6 +329,9 @@
315329
;; CHECK: [fuzz-exec] calling throwing
316330
;; CHECK-NEXT: [exception thrown: __private ()]
317331

332+
;; CHECK: [fuzz-exec] calling throwing-tag
333+
;; CHECK-NEXT: [exception thrown: imported-tag 42]
334+
318335
;; CHECK: [fuzz-exec] calling table.setting
319336
;; CHECK-NEXT: [exception thrown: __private ()]
320337

@@ -386,3 +403,4 @@
386403
;; CHECK-NEXT: [fuzz-exec] comparing table.getting
387404
;; CHECK-NEXT: [fuzz-exec] comparing table.setting
388405
;; CHECK-NEXT: [fuzz-exec] comparing throwing
406+
;; CHECK-NEXT: [fuzz-exec] comparing throwing-tag
Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,42 @@
11
Metrics
22
total
3-
[exports] : 10
4-
[funcs] : 9
3+
[exports] : 15
4+
[funcs] : 17
55
[globals] : 4
6-
[imports] : 8
6+
[imports] : 11
77
[memories] : 1
88
[memory-data] : 112
9-
[table-data] : 0
9+
[table-data] : 4
1010
[tables] : 1
11-
[tags] : 1
12-
[total] : 553
13-
[vars] : 38
14-
ArrayCopy : 2
15-
ArrayLen : 4
11+
[tags] : 0
12+
[total] : 688
13+
[vars] : 15
1614
ArrayNew : 1
17-
ArrayNewFixed : 7
18-
Binary : 69
19-
Block : 55
20-
BrOn : 1
21-
Break : 1
22-
Call : 23
23-
Const : 107
24-
Drop : 7
25-
GlobalGet : 19
26-
GlobalSet : 18
27-
If : 17
28-
Load : 17
29-
LocalGet : 66
30-
LocalSet : 39
31-
Loop : 1
15+
ArrayNewFixed : 5
16+
Binary : 70
17+
Block : 77
18+
Call : 43
19+
Const : 166
20+
Drop : 8
21+
GlobalGet : 43
22+
GlobalSet : 38
23+
If : 19
24+
Load : 16
25+
LocalGet : 45
26+
LocalSet : 21
27+
Loop : 3
3228
Nop : 3
33-
RefAs : 13
34-
RefFunc : 9
35-
RefI31 : 2
36-
RefIsNull : 2
37-
RefNull : 7
38-
RefTest : 1
29+
RefAs : 2
30+
RefFunc : 28
31+
RefNull : 8
3932
Return : 5
40-
SIMDReplace : 1
41-
Select : 3
33+
Select : 1
4234
Store : 1
43-
StringConst : 3
44-
StringEncode : 1
45-
StringMeasure : 1
46-
StructGet : 1
47-
StructNew : 15
48-
StructSet : 2
49-
TupleExtract : 1
35+
StringConst : 2
36+
StructNew : 37
37+
StructSet : 1
38+
Throw : 1
39+
TryTable : 3
5040
TupleMake : 3
51-
Unary : 16
52-
Unreachable : 9
41+
Unary : 19
42+
Unreachable : 19

0 commit comments

Comments
 (0)