Skip to content

Commit 60b2dae

Browse files
authored
Fuzzer: Remove --emit-js-shell logic and reuse fuzz_shell.js instead (#6310)
We had two JS files that could run a wasm file for fuzzing purposes: * --emit-js-shell, which emitted a custom JS file that runs the wasm. * scripts/fuzz_shell.js, which was a generic file that did the same. Both of those load the wasm and then call the exports in order and print out logging as it goes of their return values (if any), exceptions, etc. Then the fuzzer compares that output to running the same wasm in another VM, etc. The difference is that one was custom for the wasm file, and one was generic. Aside from that they are similar and duplicated a bunch of code. This PR improves things by removing 1 and using 2 in all places, that is, we now use the generic file everywhere. I believe we added 1 because we thought a generic file can't do all the things we need, like know the order of exports and the types of return values, but in practice there are ways to do those things: The exports are in fact in the proper order (JS order of iteration is deterministic, thankfully), and for the type we don't want to print type internals anyhow since that would limit fuzzing --closed-world. We do need to be careful with types in JS (see notes in the PR about the type of null) but it's not too bad. As for the types of params, it's fine to pass in null for them all anyhow (null converts to a number or a reference without error).
1 parent 4031538 commit 60b2dae

13 files changed

+62
-388
lines changed

scripts/fuzz_opt.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -665,8 +665,11 @@ def run_d8_js(js, args=[], liftoff=True):
665665
return run_vm(cmd)
666666

667667

668+
FUZZ_SHELL_JS = in_binaryen('scripts', 'fuzz_shell.js')
669+
670+
668671
def run_d8_wasm(wasm, liftoff=True):
669-
return run_d8_js(in_binaryen('scripts', 'fuzz_shell.js'), [wasm], liftoff=liftoff)
672+
return run_d8_js(FUZZ_SHELL_JS, [wasm], liftoff=liftoff)
670673

671674

672675
def all_disallowed(features):
@@ -746,8 +749,7 @@ class D8:
746749
name = 'd8'
747750

748751
def run(self, wasm, extra_d8_flags=[]):
749-
run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=' + wasm + '.js'] + FEATURE_OPTS)
750-
return run_vm([shared.V8, wasm + '.js'] + shared.V8_OPTS + extra_d8_flags + ['--', wasm])
752+
return run_vm([shared.V8, FUZZ_SHELL_JS] + shared.V8_OPTS + extra_d8_flags + ['--', wasm])
751753

752754
def can_run(self, wasm):
753755
# INITIAL_CONTENT is disallowed because some initial spec testcases
@@ -1036,7 +1038,8 @@ def fix_number(x):
10361038
compare_between_vms(before, interpreter, 'Wasm2JS (vs interpreter)')
10371039

10381040
def run(self, wasm):
1039-
wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'] + FEATURE_OPTS)
1041+
with open(FUZZ_SHELL_JS) as f:
1042+
wrapper = f.read()
10401043
cmd = [in_bin('wasm2js'), wasm, '--emscripten']
10411044
# avoid optimizations if we have nans, as we don't handle them with
10421045
# full precision and optimizations can change things

scripts/fuzz_shell.js

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,34 @@ var detrand = (function() {
3838
};
3939
})();
4040

41-
// Fuzz integration.
42-
function logValue(x, y) {
41+
// Print out a value in a way that works well for fuzzing.
42+
function printed(x, y) {
4343
if (typeof y !== 'undefined') {
44-
console.log('[LoggingExternalInterface logging ' + x + ' ' + y + ']');
44+
// A pair of i32s which are a legalized i64.
45+
return x + ' ' + y;
46+
} else if (x === null) {
47+
// JS has just one null. Print that out rather than typeof null which is
48+
// 'object', below.
49+
return 'null';
50+
} else if (typeof x !== 'number' && typeof x !== 'string') {
51+
// Something that is not a number or string, like a reference. We can't
52+
// print a reference because it could look different after opts - imagine
53+
// that a function gets renamed internally (that is, the problem is that
54+
// JS printing will emit some info about the reference and not a stable
55+
// external representation of it). In those cases just print the type,
56+
// which will be 'object' or 'function'.
57+
return typeof x;
4558
} else {
46-
console.log('[LoggingExternalInterface logging ' + x + ']');
59+
// A number. Print the whole thing.
60+
return '' + x;
4761
}
4862
}
4963

64+
// Fuzzer integration.
65+
function logValue(x, y) {
66+
console.log('[LoggingExternalInterface logging ' + printed(x, y) + ']');
67+
}
68+
5069
// Set up the imports.
5170
var imports = {
5271
'fuzzing-support': {
@@ -94,21 +113,25 @@ function refreshView() {
94113
}
95114

96115
// Run the wasm.
97-
var sortedExports = [];
98116
for (var e in exports) {
99-
sortedExports.push(e);
100-
}
101-
sortedExports.sort();
102-
sortedExports.forEach(function(e) {
103-
if (typeof exports[e] !== 'function') return;
117+
if (typeof exports[e] !== 'function') {
118+
continue;
119+
}
120+
// Send the function a null for each parameter. Null can be converted without
121+
// error to both a number and a reference.
122+
var func = exports[e];
123+
var args = [];
124+
for (var i = 0; i < func.length; i++) {
125+
args.push(null);
126+
}
104127
try {
105128
console.log('[fuzz-exec] calling ' + e);
106-
var result = exports[e]();
129+
var result = func.apply(null, args);
107130
if (typeof result !== 'undefined') {
108-
console.log('[fuzz-exec] note result: $' + e + ' => ' + result);
131+
console.log('[fuzz-exec] note result: ' + e + ' => ' + printed(result));
109132
}
110133
} catch (e) {
111134
console.log('exception!');// + [e, e.stack]);
112135
}
113-
});
136+
}
114137

src/tools/execution-results.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,18 @@ struct ExecutionResults {
119119
if (resultType.isRef() && !resultType.isString()) {
120120
// Don't print reference values, as funcref(N) contains an index
121121
// for example, which is not guaranteed to remain identical after
122-
// optimizations.
123-
std::cout << resultType << '\n';
122+
// optimizations. Do not print the type in detail (as even that
123+
// may change due to closed-world optimizations); just print a
124+
// simple type like JS does, 'object' or 'function', but also
125+
// print null for a null (so a null function does not get
126+
// printed as object, as in JS we have typeof null == 'object').
127+
if (values->size() == 1 && (*values)[0].isNull()) {
128+
std::cout << "null\n";
129+
} else if (resultType.isFunction()) {
130+
std::cout << "function\n";
131+
} else {
132+
std::cout << "object\n";
133+
}
124134
} else {
125135
// Non-references can be printed in full. So can strings, since we
126136
// always know how to print them and there is just one string

src/tools/js-wrapper.h

Lines changed: 0 additions & 134 deletions
This file was deleted.

src/tools/wasm-opt.cpp

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
#include "execution-results.h"
2525
#include "fuzzing.h"
26-
#include "js-wrapper.h"
2726
#include "optimization-options.h"
2827
#include "pass.h"
2928
#include "shell-interface.h"
@@ -86,7 +85,6 @@ int main(int argc, const char* argv[]) {
8685
bool fuzzPasses = false;
8786
bool fuzzMemory = true;
8887
bool fuzzOOB = true;
89-
std::string emitJSWrapper;
9088
std::string emitSpecWrapper;
9189
std::string emitWasm2CWrapper;
9290
std::string inputSourceMapFilename;
@@ -180,15 +178,6 @@ int main(int argc, const char* argv[]) {
180178
WasmOptOption,
181179
Options::Arguments::Zero,
182180
[&](Options* o, const std::string& arguments) { fuzzOOB = false; })
183-
.add("--emit-js-wrapper",
184-
"-ejw",
185-
"Emit a JavaScript wrapper file that can run the wasm with some test "
186-
"values, useful for fuzzing",
187-
WasmOptOption,
188-
Options::Arguments::One,
189-
[&](Options* o, const std::string& arguments) {
190-
emitJSWrapper = arguments;
191-
})
192181
.add("--emit-spec-wrapper",
193182
"-esw",
194183
"Emit a wasm spec interpreter wrapper file that can run the wasm with "
@@ -329,24 +318,11 @@ int main(int argc, const char* argv[]) {
329318
}
330319
}
331320

332-
if (emitJSWrapper.size() > 0) {
333-
// As the code will run in JS, we must legalize it.
334-
PassRunner runner(&wasm);
335-
runner.add("legalize-js-interface");
336-
runner.run();
337-
}
338-
339321
ExecutionResults results;
340322
if (fuzzExecBefore) {
341323
results.get(wasm);
342324
}
343325

344-
if (emitJSWrapper.size() > 0) {
345-
std::ofstream outfile;
346-
outfile.open(wasm::Path::to_path(emitJSWrapper), std::ofstream::out);
347-
outfile << generateJSWrapper(wasm);
348-
outfile.close();
349-
}
350326
if (emitSpecWrapper.size() > 0) {
351327
std::ofstream outfile;
352328
outfile.open(wasm::Path::to_path(emitSpecWrapper), std::ofstream::out);

test/lit/exec/no-compare-refs.wast

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
;; The type of the reference this function returns will change as a result of
1313
;; signature pruning. The fuzzer should not complain about this.
1414
;; CHECK: [fuzz-exec] calling return-ref
15-
;; CHECK-NEXT: [fuzz-exec] note result: return-ref => funcref
15+
;; CHECK-NEXT: [fuzz-exec] note result: return-ref => function
1616
(func $return-ref (export "return-ref") (result funcref)
1717
(ref.func $no-use-param)
1818
)
1919
)
2020
;; CHECK: [fuzz-exec] calling return-ref
21-
;; CHECK-NEXT: [fuzz-exec] note result: return-ref => funcref
21+
;; CHECK-NEXT: [fuzz-exec] note result: return-ref => function
2222
;; CHECK-NEXT: [fuzz-exec] comparing return-ref

test/lit/help/wasm-opt.test

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@
5353
;; CHECK-NEXT: loads/stores/indirect calls when
5454
;; CHECK-NEXT: fuzzing
5555
;; CHECK-NEXT:
56-
;; CHECK-NEXT: --emit-js-wrapper,-ejw Emit a JavaScript wrapper file
57-
;; CHECK-NEXT: that can run the wasm with some
58-
;; CHECK-NEXT: test values, useful for fuzzing
59-
;; CHECK-NEXT:
6056
;; CHECK-NEXT: --emit-spec-wrapper,-esw Emit a wasm spec interpreter
6157
;; CHECK-NEXT: wrapper file that can run the
6258
;; CHECK-NEXT: wasm with some test values,

test/lit/unicode-filenames.wast

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;; RUN: wasm-as %s -o %t-❤.wasm --source-map %t-🗺️.map
22
;; RUN: cat %t-🗺️.map | filecheck %s --check-prefix SOURCEMAP
3-
;; RUN: wasm-opt %t-❤.wasm -o %t-🤬.wasm --emit-js-wrapper %t-❤.js --input-source-map %t-🗺️.map --output-source-map %t-🗺️.out.map
3+
;; RUN: wasm-opt %t-❤.wasm -o %t-🤬.wasm --emit-spec-wrapper %t-❤.js --input-source-map %t-🗺️.map --output-source-map %t-🗺️.out.map
44
;; RUN: cat %t-🗺️.out.map | filecheck %s --check-prefix SOURCEMAP
55
;; RUN: wasm-dis %t-🤬.wasm | filecheck %s --check-prefix MODULE
66

0 commit comments

Comments
 (0)