summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2020-07-24 06:47:07 -0700
committerGitHub <noreply@github.com>2020-07-24 06:47:07 -0700
commit0efd168824ac58abaf8ac484460a43241882dc93 (patch)
tree9e6ffab15c1f510b090a22f7781e19f677ef2519
parentd25b4921d2de5218ac4107084274dbce68e95930 (diff)
downloadbinaryen-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-xscripts/fuzz_opt.py43
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']])