diff options
author | Alon Zakai <azakai@google.com> | 2020-07-24 06:47:07 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-24 06:47:07 -0700 |
commit | 0efd168824ac58abaf8ac484460a43241882dc93 (patch) | |
tree | 9e6ffab15c1f510b090a22f7781e19f677ef2519 | |
parent | d25b4921d2de5218ac4107084274dbce68e95930 (diff) | |
download | binaryen-0efd168824ac58abaf8ac484460a43241882dc93.tar.gz binaryen-0efd168824ac58abaf8ac484460a43241882dc93.tar.bz2 binaryen-0efd168824ac58abaf8ac484460a43241882dc93.zip |
wasm2js fuzzing: properly ignore trapping code (#2980)
wasm2js fuzzing should not compare outputs if the wasm would
trap. wasm2js traps on far fewer things, and if wasm would trap
(like an indirect call with the wrong type) it can just do weird undefined
things. Previously, if running wasm2js trapped then we ignored
the output, but that't not good enough, as we need to check if
wasm would, exactly for the cases just mentioned where wasm
would trap but wasm2js wouldn't. So run the wasm interpreter
to see if that happens.
When we see such a trap, ignore everything from that function
call onwards. This at least lets us compare the results of
previous calls, which adds some amount of coverage (before
we just ignored the entire output completely, so only if there
was no trap at all did we do any comparisons at all).
Also give better names than "js.js" to the JS files wasm2js
fuzzing creates.
-rwxr-xr-x | scripts/fuzz_opt.py | 43 |
1 files changed, 31 insertions, 12 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 4f35a1a77..d2b114a3e 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -161,6 +161,12 @@ def randomize_fuzz_settings(): # Test outputs we want to ignore are marked this way. IGNORE = '[binaryen-fuzzer-ignore]' +# Traps are reported as [trap REASON] +TRAP_PREFIX = '[trap ' + +# --fuzz-exec reports calls as [fuzz-exec] calling foo +FUZZ_EXEC_CALL_PREFIX = '[fuzz-exec] calling' + # compare two strings, strictly def compare(x, y, context): @@ -239,7 +245,7 @@ def fix_output(out): return 'f64.const ' + x out = re.sub(r'f64\.const (-?[nanN:abcdefxIity\d+-.]+)', fix_double, out) # mark traps from wasm-opt as exceptions, even though they didn't run in a vm - out = out.replace('[trap ', 'exception: [trap ') + out = out.replace(TRAP_PREFIX, 'exception: ' + TRAP_PREFIX) lines = out.splitlines() for i in range(len(lines)): line = lines[i] @@ -509,13 +515,29 @@ class Wasm2JS(TestCaseHandler): frequency = 0.6 def handle_pair(self, input, before_wasm, after_wasm, opts): - # always check for compiler crashes. without NaNs we can also compare - # before and after (with NaNs, a reinterpret through memory might end up - # different in JS than wasm) + # always check for compiler crashes before = self.run(before_wasm) after = self.run(after_wasm) - if not NANS: - compare(before, after, 'Wasm2JS') + if NANS: + # with NaNs we can't compare the output, as a reinterpret through + # memory might end up different in JS than wasm + return + # we also cannot compare if the wasm hits a trap, as wasm2js does not + # trap on many things wasm would, and in those cases it can do weird + # undefined things. in such a case, at least compare up until before + # the trap, which lets us compare at least some results in some cases. + interpreter_output = run([in_bin('wasm-opt'), before_wasm, '--fuzz-exec-before']) + if TRAP_PREFIX in interpreter_output: + trap_index = interpreter_output.index(TRAP_PREFIX) + # we can't test this function, which the trap is in the middle of. + # erase everything from this function's output and onward, so we + # only compare the previous trap-free code + call_start = interpreter_output.rindex(FUZZ_EXEC_CALL_PREFIX, 0, trap_index) + call_end = interpreter_output.index('\n', call_start) + call_line = interpreter_output[call_start:call_end] + before = before[:before.index(call_line)] + after = after[:after.index(call_line)] + compare(before, after, 'Wasm2JS') def run(self, wasm): wrapper = run([in_bin('wasm-opt'), wasm, '--emit-js-wrapper=/dev/stdout'] + FEATURE_OPTS) @@ -535,15 +557,12 @@ class Wasm2JS(TestCaseHandler): main = run(cmd + FEATURE_OPTS) with open(os.path.join(shared.options.binaryen_root, 'scripts', 'wasm2js.js')) as f: glue = f.read() - with open('js.js', 'w') as f: + js_file = wasm + '.js' + with open(js_file, 'w') as f: f.write(glue) f.write(main) f.write(wrapper) - out = fix_output(run_vm([shared.NODEJS, 'js.js', 'a.wasm'])) - if 'exception' in out: - # exception, so ignoring - wasm2js does not have normal wasm trapping, so opts can eliminate a trap - out = IGNORE - return out + return fix_output(run_vm([shared.NODEJS, js_file, 'a.wasm'])) def can_run_on_feature_opts(self, feature_opts): return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-threads', '--disable-bulk-memory', '--disable-nontrapping-float-to-int', '--disable-tail-call', '--disable-sign-ext', '--disable-reference-types', '--disable-multivalue']]) |