diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index e90b8fa65d6..eb5c56995b7 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2689,12 +2689,12 @@ def get_random_opts(): # We can't do this if a.wasm doesn't exist, which can be the # case if we failed to even generate the wasm. if not os.path.exists('a.wasm'): - print('''\ + print(f'''\ ================================================================================ You found a bug in the fuzzer itself! It failed to generate a valid wasm file from the random input. Please report it with - seed: %(seed)d + seed: {seed} and the exact version of Binaryen you found it on, plus the exact Python version (hopefully deterministic random numbers will be identical). diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 47d73a51248..e16dcdd00c9 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -163,6 +163,19 @@ function logValue(x, y) { console.log('[LoggingExternalInterface logging ' + printed(x, y) + ']'); } +function logRef(ref) { + // Look for VM bugs by using the reference in an API (note: we cannot do + // +ref or ref+'' as those trap). + JSON.stringify(ref); + // If not null, try to read a property, which might exercise an + // interesting code path. + if (ref) { + ref.foobar; + } + // Finally, log normally as with all other loggers. + logValue(ref); +} + // Track the exports in a map (similar to the Exports object from wasm, i.e., // whose keys are strings and whose values are the corresponding exports). var exports = {}; @@ -290,6 +303,10 @@ var imports = { // we could avoid running JS on code with SIMD in it, but it is useful to // fuzz such code as much as we can.) 'log-v128': logValue, + 'log-anyref': logRef, + 'log-funcref': logRef, + 'log-contref': logRef, + 'log-externref': logRef, // Throw an exception from JS. 'throw': (which) => { diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 7df3e3939da..f5f55bdaaee 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -52,6 +52,47 @@ Tag& getJsTag() { return tag; } +void printValue(Literal value) { + // Unwrap an externalized GC value to get the actual value, but not strings, + // which are normally a subtype of ext. + if (Type::isSubType(value.type, Type(HeapType::ext, Nullable)) && + !value.type.isString()) { + value = value.internalize(); + } + + // An anyref literal is a string. + if (value.type.isRef() && + value.type.getHeapType().isMaybeShared(HeapType::any)) { + value = value.externalize(); + } + + // Don't print most reference values, as e.g. funcref(N) contains an index, + // which is not guaranteed to remain identical after optimizations. Do not + // print the type in detail (as even that may change due to closed-world + // optimizations); just print a simple type like JS does, 'object' or + // 'function', but also print null for a null (so a null function does not + // get printed as object, as in JS we have typeof null == 'object'). + // + // The only references we print in full are strings and i31s, which have + // simple and stable internal structures that optimizations will not alter. + auto type = value.type; + if (type.isRef()) { + if (type.isString() || type.getHeapType().isMaybeShared(HeapType::i31)) { + std::cout << value; + } else if (value.isNull()) { + std::cout << "null"; + } else if (type.isFunction()) { + std::cout << "function"; + } else { + std::cout << "object"; + } + return; + } + + // Non-references can be printed in full. + std::cout << value; +} + } // namespace // Logs every relevant import call parameter. @@ -127,7 +168,8 @@ struct LoggingExternalInterface : public ShellExternalInterface { std::cout << ' ' << high; loggings.push_back(high); } else { - std::cout << ' ' << argument; + std::cout << ' '; + printValue(argument); loggings.push_back(argument); } } @@ -416,53 +458,13 @@ struct ExecutionResults { std::cout << "[fuzz-exec] note result: " << exp->name << " => "; for (auto value : *values) { printValue(value); + std::cout << '\n'; } } } } } - void printValue(Literal value) { - // Unwrap an externalized GC value to get the actual value, but not strings, - // which are normally a subtype of ext. - if (Type::isSubType(value.type, Type(HeapType::ext, Nullable)) && - !value.type.isString()) { - value = value.internalize(); - } - - // An anyref literal is a string. - if (value.type.isRef() && - value.type.getHeapType().isMaybeShared(HeapType::any)) { - value = value.externalize(); - } - - // Don't print most reference values, as e.g. funcref(N) contains an index, - // which is not guaranteed to remain identical after optimizations. Do not - // print the type in detail (as even that may change due to closed-world - // optimizations); just print a simple type like JS does, 'object' or - // 'function', but also print null for a null (so a null function does not - // get printed as object, as in JS we have typeof null == 'object'). - // - // The only references we print in full are strings and i31s, which have - // simple and stable internal structures that optimizations will not alter. - auto type = value.type; - if (type.isRef()) { - if (type.isString() || type.getHeapType().isMaybeShared(HeapType::i31)) { - std::cout << value << '\n'; - } else if (value.isNull()) { - std::cout << "null\n"; - } else if (type.isFunction()) { - std::cout << "function\n"; - } else { - std::cout << "object\n"; - } - return; - } - - // Non-references can be printed in full. - std::cout << value << '\n'; - } - // get current results and check them against previous ones void check(Module& wasm) { ExecutionResults optimizedResults; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 19c4e00ab35..aa16e2b7833 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -42,14 +42,21 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, haveInitialFunctions = !wasm.functions.empty(); - // - funcref cannot be logged because referenced functions can be inlined or - // removed during optimization - // - there's no point in logging anyref because it is opaque - // - don't bother logging tuples loggableTypes = {Type::i32, Type::i64, Type::f32, Type::f64}; if (wasm.features.hasSIMD()) { loggableTypes.push_back(Type::v128); } + if (wasm.features.hasReferenceTypes()) { + if (wasm.features.hasGC()) { + loggableTypes.push_back(Type(HeapType::any, Nullable)); + loggableTypes.push_back(Type(HeapType::func, Nullable)); + loggableTypes.push_back(Type(HeapType::ext, Nullable)); + } + if (wasm.features.hasStackSwitching()) { + loggableTypes.push_back(Type(HeapType::cont, Nullable)); + } + // Note: exnref traps on the JS boundary, so we cannot try to log it. + } // Setup params. Start with the defaults. globalParams = std::make_unique(*this); diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 2595d7e6e60..ef34fc61df8 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -7,6 +7,10 @@ (module (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) (import "fuzzing-support" "log-f64" (func $log-f64 (param f64))) + (import "fuzzing-support" "log-anyref" (func $log-anyref (param anyref))) + (import "fuzzing-support" "log-funcref" (func $log-funcref (param funcref))) + (import "fuzzing-support" "log-contref" (func $log-contref (param contref))) + (import "fuzzing-support" "log-externref" (func $log-externref (param externref))) (import "fuzzing-support" "throw" (func $throw (param i32))) @@ -24,6 +28,8 @@ (import "fuzzing-support" "wasmtag" (tag $imported-wasm-tag (param i32))) (import "fuzzing-support" "jstag" (tag $imported-js-tag (param externref))) + (type $i32 (struct i32)) + (table $table 10 20 funcref) ;; Note that the exported table appears first here, but in the binary and in @@ -33,6 +39,11 @@ ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging object] + ;; CHECK-NEXT: [LoggingExternalInterface logging function] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] (func $logging (export "logging") (call $log-i32 (i32.const 42) @@ -40,6 +51,25 @@ (call $log-f64 (f64.const 3.14159) ) + (call $log-anyref + (ref.null any) + ) + ;; struct values and func names are not logged, due to differences between + ;; VMs and changes due to optimizations, that make comparisons hard. + (call $log-anyref + (struct.new $i32 + (i32.const 42) + ) + ) + (call $log-funcref + (ref.func $logging) + ) + (call $log-contref + (ref.null cont) + ) + (call $log-externref + (ref.null extern) + ) ) ;; CHECK: [fuzz-exec] calling throwing @@ -105,6 +135,11 @@ ;; CHECK: [fuzz-exec] calling export.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging object] + ;; CHECK-NEXT: [LoggingExternalInterface logging function] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; 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. @@ -123,6 +158,11 @@ ;; CHECK: [fuzz-exec] calling export.calling.rethrow ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging object] + ;; CHECK-NEXT: [LoggingExternalInterface logging function] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $export.calling.rethrow (export "export.calling.rethrow") ;; As above, but the second param is different. @@ -142,6 +182,11 @@ ;; CHECK: [fuzz-exec] calling export.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging object] + ;; CHECK-NEXT: [LoggingExternalInterface logging function] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] (func $export.calling.catching (export "export.calling.catching") @@ -163,6 +208,11 @@ ;; CHECK: [fuzz-exec] calling ref.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging object] + ;; CHECK-NEXT: [LoggingExternalInterface logging function] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $ref.calling (export "ref.calling") ;; This will emit some logging. @@ -181,6 +231,11 @@ ;; CHECK: [fuzz-exec] calling ref.calling.rethrow ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging object] + ;; CHECK-NEXT: [LoggingExternalInterface logging function] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] (func $ref.calling.rethrow (export "ref.calling.rethrow") ;; As with calling an export, when we set the flags to 1 exceptions are @@ -199,6 +254,11 @@ ;; CHECK: [fuzz-exec] calling ref.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging object] + ;; CHECK-NEXT: [LoggingExternalInterface logging function] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] + ;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] (func $ref.calling.catching (export "ref.calling.catching") @@ -409,6 +469,11 @@ ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [LoggingExternalInterface logging function] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK: [fuzz-exec] calling throwing ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] @@ -427,32 +492,62 @@ ;; CHECK: [fuzz-exec] calling export.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [LoggingExternalInterface logging function] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling export.calling.rethrow ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [LoggingExternalInterface logging function] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling export.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [LoggingExternalInterface logging function] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] ;; CHECK: [fuzz-exec] calling ref.calling ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [LoggingExternalInterface logging function] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling ref.calling.rethrow ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [LoggingExternalInterface logging function] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [exception thrown: imported-js-tag externref] ;; CHECK: [fuzz-exec] calling ref.calling.catching ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [LoggingExternalInterface logging function] +;; CHECK-NEXT: [LoggingExternalInterface logging null] +;; CHECK-NEXT: [LoggingExternalInterface logging null] ;; CHECK-NEXT: [LoggingExternalInterface logging 0] ;; CHECK-NEXT: [LoggingExternalInterface logging 1] 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 22715bcf9c3..dc3c5e52c93 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,49 +1,54 @@ Metrics total - [exports] : 15 - [funcs] : 13 + [exports] : 12 + [funcs] : 8 [globals] : 26 - [imports] : 10 + [imports] : 14 [memories] : 1 [memory-data] : 16 - [table-data] : 5 + [table-data] : 2 [tables] : 2 [tags] : 1 - [total] : 459 - [vars] : 50 - ArrayNewFixed : 9 + [total] : 581 + [vars] : 38 + ArrayNewFixed : 10 AtomicCmpxchg : 1 - AtomicNotify : 1 - Binary : 25 - Block : 68 - Break : 2 - Call : 14 - CallIndirect : 2 + Binary : 28 + Block : 86 + BrOn : 2 + Break : 10 + Call : 21 + CallIndirect : 1 CallRef : 1 - Const : 104 - Drop : 2 + Const : 105 + ContNew : 1 + DataDrop : 1 + Drop : 3 GlobalGet : 44 - GlobalSet : 32 - If : 21 - Load : 5 - LocalGet : 15 - LocalSet : 7 - Loop : 3 - Nop : 6 - Pop : 3 - RefEq : 2 - RefFunc : 8 - RefI31 : 1 - RefNull : 3 - Return : 7 - SIMDExtract : 3 - Select : 1 + GlobalSet : 34 + If : 25 + Load : 7 + LocalGet : 30 + LocalSet : 18 + Loop : 9 + Nop : 9 + Pop : 2 + RefAs : 5 + RefCast : 5 + RefEq : 3 + RefFunc : 6 + RefI31 : 4 + RefNull : 10 + Return : 2 + SIMDExtract : 2 + Select : 5 StringConst : 6 - StringMeasure : 2 - StructNew : 4 + StringEncode : 1 + StringWTF16Get : 1 + StructNew : 6 Try : 3 - TryTable : 3 - TupleExtract : 1 - TupleMake : 11 - Unary : 23 - Unreachable : 16 + TryTable : 9 + TupleExtract : 8 + TupleMake : 12 + Unary : 28 + Unreachable : 17