summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/fuzz_opt.py28
-rw-r--r--scripts/fuzz_shell.js75
2 files changed, 96 insertions, 7 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 7522dce8c..bf712c821 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1230,6 +1230,16 @@ def filter_exports(wasm, output, keep, keep_defaults=True):
run([in_bin('wasm-metadce'), wasm, '-o', output, '--graph-file', 'graph.json'] + FEATURE_OPTS)
+# Check if a wasm file would notice changes to exports. Normally removing an
+# export that is not called, for example, would not be observable, but if the
+# "call-export*" functions are present then such changes can break us.
+def wasm_notices_export_changes(wasm):
+ # we could be more precise here and disassemble the wasm to look for an
+ # actual import with name "call-export*", but looking for the string should
+ # have practically no false positives.
+ return b'call-export' in open(wasm, 'rb').read()
+
+
# Fuzz the interpreter with --fuzz-exec -tnh. The tricky thing with traps-never-
# happen mode is that if a trap *does* happen then that is undefined behavior,
# and the optimizer was free to make changes to observable behavior there. The
@@ -1323,6 +1333,12 @@ class TrapsNeverHappen(TestCaseHandler):
compare_between_vms(before, after, 'TrapsNeverHappen')
+ def can_run_on_wasm(self, wasm):
+ # If the wasm is sensitive to changes in exports then we cannot alter
+ # them, but we must remove trapping exports (see above), so we cannot
+ # run in such a case.
+ return not wasm_notices_export_changes(wasm)
+
# Tests wasm-ctor-eval
class CtorEval(TestCaseHandler):
@@ -1354,6 +1370,12 @@ class CtorEval(TestCaseHandler):
compare_between_vms(fix_output(wasm_exec), fix_output(evalled_wasm_exec), 'CtorEval')
+ def can_run_on_wasm(self, wasm):
+ # ctor-eval modifies exports, because it assumes they are ctors and so
+ # are only called once (so if it evals them away, they can be
+ # removed). If the wasm might notice that, we cannot run.
+ return not wasm_notices_export_changes(wasm)
+
# Tests wasm-merge
class Merge(TestCaseHandler):
@@ -1427,6 +1449,12 @@ class Merge(TestCaseHandler):
compare_between_vms(output, merged_output, 'Merge')
+ def can_run_on_wasm(self, wasm):
+ # wasm-merge combines exports, which can alter their indexes and lead to
+ # noticeable differences if the wasm is sensitive to such things, which
+ # prevents us from running.
+ return not wasm_notices_export_changes(wasm)
+
FUNC_NAMES_REGEX = re.compile(r'\n [(]func [$](\S+)')
diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js
index 72120cf7f..d9a994896 100644
--- a/scripts/fuzz_shell.js
+++ b/scripts/fuzz_shell.js
@@ -134,6 +134,29 @@ function logValue(x, y) {
console.log('[LoggingExternalInterface logging ' + printed(x, y) + ']');
}
+// Some imports need to access exports by index.
+var exportsList;
+function getExportByIndex(index) {
+ if (!exportsList) {
+ exportsList = [];
+ for (var e in exports) {
+ exportsList.push(e);
+ }
+ }
+ return exports[exportsList[index]];
+}
+
+// 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);
+}
+
// 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) {
@@ -172,6 +195,50 @@ var imports = {
'table-set': (index, value) => {
exports.table.set(toAddressType(exports.table, index), value);
},
+
+ // Export operations.
+ 'call-export': (index) => {
+ callFunc(getExportByIndex(index));
+ },
+ 'call-export-catch': (index) => {
+ try {
+ callFunc(getExportByIndex(index));
+ return 0;
+ } catch (e) {
+ // 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 (wasm2js does not define RuntimeError, so use
+ // that for the check - when wasm2js is run, we override the entire
+ // WebAssembly object with a polyfill, so we know exactly what it
+ // contains).
+ var wasm2js = !WebAssembly.RuntimeError;
+ 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.).
+ return 1;
+ }
+ },
},
// Emscripten support.
'env': {
@@ -250,16 +317,10 @@ for (var e of exportsToCall) {
if (typeof exports[e] !== 'function') {
continue;
}
- // Send the function a null for each parameter. Null can be converted without
- // error to both a number and a reference.
var func = exports[e];
- var args = [];
- for (var i = 0; i < func.length; i++) {
- args.push(null);
- }
try {
console.log('[fuzz-exec] calling ' + e);
- var result = func.apply(null, args);
+ var result = callFunc(func);
if (typeof result !== 'undefined') {
console.log('[fuzz-exec] note result: ' + e + ' => ' + printed(result));
}