diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 252ee5629dc..e2e9b34323f 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -268,8 +268,12 @@ var imports = { 'log-v128': logValue, // Throw an exception from JS. - 'throw': () => { - throw 'some JS error'; + 'throw': (which) => { + if (!which) { + throw 'some JS error'; + } else { + throw new WebAssembly.Exception(jsTag, [which]); + } }, // Table operations. @@ -326,8 +330,14 @@ var imports = { }, }; -// If Tags are available, add the import j2wasm expects. +// If Tags are available, add some. if (typeof WebAssembly.Tag !== 'undefined') { + // A tag for general use in the fuzzer. + var jsTag = imports['fuzzing-support']['tag'] = new WebAssembly.Tag({ + 'parameters': ['i32'] + }); + + // This allows j2wasm content to run in the fuzzer. imports['imports'] = { 'j2wasm.ExceptionUtils.tag': new WebAssembly.Tag({ 'parameters': ['externref'] diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index ea822d5477a..ad4f5316cef 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -42,6 +42,9 @@ struct LoggingExternalInterface : public ShellExternalInterface { Name exportedTable; Module& wasm; + // The name of the imported fuzzing tag. + Name fuzzTag; + // The ModuleRunner and this ExternalInterface end up needing links both ways, // so we cannot init this in the constructor. ModuleRunner* instance = nullptr; @@ -55,6 +58,13 @@ struct LoggingExternalInterface : public ShellExternalInterface { break; } } + + for (auto& tag : wasm.tags) { + if (tag->module == "fuzzing-support" && tag->base == "tag") { + fuzzTag = tag->name; + break; + } + } } Literals callImport(Function* import, const Literals& arguments) override { @@ -82,7 +92,15 @@ struct LoggingExternalInterface : public ShellExternalInterface { std::cout << "]\n"; return {}; } else if (import->base == "throw") { - throwEmptyException(); + // Throw something, depending on the value of the argument. 0 means we + // should throw a JS exception, and any other value means we should + // throw a wasm exception (with that value as the payload). + if (arguments[0].geti32() == 0) { + throwEmptyException(); + } else { + auto payload = std::make_shared(fuzzTag, arguments); + throwException(WasmException{Literal(payload)}); + } } else if (import->base == "table-get") { // Check for errors here, duplicating tableLoad(), because that will // trap, and we just want to throw an exception (the same as JS would). diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index bc981a49f53..00181a90b31 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -634,8 +634,8 @@ void TranslateToFuzzReader::setupGlobals() { } void TranslateToFuzzReader::setupTags() { - // As in modifyInitialFunctions(), we can't allow tag imports as it would trap - // when the fuzzing infrastructure doesn't know what to provide. + // As in modifyInitialFunctions(), we can't allow arbitrary tag imports, which + // would trap when the fuzzing infrastructure doesn't know what to provide. for (auto& tag : wasm.tags) { if (tag->imported()) { tag->module = tag->base = Name(); @@ -647,6 +647,15 @@ void TranslateToFuzzReader::setupTags() { for (size_t i = 0; i < num; i++) { addTag(); } + + // Add the fuzzing support tag manually sometimes. + if (oneIn(2)) { + auto tag = builder.makeTag(Names::getValidTagName(wasm, "tag"), + Signature(Type::i32, Type::none)); + tag->module = "fuzzing-support"; + tag->base = "tag"; + wasm.addTag(std::move(tag)); + } } void TranslateToFuzzReader::addTag() { @@ -888,16 +897,14 @@ void TranslateToFuzzReader::addImportCallingSupport() { } void TranslateToFuzzReader::addImportThrowingSupport() { - // Throw some kind of exception from JS. - // TODO: Send an index, which is which exported wasm Tag we should throw, or - // something not exported if out of bounds. First we must also export - // tags sometimes. + // Throw some kind of exception from JS. If we send 0 then a pure JS + // exception is thrown, and any other value is the value in a wasm tag. throwImportName = Names::getValidFunctionName(wasm, "throw"); auto func = std::make_unique(); func->name = throwImportName; func->module = "fuzzing-support"; func->base = "throw"; - func->type = Signature(Type::none, Type::none); + func->type = Signature(Type::i32, Type::none); wasm.addFunction(std::move(func)); } @@ -1067,12 +1074,21 @@ Expression* TranslateToFuzzReader::makeImportLogging() { } Expression* TranslateToFuzzReader::makeImportThrowing(Type type) { + // TODO: This and makeThrow should probably be rare, as they halt the program. + // We throw from the import, so this call appears to be none and not // unreachable. assert(type == Type::none); - // TODO: This and makeThrow should probably be rare, as they halt the program. - return builder.makeCall(throwImportName, {}, Type::none); + // An argument of 0 means to throw a JS exception, and otherwise the value in + // a wasm tag. Emit 0 or non-zero with ~equal probability. + Expression* arg; + if (oneIn(2)) { + arg = builder.makeConst(int32_t(0)); + } else { + arg = makeConst(Type::i32); + } + return builder.makeCall(throwImportName, {arg}, Type::none); } Expression* TranslateToFuzzReader::makeImportTableGet() { diff --git a/test/lit/d8/fuzz_shell_exceptions.wast b/test/lit/d8/fuzz_shell_exceptions.wast new file mode 100644 index 00000000000..ed3b070104b --- /dev/null +++ b/test/lit/d8/fuzz_shell_exceptions.wast @@ -0,0 +1,35 @@ +;; Test throwing from JS by calling the throw import. + +(module + (import "fuzzing-support" "throw" (func $throw (param i32))) + + (func $throwing-js (export "throwing-js") + ;; Telling JS to throw with arg 0 leads to a JS exception thrown. + (call $throw + (i32.const 0) + ) + ) + + (func $throwing-tag (export "throwing-tag") + ;; A non-0 arg leads to a wasm Tag being thrown. + (call $throw + (i32.const 42) + ) + ) +) + +;; Build to a binary wasm. +;; +;; RUN: wasm-opt %s -o %t.wasm -q + +;; Run in node. +;; +;; RUN: v8 %S/../../../scripts/fuzz_shell.js -- %t.wasm | filecheck %s +;; +;; CHECK: [fuzz-exec] calling throwing-js +;; CHECK: exception thrown: some JS error +;; CHECK: [fuzz-exec] calling throwing-tag +;; CHECK: exception thrown: [object WebAssembly.Exception] + + + diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 8e251b2ed4f..5e6b0633977 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -8,7 +8,7 @@ (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) (import "fuzzing-support" "log-f64" (func $log-f64 (param f64))) - (import "fuzzing-support" "throw" (func $throw)) + (import "fuzzing-support" "throw" (func $throw (param i32))) (import "fuzzing-support" "table-set" (func $table.set (param i32 funcref))) (import "fuzzing-support" "table-get" (func $table.get (param i32) (result funcref))) @@ -21,6 +21,8 @@ (import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32))) + (import "fuzzing-support" "tag" (tag $imported-tag (param i32))) + (table $table 10 20 funcref) ;; Note that the exported table appears first here, but in the binary and in @@ -42,7 +44,19 @@ ;; CHECK: [fuzz-exec] calling throwing ;; CHECK-NEXT: [exception thrown: __private ()] (func $throwing (export "throwing") - (call $throw) + ;; Throwing 0 throws a JS ("private") exception. + (call $throw + (i32.const 0) + ) + ) + + ;; CHECK: [fuzz-exec] calling throwing-tag + ;; CHECK-NEXT: [exception thrown: imported-tag 42] + (func $throwing-tag (export "throwing-tag") + ;; Throwing non-0 throws using the tag we imported. + (call $throw + (i32.const 42) + ) ) ;; CHECK: [fuzz-exec] calling table.setting @@ -315,6 +329,9 @@ ;; CHECK: [fuzz-exec] calling throwing ;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK: [fuzz-exec] calling throwing-tag +;; CHECK-NEXT: [exception thrown: imported-tag 42] + ;; CHECK: [fuzz-exec] calling table.setting ;; CHECK-NEXT: [exception thrown: __private ()] @@ -386,3 +403,4 @@ ;; CHECK-NEXT: [fuzz-exec] comparing table.getting ;; CHECK-NEXT: [fuzz-exec] comparing table.setting ;; CHECK-NEXT: [fuzz-exec] comparing throwing +;; CHECK-NEXT: [fuzz-exec] comparing throwing-tag diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index a702187b204..a32de90e7d4 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,52 +1,42 @@ Metrics total - [exports] : 10 - [funcs] : 9 + [exports] : 15 + [funcs] : 17 [globals] : 4 - [imports] : 8 + [imports] : 11 [memories] : 1 [memory-data] : 112 - [table-data] : 0 + [table-data] : 4 [tables] : 1 - [tags] : 1 - [total] : 553 - [vars] : 38 - ArrayCopy : 2 - ArrayLen : 4 + [tags] : 0 + [total] : 688 + [vars] : 15 ArrayNew : 1 - ArrayNewFixed : 7 - Binary : 69 - Block : 55 - BrOn : 1 - Break : 1 - Call : 23 - Const : 107 - Drop : 7 - GlobalGet : 19 - GlobalSet : 18 - If : 17 - Load : 17 - LocalGet : 66 - LocalSet : 39 - Loop : 1 + ArrayNewFixed : 5 + Binary : 70 + Block : 77 + Call : 43 + Const : 166 + Drop : 8 + GlobalGet : 43 + GlobalSet : 38 + If : 19 + Load : 16 + LocalGet : 45 + LocalSet : 21 + Loop : 3 Nop : 3 - RefAs : 13 - RefFunc : 9 - RefI31 : 2 - RefIsNull : 2 - RefNull : 7 - RefTest : 1 + RefAs : 2 + RefFunc : 28 + RefNull : 8 Return : 5 - SIMDReplace : 1 - Select : 3 + Select : 1 Store : 1 - StringConst : 3 - StringEncode : 1 - StringMeasure : 1 - StructGet : 1 - StructNew : 15 - StructSet : 2 - TupleExtract : 1 + StringConst : 2 + StructNew : 37 + StructSet : 1 + Throw : 1 + TryTable : 3 TupleMake : 3 - Unary : 16 - Unreachable : 9 + Unary : 19 + Unreachable : 19