| // This script can be customized by setting the following variables in code that | |
| // runs before this script. | |
| // | |
| // The binary to be run. (If not set, we get the filename from argv and read | |
| // from it.) | |
| var binary; | |
| // A second binary to be linked in and run as well. (Can also be read from | |
| // argv.) | |
| var secondBinary; | |
| // Whether we are fuzzing JSPI. In addition to this being set, the "async" and | |
| // "await" keywords must be taken out of the /* KEYWORD */ comments (which they | |
| // are normally in, so as not to affect normal fuzzing). | |
| var JSPI; | |
| // Shell integration: find argv and set up readBinary(). | |
| var argv; | |
| var readBinary; | |
| if (typeof process === 'object' && typeof require === 'function') { | |
| // Node.js. | |
| argv = process.argv.slice(2); | |
| readBinary = function(name) { | |
| var data = require('fs').readFileSync(name); | |
| if (!data.buffer) data = new Uint8Array(data); | |
| return data; | |
| }; | |
| } else { | |
| // A shell like D8. | |
| if (typeof scriptArgs != 'undefined') { | |
| argv = scriptArgs; | |
| } else if (typeof arguments != 'undefined') { | |
| argv = arguments; | |
| } | |
| readBinary = function(name) { | |
| if (typeof readbuffer === 'function') { | |
| return new Uint8Array(readbuffer(name)); | |
| } else { | |
| return read(name, 'binary'); | |
| } | |
| }; | |
| } | |
| if (!binary) { | |
| binary = readBinary(argv[0]); | |
| } | |
| // Normally we call all the exports of the given wasm file. But, if we are | |
| // passed a final parameter in the form of "exports:X,Y,Z" then we call | |
| // specifically the exports X, Y, and Z. | |
| var exportsToCall; | |
| // Passing --fuzz-split makes us treat the two input files as split off | |
| // from a single one, and we will run them as that single file (ignoring extra | |
| // exports from the second one, and from wasm-split itself). This allows us to | |
| // get the same behavior from split modules as before the split. | |
| var fuzzSplit = false; | |
| for (var i = 0; i < argv.length; i++) { | |
| var curr = argv[i]; | |
| if (curr.startsWith('exports:')) { | |
| var payload = curr.substr('exports:'.length); | |
| if (payload.startsWith('[')) { | |
| exportsToCall = JSON.parse(payload); | |
| } else { | |
| exportsToCall = payload ? payload.split(',') : []; | |
| } | |
| argv.splice(i, 1); | |
| i--; | |
| } else if (curr == '--fuzz-split') { | |
| fuzzSplit = true; | |
| argv.splice(i, 1); | |
| i--; | |
| } | |
| } | |
| // If a second parameter is given, it is a second binary that we will link in | |
| // with it. | |
| if (argv[1]) { | |
| secondBinary = readBinary(argv[1]); | |
| } | |
| // Utilities. | |
| function assert(x, y) { | |
| if (!x) throw (y || 'assertion failed');// + new Error().stack; | |
| } | |
| // Print out a value in a way that works well for fuzzing. | |
| function printed(x, y) { | |
| if (typeof y !== 'undefined') { | |
| // A pair of i32s which are a legalized i64. | |
| return x + ' ' + y; | |
| } else if (x === null) { | |
| // JS has just one null. Print that out rather than typeof null which is | |
| // 'object', below. | |
| return 'null'; | |
| } else if (typeof x === 'string') { | |
| // Emit a string in the same format as the binaryen interpreter. This | |
| // escaping routine must be kept in sync with String::printEscapedJSON. | |
| var escaped = ''; | |
| for (u of x) { | |
| switch (u) { | |
| case '"': | |
| escaped += '\\"'; | |
| continue; | |
| case '\\': | |
| escaped += '\\\\'; | |
| continue; | |
| case '\b': | |
| escaped += '\\b'; | |
| continue; | |
| case '\f': | |
| escaped += '\\f'; | |
| continue; | |
| case '\n': | |
| escaped += '\\n'; | |
| continue; | |
| case '\r': | |
| escaped += '\\r'; | |
| continue; | |
| case '\t': | |
| escaped += '\\t'; | |
| continue; | |
| default: | |
| break; | |
| } | |
| var codePoint = u.codePointAt(0); | |
| if (32 <= codePoint && codePoint < 127) { | |
| escaped += u; | |
| continue | |
| } | |
| var printEscape = (codePoint) => { | |
| escaped += '\\u' | |
| escaped += ((codePoint & 0xF000) >> 12).toString(16); | |
| escaped += ((codePoint & 0x0F00) >> 8).toString(16); | |
| escaped += ((codePoint & 0x00F0) >> 4).toString(16); | |
| escaped += (codePoint & 0x000F).toString(16); | |
| }; | |
| if (codePoint < 0x10000) { | |
| printEscape(codePoint); | |
| } else { | |
| printEscape(0xD800 + ((codePoint - 0x10000) >> 10)); | |
| printEscape(0xDC00 + ((codePoint - 0x10000) & 0x3FF)); | |
| } | |
| } | |
| return 'string("' + escaped + '")'; | |
| } else if (typeof x === 'bigint') { | |
| // Print bigints in legalized form, which is two 32-bit numbers of the low | |
| // and high bits. | |
| return (Number(x & 0xffffffffn) | 0) + ' ' + (Number(x >> 32n) | 0) | |
| } else if (typeof x === 'object') { | |
| // This may be one of the externref imports, in which case we can print its | |
| // payload. | |
| if (Object.hasOwn(x, 'payload')) { | |
| return 'externref(' + x.payload + ')'; | |
| } | |
| // Or maybe this is a JS error we caught. | |
| if (x instanceof Error) { | |
| return 'jserror'; | |
| } | |
| // If this is a Wasm object, we can't access its type or any of its | |
| // internal structure, which might have been changed by optimizations | |
| // anyway. It might have a configured prototype, though, and that | |
| // prototype may be an imported externref global we can identify by the | |
| // payload we gave it. | |
| return 'object(' + printed(Object.getPrototypeOf(x)) + ')'; | |
| } else if (typeof x === 'function') { | |
| // We cannot print function names because they might have been changed by | |
| // optimizations. | |
| return 'function'; | |
| } else { | |
| // A number. Print the whole thing. | |
| assert(typeof x === 'number'); | |
| return '' + x; | |
| } | |
| } | |
| // Fuzzer integration. | |
| 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 = {}; | |
| // The raw exports, uninstrumented by JSPI. This is necessary as we need the | |
| // JSPI wrapping when calling from JS, but when calling from wasm, the exports | |
| // need to be linked to imports directly, in the raw form. | |
| var rawExports = {}; | |
| // Also track exports in a list, to allow access by index. Each entry here will | |
| // be in the form of { name: .., value: .. }. That allows us to log the name of | |
| // the function and also to call it. This is important because different | |
| // functions may have the same name, if they were exported by different | |
| // Instances under the same export names. | |
| var exportList = []; | |
| // Given a wasm function, call it as best we can from JS, and return the result. | |
| function callFunc(func) { | |
| // Send the function a null for each parameter. Null can be converted without | |
| // error to both a number and a reference. | |
| var args = []; | |
| for (var i = 0; i < func.length; i++) { | |
| args.push(null); | |
| } | |
| return func.apply(null, args); | |
| } | |
| // wasm2js does not define RuntimeError, so use that to check for it. wasm2js | |
| // overrides the entire WebAssembly object with a polyfill, so we know exactly | |
| // what it contains, and we need to handle some things differently below. | |
| var wasm2js = !WebAssembly.RuntimeError; | |
| // Calls a given function in a try-catch. Return 1 if an exception was thrown. | |
| // If |rethrow| is set, and an exception is thrown, it is caught and rethrown. | |
| // Wasm traps are not swallowed (see details below). | |
| /* async */ function tryCall(func, rethrow) { | |
| try { | |
| /* await */ func(); | |
| return 0; | |
| } catch (e) { | |
| // The exception might be a JS null, but otherwise it must be valid to check | |
| // if a property exists on it (VM bugs could cause errors here, specifically | |
| // if a wasm exception is caught here, and it is not represented properly). | |
| if (e !== null) e.a; | |
| // We only want to catch exceptions, not wasm traps: traps should still | |
| // halt execution. Handling this requires different code in wasm2js, so | |
| // check for that first. | |
| if (!wasm2js) { | |
| // When running native wasm, we can detect wasm traps. | |
| if (e instanceof WebAssembly.RuntimeError) { | |
| throw e; | |
| } | |
| } | |
| var text = e + ''; | |
| // We must not swallow host limitations here: a host limitation is a | |
| // problem that means we must not compare the outcome here to any other | |
| // VM. | |
| var hostIssues = ['requested new array is too large', | |
| 'out of memory', | |
| 'Maximum call stack size exceeded']; | |
| if (wasm2js) { | |
| // When wasm2js does trap, it just throws an "abort" error. | |
| hostIssues.push('abort'); | |
| } | |
| for (var hostIssue of hostIssues) { | |
| if (text.includes(hostIssue)) { | |
| throw e; | |
| } | |
| } | |
| // Otherwise, this is a normal exception we want to catch (a wasm | |
| // exception, or a conversion error on the wasm/JS boundary, etc.). Rethrow | |
| // if we were asked to. | |
| if (rethrow) { | |
| throw e; | |
| } | |
| return 1; | |
| } | |
| } | |
| // Table get/set operations need a BigInt if the table has 64-bit indexes. This | |
| // adds a proper cast as needed. | |
| function toAddressType(table, index) { | |
| // First, cast to unsigned. We do not support larger indexes anyhow. | |
| index = index >>> 0; | |
| if (typeof table.length == 'bigint') { | |
| return BigInt(index); | |
| } | |
| return index; | |
| } | |
| // Simple deterministic hashing, on an unsigned 32-bit seed. See e.g. | |
| // https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine | |
| var hashSeed; | |
| function hasHashSeed() { | |
| return hashSeed !== undefined; | |
| } | |
| function hashCombine(value) { | |
| // hashSeed must be set before we do anything. | |
| assert(hasHashSeed()); | |
| hashSeed ^= value + 0x9e3779b9 + (hashSeed << 6) + (hashSeed >>> 2); | |
| return hashSeed >>> 0; | |
| } | |
| // Get a random 32-bit number. This is like hashCombine but does not take a | |
| // parameter. | |
| function randomBits() { | |
| return hashCombine(-1); | |
| } | |
| // Return true with probability 1 in n. E.g. oneIn(3) returns false 2/3 of the | |
| // time, and true 1/3 of the time. | |
| function oneIn(n) { | |
| return (randomBits() % n) == 0; | |
| } | |
| // Set up the imports. | |
| var tempRet0; | |
| var baseImports = { | |
| 'fuzzing-support': { | |
| // Logging. | |
| 'log-i32': logValue, | |
| 'log-i64': logValue, | |
| 'log-f32': logValue, | |
| 'log-f64': logValue, | |
| // JS cannot log v128 values (we trap on the boundary), but we must still | |
| // provide an import so that we do not trap during linking. (Alternatively, | |
| // 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) => { | |
| if (!which) { | |
| // Throw a JS exception. | |
| throw new Error('js exception'); | |
| } else { | |
| // Throw a wasm exception. | |
| throw new WebAssembly.Exception(wasmTag, [which]); | |
| } | |
| }, | |
| // Table operations. | |
| 'table-get': (index) => { | |
| return exports.table.get(toAddressType(exports.table, index)); | |
| }, | |
| 'table-set': (index, value) => { | |
| exports.table.set(toAddressType(exports.table, index), value); | |
| }, | |
| // Export operations. | |
| 'call-export': /* async */ (index, flags) => { | |
| var rethrow = flags & 1; | |
| if (JSPI) { | |
| // TODO: Figure out why JSPI fails here. | |
| rethrow = 0; | |
| } | |
| if (!rethrow) { | |
| /* await */ callFunc(exportList[index].value); | |
| } else { | |
| tryCall(/* async */ () => /* await */ callFunc(exportList[index].value), | |
| rethrow); | |
| } | |
| }, | |
| 'call-export-catch': /* async */ (index) => { | |
| return tryCall(/* async */ () => /* await */ callFunc(exportList[index].value)); | |
| }, | |
| // Funcref operations. | |
| 'call-ref': /* async */ (ref, flags) => { | |
| // This is a direct function reference, and just like an export, it must | |
| // be wrapped for JSPI. | |
| ref = wrapExportForJSPI(ref); | |
| var rethrow = flags & 1; | |
| if (JSPI) { | |
| // TODO: Figure out why JSPI fails here. | |
| rethrow = 0; | |
| } | |
| if (!rethrow) { | |
| /* await */ callFunc(ref); | |
| } else { | |
| tryCall(/* async */ () => /* await */ callFunc(ref), | |
| rethrow); | |
| } | |
| }, | |
| 'call-ref-catch': /* async */ (ref) => { | |
| ref = wrapExportForJSPI(ref); | |
| return tryCall(/* async */ () => /* await */ callFunc(ref)); | |
| }, | |
| // Sleep a given amount of ms (when JSPI) and return a given id after that. | |
| 'sleep': (ms, id) => { | |
| // Also avoid sleeping even in JSPI mode, rarely, just to add variety | |
| // here. Only do this when we have a hash seed, that is, when we are | |
| // allowing randomness. | |
| if (!JSPI || (hasHashSeed() && oneIn(10))) { | |
| return id; | |
| } | |
| return new Promise((resolve, reject) => { | |
| setTimeout(() => { | |
| resolve(id); | |
| }, 0); // TODO: Use the ms in some reasonable, deterministic manner. | |
| // Rather than actually setTimeout on them we could manage | |
| // a queue of pending sleeps manually, and order them based | |
| // on the "ms" (which would not be literal ms, but just | |
| // how many time units to wait). | |
| }); | |
| }, | |
| 'log-branch': (actual, expected, id) => { | |
| console.log(`[LoggingExternalInterface log-branch ${actual} ${expected} ${id}]`); | |
| return actual; | |
| }, | |
| }, | |
| // Emscripten support. | |
| 'env': { | |
| 'setTempRet0': function(x) { tempRet0 = x }, | |
| 'getTempRet0': function() { return tempRet0 }, | |
| }, | |
| }; | |
| // If Tags are available, add some. | |
| if (typeof WebAssembly.Tag !== 'undefined') { | |
| // A tag for general use in the fuzzer. | |
| var wasmTag = baseImports['fuzzing-support']['wasmtag'] = new WebAssembly.Tag({ | |
| 'parameters': ['i32'] | |
| }); | |
| // The JSTag that represents a JS tag. | |
| baseImports['fuzzing-support']['jstag'] = WebAssembly.JSTag; | |
| // This allows j2wasm content to run in the fuzzer. | |
| baseImports['imports'] = { | |
| 'j2wasm.ExceptionUtils.tag': new WebAssembly.Tag({ | |
| 'parameters': ['externref'] | |
| }), | |
| }; | |
| } | |
| // If JSPI is available, wrap the imports and exports. | |
| if (JSPI) { | |
| for (var name of ['sleep', 'call-export', 'call-export-catch', 'call-ref', | |
| 'call-ref-catch']) { | |
| baseImports['fuzzing-support'][name] = | |
| new WebAssembly.Suspending(baseImports['fuzzing-support'][name]); | |
| } | |
| } | |
| function wrapExportForJSPI(value) { | |
| if (JSPI && typeof value === 'function') { | |
| value = WebAssembly.promising(value); | |
| } | |
| return value; | |
| } | |
| // If we are fuzzing a split module, a secondary binary will have imports for | |
| // placeholders, whose module name defaults to 'placeholder.deferred' for the | |
| // two-module split. Any import like | |
| // (import "placeholder.deferred" "0" (func ..)) | |
| // will be provided by the secondary module, and must be called using an | |
| // indirection. | |
| if (secondBinary) { | |
| baseImports['placeholder.deferred'] = new Proxy({}, { | |
| get(target, prop, receiver) { | |
| // Return a function that throws. We could do an indirect call using the | |
| // exported table, but as we immediately link in the secondary module, | |
| // these stubs will not be called (they are written to the table, and the | |
| // secondary module overwrites them). We do need to return something so | |
| // the primary module links without erroring, though. | |
| return () => { | |
| throw 'proxy stub should not be called'; | |
| } | |
| } | |
| }); | |
| } | |
| function makeImports(module) { | |
| // Reflect on the imports to add necessary externref globals. | |
| if (WebAssembly.Module.imports === undefined) { | |
| // We must be running with wasm2js, in which case reference types must not | |
| // be enabled and there are no externref globals. | |
| return baseImports; | |
| } | |
| // Add missing imported immutable externref globals. | |
| // TODO: Support more kinds of imported globals, but this would require being | |
| // able to reflect more precisely on the global externtypes. | |
| for (var {module, name, kind} of WebAssembly.Module.imports(module)) { | |
| if (kind == 'global') { | |
| if (!baseImports[module]) { | |
| baseImports[module] = {}; | |
| } | |
| if (!baseImports[module][name]) { | |
| // Compute a payload from the import names. This must be kept in sync | |
| // with execution-results.h. | |
| var payload = 0; | |
| for (var name of [module, name]) { | |
| for (var c of name) { | |
| payload = (payload + c.charCodeAt(0)) % 251; | |
| } | |
| } | |
| baseImports[module][name] = { payload }; | |
| } | |
| } | |
| } | |
| return baseImports; | |
| } | |
| // Compile and instantiate a wasm file. Receives the binary to build, and | |
| // whether it is the second one. | |
| function build(binary, isSecond) { | |
| var module = new WebAssembly.Module(binary); | |
| var imports = makeImports(module); | |
| if (isSecond) { | |
| assert(secondBinary); | |
| // Provide the primary module's exports to the secondary. This is a direct | |
| // wasm-wasm link, so we use the raw exports. | |
| imports['primary'] = rawExports; | |
| } | |
| var instance; | |
| try { | |
| instance = new WebAssembly.Instance(module, imports); | |
| } catch (e) { | |
| console.log('exception thrown: failed to instantiate module:', e); | |
| return; | |
| } | |
| // Do not add the second instance's exports to the list, as that would be | |
| // noticeable by calls to call-export-*. When fuzzing wasm-split, we want the | |
| // original module's exports to be provided from the primary module, and it is | |
| // the only interface to the outside. | |
| if (fuzzSplit && isSecond) { | |
| return; | |
| } | |
| // Update the exports. Note that this adds onto |exports| and |exportList|, | |
| // which is intentional: if we build another wasm, or build this one more | |
| // than once, we want to be able to call them all, so we unify all their | |
| // exports. (We do trample in |exports| when keys are equal - basically this | |
| // is a single global namespace - but |exportList| is appended to, so we do | |
| // keep the ability to call anything that was ever exported.) | |
| // | |
| // Note we do not iterate on instance.exports: the order there is not | |
| // necessarily the order in the wasm (which is what wasm-opt --fuzz-exec uses, | |
| // for example), because of JS object iteration rules (numbers are considered | |
| // "array indexes" and appear first, | |
| // https://tc39.es/ecma262/#sec-ordinaryownpropertykeys). | |
| for (var e of WebAssembly.Module.exports(module)) { | |
| var key = e.name; | |
| var value = instance.exports[key]; | |
| rawExports[key] = value; | |
| value = wrapExportForJSPI(value); | |
| exports[key] = value; | |
| if (fuzzSplit && key.startsWith('__fuzz_split_')) { | |
| // We are fuzzing wasm-split, and this is a new export generated by | |
| // wasm-split. Do not note these exports as callable from call-export*, | |
| // as they do not match the original pre-split module. | |
| continue; | |
| } | |
| exportList.push({ name: key, value: value }); | |
| } | |
| } | |
| // Run the code by calling exports. The optional |ordering| parameter indicates | |
| // howe we should order the calls to the exports: if it is not provided, we call | |
| // them in the natural order, which allows our output to be compared to other | |
| // executions of the wasm (e.g. from wasm-opt --fuzz-exec). If |ordering| is | |
| // provided, it is a random seed we use to make deterministic choices on | |
| // the order of calls. | |
| /* async */ function callExports(ordering) { | |
| hashSeed = ordering; | |
| // Call the exports we were told, or if we were not given an explicit list, | |
| // call them all. | |
| let relevantExports = exportsToCall || exportList; | |
| // Build the list of call tasks to run, one for each relevant export. | |
| let tasks = []; | |
| for (let e of relevantExports) { | |
| let name, value; | |
| if (typeof e === 'string') { | |
| // We are given a string name to call. Look it up in the global namespace. | |
| name = e; | |
| value = exports[e]; | |
| } else { | |
| // We are given an object from exportList, which has both a name and a | |
| // value. | |
| name = e.name; | |
| value = e.value; | |
| } | |
| // Check for a global. Note we must be careful in wasm2js mode, where we | |
| // can't do instanceof here (the wasm polyfill there doesn't have such | |
| // things). In wasm2js we strip global exports to avoid needing to handle | |
| // them here (using stub-unsupported-js). | |
| if (!wasm2js && (value instanceof WebAssembly.Global)) { | |
| // We can log a global value and do other operations to check for bugs. | |
| // First, do some operations on the Global wrapper itself. | |
| JSON.stringify(value); | |
| value.foobar; | |
| // Log it at the right time later using a lambda. Note that we can't just | |
| // capture |value| for the lambda, as the loop modifies it. | |
| (() => { | |
| var global = value; | |
| value = () => { | |
| // Time to log. Look at the exported value itself, not the global | |
| // wrapper. | |
| let actualValue; | |
| try { | |
| actualValue = global.value; | |
| } catch (e) { | |
| if (e.message.startsWith('get WebAssembly.Global.value')) { | |
| // Just log a string instead of a value we cannot access from JS, | |
| // like an exnref. Note we don't need matching code on the C++ | |
| // side in execution-results.h because illegal exports are pruned | |
| // anyhow if we are going to compare execution in JS to C++. | |
| actualValue = '<illegal value>'; | |
| } else { | |
| throw e; | |
| } | |
| } | |
| if (typeof actualValue === 'object') { | |
| // logRef can do a little more than logValue, so use it when possible. | |
| logRef(actualValue); | |
| } else { | |
| logValue(actualValue); | |
| } | |
| }; | |
| })(); | |
| } | |
| if (typeof value !== 'function') { | |
| // Nothing we can call. | |
| continue; | |
| } | |
| // A task is a name + a function to call. For an export, the function is | |
| // simply a call of the export. | |
| tasks.push({ name: name, func: /* async */ () => callFunc(value) }); | |
| } | |
| // Reverse the array, so the first task is at the end, for efficient | |
| // popping in the common case. | |
| tasks.reverse(); | |
| // Execute tasks while they remain. | |
| while (tasks.length) { | |
| let task; | |
| if (ordering === undefined) { | |
| // Use the natural order. | |
| task = tasks.pop(); | |
| } else { | |
| // Pick a random task. | |
| let i = hashCombine(tasks.length) % tasks.length; | |
| task = tasks.splice(i, 1)[0]; | |
| } | |
| // Execute the task. | |
| console.log(`[fuzz-exec] export ${task.name}${task.deferred ? ' (after defer)' : ''}`); | |
| let result; | |
| try { | |
| result = task.func(); | |
| } catch (e) { | |
| console.log('exception thrown: ' + e); | |
| continue; | |
| } | |
| if (JSPI) { | |
| // When we are changing up the order, in JSPI we can also leave some | |
| // promises unresolved until later, which lets us interleave them. Note we | |
| // never defer a task more than once, and we only defer a promise (which | |
| // we check for using .then). | |
| // TODO: Deferring more than once may make sense, by chaining promises in | |
| // JS (that would not add wasm execution in the middle, but might | |
| // find JS issues in principle). We could also link promises by | |
| // depending on each other, ensuring certain orders of execution. | |
| if (ordering !== undefined && !task.deferred && result && | |
| typeof result == 'object' && typeof result.then === 'function') { | |
| if (randomBits() & 1) { | |
| // Defer it for later. Reuse the existing task for simplicity. | |
| console.log(`(jspi: defer ${task.name})`); | |
| task.func = /* async */ () => { | |
| console.log(`(jspi: finish ${task.name})`); | |
| return /* await */ result; | |
| }; | |
| task.deferred = true; | |
| tasks.push(task); | |
| continue; | |
| } | |
| // Otherwise, continue down. | |
| } | |
| // Await it right now. | |
| try { | |
| result = /* await */ result; | |
| } catch (e) { | |
| console.log('exception thrown: ' + e); | |
| continue; | |
| } | |
| } | |
| // Log the result. | |
| if (typeof result !== 'undefined') { | |
| console.log('[fuzz-exec] note result: ' + task.name + ' => ' + printed(result)); | |
| } | |
| } | |
| } | |
| // Build the main wasm. | |
| build(binary); | |
| // Build the second wasm, if one was provided. | |
| if (secondBinary) { | |
| build(secondBinary, true); | |
| } | |
| // Run. | |
| callExports(); |