summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/fuzz_opt.py72
-rw-r--r--scripts/fuzz_shell.js209
-rw-r--r--src/tools/fuzzing.h2
-rw-r--r--src/wasm-builder.h2
-rw-r--r--test/passes/translate-to-fuzz_all-features.txt1
-rw-r--r--test/passes/translate-to-fuzz_no-fuzz-nans_all-features.txt1
-rw-r--r--test/unit/input/bysyncify.js8
7 files changed, 273 insertions, 22 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 5effaad4b..bea4635ac 100644
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -52,6 +52,10 @@ LOG_LIMIT = 125
# utilities
+def in_binaryen(*args):
+ return os.path.join(options.binaryen_root, *args)
+
+
def in_bin(tool):
return os.path.join(options.binaryen_root, 'bin', tool)
@@ -139,8 +143,17 @@ def run_vm(cmd):
raise
-def run_bynterp(wasm):
- return fix_output(run_vm([in_bin('wasm-opt'), wasm, '--fuzz-exec-before'] + FEATURE_OPTS))
+def run_bynterp(wasm, args):
+ # increase the interpreter stack depth, to test more things
+ os.environ['BINARYEN_MAX_INTERPRETER_DEPTH'] = '1000'
+ try:
+ return run_vm([in_bin('wasm-opt'), wasm] + FEATURE_OPTS + args)
+ finally:
+ del os.environ['BINARYEN_MAX_INTERPRETER_DEPTH']
+
+
+def run_d8(wasm):
+ return run_vm(['d8', in_binaryen('scripts', 'fuzz_shell.js'), '--', wasm])
# Each test case handler receives two wasm files, one before and one after some changes
@@ -166,7 +179,7 @@ class CompareVMs(TestCaseHandler):
def run_vms(self, js, wasm):
results = []
- results.append(run_bynterp(wasm))
+ results.append(fix_output(run_bynterp(wasm, ['--fuzz-exec-before'])))
results.append(fix_output(run_vm(['d8', js] + V8_OPTS + ['--', wasm])))
# append to add results from VMs
@@ -200,7 +213,7 @@ class CompareVMs(TestCaseHandler):
class FuzzExec(TestCaseHandler):
def handle_pair(self, before_wasm, after_wasm, opts):
# fuzz binaryen interpreter itself. separate invocation so result is easily fuzzable
- run([in_bin('wasm-opt'), before_wasm, '--fuzz-exec', '--fuzz-binary'] + opts)
+ run_bynterp(before_wasm, ['--fuzz-exec', '--fuzz-binary'])
# Check for determinism - the same command must have the same output
@@ -241,19 +254,44 @@ class Wasm2JS(TestCaseHandler):
class Bysyncify(TestCaseHandler):
- def handle(self, wasm):
- # run normally and run in an async manner, and compare
- before = run([in_bin('wasm-opt'), wasm, '--fuzz-exec'])
+ def handle_pair(self, before_wasm, after_wasm, opts):
+ # we must legalize in order to run in JS
+ run([in_bin('wasm-opt'), before_wasm, '--legalize-js-interface', '-o', before_wasm])
+ run([in_bin('wasm-opt'), after_wasm, '--legalize-js-interface', '-o', after_wasm])
+ before = fix_output(run_d8(before_wasm))
+ after = fix_output(run_d8(after_wasm))
+
# TODO: also something that actually does async sleeps in the code, say
# on the logging commands?
# --remove-unused-module-elements removes the bysyncify intrinsics, which are not valid to call
- cmd = [in_bin('wasm-opt'), wasm, '--bysyncify', '--remove-unused-module-elements', '-o', 'by.wasm']
- if random.random() < 0.5:
- cmd += ['--optimize-level=3'] # TODO: more
- run(cmd)
- after = run([in_bin('wasm-opt'), 'by.wasm', '--fuzz-exec'])
- after = '\n'.join([line for line in after.splitlines() if '[fuzz-exec] calling $bysyncify' not in line])
- compare(before, after, 'Bysyncify')
+
+ def do_bysyncify(wasm):
+ cmd = [in_bin('wasm-opt'), wasm, '--bysyncify', '-o', 't.wasm']
+ if random.random() < 0.5:
+ cmd += ['--optimize-level=%d' % random.randint(1, 3)]
+ if random.random() < 0.5:
+ cmd += ['--shrink-level=%d' % random.randint(1, 2)]
+ run(cmd)
+ out = run_d8('t.wasm')
+ # emit some status logging from bysyncify
+ print(out.splitlines()[-1])
+ # ignore the output from the new bysyncify API calls - the ones with asserts will trap, too
+ for ignore in ['[fuzz-exec] calling $bysyncify_start_unwind\nexception!\n',
+ '[fuzz-exec] calling $bysyncify_start_unwind\n',
+ '[fuzz-exec] calling $bysyncify_start_rewind\nexception!\n',
+ '[fuzz-exec] calling $bysyncify_start_rewind\n',
+ '[fuzz-exec] calling $bysyncify_stop_rewind\n',
+ '[fuzz-exec] calling $bysyncify_stop_unwind\n']:
+ out = out.replace(ignore, '')
+ out = '\n'.join([l for l in out.splitlines() if 'bysyncify: ' not in l])
+ return fix_output(out)
+
+ before_bysyncify = do_bysyncify(before_wasm)
+ after_bysyncify = do_bysyncify(after_wasm)
+
+ compare(before, after, 'Bysyncify (before/after)')
+ compare(before, before_bysyncify, 'Bysyncify (before/before_bysyncify)')
+ compare(before, after_bysyncify, 'Bysyncify (before/after_bysyncify)')
# The global list of all test case handlers
@@ -262,19 +300,19 @@ testcase_handlers = [
FuzzExec(),
CheckDeterminism(),
Wasm2JS(),
- # TODO Bysyncify(),
+ Bysyncify(),
]
# Do one test, given an input file for -ttf and some optimizations to run
-def test_one(infile, opts):
+def test_one(random_input, opts):
randomize_pass_debug()
bytes = 0
# fuzz vms
# gather VM outputs on input file
- run([in_bin('wasm-opt'), infile, '-ttf', '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS)
+ run([in_bin('wasm-opt'), random_input, '-ttf', '-o', 'a.wasm'] + FUZZ_OPTS + FEATURE_OPTS)
wasm_size = os.stat('a.wasm').st_size
bytes += wasm_size
print('pre js size :', os.stat('a.js').st_size, ' wasm size:', wasm_size)
diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js
new file mode 100644
index 000000000..4aafdacce
--- /dev/null
+++ b/scripts/fuzz_shell.js
@@ -0,0 +1,209 @@
+// Shell integration.
+if (typeof console === 'undefined') {
+ console = { log: print };
+}
+var tempRet0;
+var binary;
+if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) {
+ var args = process.argv.slice(2);
+ binary = require('fs').readFileSync(args[0]);
+ if (!binary.buffer) binary = new Uint8Array(binary);
+} else {
+ var args;
+ if (typeof scriptArgs != 'undefined') {
+ args = scriptArgs;
+ } else if (typeof arguments != 'undefined') {
+ args = arguments;
+ }
+ if (typeof readbuffer === 'function') {
+ binary = new Uint8Array(readbuffer(args[0]));
+ } else {
+ binary = read(args[0], 'binary');
+ }
+}
+
+// Utilities.
+function assert(x, y) {
+ if (!x) throw (y || 'assertion failed');// + new Error().stack;
+}
+
+// Deterministic randomness.
+var detrand = (function() {
+ var hash = 5381; // TODO DET_RAND_SEED;
+ var x = 0;
+ return function() {
+ hash = (((hash << 5) + hash) ^ (x & 0xff)) >>> 0;
+ x = (x + 1) % 256;
+ return (hash % 256) / 256;
+ };
+})();
+
+// Bysyncify integration.
+var Bysyncify = {
+ sleeping: false,
+ sleepingFunction: null,
+ sleeps: 0,
+ maxDepth: 0,
+ DATA_ADDR: 4,
+ DATA_MAX: 65536,
+ savedMemory: null,
+ instrumentImports: function(imports) {
+ var ret = {};
+ for (var module in imports) {
+ ret[module] = {};
+ for (var i in imports[module]) {
+ if (typeof imports[module][i] === 'function') {
+ (function(module, i) {
+ ret[module][i] = function() {
+ if (!Bysyncify.sleeping) {
+ // Sleep if bysyncify support is present, and at a certain
+ // probability.
+ if (exports.bysyncify_start_unwind &&
+ detrand() < 0.5) {
+ // We are called in order to start a sleep/unwind.
+ console.log('bysyncify: sleep in ' + i + '...');
+ Bysyncify.sleepingFunction = i;
+ Bysyncify.sleeps++;
+ var depth = new Error().stack.split('\n').length - 6;
+ Bysyncify.maxDepth = Math.max(Bysyncify.maxDepth, depth);
+ // Save the memory we use for data, so after we restore it later, the
+ // sleep/resume appears to have had no change to memory.
+ Bysyncify.savedMemory = new Int32Array(view.subarray(Bysyncify.DATA_ADDR >> 2, Bysyncify.DATA_MAX >> 2));
+ // Unwinding.
+ // Fill in the data structure. The first value has the stack location,
+ // which for simplicity we can start right after the data structure itself.
+ view[Bysyncify.DATA_ADDR >> 2] = Bysyncify.DATA_ADDR + 8;
+ // The end of the stack will not be reached here anyhow.
+ view[Bysyncify.DATA_ADDR + 4 >> 2] = Bysyncify.DATA_MAX;
+ exports.bysyncify_start_unwind(Bysyncify.DATA_ADDR);
+ Bysyncify.sleeping = true;
+ } else {
+ // Don't sleep, normal execution.
+ return imports[module][i].apply(null, arguments);
+ }
+ } else {
+ // We are called as part of a resume/rewind. Stop sleeping.
+ console.log('bysyncify: resume in ' + i + '...');
+ assert(Bysyncify.sleepingFunction === i);
+ exports.bysyncify_stop_rewind();
+ // The stack should have been all used up, and so returned to the original state.
+ assert(view[Bysyncify.DATA_ADDR >> 2] == Bysyncify.DATA_ADDR + 8);
+ assert(view[Bysyncify.DATA_ADDR + 4 >> 2] == Bysyncify.DATA_MAX);
+ Bysyncify.sleeping = false;
+ // Restore the memory to the state from before we slept.
+ view.set(Bysyncify.savedMemory, Bysyncify.DATA_ADDR >> 2);
+ return imports[module][i].apply(null, arguments);
+ }
+ };
+ })(module, i);
+ } else {
+ ret[module][i] = imports[module][i];
+ }
+ }
+ }
+ // Add ignored.print, which is ignored by bysyncify, and allows debugging of bysyncified code.
+ ret['ignored'] = { 'print': function(x, y) { console.log(x, y) } };
+ return ret;
+ },
+ instrumentExports: function(exports) {
+ var ret = {};
+ for (var e in exports) {
+ if (typeof exports[e] === 'function' &&
+ !e.startsWith('bysyncify_')) {
+ (function(e) {
+ ret[e] = function() {
+ while (1) {
+ var ret = exports[e].apply(null, arguments);
+ // If we are sleeping, then the stack was unwound; rewind it.
+ if (Bysyncify.sleeping) {
+ console.log('bysyncify: stop unwind; rewind');
+ assert(!ret, 'results during sleep are meaningless, just 0');
+ //console.log('bysyncify: after unwind', view[Bysyncify.DATA_ADDR >> 2], view[Bysyncify.DATA_ADDR + 4 >> 2]);
+ try {
+ exports.bysyncify_stop_unwind();
+ exports.bysyncify_start_rewind(Bysyncify.DATA_ADDR);
+ } catch (e) {
+ console.log('error in unwind/rewind switch', e);
+ }
+ continue;
+ }
+ return ret;
+ }
+ };
+ })(e);
+ } else {
+ ret[e] = exports[e];
+ }
+ }
+ return ret;
+ },
+ check: function() {
+ assert(!Bysyncify.sleeping);
+ },
+ finish: function() {
+ if (Bysyncify.sleeps > 0) {
+ print('bysyncify:', 'sleeps:', Bysyncify.sleeps, 'max depth:', Bysyncify.maxDepth);
+ }
+ },
+};
+
+// Fuzz integration.
+function logValue(x, y) {
+ if (typeof y !== 'undefined') {
+ console.log('[LoggingExternalInterface logging ' + x + ' ' + y + ']');
+ } else {
+ console.log('[LoggingExternalInterface logging ' + x + ']');
+ }
+}
+
+// Set up the imports.
+var imports = {
+ 'fuzzing-support': {
+ 'log-i32': logValue,
+ 'log-i64': logValue,
+ 'log-f32': logValue,
+ 'log-f64': logValue,
+ },
+ 'env': {
+ 'setTempRet0': function(x) { tempRet0 = x },
+ 'getTempRet0': function() { return tempRet0 },
+ },
+};
+
+imports = Bysyncify.instrumentImports(imports);
+
+// Create the wasm.
+var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), imports);
+
+// Handle the exports.
+var exports = instance.exports;
+exports = Bysyncify.instrumentExports(exports);
+var view = new Int32Array(exports.memory.buffer);
+
+// Run the wasm.
+var sortedExports = [];
+for (var e in exports) {
+ sortedExports.push(e);
+}
+sortedExports.sort();
+sortedExports = sortedExports.filter(function(e) {
+ // Filter special intrinsic functions.
+ return !e.startsWith('bysyncify_');
+});
+sortedExports.forEach(function(e) {
+ Bysyncify.check();
+ if (typeof exports[e] !== 'function') return;
+ try {
+ console.log('[fuzz-exec] calling $' + e);
+ var result = exports[e]();
+ if (typeof result !== 'undefined') {
+ console.log('[fuzz-exec] note result: $' + e + ' => ' + result);
+ }
+ } catch (e) {
+ console.log('exception!');// + [e, e.stack]);
+ }
+});
+
+// Finish up
+Bysyncify.finish();
+
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index 0a7128e3e..80945eb9e 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -375,6 +375,8 @@ private:
hasher->type = ensureFunctionType(getSig(hasher), &wasm)->name;
wasm.addExport(
builder.makeExport(hasher->name, hasher->name, ExternalKind::Function));
+ // Export memory so JS fuzzing can use it
+ wasm.addExport(builder.makeExport("memory", "0", ExternalKind::Memory));
}
void setupTable() {
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index 284608105..7d839357e 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -84,7 +84,7 @@ public:
auto* export_ = new Export();
export_->name = name;
export_->value = value;
- export_->kind = ExternalKind::Function;
+ export_->kind = kind;
return export_;
}
diff --git a/test/passes/translate-to-fuzz_all-features.txt b/test/passes/translate-to-fuzz_all-features.txt
index c3fd77b20..efbe0c232 100644
--- a/test/passes/translate-to-fuzz_all-features.txt
+++ b/test/passes/translate-to-fuzz_all-features.txt
@@ -20,6 +20,7 @@
(global $hangLimit (mut i32) (i32.const 10))
(event $event$0 (attr 0) (param i32 f32 i32 f64 i32))
(export "hashMemory" (func $hashMemory))
+ (export "memory" (memory $0))
(export "func_5" (func $func_5))
(export "hangLimitInitializer" (func $hangLimitInitializer))
(func $hashMemory (; 4 ;) (type $FUNCSIG$i) (result i32)
diff --git a/test/passes/translate-to-fuzz_no-fuzz-nans_all-features.txt b/test/passes/translate-to-fuzz_no-fuzz-nans_all-features.txt
index 57afb02b7..ba8dec2d4 100644
--- a/test/passes/translate-to-fuzz_no-fuzz-nans_all-features.txt
+++ b/test/passes/translate-to-fuzz_no-fuzz-nans_all-features.txt
@@ -19,6 +19,7 @@
(global $hangLimit (mut i32) (i32.const 10))
(event $event$0 (attr 0) (param i32 f32 i32 f64 i32))
(export "hashMemory" (func $hashMemory))
+ (export "memory" (memory $0))
(export "hangLimitInitializer" (func $hangLimitInitializer))
(func $hashMemory (; 4 ;) (type $FUNCSIG$i) (result i32)
(local $0 i32)
diff --git a/test/unit/input/bysyncify.js b/test/unit/input/bysyncify.js
index 4fd6f4b84..2e1bb4895 100644
--- a/test/unit/input/bysyncify.js
+++ b/test/unit/input/bysyncify.js
@@ -28,14 +28,14 @@ function sleepTests() {
// We are called in order to start a sleep/unwind.
console.log('sleep...');
sleeps++;
+ sleeping = true;
// Unwinding.
- exports.bysyncify_start_unwind(DATA_ADDR);
// Fill in the data structure. The first value has the stack location,
// which for simplicity we can start right after the data structure itself.
view[DATA_ADDR >> 2] = DATA_ADDR + 8;
// The end of the stack will not be reached here anyhow.
view[DATA_ADDR + 4 >> 2] = 1024;
- sleeping = true;
+ exports.bysyncify_start_unwind(DATA_ADDR);
} else {
// We are called as part of a resume/rewind. Stop sleeping.
console.log('resume...');
@@ -77,7 +77,7 @@ function sleepTests() {
if (expectedSleeps > 0) {
assert(!result, 'results during sleep are meaningless, just 0');
- exports.bysyncify_stop_unwind(DATA_ADDR);
+ exports.bysyncify_stop_unwind();
for (var i = 0; i < expectedSleeps - 1; i++) {
console.log('rewind, run until the next sleep');
@@ -85,7 +85,7 @@ function sleepTests() {
result = exports[name](); // no need for params on later times
assert(!result, 'results during sleep are meaningless, just 0');
logMemory();
- exports.bysyncify_stop_unwind(DATA_ADDR);
+ exports.bysyncify_stop_unwind();
}
console.log('rewind and run til the end.');