diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index bf7e1ef480e..823c57780a0 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1372,6 +1372,18 @@ def can_run_on_wasm(self, wasm): return not wasm_notices_export_changes(wasm) +# see https://github.com/WebAssembly/binaryen/issues/6823#issuecomment-2649122032 +# as the interpreter refers to tags by name, two imports of the same Tag give it +# two different names, but they should behave as if they are one. +def wasm_has_duplicate_tags(wasm): + # as with wasm_notices_export_changes, we could be more precise here and + # disassemble the wasm. + binary = open(wasm, 'rb').read() + # check if we import jstag or wasmtag, which are used in the wasm, so any + # duplication may hit the github issue mentioned above. + return binary.count(b'jstag') >= 2 or binary.count(b'wasmtag') >= 2 + + # Tests wasm-merge class Merge(TestCaseHandler): frequency = 0.15 @@ -1424,6 +1436,10 @@ def handle(self, wasm): abspath('second.wasm'), 'second', '-o', merged, '--skip-export-conflicts'] + FEATURE_OPTS + ['-all']) + if wasm_has_duplicate_tags(merged): + note_ignored_vm_run('dupe_tags') + return + # sometimes also optimize the merged module if random.random() < 0.5: opts = get_random_opts() diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index e2e9b34323f..718692498d1 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -272,7 +272,7 @@ var imports = { if (!which) { throw 'some JS error'; } else { - throw new WebAssembly.Exception(jsTag, [which]); + throw new WebAssembly.Exception(wasmTag, [which]); } }, @@ -333,10 +333,13 @@ var imports = { // 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({ + var wasmTag = imports['fuzzing-support']['wasmtag'] = new WebAssembly.Tag({ 'parameters': ['i32'] }); + // The JSTag that represents a JS tag. + imports['fuzzing-support']['jstag'] = WebAssembly.JSTag; + // This allows j2wasm content to run in the fuzzer. imports['imports'] = { 'j2wasm.ExceptionUtils.tag': new WebAssembly.Tag({ diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index ad4f5316cef..c6cb7ecc571 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -42,8 +42,13 @@ struct LoggingExternalInterface : public ShellExternalInterface { Name exportedTable; Module& wasm; - // The name of the imported fuzzing tag. - Name fuzzTag; + // The name of the imported fuzzing tag for wasm. + Name wasmTag; + + // The name of the imported tag for js exceptions. If it is not imported, we + // use a default name here (which should differentiate it from any wasm + // exceptions). + Name jsTag = "__private"; // The ModuleRunner and this ExternalInterface end up needing links both ways, // so we cannot init this in the constructor. @@ -60,9 +65,12 @@ struct LoggingExternalInterface : public ShellExternalInterface { } for (auto& tag : wasm.tags) { - if (tag->module == "fuzzing-support" && tag->base == "tag") { - fuzzTag = tag->name; - break; + if (tag->module == "fuzzing-support") { + if (tag->base == "wasmtag") { + wasmTag = tag->name; + } else if (tag->base == "jstag") { + jsTag = tag->name; + } } } } @@ -96,29 +104,29 @@ struct LoggingExternalInterface : public ShellExternalInterface { // 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(); + throwJSException(); } else { - auto payload = std::make_shared(fuzzTag, arguments); + auto payload = std::make_shared(wasmTag, 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). if (!exportedTable) { - throwEmptyException(); + throwJSException(); } auto index = arguments[0].getUnsigned(); if (index >= tables[exportedTable].size()) { - throwEmptyException(); + throwJSException(); } return {tableLoad(exportedTable, index)}; } else if (import->base == "table-set") { if (!exportedTable) { - throwEmptyException(); + throwJSException(); } auto index = arguments[0].getUnsigned(); if (index >= tables[exportedTable].size()) { - throwEmptyException(); + throwJSException(); } tableStore(exportedTable, index, arguments[1]); return {}; @@ -172,21 +180,24 @@ struct LoggingExternalInterface : public ShellExternalInterface { return {}; } - void throwEmptyException() { - // Use a hopefully private tag. - auto payload = std::make_shared("__private", Literals{}); + void throwJSException() { + // JS exceptions contain an externref, which wasm can't read (so the actual + // value here does not matter). + Literal externref = Literal::makeI31(0, Unshared).externalize(); + Literals arguments = {externref}; + auto payload = std::make_shared(jsTag, arguments); throwException(WasmException{Literal(payload)}); } Literals callExportAsJS(Index index) { if (index >= wasm.exports.size()) { // No export. - throwEmptyException(); + throwJSException(); } auto& exp = wasm.exports[index]; if (exp->kind != ExternalKind::Function) { // No callable export. - throwEmptyException(); + throwJSException(); } return callFunctionAsJS(exp->value); } @@ -194,7 +205,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { Literals callRefAsJS(Literal ref) { if (!ref.isFunction()) { // Not a callable ref. - throwEmptyException(); + throwJSException(); } return callFunctionAsJS(ref.getFunc()); } @@ -210,10 +221,10 @@ struct LoggingExternalInterface : public ShellExternalInterface { // An i64 param can work from JS, but fuzz_shell provides 0, which errors // on attempts to convert it to BigInt. v128 and exnref are disalloewd. if (param == Type::i64 || param == Type::v128 || param.isExn()) { - throwEmptyException(); + throwJSException(); } if (!param.isDefaultable()) { - throwEmptyException(); + throwJSException(); } arguments.push_back(Literal::makeZero(param)); } @@ -224,7 +235,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { // An i64 result is fine: a BigInt will be provided. But v128 and exnref // still error. if (result == Type::v128 || result.isExn()) { - throwEmptyException(); + throwJSException(); } } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 00181a90b31..c853e37f790 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -648,13 +648,20 @@ void TranslateToFuzzReader::setupTags() { addTag(); } - // Add the fuzzing support tag manually sometimes. + // Add the fuzzing support tags 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)); + auto wasmTag = builder.makeTag(Names::getValidTagName(wasm, "wasmtag"), + Signature(Type::i32, Type::none)); + wasmTag->module = "fuzzing-support"; + wasmTag->base = "wasmtag"; + wasm.addTag(std::move(wasmTag)); + + auto externref = Type(HeapType::ext, Nullable); + auto jsTag = builder.makeTag(Names::getValidTagName(wasm, "jstag"), + Signature(externref, Type::none)); + jsTag->module = "fuzzing-support"; + jsTag->base = "jstag"; + wasm.addTag(std::move(jsTag)); } } diff --git a/test/lit/d8/fuzz_shell_exceptions.wast b/test/lit/d8/fuzz_shell_exceptions.wast index ed3b070104b..6b46ee2adfa 100644 --- a/test/lit/d8/fuzz_shell_exceptions.wast +++ b/test/lit/d8/fuzz_shell_exceptions.wast @@ -3,6 +3,10 @@ (module (import "fuzzing-support" "throw" (func $throw (param i32))) + ;; Verify that fuzz_shell.js provides these imports for the wasm. + (import "fuzzing-support" "wasmtag" (tag $imported-wasm-tag (param i32))) + (import "fuzzing-support" "jstag" (tag $imported-js-tag (param externref))) + (func $throwing-js (export "throwing-js") ;; Telling JS to throw with arg 0 leads to a JS exception thrown. (call $throw @@ -20,7 +24,7 @@ ;; Build to a binary wasm. ;; -;; RUN: wasm-opt %s -o %t.wasm -q +;; RUN: wasm-opt %s -o %t.wasm -q -all ;; Run in node. ;; diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 5e6b0633977..0d9eb0c9cc1 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -21,7 +21,8 @@ (import "fuzzing-support" "sleep" (func $sleep (param i32 i32) (result i32))) - (import "fuzzing-support" "tag" (tag $imported-tag (param i32))) + (import "fuzzing-support" "wasmtag" (tag $imported-wasm-tag (param i32))) + (import "fuzzing-support" "jstag" (tag $imported-js-tag (param externref))) (table $table 10 20 funcref) @@ -42,7 +43,7 @@ ) ;; CHECK: [fuzz-exec] calling throwing - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $throwing (export "throwing") ;; Throwing 0 throws a JS ("private") exception. (call $throw @@ -51,7 +52,7 @@ ) ;; CHECK: [fuzz-exec] calling throwing-tag - ;; CHECK-NEXT: [exception thrown: imported-tag 42] + ;; CHECK-NEXT: [exception thrown: imported-wasm-tag 42] (func $throwing-tag (export "throwing-tag") ;; Throwing non-0 throws using the tag we imported. (call $throw @@ -60,7 +61,7 @@ ) ;; CHECK: [fuzz-exec] calling table.setting - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $table.setting (export "table.setting") (call $table.set (i32.const 5) @@ -76,7 +77,7 @@ ;; CHECK: [fuzz-exec] calling table.getting ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $table.getting (export "table.getting") ;; There is a non-null value at 5, and a null at 6. (call $log-i32 @@ -104,7 +105,7 @@ ;; CHECK: [fuzz-exec] calling export.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $export.calling (export "export.calling") ;; At index 0 in the exports we have $logging, so we will do those loggings. (call $call.export @@ -140,7 +141,7 @@ ;; CHECK: [fuzz-exec] calling ref.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] - ;; CHECK-NEXT: [exception thrown: __private ()] + ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $ref.calling (export "ref.calling") ;; This will emit some logging. (call $call.ref @@ -310,6 +311,28 @@ ) ) + ;; CHECK: [fuzz-exec] calling catch-js-tag + ;; CHECK-NEXT: [fuzz-exec] note result: catch-js-tag => 100 + (func $catch-js-tag (export "catch-js-tag") (result i32) + ;; The table.set out of bounds will throw a JS exception, so it will be caught + ;; by the catch here, and we'll return the number at the end. + (drop + (block $out (result externref) + (try_table (catch $imported-js-tag $out) + (call $table.set + (i32.const 9999) + (ref.func $table.setting) + ) + (return + (i32.const -1) + ) + ) + ) + ) + (i32.const 100) + ) + + ;; CHECK: [fuzz-exec] calling do-sleep ;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 ;; CHECK-NEXT: warning: no passes specified, not doing any work @@ -327,23 +350,23 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] ;; CHECK: [fuzz-exec] calling throwing -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling throwing-tag -;; CHECK-NEXT: [exception thrown: imported-tag 42] +;; CHECK-NEXT: [exception thrown: imported-wasm-tag 42] ;; CHECK: [fuzz-exec] calling table.setting -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling table.getting ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling export.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling export.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -354,7 +377,7 @@ ;; CHECK: [fuzz-exec] calling ref.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] -;; CHECK-NEXT: [exception thrown: __private ()] +;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling ref.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] @@ -385,8 +408,12 @@ ;; CHECK: [fuzz-exec] calling ref.calling.trap ;; CHECK-NEXT: [trap unreachable] +;; CHECK: [fuzz-exec] calling catch-js-tag +;; CHECK-NEXT: [fuzz-exec] note result: catch-js-tag => 100 + ;; CHECK: [fuzz-exec] calling do-sleep ;; CHECK-NEXT: [fuzz-exec] note result: do-sleep => 42 +;; CHECK-NEXT: [fuzz-exec] comparing catch-js-tag ;; CHECK-NEXT: [fuzz-exec] comparing do-sleep ;; CHECK-NEXT: [fuzz-exec] comparing export.calling ;; CHECK-NEXT: [fuzz-exec] comparing export.calling.catching 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 a32de90e7d4..d36325f58b8 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,42 +1,45 @@ Metrics total - [exports] : 15 - [funcs] : 17 + [exports] : 14 + [funcs] : 18 [globals] : 4 - [imports] : 11 + [imports] : 12 [memories] : 1 [memory-data] : 112 - [table-data] : 4 + [table-data] : 3 [tables] : 1 [tags] : 0 - [total] : 688 - [vars] : 15 + [total] : 626 + [vars] : 49 ArrayNew : 1 ArrayNewFixed : 5 - Binary : 70 - Block : 77 - Call : 43 - Const : 166 - Drop : 8 - GlobalGet : 43 + ArraySet : 1 + AtomicCmpxchg : 1 + AtomicFence : 1 + Binary : 74 + Block : 79 + Call : 40 + Const : 138 + Drop : 9 + GlobalGet : 42 GlobalSet : 38 - If : 19 + If : 20 Load : 16 - LocalGet : 45 - LocalSet : 21 - Loop : 3 - Nop : 3 + LocalGet : 46 + LocalSet : 22 + Loop : 2 + Nop : 4 RefAs : 2 - RefFunc : 28 - RefNull : 8 - Return : 5 + RefFunc : 8 + RefNull : 4 + Return : 3 Select : 1 Store : 1 - StringConst : 2 - StructNew : 37 + StringConst : 3 + StructNew : 18 StructSet : 1 Throw : 1 - TryTable : 3 + TryTable : 2 TupleMake : 3 - Unary : 19 + Unary : 21 Unreachable : 19